diff --git a/vue/src/system/GraphBuilder.vue b/vue/src/system/GraphBuilder.vue index e5db356..80cf9ba 100644 --- a/vue/src/system/GraphBuilder.vue +++ b/vue/src/system/GraphBuilder.vue @@ -335,11 +335,13 @@ export default { allSelect(msg){ if (msg.entities && msg.entities.length > 0) { msg.entities.forEach(ent => { + ent.selected=true this.selectedEntities.push(ent); }); } if (msg.relations && msg.relations.length > 0) { msg.relations.forEach(rel => { + rel.selected=true this.selectedRelations.push(rel); }); } diff --git a/vue/src/system/GraphDemo.vue b/vue/src/system/GraphDemo.vue index fee8ee3..1439871 100644 --- a/vue/src/system/GraphDemo.vue +++ b/vue/src/system/GraphDemo.vue @@ -134,13 +134,6 @@
-
@@ -162,18 +155,14 @@ import { import {Graph, Tooltip} from '@antv/g6'; import Menu from "@/components/Menu.vue"; import {a} from "vue-router/dist/devtools-EWN81iOl.mjs"; -import GraphToolbar from "@/components/GraphToolbar.vue"; import Fuse from 'fuse.js'; -import {ElMessage} from "element-plus"; import {getGraphStyleActive} from "@/api/style"; - export default { name: 'Display', - components: {Menu, GraphToolbar}, + components: {Menu}, data() { return { - _graph: null, G6: null, // 添加这个 // 节点样式 nodeShowLabel: true, @@ -212,41 +201,41 @@ export default { children: 'children', label: 'title' // 虽然用插槽,但 el-tree 内部仍会读取,可留空或任意 }, - typeRadio: "Disease", - DiseaseRadio: "ICD10", - drugTree: [], - diseaseDepartTree: [], - diseaseICD10Tree: [], - diseaseSZMTree: [], - checkTree: [], + typeRadio:"Disease", + DiseaseRadio:"ICD10", + drugTree:[], + diseaseDepartTree:[], + diseaseICD10Tree:[], + diseaseSZMTree:[], + checkTree:[], legendItems: [ - {key: 'Disease', label: '疾病', color: '#EF4444'}, - {key: 'Drug', label: '药品', color: '#91cc75'}, - {key: 'Check', label: '检查', color: '#336eee'}, - {key: 'Symptom', label: '症状', color: '#fac858'}, - {key: 'Other', label: '其他', color: '#59d1d4'} + { key: 'Disease', label: '疾病', color: '#EF4444' }, + { key: 'Drug', label: '药品', color: '#91cc75' }, + { key: 'Check', label: '检查', color: '#336eee' }, + { key: 'Symptom', label: '症状', color: '#fac858' }, + { key: 'Other', label: '其他', color: '#59d1d4' } ], visibleCategories: new Set(), // 先空着 - diseaseCount: 0, - drugCount: 0, - checkCount: 0, + diseaseCount:0, + drugCount:0, + checkCount:0, originalNodeStyles: new Map(), // 保存原始节点样式 originalEdgeStyles: new Map(), // 保存原始边样式 searchResults: { nodes: [], edges: [] }, - searchKeyword: "", - drugSubjectTree: [], - DrugRadio: "Subject", + searchKeyword:"", + drugSubjectTree:[], + DrugRadio:"Subject", isDropdownOpen: false, typeOptions: [ - {value: 'Disease', label: '疾病'}, - {value: 'Drug', label: '药品'}, - {value: 'Check', label: '检查'} + { value: 'Disease', label: '疾病' }, + { value: 'Drug', label: '药品' }, + { value: 'Check', label: '检查' } ], - configs: [], - parsedStyles: {}, + configs:[], + parsedStyles:{}, enToZhLabelMap: { Disease: '疾病', Drug: '药品', @@ -325,7 +314,7 @@ export default { this.loadDrugTreeData() this.loadCheckTreeData() this.loadDrugSubjectTreeData() - this.treeData = this.diseaseICD10Tree + this.treeData=this.diseaseICD10Tree await this.$nextTick(); try { await this.getDefault() @@ -367,7 +356,7 @@ export default { return { ...edge, id: edge.data?.relationship?.id || edge.id, - type: styleConf.edgeType || this.edgeType, + type: styleConf.edgeType ||this.edgeType, style: { endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, stroke: styleConf.edgeStroke || this.edgeStroke, @@ -443,7 +432,7 @@ export default { }, beforeUnmount() { - if (this._graph != null) { + if (this._graph!=null){ this._graph.stopLayout(); this.clearGraphState(); this._graph.destroy() @@ -482,7 +471,7 @@ export default { return {}; } }, - async getDefault() { + async getDefault(){ const response = await getGraphStyleActive(); const data = response.data; if (!Array.isArray(data) || data.length === 0) { @@ -650,7 +639,7 @@ export default { // 4️⃣ 聚焦第一个匹配项 const firstMatch = nodeIds[0] || edgeIds[0]; if (firstMatch && (this._graph.hasNode(firstMatch) || this._graph.hasEdge(firstMatch))) { - this._graph.focusElement(firstMatch, {animation: true, duration: 600}); + this._graph.focusElement(firstMatch, { animation: true, duration: 600 }); } }, clearSearchHighlight() { @@ -664,26 +653,26 @@ export default { this._graph.setElementState(id, 'normal', true); }); }, - getCount() { - getCount().then(res => { - this.diseaseCount = res.Disease - this.drugCount = res.Drug - this.checkCount = res.Check + getCount(){ + getCount().then(res=>{ + this.diseaseCount=res.Disease + this.drugCount=res.Drug + this.checkCount=res.Check console.log(res) }) }, buildCategoryIndex() { const index = {}; - if (this._graph != null) { + if (this._graph!=null){ const nodes = this._graph.getNodeData() // 获取所有节点 nodes.forEach(node => { console.log(node.data.label) const category = node.data.label; // 假设 label 是类别 - if (category == 'Drug' || category == 'Symptom' || - category == 'Disease' || category == 'Check') { + if(category=='Drug'||category=='Symptom'|| + category=='Disease'||category=='Check'){ if (!index[category]) index[category] = []; index[category].push(node.id); - } else { + }else{ if (!index["Other"]) index["Other"] = []; index["Other"].push(node.id); } @@ -693,7 +682,7 @@ export default { }, // 切换某个类别的显示状态 - toggleCategory(key) { + toggleCategory (key){ if (this.visibleCategories.has(key)) { this.visibleCategories.delete(key) } else { @@ -748,28 +737,28 @@ export default { } }, - changeTree() { - if (this.typeRadio == "Disease") { - if (this.DiseaseRadio == "ICD10") { - this.treeData = this.diseaseICD10Tree + changeTree(){ + if(this.typeRadio=="Disease"){ + if(this.DiseaseRadio=="ICD10") { + this.treeData=this.diseaseICD10Tree } - if (this.DiseaseRadio == "Department") { - this.treeData = this.diseaseDepartTree + if(this.DiseaseRadio=="Department") { + this.treeData=this.diseaseDepartTree } - if (this.DiseaseRadio == "SZM") { - this.treeData = this.diseaseSZMTree + if(this.DiseaseRadio=="SZM") { + this.treeData=this.diseaseSZMTree } } - if (this.typeRadio == "Drug") { - if (this.DrugRadio == "Subject") { - this.treeData = this.drugSubjectTree + if(this.typeRadio=="Drug") { + if(this.DrugRadio=="Subject") { + this.treeData=this.drugSubjectTree } - if (this.DrugRadio == "SZM") { - this.treeData = this.drugTree + if(this.DrugRadio=="SZM") { + this.treeData=this.drugTree } } - if (this.typeRadio == "Check") { - this.treeData = this.checkTree + if(this.typeRadio=="Check") { + this.treeData=this.checkTree } }, async loadDiseaseICD10TreeData() { @@ -829,15 +818,15 @@ export default { const response = await getGraph(data); // 等待 Promise 解析 this.formatData(response) } - if (data.level == "category" || - data.level == "subcategory" || - data.level == "diagnosis") { - data.type = "Disease" + if(data.level=="category"|| + data.level=="subcategory"|| + data.level=="diagnosis"){ + data.type="Disease" const response = await getGraph(data); // 等待 Promise 解析 this.formatData(response) } - if (data.type === "Disease") { - data.type = "Disease" + if(data.type === "Disease"){ + data.type="Disease" const response = await getGraph(data); // 等待 Promise 解析 this.formatData(response) } @@ -853,17 +842,17 @@ export default { // 1. 清除所有节点和边的状态 this._graph.getNodeData().forEach(node => { - this._graph.setElementState(node.id, []); + this._graph.setElementState(node.id,[]); }); this._graph.getEdgeData().forEach(edge => { - this._graph.setElementState(edge.id, []); + this._graph.setElementState(edge.id,[]); }); // 2. (可选)取消所有 pending 的交互 // 比如如果你有高亮定时器,这里 clearTimeout }, - formatData(data) { + formatData(data){ this._graph.stopLayout(); this.clearGraphState(); @@ -904,7 +893,7 @@ export default { return { ...edge, id: edge.data?.relationship?.id || edge.id, - type: styleConf.edgeType || this.edgeType, + type: styleConf.edgeType ||this.edgeType, style: { endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, stroke: styleConf.edgeStroke || this.edgeStroke, @@ -950,7 +939,7 @@ export default { }, initGraph() { - if (this._graph != null) { + if (this._graph!=null){ this._graph.destroy() this._graph = null; } @@ -961,7 +950,7 @@ export default { console.log(this._nodeLabelMap) const container = this.$refs.graphContainer; if (!container) return; - if (container != null) { + if (container!=null){ const width = container.clientWidth || 800; const height = container.clientHeight || 600; console.log(width) @@ -1001,8 +990,8 @@ export default { easing: 'ease-in-out', // 动画缓动函数 }, }, - behaviors: ['zoom-canvas', 'drag-element', - 'click-select', 'focus-element', { + behaviors: [ 'zoom-canvas', 'drag-element', + 'click-select','focus-element', { type: 'hover-activate', degree: 1, }, @@ -1049,7 +1038,7 @@ export default { shadowBlur: 10, opacity: 1 }, - highlight: { + highlight:{ stroke: '#FF5722', lineWidth: 4, opacity: 1 @@ -1057,7 +1046,7 @@ export default { inactive: { opacity: 0.8 }, - normal: { + normal:{ opacity: 1 } @@ -1105,14 +1094,14 @@ export default { inactive: { opacity: 0.8 }, - normal: { + normal:{ opacity: 1 } }, }, - data: this.defaultData, + data:this.defaultData, }); this.$nextTick(() => { @@ -1170,23 +1159,23 @@ export default { // }); graph.on('node:click', (evt) => { const nodeItem = evt.target.id; // 获取当前鼠标进入的节点元素 - let node = graph.getNodeData(nodeItem).data - let data = { - label: node.name, - type: node.label + let node=graph.getNodeData(nodeItem).data + let data={ + label:node.name, + type:node.label } - getGraph(data).then(response => { + getGraph(data).then(response=>{ console.log(response) this.formatData(response) }); // 等待 Promise 解析 }); graph.once('afterlayout', () => { if (!graph.destroyed) { - graph.fitCenter({padding: 40, duration: 1000}); + graph.fitCenter({ padding: 40, duration: 1000 }); } }); this._graph = graph - this._graph.setPlugins([{ + this._graph.setPlugins([ { type: 'tooltip', // 只对节点启用,边不显示tooltip enable: (e) => e.targetType === 'edge', @@ -1195,7 +1184,7 @@ export default { console.log(e) const edge = items[0]; // 当前悬停的边 if (!edge) return ''; - const data = items[0].data + const data=items[0].data const sourceId = edge.source; const targetId = edge.target; @@ -1206,73 +1195,16 @@ export default { const sourceName = sourceNode?.data.name || sourceId; const targetName = targetNode?.data.name || targetId; - const rel = data.relationship.properties.label || '关联'; + const rel = data.relationship.properties.label || '关联'; return `
${sourceName} — ${rel} —> ${targetName}
`; }, - }, - { - type: 'toolbar', - onClick: (id) => { - if (id === 'reset') { - // 1. 如果是重置,直接在本地执行方法 - this.localResetGraph(); - } else if (this.$refs.toolbarRef) { - // 2. 其他功能(放大、导出等)继续交给组件处理 - this.$refs.toolbarRef.handleToolbarAction(id); - } - }, - getItems: () => { - return [ - {id: 'zoom-in', value: 'zoom-in', title: '放大'}, - {id: 'zoom-out', value: 'zoom-out', title: '缩小'}, - {id: 'auto-fit', value: 'auto-fit', title: '聚焦'}, - {id: 'reset', value: 'reset', title: '重置'}, - {id: 'export', value: 'export', title: '导出图谱'}, - ]; - }, - }, - ]) - } - }, - localResetGraph() { - // 1. 检查图表实例和数据是否存在 - if (!this._graph) return; - - if (!this.defaultData || !this.defaultData.nodes) { - this.$message.warning("未找到可重置的数据源"); - return; + },]) } - try { - // 2. 彻底清理鼠标指针状态 - const canvas = this._graph.getCanvas(); - if (canvas && typeof canvas.setCursor === 'function') { - canvas.setCursor('default'); - } - - // 3. 彻底销毁当前图谱实例 - // 销毁会移除所有 DOM 节点和事件监听,彻底解决 EventBoundary 问题 - this._graph.destroy(); - this._graph = null; - // 4. 重新初始化 - this.$nextTick(() => { - // initGraph 内部会自动读取 this.defaultData - this.initGraph(); - - // 5. 重新构建分类索引(因为 destroy 也会清空之前的交互索引) - this.buildCategoryIndex(); - - this.$message.success("图谱已重置"); - }); - } catch (err) { - console.error('重置图谱失败:', err); - // 降级处理:如果销毁失败,尝试刷新页面(可选) - // location.reload(); - } }, updateGraph(data) { @@ -1286,7 +1218,7 @@ export default { const updatedNodes = this.defaultData.nodes.map(node => ({ ...node, type: this.nodeShape, - style: { + style:{ size: this.nodeSize, fill: this.nodeFill, stroke: this.nodeStroke, @@ -1309,7 +1241,7 @@ export default { if (!this._graph) return const updatedEdges = this.defaultData.edges.map(edge => ({ ...edge, - type: this.edgeType, + type:this.edgeType, style: { endArrow: this.edgeEndArrow, stroke: this.edgeStroke, @@ -1544,7 +1476,6 @@ button:hover { width: 16px; } - /*.search-btn:hover { background-color: rgba(34, 101, 244, 0.64); border-radius: 50%; @@ -1593,14 +1524,12 @@ button:hover { padding: 0 2px 0px 15px; justify-content: space-between; } - -.d-title { +.d-title{ display: flex; align-items: center; font-size: 13px; } - -.d-count { +.d-count{ font-size: 9px; background-color: #5989F0; border-radius: 7px; @@ -1647,21 +1576,19 @@ button:hover { text-align: left; font-size: 12px; } - -.disease-body { +.disease-body{ width: 360px; overflow: scroll; height: 74vh; } - /* 隐藏滚动条,但允许滚动 */ .disease-body { /* Firefox */ scrollbar-width: none; /* 'auto' | 'thin' | 'none' */ /* Webkit (Chrome, Safari, Edge) */ - -ms-overflow-style: none; /* IE and Edge */ - scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ padding-bottom: 9px; } @@ -1679,12 +1606,12 @@ button:hover { /deep/ .radio-drug .el-radio__input.is-checked .el-radio__inner { background: #52c41a; /* 检查的颜色 */ - border-color: #52c41a; + border-color:#52c41a; } /deep/ .radio-check .el-radio__input.is-checked .el-radio__inner { background: #1890ff; /* 药品的颜色 */ - border-color: #1890ff; + border-color:#1890ff; } @@ -1697,28 +1624,27 @@ button:hover { /deep/ .radio-drug .el-radio__input.is-checked .el-radio__inner:hover { background: #52c41a; /* 检查的颜色 */ - border-color: #52c41a; + border-color:#52c41a; } /deep/ .radio-check .el-radio__input.is-checked .el-radio__inner:hover { background: #1890ff; /* 药品的颜色 */ - border-color: #1890ff; + border-color:#1890ff; } + /* 自定义选中后的样式 */ -/deep/ .radio-disease .el-radio__input.is-checked + .el-radio__label { +/deep/ .radio-disease .el-radio__input.is-checked+.el-radio__label { color: rgb(153, 10, 0); } -/deep/ .radio-drug .el-radio__input.is-checked + .el-radio__label { +/deep/ .radio-drug .el-radio__input.is-checked+.el-radio__label { color: #52c41a; } - -/deep/ .radio-check .el-radio__input.is-checked + .el-radio__label { +/deep/ .radio-check .el-radio__input.is-checked+.el-radio__label { color: #1890ff; } - /* 自定义下拉样式 */ .select-container { position: relative; @@ -1748,7 +1674,7 @@ button:hover { background: rgba(255, 255, 255, 0.64); border: 1px solid #e0e0e0; border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10; min-width: 80px; color: #000; diff --git a/vue/src/system/GraphQA.vue b/vue/src/system/GraphQA.vue index 5c1860f..badd374 100644 --- a/vue/src/system/GraphQA.vue +++ b/vue/src/system/GraphQA.vue @@ -33,7 +33,7 @@
-{{queryRecord}} + {{queryRecord}}

问答结果

@@ -63,12 +63,6 @@
- -
@@ -79,21 +73,21 @@ \ No newline at end of file diff --git a/vue/src/system/GraphStyle.vue b/vue/src/system/GraphStyle.vue index 5011d83..46c917c 100644 --- a/vue/src/system/GraphStyle.vue +++ b/vue/src/system/GraphStyle.vue @@ -53,7 +53,7 @@
- +
@@ -82,7 +82,7 @@
- +
@@ -90,7 +90,7 @@
- +
@@ -140,7 +140,7 @@
- +
@@ -169,7 +169,7 @@
- +
@@ -1034,7 +1034,7 @@ export default { } }; }); - this._graph.setData({nodes, edges}); + this._graph.setData({ nodes, edges }); this._graph.render(); }, safeNum(val, defaultVal = 1) { @@ -1050,7 +1050,7 @@ export default { ...conf, styles: typeof conf.styles === 'string' ? JSON.parse(conf.styles) : conf.styles })); - return {...group, configs: uniqueConfigs}; + return { ...group, configs: uniqueConfigs }; }); const activeGroup = this.styleGroups.find(g => g.is_active); @@ -1103,27 +1103,19 @@ export default { const isLabelExist = group.configs.some(c => c.current_label === labelName && c.id !== excludeId); if (isLabelExist) { - ElMessageBox.alert(`方案【${groupName}】中已存在【${labelName}】标签的配置。`, '校验失败', {type: 'error'}).catch(() => { - }); + ElMessageBox.alert(`方案【${groupName}】中已存在【${labelName}】标签的配置,请先删除旧配置或选择其他方案。`, '校验失败', { type: 'error' }).catch(() => {}); return false; } if (group.configs.length >= 5 && !group.configs.some(c => c.id === excludeId)) { - ElMessageBox.alert(`方案【${groupName}】已满(上限5个)。`, '校验失败', {type: 'error'}).catch(() => { - }); + ElMessageBox.alert(`方案【${groupName}】的配置已满(上限5个),无法添加。`, '校验失败', { type: 'error' }).catch(() => {}); return false; } return true; }, async moveConfigToGroup(config, targetGroup) { - // 1. 拦截无效移动 - if (config.group_id === targetGroup.id) { - return ElMessage.info("该配置已在该方案中"); - } - if (!this.validateGroupConstraint(targetGroup.group_name, config.current_label, config.id)) return; - try { // 2. 构造 Payload:直接传入 target_group_id const payload = { @@ -1186,8 +1178,7 @@ export default { const missingTags = REQUIRED_TAGS.filter(tag => !new Set(currentLabels).has(tag)); if (missingTags.length > 0) { this.isInitialEcho = false; - return ElMessageBox.alert(`该方案配置不完整,缺失:${missingTags.join('、')}`, '提示', {type: 'warning'}).catch(() => { - }); + return ElMessageBox.alert(`该方案配置不完整,缺失:${missingTags.join('、')}`, '提示', { type: 'warning' }).catch(() => {}); } this.usingConfigIds = group.configs.map(c => c.id); const res = await applyGraphStyleGroup(group.id); @@ -1774,7 +1765,6 @@ export default { border-color: #e6e6e6 !important; color: #333 !important; } - :deep(.el-message-box__btns .el-button--primary) { background-color: #1559f3 !important; border-color: #1559f3 !important;