diff --git a/vue/src/components/GraphToolbar.vue b/vue/src/components/GraphToolbar.vue
index 9ae4173..b83a14d 100644
--- a/vue/src/components/GraphToolbar.vue
+++ b/vue/src/components/GraphToolbar.vue
@@ -127,9 +127,14 @@ export default {
},
exportToSVGManual() {
- const graph = this.graph;
- if (!graph) return;
+ const graph = this._graph || this.graph;
+ if (!graph) {
+ this.$message.error("未找到图表实例");
+ return;
+ }
+
const { nodes, edges } = graph.getData();
+ // 1. 计算画布边界,确保所有节点都在视口内
const bBox = graph.getCanvas().getRoot().getRenderBounds();
const padding = 60;
const minX = bBox.min[0] - padding;
@@ -139,7 +144,6 @@ export default {
const vWidth = maxX - minX;
const vHeight = maxY - minY;
- // 1. 定义与问答页面一致的颜色映射(用于兜底和逻辑计算)
const colorMap = {
'Disease': { fill: '#EF4444', stroke: '#B91C1C', edge: '#EF4444' },
'Drug': { fill: '#91cc75', stroke: '#047857', edge: '#91cc75' },
@@ -148,39 +152,101 @@ export default {
'Other': { fill: '#59d1d4', stroke: '#40999b', edge: '#59d1d4' }
};
+ // 2. 预收集颜色用于生成对应的箭头定义
+ const usedColors = new Set();
+ edges.forEach(edge => {
+ const s = nodes.find(n => n.id === edge.source);
+ const sourceType = s?.data?.label || 'Other';
+ const color = edge.style?.stroke || colorMap[sourceType]?.edge || '#cccccc';
+ usedColors.add(color);
+ });
+
let svgContent = ``;
- // 保存文件逻辑保持不变...
+ // 6. 导出文件
try {
- const blob = new Blob([svgContent], {type: 'image/svg+xml;charset=utf-8'});
+ const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
- link.download = `矢量图谱_${Date.now()}.svg`;
+ link.download = `医疗图谱导出_${Date.now()}.svg`;
+ document.body.appendChild(link);
link.click();
+ document.body.removeChild(link);
URL.revokeObjectURL(url);
- this.$message.success('全量矢量图导出成功');
+ this.$message.success('高精度矢量图导出成功');
} catch (err) {
this.$message.error('导出失败');
}
diff --git a/vue/src/system/GraphDemo.vue b/vue/src/system/GraphDemo.vue
index 1439871..fe1b291 100644
--- a/vue/src/system/GraphDemo.vue
+++ b/vue/src/system/GraphDemo.vue
@@ -134,6 +134,13 @@
@@ -155,14 +162,18 @@ 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},
+ components: {Menu, GraphToolbar},
data() {
return {
+ _graph: null,
G6: null, // 添加这个
// 节点样式
nodeShowLabel: true,
@@ -201,41 +212,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: '药品',
@@ -314,7 +325,7 @@ export default {
this.loadDrugTreeData()
this.loadCheckTreeData()
this.loadDrugSubjectTreeData()
- this.treeData=this.diseaseICD10Tree
+ this.treeData = this.diseaseICD10Tree
await this.$nextTick();
try {
await this.getDefault()
@@ -356,7 +367,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,
@@ -432,7 +443,7 @@ export default {
},
beforeUnmount() {
- if (this._graph!=null){
+ if (this._graph != null) {
this._graph.stopLayout();
this.clearGraphState();
this._graph.destroy()
@@ -471,7 +482,7 @@ export default {
return {};
}
},
- async getDefault(){
+ async getDefault() {
const response = await getGraphStyleActive();
const data = response.data;
if (!Array.isArray(data) || data.length === 0) {
@@ -639,7 +650,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() {
@@ -653,26 +664,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);
}
@@ -682,7 +693,7 @@ export default {
},
// 切换某个类别的显示状态
- toggleCategory (key){
+ toggleCategory(key) {
if (this.visibleCategories.has(key)) {
this.visibleCategories.delete(key)
} else {
@@ -737,28 +748,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() {
@@ -818,15 +829,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)
}
@@ -842,17 +853,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();
@@ -893,7 +904,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,
@@ -939,7 +950,7 @@ export default {
},
initGraph() {
- if (this._graph!=null){
+ if (this._graph != null) {
this._graph.destroy()
this._graph = null;
}
@@ -950,7 +961,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)
@@ -990,8 +1001,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,
},
@@ -1038,7 +1049,7 @@ export default {
shadowBlur: 10,
opacity: 1
},
- highlight:{
+ highlight: {
stroke: '#FF5722',
lineWidth: 4,
opacity: 1
@@ -1046,7 +1057,7 @@ export default {
inactive: {
opacity: 0.8
},
- normal:{
+ normal: {
opacity: 1
}
@@ -1094,14 +1105,14 @@ export default {
inactive: {
opacity: 0.8
},
- normal:{
+ normal: {
opacity: 1
}
},
},
- data:this.defaultData,
+ data: this.defaultData,
});
this.$nextTick(() => {
@@ -1159,10 +1170,10 @@ 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=>{
console.log(response)
@@ -1195,16 +1206,73 @@ 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) {
@@ -1218,7 +1286,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,
@@ -1645,6 +1713,7 @@ button:hover {
/deep/ .radio-check .el-radio__input.is-checked+.el-radio__label {
color: #1890ff;
}
+
/* 自定义下拉样式 */
.select-container {
position: relative;
@@ -1674,7 +1743,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/GraphStyle.vue b/vue/src/system/GraphStyle.vue
index 1c502f3..5011d83 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 @@
-
+
@@ -571,9 +571,10 @@ export default {
this.tagStyles[labelEn] = currentStyle;
this.updateAllElements();
- // 只有在编辑模式下才同步到数据库
+ // 只有在“编辑模式”下才触发后端自动同步
if (this.editingConfigId) {
if (this.saveTimer) clearTimeout(this.saveTimer);
+
const currentEditId = this.editingConfigId;
this.saveTimer = setTimeout(async () => {
try {
@@ -679,19 +680,280 @@ export default {
this.updateAllElements();
},
+ // --- 第三种方法:XML 模板映射法 ---
+ async handleExportClick() {
+ if (!this._graph) return;
+ ElMessageBox.confirm(
+ '请选择您要导出的图片格式:',
+ '导出图谱',
+ {
+ confirmButtonText: '导出为 PNG',
+ cancelButtonText: '导出为 SVG',
+ distinguishCancelAndClose: true,
+ type: 'info',
+ draggable: true,
+ }
+ ).then(async () => {
+ try {
+ // PNG 导出保持官方推荐姿势
+ const dataURL = await this._graph.toDataURL({
+ type: 'image/png',
+ backgroundColor: '#ffffff'
+ });
+ const link = document.createElement('a');
+ link.href = dataURL;
+ link.download = `图谱_${Date.now()}.png`;
+ link.click();
+ ElMessage.success('PNG 导出成功');
+ } catch (e) {
+ ElMessage.error('PNG 导出失败');
+ }
+ }).catch((action) => {
+ if (action === 'cancel') {
+ this.exportToSVGManual();
+ }
+ });
+ },
+
+ exportToSVGManual() {
+ const graph = this._graph;
+ if (!graph) return;
+ const { nodes, edges } = graph.getData();
+ const width = graph.getSize()[0];
+ const height = graph.getSize()[1];
+
+ const usedColors = new Set();
+ edges.forEach(edge => { if (edge.style?.stroke) usedColors.add(edge.style.stroke); });
+ if (usedColors.size === 0) usedColors.add('#cccccc');
+
+ let svgContent = ``;
+
+ // 4. 下载
+ const blob = new Blob([svgContent], { type: 'image/svg+xml;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `医疗图谱导出_${Date.now()}.svg`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ URL.revokeObjectURL(url);
+ },
+
+ initDraggableToolbar() {
+ const toolbar = document.querySelector('.draggable-toolbar');
+ if (!toolbar) return;
+ let isDragging = false;
+ let startPos = { x: 0, y: 0 };
+ let offset = { x: 0, y: 0 };
+ const onMouseDown = (e) => {
+ if (e.target.closest('.g6-toolbar-item')) return;
+ isDragging = true;
+ startPos = { x: e.clientX, y: e.clientY };
+ offset.x = e.clientX - toolbar.offsetLeft;
+ offset.y = e.clientY - toolbar.offsetTop;
+ toolbar.style.transition = 'none';
+ document.addEventListener('mousemove', onMouseMove);
+ document.addEventListener('mouseup', onMouseUp);
+ e.preventDefault();
+ };
+ const onMouseMove = (e) => {
+ if (!isDragging) return;
+ let left = e.clientX - offset.x;
+ let top = e.clientY - offset.y;
+ left = Math.max(10, Math.min(window.innerWidth - toolbar.offsetWidth - 10, left));
+ top = Math.max(10, Math.min(window.innerHeight - toolbar.offsetHeight - 10, top));
+ toolbar.style.left = left + 'px';
+ toolbar.style.top = top + 'px';
+ toolbar.style.right = 'auto';
+ };
+ const onMouseUp = () => {
+ isDragging = false;
+ document.removeEventListener('mousemove', onMouseMove);
+ document.removeEventListener('mouseup', onMouseUp);
+ };
+ toolbar.addEventListener('mousedown', onMouseDown);
+ },
+
initGraph() {
const container = this.$refs.graphContainer;
if (!container || container.clientWidth === 0) return;
this.defaultData.nodes.forEach(n => this._nodeLabelMap.set(n.id, n.data?.label));
if (this._graph) this._graph.destroy();
+
const graph = new Graph({
- container, width: container.clientWidth, height: container.clientHeight || 600,
+ container,
+ width: container.clientWidth,
+ height: container.clientHeight || 600,
layout: { type: 'radial', unitRadius: 100, preventOverlap: true, nodeSpacing: 50 },
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element', 'hover-activate'],
- autoFit: 'center', animation: false
+ autoFit: 'center',
+ animation: false,
+ plugins: [
+ { type: 'history', key: 'history' },
+ {
+ type: 'toolbar',
+ position: 'top-right',
+ className: 'g6-toolbar draggable-toolbar',
+ style: { marginRight: '320px', marginTop: '10px', cursor: 'move', zIndex: 999 },
+ onClick: (id) => {
+ const historyPlugin = this._graph.getPluginInstance('history');
+ switch (id) {
+ case 'zoom-in': this._graph.zoomBy(1.2); break;
+ case 'zoom-out': this._graph.zoomBy(0.8); break;
+ case 'undo':
+ if (historyPlugin && historyPlugin.canUndo()) historyPlugin.undo();
+ break;
+ case 'redo':
+ if (historyPlugin && historyPlugin.canRedo()) historyPlugin.redo();
+ break;
+ case 'auto-fit': this._graph.fitView(); break;
+ case 'reset':
+ const currentData = this._graph.getData();
+ const cleanNodes = currentData.nodes.map(node => {
+ const { x, y, ...rest } = node;
+ if (rest.style) {
+ delete rest.style.x;
+ delete rest.style.y;
+ }
+ return rest;
+ });
+ this._graph.zoomTo(1);
+ this._graph.translateTo([container.clientWidth / 2, container.clientHeight / 2]);
+ this._graph.setData({
+ nodes: cleanNodes,
+ edges: currentData.edges
+ });
+ this._graph.layout().then(() => {
+ this._graph.fitCenter();
+ });
+ ElMessage.success("已重置图谱位置");
+ break;
+ case 'export': this.handleExportClick(); break;
+ }
+ },
+ getItems: () => {
+ return [
+ { id: 'zoom-in', value: 'zoom-in', title: '放大' },
+ { id: 'zoom-out', value: 'zoom-out', title: '缩小' },
+ { id: 'undo', value: 'undo', title: '撤销' },
+ { id: 'redo', value: 'redo', title: '重做' },
+ { id: 'auto-fit', value: 'auto-fit', title: '聚焦' },
+ { id: 'reset', value: 'reset', title: '重置' },
+ { id: 'export', value: 'export', title: '导出图谱' },
+ ];
+ }
+ }
+ ]
});
this._graph = markRaw(graph);
this.updateAllElements();
+ this.$nextTick(() => {
+ setTimeout(this.initDraggableToolbar, 800);
+ });
},
getEffectiveStyleKey(label) {
return CORE_LABELS.includes(label) ? label : 'Other';
@@ -772,7 +1034,7 @@ export default {
}
};
});
- this._graph.setData({ nodes, edges });
+ this._graph.setData({nodes, edges});
this._graph.render();
},
safeNum(val, defaultVal = 1) {
@@ -788,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);
@@ -807,7 +1069,9 @@ export default {
this.applyStylesToPanel(currentActiveConf.styles);
this.editingConfigId = currentActiveConf.id;
this.editingConfigLabel = currentActiveConf.current_label;
- this.$nextTick(() => { this.isInitialEcho = false; });
+ this.$nextTick(() => {
+ this.isInitialEcho = false;
+ });
}
}
this.updateAllElements();
@@ -839,35 +1103,53 @@ 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 = {
- id: config.id, canvas_name: config.canvas_name, group_name: targetGroup.group_name,
- current_label: config.current_label, styles: config.styles, is_auto_save: false
+ id: config.id,
+ target_group_id: targetGroup.id, // 新增:直接传 ID,最准确D
+ canvas_name: config.canvas_name,
+ current_label: config.current_label,
+ styles: config.styles,
+ is_auto_save: false
};
+
const res = await saveGraphStyle(payload);
if (res.code === 200) {
ElMessage.success(`已移动至【${targetGroup.group_name}】`);
- await this.fetchConfigs();
+ await this.fetchConfigs(); // 刷新列表
+ } else {
+ ElMessage.error(res.msg || "移动失败");
}
- } catch (err) { ElMessage.error("操作失败"); }
+ } catch (err) {
+ ElMessage.error("移动操作异常");
+ }
},
-
handleSaveClick() {
this.fetchGroupNames();
this.saveForm.canvas_name = `${this.activeTags}_${Date.now()}`;
+ this.saveForm.group_name = '';
this.saveDialogVisible = true;
},
@@ -876,13 +1158,19 @@ export default {
if (!this.validateGroupConstraint(this.saveForm.group_name.trim(), this.activeTags)) return;
const labelEn = tagToLabelMap[this.activeTags];
const payload = {
- canvas_name: this.saveForm.canvas_name.trim(), group_name: this.saveForm.group_name.trim(),
- current_label: this.activeTags, styles: { ...this.tagStyles[labelEn] }, is_auto_save: false
+ canvas_name: this.saveForm.canvas_name.trim(),
+ group_name: this.saveForm.group_name.trim(),
+ current_label: this.activeTags,
+ styles: {...this.tagStyles[labelEn]},
+ is_auto_save: false // 标记为手动保存
};
const res = await saveGraphStyle(payload);
if (res.code === 200) {
ElMessage.success("保存成功");
this.saveDialogVisible = false;
+ this.editingConfigId = null;
+ this.editingConfigLabel = '';
+
await this.fetchConfigs();
}
},
@@ -898,7 +1186,8 @@ 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);
@@ -908,7 +1197,10 @@ export default {
if (group.configs.length > 0) this.handleEditConfig(group.configs[0]);
ElMessage.success(`已应用方案【${group.group_name}】`);
}
- } catch (err) { this.isInitialEcho = false; ElMessage.error("切换失败"); }
+ } catch (err) {
+ this.isInitialEcho = false;
+ ElMessage.error("切换失败");
+ }
},
resetStyle() {
@@ -928,8 +1220,12 @@ export default {
try {
await ElMessageBox.confirm('确定删除吗?', '提示');
const res = await deleteGraphStyle(id);
- if (res.code === 200) { ElMessage.success("删除成功"); this.fetchConfigs(); }
- } catch (err) { }
+ if (res.code === 200) {
+ ElMessage.success("删除成功");
+ this.fetchConfigs();
+ }
+ } catch (err) {
+ }
},
async deleteGroup(groupId) {
@@ -938,21 +1234,35 @@ export default {
try {
await ElMessageBox.confirm('确定删除全案吗?', '提示');
const res = await deleteGraphStyleGroup(groupId);
- if (res.code === 200) { ElMessage.success("已删除"); this.fetchConfigs(); }
- } catch (err) { }
+ if (res.code === 200) {
+ ElMessage.success("已删除");
+ this.fetchConfigs();
+ }
+ } catch (err) {
+ }
},
async handleUnifiedBatchDelete() {
if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) return;
try {
await ElMessageBox.confirm('确定批量删除吗?', '提示');
- if (this.checkedGroupIds.length > 0) { for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid); }
- if (this.checkedConfigIds.length > 0) { await batchDeleteGraphStyle({ ids: this.checkedConfigIds }); }
- ElMessage.success("成功"); this.clearSelection(); this.fetchConfigs();
- } catch (e) { }
+ if (this.checkedGroupIds.length > 0) {
+ for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid);
+ }
+ if (this.checkedConfigIds.length > 0) {
+ await batchDeleteGraphStyle({ids: this.checkedConfigIds});
+ }
+ ElMessage.success("成功");
+ this.clearSelection();
+ this.fetchConfigs();
+ } catch (e) {
+ }
},
- clearSelection() { this.checkedConfigIds = []; this.checkedGroupIds = []; },
+ clearSelection() {
+ this.checkedConfigIds = [];
+ this.checkedGroupIds = [];
+ },
handleResize() {
if (this._graph && this.$refs.graphContainer) {
this._graph.setSize(this.$refs.graphContainer.clientWidth, this.$refs.graphContainer.clientHeight);
@@ -961,6 +1271,7 @@ export default {
}
}
+