diff --git a/frontend/src/api/tools/mmsmapping/index.ts b/frontend/src/api/tools/mmsmapping/index.ts index 85093fd..20f5460 100644 --- a/frontend/src/api/tools/mmsmapping/index.ts +++ b/frontend/src/api/tools/mmsmapping/index.ts @@ -30,6 +30,17 @@ export const getIcdMmsJsonApi = (params: MmsMapping.GetIcdMmsJsonParams) => { }) } +export const getXmlFromJsonApi = (params: MmsMapping.GetXmlFromJsonParams) => { + const formData = new FormData() + + // 关键业务节点:XML 映射由后端根据已生成的 mappingJson 转换,前端保持 request JSON Part 的提交格式。 + formData.append('request', new Blob([JSON.stringify(params.request)], { type: 'application/json' })) + + return http.post('/api/mms-mapping/get-xml-from-json', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + export const buildIndexConfirmDataApi = (params: MmsMapping.IndexCandidateGroup[]) => { // 关键业务节点:ICD 候选数据需要先转换成前端确认弹窗模型,后续人工确认才能继续生成正式索引配置。 return http.post('/api/mms-mapping/build-index-confirm-data', params) diff --git a/frontend/src/api/tools/mmsmapping/interface/index.ts b/frontend/src/api/tools/mmsmapping/interface/index.ts index b73a710..a9e940f 100644 --- a/frontend/src/api/tools/mmsmapping/interface/index.ts +++ b/frontend/src/api/tools/mmsmapping/interface/index.ts @@ -63,15 +63,34 @@ export namespace MmsMapping { request: GetIcdMmsJsonRequestPayload } + export interface GetXmlFromJsonRequestPayload { + mappingJson: string + } + + export interface GetXmlFromJsonParams { + request: GetXmlFromJsonRequestPayload + } + export interface BuildIndexSelectionRequest { confirmData: IndexConfirmGroup[] confirmedData: ConfirmedIndexGroup[] } + export interface XmlFileResponse { + fileName?: string + contentType?: string + encoding?: string + content?: string + } + export interface IcdDocument { [key: string]: unknown } + export interface MappingDocument { + [key: string]: unknown + } + export interface IndexCandidateReport { reportName?: string dataSetName?: string @@ -92,8 +111,14 @@ export namespace MmsMapping { export interface MappingTaskResponse { status?: MappingTaskStatus message?: string + methodDescribe?: string icdDocument?: IcdDocument + mappingDocument?: MappingDocument mappingJson?: string + mappingXml?: string + xmlContent?: string + xmlText?: string + xmlFile?: XmlFileResponse savedPath?: string indexCandidates?: IndexCandidateGroup[] problems?: string[] diff --git a/frontend/src/components/echarts/line/index.vue b/frontend/src/components/echarts/line/index.vue index b857c39..910a2f8 100644 --- a/frontend/src/components/echarts/line/index.vue +++ b/frontend/src/components/echarts/line/index.vue @@ -40,12 +40,68 @@ const emit = defineEmits<{ end: number } ] + 'chart-click': [ + value: { + dataIndex: number + axisValue: string | number + } + ] }>() let chart: echarts.ECharts | any = null let isPanPointerDown = false const getChartViewportRoot = () => chart?.getZr()?.painter?.getViewportRoot?.() as HTMLElement | undefined +const getAxisPixel = (dataIndex: number) => { + const pixelValue = chart?.convertToPixel?.({ xAxisIndex: 0 }, dataIndex) + + if (Array.isArray(pixelValue)) return Number(pixelValue[0]) + + return Number(pixelValue) +} + +const getClosestAxisDataIndex = (axisValue: unknown, offsetX: number) => { + const xAxisData = props.options?.xAxis?.data + + if (!Array.isArray(xAxisData) || !xAxisData.length) return -1 + + const candidateIndexes = new Set() + const axisNumber = Number(axisValue) + const directIndex = xAxisData.findIndex((item: unknown) => item === axisValue) + + if (Number.isFinite(axisNumber)) { + candidateIndexes.add(Math.round(axisNumber)) + } + + if (directIndex >= 0) { + candidateIndexes.add(directIndex) + } + + xAxisData.forEach((item: unknown, index: number) => { + if (String(item) === String(axisValue)) { + candidateIndexes.add(index) + } + }) + + const validCandidates = Array.from(candidateIndexes).filter(index => index >= 0 && index < xAxisData.length) + + if (validCandidates.length) { + return validCandidates.reduce((closestIndex, currentIndex) => { + const closestDistance = Math.abs(getAxisPixel(closestIndex) - offsetX) + const currentDistance = Math.abs(getAxisPixel(currentIndex) - offsetX) + + return currentDistance < closestDistance ? currentIndex : closestIndex + }, validCandidates[0]) + } + + return xAxisData.reduce((closestIndex: number, _item: unknown, currentIndex: number) => { + const closestDistance = Math.abs(getAxisPixel(closestIndex) - offsetX) + const currentDistance = Math.abs(getAxisPixel(currentIndex) - offsetX) + + return currentDistance < closestDistance ? currentIndex : closestIndex + }, 0) +} + const resetChartCursor = () => { const viewportRoot = getChartViewportRoot() if (viewportRoot) viewportRoot.style.cursor = '' @@ -55,14 +111,20 @@ const resetChartCursor = () => { const updatePanCursor = (event: { offsetX: number; offsetY: number }) => { const viewportRoot = getChartViewportRoot() - if (!viewportRoot || props.options?.activeTool !== 'pan') { + if (!viewportRoot || (props.options?.activeTool !== 'pan' && props.options?.activeTool !== 'mark')) { resetChartCursor() return } // 平移只在图形绘图区内生效,鼠标样式同步限制到同一范围,避免坐标轴和空白区误导操作。 const isInGrid = chart?.containPixel?.({ gridIndex: 0 }, [event.offsetX, event.offsetY]) - viewportRoot.style.cursor = isInGrid ? (isPanPointerDown ? 'grabbing' : 'grab') : '' + viewportRoot.style.cursor = isInGrid + ? props.options?.activeTool === 'mark' + ? 'crosshair' + : isPanPointerDown + ? 'grabbing' + : 'grab' + : '' } const bindPanCursorEvents = () => { @@ -73,10 +135,12 @@ const bindPanCursorEvents = () => { zr.off('mousedown', handlePanCursorMouseDown) zr.off('mouseup', handlePanCursorMouseUp) zr.off('globalout', resetChartCursor) + zr.off('click', handleChartClick) zr.on('mousemove', updatePanCursor) zr.on('mousedown', handlePanCursorMouseDown) zr.on('mouseup', handlePanCursorMouseUp) zr.on('globalout', resetChartCursor) + zr.on('click', handleChartClick) } const unbindPanCursorEvents = () => { @@ -87,9 +151,34 @@ const unbindPanCursorEvents = () => { zr.off('mousedown', handlePanCursorMouseDown) zr.off('mouseup', handlePanCursorMouseUp) zr.off('globalout', resetChartCursor) + zr.off('click', handleChartClick) resetChartCursor() } +function handleChartClick(params: any) { + if (props.options?.activeTool !== 'mark' || !chart) return + + const event = params?.event?.event || params?.event || params + const offsetX = Number(event?.offsetX) + const offsetY = Number(event?.offsetY) + + if (!Number.isFinite(offsetX) || !Number.isFinite(offsetY)) return + if (!chart.containPixel?.({ gridIndex: 0 }, [offsetX, offsetY])) return + + const convertedValue = chart.convertFromPixel?.({ xAxisIndex: 0 }, [offsetX, offsetY]) + const rawAxisValue = Array.isArray(convertedValue) ? convertedValue[0] : convertedValue + const xAxisData = props.options?.xAxis?.data + const dataIndex = getClosestAxisDataIndex(rawAxisValue, offsetX) + const axisValue = Array.isArray(xAxisData) ? xAxisData[dataIndex] : dataIndex + + if (!Number.isInteger(dataIndex) || dataIndex < 0 || axisValue === undefined) return + + emit('chart-click', { + dataIndex, + axisValue + }) +} + function handlePanCursorMouseDown(event: { offsetX: number; offsetY: number }) { isPanPointerDown = true updatePanCursor(event) diff --git a/frontend/src/views/tools/mmsMapping/components/JsonMappingTree.vue b/frontend/src/views/tools/mmsMapping/components/JsonMappingTree.vue new file mode 100644 index 0000000..bd292d2 --- /dev/null +++ b/frontend/src/views/tools/mmsMapping/components/JsonMappingTree.vue @@ -0,0 +1,196 @@ + + + + + diff --git a/frontend/src/views/tools/mmsMapping/components/JsonMappingTreeNode.vue b/frontend/src/views/tools/mmsMapping/components/JsonMappingTreeNode.vue new file mode 100644 index 0000000..14433f5 --- /dev/null +++ b/frontend/src/views/tools/mmsMapping/components/JsonMappingTreeNode.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/frontend/src/views/tools/mmsMapping/components/MappingConfirmDialog.vue b/frontend/src/views/tools/mmsMapping/components/MappingConfirmDialog.vue index 7345e1c..d71efb8 100644 --- a/frontend/src/views/tools/mmsMapping/components/MappingConfirmDialog.vue +++ b/frontend/src/views/tools/mmsMapping/components/MappingConfirmDialog.vue @@ -1,7 +1,7 @@