新建监控功能
This commit is contained in:
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,43 +1,383 @@
|
||||
<template>
|
||||
<div class="mms-mapping-view">
|
||||
<div class="mms-mapping-card">
|
||||
<h1 class="page-title">MMS 映射</h1>
|
||||
<p class="page-description">当前页面已创建,后续可在这里接入 MMS 映射配置、映射预览和导入导出能力。</p>
|
||||
<div class="table-box mms-mapping-page">
|
||||
<div class="mms-mapping-layout">
|
||||
<div class="left-panel-stack">
|
||||
<MappingRequestPanel
|
||||
:selected-icd-file-name="selectedIcdFileName"
|
||||
:is-submitting="isSubmitting"
|
||||
:icd-file-accept="icdFileAccept"
|
||||
:request-status-text="requestStatusText"
|
||||
:request-status-tag-type="requestStatusTagType"
|
||||
:can-reset="Boolean(selectedIcdFile || responsePayload || indexSelectionJsonText.trim())"
|
||||
@file-change="handleIcdFileChange"
|
||||
@parse="handleParseIcd"
|
||||
@reset="resetPage"
|
||||
/>
|
||||
|
||||
<MappingConfigPanel
|
||||
v-if="showConfigPanel"
|
||||
v-model:index-selection-json="indexSelectionJsonText"
|
||||
:is-submitting="isSubmitting"
|
||||
:can-generate="canGenerate"
|
||||
:json-error="indexSelectionError"
|
||||
:show-generate-button="showGenerateButton"
|
||||
:has-default-json="Boolean(indexSelectionJsonText.trim())"
|
||||
:empty-description="configEmptyDescription"
|
||||
@generate="handleGenerateMapping"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MappingResultPanel
|
||||
v-model:active-result-tab="activeResultTab"
|
||||
:response-status-text="responseStatusText"
|
||||
:response-status-tag-type="responseStatusTagType"
|
||||
:mapping-meta-text="mappingMetaText"
|
||||
:mapping-json-preview="mappingJsonPreview"
|
||||
:problem-tab-label="problemTabLabel"
|
||||
:problem-list="problemList"
|
||||
:problem-empty-text="problemEmptyText"
|
||||
:can-export-mapping="Boolean(mappingJsonPreview)"
|
||||
@export-mapping="handleExportMapping"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { ResultData } from '@/api/interface'
|
||||
import { getIcdApi, getIcdMmsJsonApi } from '@/api/tools/mmsmapping'
|
||||
import type { MmsMapping } from '@/api/tools/mmsmapping/interface'
|
||||
import MappingRequestPanel from './components/MappingRequestPanel.vue'
|
||||
import MappingResultPanel from './components/MappingResultPanel.vue'
|
||||
import MappingConfigPanel from './components/MappingConfigPanel.vue'
|
||||
import { buildDefaultIndexSelection, formatIndexSelectionJson, parseIndexSelectionJson } from './utils/indexSelection'
|
||||
import { createBaseRequestPayload } from './utils/requestPayload'
|
||||
|
||||
defineOptions({
|
||||
name: 'MmsMappingView'
|
||||
})
|
||||
|
||||
type ResultTab = 'mapping' | 'problem'
|
||||
type TagType = 'success' | 'warning' | 'info' | 'primary' | 'danger'
|
||||
|
||||
const DEFAULT_REQUEST_FORM: MmsMapping.BaseRequestForm = {
|
||||
version: '1.0',
|
||||
author: 'system'
|
||||
}
|
||||
|
||||
const selectedIcdFile = ref<File | null>(null)
|
||||
const responsePayload = ref<MmsMapping.MappingTaskResponse | null>(null)
|
||||
const activeResultTab = ref<ResultTab>('mapping')
|
||||
const parsedCandidates = ref<MmsMapping.IndexCandidateGroup[]>([])
|
||||
const indexSelectionJsonText = ref('')
|
||||
const isParsing = ref(false)
|
||||
const isGenerating = ref(false)
|
||||
const icdFileAccept = '.icd,.cid,.scd,.xml'
|
||||
const problemEmptyText = '当前返回未包含 problems'
|
||||
|
||||
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) return error.message
|
||||
return '接口调用失败,请检查后端服务和请求参数'
|
||||
}
|
||||
|
||||
const getFileExtension = (fileName: string) => fileName.split('.').pop()?.toLowerCase() || ''
|
||||
|
||||
const isSupportedIcdFile = (fileName: string) => ['icd', 'cid', 'scd', 'xml'].includes(getFileExtension(fileName))
|
||||
|
||||
const parsedIndexSelectionState = computed(() => {
|
||||
const source = indexSelectionJsonText.value.trim()
|
||||
|
||||
if (!source) {
|
||||
return {
|
||||
value: [] as MmsMapping.IndexSelectionGroup[],
|
||||
error: parsedCandidates.value.length ? 'request.indexSelection 不能为空' : ''
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
value: parseIndexSelectionJson(source),
|
||||
error: ''
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
value: [] as MmsMapping.IndexSelectionGroup[],
|
||||
error: getErrorMessage(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const isSubmitting = computed(() => isParsing.value || isGenerating.value)
|
||||
const indexSelectionError = computed(() => parsedIndexSelectionState.value.error)
|
||||
const canGenerate = computed(
|
||||
() => Boolean(selectedIcdFile.value && indexSelectionJsonText.value.trim() && !indexSelectionError.value)
|
||||
)
|
||||
// 关键业务节点:请求配置区只在用户已经选择 ICD 后展示,避免初始态暴露无效的 JSON 编辑区和按钮。
|
||||
const showConfigPanel = computed(() => Boolean(selectedIcdFile.value))
|
||||
const showGenerateButton = computed(() => Boolean(selectedIcdFile.value))
|
||||
const configEmptyDescription = computed(() => {
|
||||
if (isParsing.value) return '正在根据当前 ICD 生成 request.indexSelection,请稍候。'
|
||||
if (selectedIcdFile.value) return '已选择 ICD 文件,请先点击“解析 ICD”生成 request.indexSelection。'
|
||||
return '当前 ICD 暂未生成可编辑的 request.indexSelection。'
|
||||
})
|
||||
const selectedIcdFileName = computed(() => selectedIcdFile.value?.name || '')
|
||||
|
||||
const requestStatusText = computed(() => {
|
||||
if (isParsing.value) return '解析中'
|
||||
if (isGenerating.value) return '生成中'
|
||||
if (selectedIcdFile.value && indexSelectionJsonText.value.trim()) return '已生成默认配置'
|
||||
if (selectedIcdFile.value) return '待解析'
|
||||
return '未选择文件'
|
||||
})
|
||||
|
||||
const requestStatusTagType = computed<TagType>(() => {
|
||||
if (isParsing.value || isGenerating.value) return 'warning'
|
||||
if (selectedIcdFile.value && indexSelectionJsonText.value.trim()) return 'success'
|
||||
if (selectedIcdFile.value) return 'primary'
|
||||
return 'info'
|
||||
})
|
||||
|
||||
const responseStatusText = computed(() => {
|
||||
if (isSubmitting.value) return '等待返回'
|
||||
return responsePayload.value?.status || '暂无结果'
|
||||
})
|
||||
|
||||
const responseStatusTagType = computed<TagType>(() => {
|
||||
if (isSubmitting.value) return 'warning'
|
||||
if (responsePayload.value?.status === 'FAILED') return 'danger'
|
||||
if (responsePayload.value?.status === 'NEED_INDEX_SELECTION') return 'warning'
|
||||
if (responsePayload.value) return 'success'
|
||||
return 'info'
|
||||
})
|
||||
|
||||
const mappingJsonPreview = computed(() => {
|
||||
const source = responsePayload.value?.mappingJson?.trim()
|
||||
|
||||
if (!source) return ''
|
||||
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(source), null, 4)
|
||||
} catch {
|
||||
return source
|
||||
}
|
||||
})
|
||||
|
||||
const mappingMetaText = computed(() => {
|
||||
if (!mappingJsonPreview.value) return '当前返回未包含 mappingJson'
|
||||
return `mappingJson ${mappingJsonPreview.value.length} 字符`
|
||||
})
|
||||
|
||||
const problemList = computed(() => responsePayload.value?.problems?.filter(Boolean) || [])
|
||||
|
||||
const problemTabLabel = computed(() => {
|
||||
if (!problemList.value.length) return '问题列表'
|
||||
return `问题列表(${problemList.value.length})`
|
||||
})
|
||||
|
||||
const resolveResultTab = (payload: MmsMapping.MappingTaskResponse | null): ResultTab => {
|
||||
if (payload?.mappingJson?.trim()) return 'mapping'
|
||||
if (payload?.problems?.filter(Boolean).length) return 'problem'
|
||||
return 'mapping'
|
||||
}
|
||||
|
||||
const stripProblemsFromIcdPayload = (payload: MmsMapping.MappingTaskResponse): MmsMapping.MappingTaskResponse => {
|
||||
// 关键业务节点:解析 ICD 只消费候选数据和文档结构,不把后端返回的 problems 绑定到结果区。
|
||||
const sanitizedPayload = { ...payload }
|
||||
|
||||
delete sanitizedPayload.problems
|
||||
|
||||
return sanitizedPayload
|
||||
}
|
||||
|
||||
const resetParsedState = () => {
|
||||
responsePayload.value = null
|
||||
parsedCandidates.value = []
|
||||
indexSelectionJsonText.value = ''
|
||||
activeResultTab.value = 'mapping'
|
||||
}
|
||||
|
||||
const handleIcdFileChange = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
|
||||
if (!file) return
|
||||
if (!isSupportedIcdFile(file.name)) {
|
||||
ElMessage.warning('请选择 ICD、CID、SCD 或 XML 文件')
|
||||
input.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
// 关键业务节点:切换 ICD 文件后先只清空旧解析结果,等用户明确点击“解析 ICD”后再请求后台。
|
||||
selectedIcdFile.value = file
|
||||
resetParsedState()
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
const handleParseIcd = async () => {
|
||||
if (!selectedIcdFile.value) {
|
||||
ElMessage.warning('请先选择 ICD 文件')
|
||||
return
|
||||
}
|
||||
|
||||
isParsing.value = true
|
||||
responsePayload.value = null
|
||||
|
||||
try {
|
||||
// 关键业务节点:解析 ICD 时先走 get-icd,拿到当前文件的候选数据后再生成默认 request.indexSelection。
|
||||
const response = await getIcdApi({
|
||||
icdFile: selectedIcdFile.value
|
||||
})
|
||||
|
||||
const payload = unwrapApiPayload<MmsMapping.MappingTaskResponse>(response)
|
||||
const sanitizedPayload = stripProblemsFromIcdPayload(payload)
|
||||
const candidateGroups = payload.indexCandidates || []
|
||||
|
||||
responsePayload.value = sanitizedPayload
|
||||
activeResultTab.value = resolveResultTab(sanitizedPayload)
|
||||
|
||||
if (payload.status === 'FAILED') {
|
||||
parsedCandidates.value = []
|
||||
indexSelectionJsonText.value = ''
|
||||
ElMessage.error(payload.message || 'ICD 解析失败')
|
||||
return
|
||||
}
|
||||
|
||||
parsedCandidates.value = candidateGroups
|
||||
indexSelectionJsonText.value = formatIndexSelectionJson(buildDefaultIndexSelection(candidateGroups))
|
||||
ElMessage.success(payload.message || 'ICD 解析完成,已生成 request.indexSelection')
|
||||
} catch (error) {
|
||||
resetParsedState()
|
||||
ElMessage.error(getErrorMessage(error))
|
||||
} finally {
|
||||
isParsing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleGenerateMapping = async () => {
|
||||
if (!selectedIcdFile.value) {
|
||||
ElMessage.warning('请先选择 ICD 文件')
|
||||
return
|
||||
}
|
||||
|
||||
if (!indexSelectionJsonText.value.trim()) {
|
||||
ElMessage.warning('请先解析 ICD,系统会自动生成 request.indexSelection')
|
||||
return
|
||||
}
|
||||
|
||||
const { error, value } = parsedIndexSelectionState.value
|
||||
if (error) {
|
||||
responsePayload.value = {
|
||||
status: 'NEED_INDEX_SELECTION',
|
||||
message: 'request.indexSelection 格式有误,请继续修正',
|
||||
problems: [error]
|
||||
}
|
||||
activeResultTab.value = 'problem'
|
||||
ElMessage.warning(error)
|
||||
return
|
||||
}
|
||||
|
||||
isGenerating.value = true
|
||||
responsePayload.value = null
|
||||
|
||||
try {
|
||||
// 关键业务节点:正式生成阶段只消费当前编辑区里的 request.indexSelection,确保导出的映射与页面编辑态一致。
|
||||
const response = await getIcdMmsJsonApi({
|
||||
icdFile: selectedIcdFile.value,
|
||||
request: {
|
||||
...createBaseRequestPayload(DEFAULT_REQUEST_FORM),
|
||||
indexSelection: value
|
||||
}
|
||||
})
|
||||
|
||||
const payload = unwrapApiPayload<MmsMapping.MappingTaskResponse>(response)
|
||||
|
||||
responsePayload.value = payload
|
||||
activeResultTab.value = resolveResultTab(payload)
|
||||
|
||||
if (payload.status === 'FAILED') {
|
||||
ElMessage.error(payload.message || '映射生成失败')
|
||||
return
|
||||
}
|
||||
|
||||
if (payload.status === 'NEED_INDEX_SELECTION') {
|
||||
ElMessage.warning(payload.message || '当前配置仍需补充索引信息')
|
||||
return
|
||||
}
|
||||
|
||||
ElMessage.success(payload.message || '映射生成完成')
|
||||
} catch (error) {
|
||||
responsePayload.value = null
|
||||
ElMessage.error(getErrorMessage(error))
|
||||
} finally {
|
||||
isGenerating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const buildExportFileName = () => {
|
||||
const baseFileName = selectedIcdFile.value?.name.replace(/\.[^.]+$/, '') || 'mapping-summary'
|
||||
return `${baseFileName}-mapping-summary.json`
|
||||
}
|
||||
|
||||
const handleExportMapping = () => {
|
||||
if (!mappingJsonPreview.value) {
|
||||
ElMessage.warning('当前没有可导出的映射摘要')
|
||||
return
|
||||
}
|
||||
|
||||
const blob = new Blob([mappingJsonPreview.value], { type: 'application/json;charset=utf-8' })
|
||||
const objectUrl = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
|
||||
link.href = objectUrl
|
||||
link.download = buildExportFileName()
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(objectUrl)
|
||||
ElMessage.success('映射摘要已导出')
|
||||
}
|
||||
|
||||
const resetPage = () => {
|
||||
selectedIcdFile.value = null
|
||||
resetParsedState()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.mms-mapping-view {
|
||||
min-height: 100%;
|
||||
padding: 24px;
|
||||
background: #f5f7fa;
|
||||
.mms-mapping-page {
|
||||
gap: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mms-mapping-card {
|
||||
padding: 32px;
|
||||
border-radius: 12px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||
.mms-mapping-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 0.85fr) minmax(0, 1.15fr);
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
margin: 0 0 12px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
.left-panel-stack {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #4b5563;
|
||||
@media (max-width: 1280px) {
|
||||
.mms-mapping-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user