feat(auth): 优化权限模块菜单数据处理逻辑

- 添加showMenuList、flatMenuList和breadcrumbList状态字段
- 修改getter方法直接返回缓存的状态数据
- 新增refreshDerivedMenus方法统一处理菜单衍生数据计算
- 在重置授权存储时清理新增的菜单相关状态
- 避免每次路由跳转时重复深拷贝整个菜单树结构

feat(checksquare): 完善校验功能组件和业务逻辑

- 新增测量点对话框组件用于显示监测点详细信息
- 添加校验台账工具函数解析测量点详情
- 实现任务表格删除功能包括确认提示和数据刷新
- 更新任务表格将缺失率字段替换为数据完整性字段
- 重构详情面板使用标签页展示不同类型的校验详情
- 优化摘要表格样式包括紧凑布局和危险颜色标识
- 统一详情对话框尺寸样式保持界面一致性
- 实现数据完整性字段的百分比单位去除处理

refactor(influxdb): 简化数据库启动流程移除命令行包装器

- 直接通过influxd.exe启动InfluxDB服务
- 移除对cmd.exe包装器的依赖和进程ID记录
- 保持进程管理和停止功能的完整性
This commit is contained in:
2026-06-12 08:44:07 +08:00
parent 8622f25048
commit 81f90ce0f2
26 changed files with 1279 additions and 243 deletions

View File

@@ -19,6 +19,11 @@
<div class="tool-text">进入 MMS 映射页面后续可继续补充映射配置和预览能力</div>
</button>
<button class="tool-item" type="button" @click="handleNavigate('/tools/mmsMapping/deviceTypes')">
<div class="tool-name">设备类型校验</div>
<div class="tool-text">维护设备类型并从列表进入 ICD 一致性校验和 PQDIF 预留校验</div>
</button>
<button class="tool-item" type="button" @click="handleNavigate('/tools/addData')">
<div class="tool-name">addData</div>
<div class="tool-text">进入 addData 页面壳子后续在此扩展补录数据能力和业务交互</div>

View File

@@ -0,0 +1,287 @@
<template>
<div class="table-box mms-device-type-page">
<section class="table-main card mms-device-type-card">
<div class="table-header">
<div class="header-button-lf">
<el-button type="primary" :icon="Plus" @click="openCreateDialog">新增设备类型</el-button>
</div>
<div class="header-button-ri">
<el-button circle :icon="Refresh" :loading="loading" @click="loadDeviceTypes" />
</div>
</div>
<div class="device-type-table-body">
<el-table v-loading="loading" :data="deviceTypes" border stripe height="100%">
<el-table-column
prop="name"
label="设备类型名称"
min-width="180"
fixed="left"
show-overflow-tooltip
/>
<el-table-column prop="icdName" label="ICD 名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="icdPath" label="ICD 路径" min-width="220" show-overflow-tooltip />
<el-table-column prop="reportName" label="报告模板" min-width="160" show-overflow-tooltip />
<el-table-column label="ICD 校验结论" min-width="150" align="center">
<template #default="{ row }">
<el-tag :type="getIcdResultTagType(row.icdResult)" effect="light">
{{ getIcdResultText(row.icdResult) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="icdMsg" label="结论描述" min-width="220" show-overflow-tooltip />
<el-table-column label="操作" width="230" fixed="right" align="center">
<template #default="{ row }">
<el-button
link
type="primary"
:icon="Connection"
:disabled="!row.canCheckIcd"
@click="handleIcdCheck(row)"
>
ICD一致性校验
</el-button>
<el-button
link
type="primary"
:icon="DocumentChecked"
:loading="pqdifCheckingId === row.id"
:disabled="!row.canCheckPqdif"
@click="handlePqdifCheck(row)"
>
PQDIF校验
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</section>
<el-dialog v-model="createDialogVisible" title="新增设备类型" width="520px" destroy-on-close>
<el-form ref="createFormRef" :model="createForm" :rules="createRules" label-width="110px">
<el-form-item label="设备类型名称" prop="name">
<el-input v-model="createForm.name" maxlength="80" clearable placeholder="请输入设备类型名称" />
</el-form-item>
<el-form-item label="ICD ID">
<el-input v-model="createForm.icdId" maxlength="80" clearable placeholder="可选,关联 ICD ID" />
</el-form-item>
<el-form-item label="ICD 名称">
<el-input v-model="createForm.icdName" maxlength="120" clearable placeholder="可选ICD 名称" />
</el-form-item>
<el-form-item label="ICD 路径">
<el-input v-model="createForm.icdPath" maxlength="260" clearable placeholder="可选ICD 存储路径" />
</el-form-item>
<el-form-item label="报告模板">
<el-input
v-model="createForm.reportName"
maxlength="120"
clearable
placeholder="可选,报告模板名称"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="createDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="creating" @click="handleCreateDeviceType">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { Connection, DocumentChecked, Plus, Refresh } from '@element-plus/icons-vue'
import { ElMessage, type FormInstance, type FormRules, type TagProps } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import type { ResultData } from '@/api/interface'
import { createDeviceTypeApi, listDeviceTypesApi, pqdifCheckApi } from '@/api/tools/mmsmapping'
import type { MmsMapping } from '@/api/tools/mmsmapping/interface'
defineOptions({
name: 'MmsDeviceTypesView'
})
type TagType = TagProps['type']
const router = useRouter()
const loading = ref(false)
const creating = ref(false)
const pqdifCheckingId = ref('')
const createDialogVisible = ref(false)
const createFormRef = ref<FormInstance>()
const deviceTypes = ref<MmsMapping.DeviceType[]>([])
const createForm = reactive<MmsMapping.CreateDeviceTypeRequest>({
name: '',
icdId: '',
icdName: '',
icdPath: '',
reportName: ''
})
const createRules: FormRules<MmsMapping.CreateDeviceTypeRequest> = {
name: [{ required: true, message: '请输入设备类型名称', trigger: 'blur' }]
}
function unwrapApiPayload<T>(response: ResultData<T> | T): T {
if (response && typeof response === 'object' && 'data' in response) {
return (response as ResultData<T>).data
}
return response as T
}
const getErrorMessage = (error: unknown) => {
if (error instanceof Error && error.message) return error.message
return '接口调用失败,请检查后端服务和请求参数'
}
const trimOptionalText = (value?: string) => {
const text = value?.trim()
return text || undefined
}
const resetCreateForm = () => {
createForm.name = ''
createForm.icdId = ''
createForm.icdName = ''
createForm.icdPath = ''
createForm.reportName = ''
createFormRef.value?.clearValidate()
}
const openCreateDialog = () => {
resetCreateForm()
createDialogVisible.value = true
}
const getIcdResultText = (value?: number) => {
if (value === 1) return '一致'
if (value === 0) return '不一致'
return '未校验'
}
const getIcdResultTagType = (value?: number): TagType => {
if (value === 1) return 'success'
if (value === 0) return 'danger'
return 'info'
}
const loadDeviceTypes = async () => {
loading.value = true
try {
const response = await listDeviceTypesApi()
deviceTypes.value = unwrapApiPayload<MmsMapping.DeviceType[]>(response) || []
} catch (error) {
deviceTypes.value = []
ElMessage.error(getErrorMessage(error))
} finally {
loading.value = false
}
}
const handleCreateDeviceType = async () => {
const valid = await createFormRef.value?.validate().catch(() => false)
if (!valid) return
creating.value = true
try {
await createDeviceTypeApi({
name: createForm.name.trim(),
icdId: trimOptionalText(createForm.icdId),
icdName: trimOptionalText(createForm.icdName),
icdPath: trimOptionalText(createForm.icdPath),
reportName: trimOptionalText(createForm.reportName)
})
createDialogVisible.value = false
ElMessage.success('设备类型新增成功')
await loadDeviceTypes()
} catch (error) {
ElMessage.error(getErrorMessage(error))
} finally {
creating.value = false
}
}
const handleIcdCheck = async (row: MmsMapping.DeviceType) => {
if (!row.id) {
ElMessage.warning('当前设备类型缺少 ID不能执行 ICD 一致性校验')
return
}
// 关键业务节点设备类型页只负责传递校验上下文ICD 文件选择、索引确认和映射生成复用现有 MMS 映射页流程。
await router.push({
path: '/tools/mmsMapping',
query: {
deviceTypeId: row.id,
deviceTypeName: row.name || '',
fromDeviceTypeCheck: '1'
}
})
}
const handlePqdifCheck = async (row: MmsMapping.DeviceType) => {
if (!row.id) {
ElMessage.warning('当前设备类型缺少 ID不能执行 PQDIF 校验')
return
}
pqdifCheckingId.value = row.id
try {
const response = await pqdifCheckApi(row.id)
const payload = unwrapApiPayload<MmsMapping.PqdifCheckPlaceholder>(response)
ElMessage.info(payload?.message || 'PQDIF校验功能待实现')
} catch (error) {
ElMessage.error(getErrorMessage(error))
} finally {
pqdifCheckingId.value = ''
}
}
onMounted(() => {
loadDeviceTypes()
})
</script>
<style scoped lang="scss">
.mms-device-type-page {
overflow: hidden;
}
.mms-device-type-card {
padding: 16px;
overflow: hidden;
}
.table-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex: none;
}
.header-button-lf,
.header-button-ri {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.device-type-table-body {
display: flex;
flex: 1;
min-height: 0;
margin-top: 12px;
overflow: hidden;
}
.device-type-table-body :deep(.el-table__inner-wrapper) {
height: 100%;
}
</style>

View File

@@ -52,9 +52,14 @@
:can-generate-xml-mapping="canGenerateXmlMapping"
:is-generating-xml="isGeneratingXml"
:show-xml-mapping-tab="showXmlMappingTab"
:show-save-icd-check-result="showSaveIcdCheckResult"
:can-save-icd-check-result="canSaveIcdCheckResult"
:is-saving-icd-check-result="isSavingIcdCheckResult"
:save-icd-check-result-text="saveIcdCheckResultText"
@export-mapping="handleExportMapping"
@generate-xml-mapping="handleGenerateXmlMapping"
@update-mapping-json="handleUpdateMappingJson"
@save-icd-check-result="handleSaveIcdCheckResult"
/>
</div>
@@ -70,6 +75,7 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import type { ResultData } from '@/api/interface'
import {
@@ -77,7 +83,8 @@ import {
buildIndexSelectionApi,
getIcdApi,
getIcdMmsJsonApi,
getXmlFromJsonApi
getXmlFromJsonApi,
saveIcdCheckResultApi
} from '@/api/tools/mmsmapping'
import type { MmsMapping } from '@/api/tools/mmsmapping/interface'
import MappingRequestPanel from './components/MappingRequestPanel.vue'
@@ -112,7 +119,9 @@ const isParsing = ref(false)
const isConfirmingSelection = ref(false)
const isGenerating = ref(false)
const isGeneratingXml = ref(false)
const isSavingIcdCheckResult = ref(false)
const icdFileAccept = '.icd,.cid,.scd,.xml'
const route = useRoute()
const problemEmptyText = '当前返回未包含 problems'
function unwrapApiPayload<T>(response: ResultData<T> | T): T {
@@ -202,7 +211,12 @@ const parsedIndexSelectionState = computed(() => {
})
const isSubmitting = computed(
() => isParsing.value || isConfirmingSelection.value || isGenerating.value || isGeneratingXml.value
() =>
isParsing.value ||
isConfirmingSelection.value ||
isGenerating.value ||
isGeneratingXml.value ||
isSavingIcdCheckResult.value
)
const canResetPage = computed(() =>
Boolean(
@@ -216,7 +230,12 @@ const canResetPage = computed(() =>
const indexSelectionError = computed(() => parsedIndexSelectionState.value.error)
const canParseIcd = computed(() => Boolean(selectedIcdFile.value && !isSubmitting.value))
const canGenerate = computed(() =>
Boolean(selectedIcdFile.value && indexSelectionJsonText.value.trim() && !indexSelectionError.value && !isSubmitting.value)
Boolean(
selectedIcdFile.value &&
indexSelectionJsonText.value.trim() &&
!indexSelectionError.value &&
!isSubmitting.value
)
)
// 关键业务节点:请求配置区只在用户已经选择 ICD 后展示,避免初始态暴露无效的请求编辑区。
const showConfigPanel = computed(() => Boolean(selectedIcdFile.value))
@@ -307,6 +326,23 @@ const canGenerateXmlMapping = computed(() => Boolean(mappingJsonPreview.value &&
const showXmlMappingTab = computed(() =>
Boolean(xmlResponsePayload.value && xmlResponsePayload.value.status !== 'FAILED')
)
const deviceTypeCheckId = computed(() => {
const value = route.query.deviceTypeId
return typeof value === 'string' ? value.trim() : ''
})
const deviceTypeCheckName = computed(() => {
const value = route.query.deviceTypeName
return typeof value === 'string' ? value.trim() : ''
})
const showSaveIcdCheckResult = computed(() => Boolean(deviceTypeCheckId.value))
const canSaveIcdCheckResult = computed(() =>
Boolean(deviceTypeCheckId.value && mappingJsonPreview.value && !isSubmitting.value)
)
const saveIcdCheckResultText = computed(() =>
deviceTypeCheckName.value ? `入库:${deviceTypeCheckName.value}` : '校验结果入库'
)
const xmlMappingPreview = computed(() => {
const source = xmlContentForExport.value
@@ -632,6 +668,44 @@ const handleExportMapping = (type: ExportMappingType) => {
ElMessage.success('XML 映射已导出')
}
const handleSaveIcdCheckResult = async () => {
if (!deviceTypeCheckId.value) {
ElMessage.warning('当前缺少设备类型 ID不能入库校验结果')
return
}
const mappingJson = responsePayload.value?.mappingJson?.trim() || mappingJsonPreview.value
if (!mappingJson) {
ElMessage.warning('请先生成 JSON 映射后再入库')
return
}
isSavingIcdCheckResult.value = true
try {
// 关键业务节点:设备类型 ICD 校验入库只保存本次生成的映射结果XML 已生成时一并回写。
const response = await saveIcdCheckResultApi(deviceTypeCheckId.value, {
mappingJson,
xml: xmlContentForExport.value || undefined,
result: 1,
msg: xmlContentForExport.value ? 'ICD一致性校验通过JSON/XML已生成' : 'ICD一致性校验通过JSON已生成'
})
const saved = unwrapApiPayload<boolean>(response)
if (!saved) {
ElMessage.warning('校验结果入库未返回成功')
return
}
ElMessage.success('校验结果已入库')
} catch (error) {
ElMessage.error(getErrorMessage(error))
} finally {
isSavingIcdCheckResult.value = false
}
}
const handleUpdateMappingJson = (mappingJson: string) => {
if (!responsePayload.value) return

View File

@@ -15,6 +15,17 @@
>
生成XML映射
</el-button>
<el-button
v-if="showSaveIcdCheckResult"
type="primary"
plain
:icon="Finished"
:loading="isSavingIcdCheckResult"
:disabled="!canSaveIcdCheckResult"
@click="emit('save-icd-check-result')"
>
{{ saveIcdCheckResultText }}
</el-button>
<div class="export-actions">
<el-button
type="primary"
@@ -128,13 +139,7 @@
<el-empty v-else :description="problemEmptyText" />
</el-dialog>
<el-dialog
v-model="matchResultDialogVisible"
title="匹配结果展示"
width="640px"
destroy-on-close
top="12vh"
>
<el-dialog v-model="matchResultDialogVisible" title="匹配结果展示" width="640px" destroy-on-close top="12vh">
<div class="match-result-detail">
{{ methodDescribe || '当前接口返回未包含 methodDescribe' }}
</div>
@@ -179,7 +184,9 @@
>
<div class="sequence-type-header">
<div class="sequence-type-title">{{ typeGroup.typeName }}</div>
<el-tag type="primary" effect="plain" size="small">{{ typeGroup.rows.length }} </el-tag>
<el-tag type="primary" effect="plain" size="small">
{{ typeGroup.rows.length }}
</el-tag>
</div>
<div v-for="row in typeGroup.rows" :key="row.id" class="sequence-config-item">
@@ -220,7 +227,7 @@
</template>
<script setup lang="ts">
import { ArrowDown, Connection, Document, Download, Search, Setting, Warning } from '@element-plus/icons-vue'
import { ArrowDown, Connection, Document, Download, Finished, Search, Setting, Warning } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { computed, ref } from 'vue'
import JsonMappingTree from './JsonMappingTree.vue'
@@ -282,6 +289,10 @@ const props = defineProps<{
canGenerateXmlMapping: boolean
isGeneratingXml: boolean
showXmlMappingTab: boolean
showSaveIcdCheckResult: boolean
canSaveIcdCheckResult: boolean
isSavingIcdCheckResult: boolean
saveIcdCheckResultText: string
}>()
const emit = defineEmits<{
@@ -289,6 +300,7 @@ const emit = defineEmits<{
(event: 'export-mapping', value: ExportMappingType): void
(event: 'generate-xml-mapping'): void
(event: 'update-mapping-json', value: string): void
(event: 'save-icd-check-result'): void
}>()
const activeTabProxy = computed({
@@ -322,9 +334,17 @@ const filteredSequenceRows = computed(() => {
if (!keyword) return sequenceConfigRows.value
return sequenceConfigRows.value.filter(row =>
[row.topKey, row.topDesc, row.desc, row.parentDesc, row.name, row.baseflag, row.pathText, row.start, row.end].some(
value => value.toLowerCase().includes(keyword)
)
[
row.topKey,
row.topDesc,
row.desc,
row.parentDesc,
row.name,
row.baseflag,
row.pathText,
row.start,
row.end
].some(value => value.toLowerCase().includes(keyword))
)
})
const sequenceConfigGroups = computed<SequenceConfigGroup[]>(() => {
@@ -365,7 +385,8 @@ const handleExportCommand = (command: string | number | object) => {
}
}
const isRecord = (value: unknown): value is JsonObject => Boolean(value && typeof value === 'object' && !Array.isArray(value))
const isRecord = (value: unknown): value is JsonObject =>
Boolean(value && typeof value === 'object' && !Array.isArray(value))
const toDisplayText = (value: unknown, fallback: string) => {
if (typeof value === 'string' && value.trim()) return value.trim()
@@ -392,7 +413,12 @@ const isConfigurableBaseflag = (value: unknown) => {
return ['1', '2'].includes(value.trim())
}
const collectSequenceRows = (source: unknown, path: JsonPath = [], parentDesc = '', rootSource = source): SequenceConfigRow[] => {
const collectSequenceRows = (
source: unknown,
path: JsonPath = [],
parentDesc = '',
rootSource = source
): SequenceConfigRow[] => {
if (Array.isArray(source)) {
return source.flatMap((item, index) => collectSequenceRows(item, [...path, index], parentDesc, rootSource))
}