From 91572f3d104cf7f5ec95b240fa633420fd8f7aa8 Mon Sep 17 00:00:00 2001 From: hanyuqing <1106611654@qq.com> Date: Wed, 7 Jan 2026 13:17:32 +0800 Subject: [PATCH] all --- controller/GraphStyleController.py | 9 ++ service/GraphStyleService.py | 46 ++++++++ vue/src/api/style.js | 7 ++ vue/src/system/GraphDemo.vue | 225 +++++++++++++++++++++++++++---------- vue/src/system/GraphStyle.vue | 78 ++++++------- 5 files changed, 269 insertions(+), 96 deletions(-) diff --git a/controller/GraphStyleController.py b/controller/GraphStyleController.py index 8141396..fd64104 100644 --- a/controller/GraphStyleController.py +++ b/controller/GraphStyleController.py @@ -50,6 +50,15 @@ async def get_grouped_style_list(request): return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) except Exception as e: return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"}) +@app.get("/api/graph/style/active") +async def get_active_style(request): + """获取【分组嵌套】格式的配置列表(用于右侧折叠面板)""" + try: + # 调用 Service 的嵌套聚合方法,现在内部已包含 is_active/is_default 逻辑 + data = GraphStyleService.get_active_configs() + return create_response(200, {"code": 200, "data": data, "msg": "查询成功"}) + except Exception as e: + return create_response(200, {"code": 500, "msg": f"查询异常: {str(e)}"}) @app.post("/api/graph/style/group/apply") diff --git a/service/GraphStyleService.py b/service/GraphStyleService.py index e41c38d..3a2c7e5 100644 --- a/service/GraphStyleService.py +++ b/service/GraphStyleService.py @@ -87,6 +87,52 @@ class GraphStyleService: return result @staticmethod + def get_active_configs() -> list: + """ + 获取 is_active = 1 的方案组及其配置项 + 返回长度为 0 或 1 的列表(因为通常只有一个激活组) + """ + # 1. 查询 is_active = 1 的组(按 id 排序,取第一个以防有多个) + group_sql = """ + SELECT id, group_name, is_active, is_default + FROM graph_style_groups + WHERE is_active = 1 + ORDER BY id ASC + LIMIT 1 + """ + groups = mysql_client.execute_query(group_sql) or [] + + if not groups: + return [] # 没有激活的组 + + group = groups[0] + group_id = group['id'] + + # 2. 查询该组下的所有配置 + configs_sql = """ + SELECT id, group_id, canvas_name, current_label, config_json, create_time + FROM graph_configs + WHERE group_id = %s + """ + configs = mysql_client.execute_query(configs_sql, (group_id,)) or [] + + # 3. 处理配置项 + for conf in configs: + if conf.get('config_json'): + try: + conf['styles'] = json.loads(conf['config_json']) + except (TypeError, ValueError): + conf['styles'] = {} + del conf['config_json'] + + if conf.get('create_time') and not isinstance(conf['create_time'], str): + conf['create_time'] = conf['create_time'].strftime('%Y-%m-%d %H:%M:%S') + + # 4. 组装结果 + group['configs'] = configs + + return [group] # 返回单元素列表,保持接口兼容性 + @staticmethod def apply_group_all(group_id: int) -> bool: """ 核心新增:应用全案 diff --git a/vue/src/api/style.js b/vue/src/api/style.js index ed88a88..85dd4ab 100644 --- a/vue/src/api/style.js +++ b/vue/src/api/style.js @@ -72,6 +72,13 @@ export function getGraphStyleGroups() { }); } +export function getGraphStyleActive() { + return request({ + url: '/api/graph/style/active', + method: 'get' + }); +} + /** * 获取所有图谱样式配置列表 * 保留此接口用于兼容旧版逻辑或后台管理 diff --git a/vue/src/system/GraphDemo.vue b/vue/src/system/GraphDemo.vue index d71bad0..d68488f 100644 --- a/vue/src/system/GraphDemo.vue +++ b/vue/src/system/GraphDemo.vue @@ -157,6 +157,7 @@ import Menu from "@/components/Menu.vue"; import {a} from "vue-router/dist/devtools-EWN81iOl.mjs"; import Fuse from 'fuse.js'; +import {getGraphStyleActive} from "@/api/style"; export default { name: 'Display', components: {Menu}, @@ -232,7 +233,16 @@ export default { { value: 'Disease', label: '疾病' }, { value: 'Drug', label: '药品' }, { value: 'Check', label: '检查' } - ] + ], + configs:[], + parsedStyles:{}, + enToZhLabelMap: { + Disease: '疾病', + Drug: '药品', + Check: '检查', + Symptom: '症状', + Other: '其他' + } } }, computed: { @@ -307,41 +317,66 @@ export default { this.treeData=this.diseaseICD10Tree await this.$nextTick(); try { + await this.getDefault() const response = await getTestGraphData(); // 等待 Promise 解析 - const updatedNodes = response.nodes.map(node => ({ - ...node, - type: this.nodeShape, - style:{ - size: this.nodeSize, - fill: this.nodeFill, - stroke: this.nodeStroke, - lineWidth: this.nodeLineWidth, - label: this.nodeShowLabel, - labelFontSize: this.nodeFontSize, - labelFontFamily: this.nodeFontFamily, - labelFill: this.nodeFontColor, - } - })) - const updatedEdges = response.edges.map(edge => ({ - ...edge, - id: edge.data.relationship.id, - type: this.edgeType, - style: { - endArrow: this.edgeEndArrow, - stroke: this.edgeStroke, - lineWidth: this.edgeLineWidth, - label: this.edgeShowLabel, - labelFontSize: this.edgeFontSize, - labelFontFamily: this.edgeFontFamily, - labelFill: this.edgeFontColor, - }, - })) - const updatedData = { + // === 1. 构建 nodeId → label 映射 === + const nodeIdToEnLabel = {}; + response.nodes.forEach(node => { + nodeIdToEnLabel[node.id] = node.data.label; // e.g. "Disease" + }); + + // === 2. 处理节点:根据自身 label 设置样式 === + const updatedNodes = response.nodes.map(node => { + const enLabel = node.data.label; + const zhLabel = this.enToZhLabelMap[enLabel] || '其他'; // 默认回退到“其他” + const styleConf = this.parsedStyles[zhLabel] || {}; + return { + ...node, + type: styleConf.nodeShape || this.nodeShape, + style: { + size: styleConf.nodeSize || this.nodeSize, + fill: styleConf.nodeFill || this.nodeFill, + stroke: styleConf.nodeStroke || this.nodeStroke, + lineWidth: styleConf.nodeLineWidth || this.nodeLineWidth, + label: styleConf.nodeShowLabel !== undefined ? styleConf.nodeShowLabel : true, + labelFontSize: styleConf.nodeFontSize || this.nodeFontSize, + labelFontFamily: styleConf.nodeFontFamily || this.nodeFontFamily, + labelFill: styleConf.nodeFontColor || this.nodeFontColor + } + }; + }); + + // === 3. 处理边:根据 source 节点的 label 设置样式 === + const updatedEdges = response.edges.map(edge => { + console.log(edge) + const sourceEnLabel = nodeIdToEnLabel[edge.source]; // e.g. "Disease" + const sourceZhLabel = this.enToZhLabelMap[sourceEnLabel] || '其他'; + const styleConf = this.parsedStyles[sourceZhLabel] || {}; + + return { + ...edge, + id: edge.data?.relationship?.id || edge.id, + type: styleConf.edgeType ||this.edgeType, + style: { + endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, + stroke: styleConf.edgeStroke || this.edgeStroke, + lineWidth: styleConf.edgeLineWidth || this.edgeLineWidth, + label: styleConf.edgeShowLabel !== undefined ? styleConf.edgeShowLabel : false, + labelFontSize: styleConf.edgeFontSize || this.edgeFontSize, + labelFontFamily: styleConf.edgeFontFamily || this.edgeFontFamily, + labelFill: styleConf.edgeFontColor || this.edgeFontColor + } + }; + }); + + // === 4. 更新图数据 === + let updatedData = { nodes: updatedNodes, edges: updatedEdges - } + }; + console.log(updatedData) this.defaultData = updatedData - setTimeout(() => { + setTimeout(() => { this.initGraph(); this.buildCategoryIndex(); window.addEventListener('resize', this.handleResize); @@ -349,6 +384,50 @@ export default { } catch (error) { console.error('加载图谱数据失败:', error); } + // try { + // await this.getDefault() + // const response = await getTestGraphData(); // 等待 Promise 解析 + // const updatedNodes = response.nodes.map(node => ({ + // ...node, + // type: this.nodeShape, + // style:{ + // size: this.nodeSize, + // fill: this.nodeFill, + // stroke: this.nodeStroke, + // lineWidth: this.nodeLineWidth, + // label: this.nodeShowLabel, + // labelFontSize: this.nodeFontSize, + // labelFontFamily: this.nodeFontFamily, + // labelFill: this.nodeFontColor, + // } + // })) + // const updatedEdges = response.edges.map(edge => ({ + // ...edge, + // id: edge.data.relationship.id, + // type: this.edgeType, + // style: { + // endArrow: this.edgeEndArrow, + // stroke: this.edgeStroke, + // lineWidth: this.edgeLineWidth, + // label: this.edgeShowLabel, + // labelFontSize: this.edgeFontSize, + // labelFontFamily: this.edgeFontFamily, + // labelFill: this.edgeFontColor, + // }, + // })) + // const updatedData = { + // nodes: updatedNodes, + // edges: updatedEdges + // } + // this.defaultData = updatedData + // setTimeout(() => { + // this.initGraph(); + // this.buildCategoryIndex(); + // window.addEventListener('resize', this.handleResize); + // }, 1000); + // } catch (error) { + // console.error('加载图谱数据失败:', error); + // } }, @@ -384,6 +463,36 @@ export default { edgeFontFamily: 'updateAllEdges', }, methods: { + safeParseStyles(stylesStr) { + try { + return JSON.parse(stylesStr || '{}'); + } catch (e) { + console.warn('Failed to parse styles:', stylesStr); + return {}; + } + }, + async getDefault(){ + const response = await getGraphStyleActive(); + const data = response.data; + if (!Array.isArray(data) || data.length === 0) { + this.configs = []; + this.parsedStyles = {}; + return; + } + + // 只取第一个(即 is_active=1 的组) + const activeGroup = data[0]; + this.configs = Array.isArray(activeGroup.configs) ? activeGroup.configs : []; + + // 构建 label -> style 映射 + const styleMap = {}; + this.configs.forEach(config => { + const label = config.current_label; + styleMap[label] = this.safeParseStyles(config.styles); + }); + this.parsedStyles = styleMap; + console.log(this.parsedStyles) + }, // 切换下拉菜单显隐 toggleDropdown() { this.isDropdownOpen = !this.isDropdownOpen; @@ -871,22 +980,22 @@ export default { node: { style: { - fill: (d) => { - const label = d.data?.label; - if (label === 'Disease') return '#EF4444'; // 红 - if (label === 'Drug') return '#91cc75'; // 绿 - if (label === 'Symptom') return '#fac858'; // 橙 - if (label === 'Check') return '#336eee'; // 橙 - return '#59d1d4'; // 默认灰蓝 - }, - stroke: (d) => { - const label = d.data?.label; - if (label === 'Disease') return '#B91C1C'; - if (label === 'Drug') return '#047857'; - if (label === 'Check') return '#1D4ED8'; // 橙 - if (label === 'Symptom') return '#B45309'; - return '#40999b'; - }, + // fill: (d) => { + // const label = d.data?.label; + // if (label === 'Disease') return '#EF4444'; // 红 + // if (label === 'Drug') return '#91cc75'; // 绿 + // if (label === 'Symptom') return '#fac858'; // 橙 + // if (label === 'Check') return '#336eee'; // 橙 + // return '#59d1d4'; // 默认灰蓝 + // }, + // stroke: (d) => { + // const label = d.data?.label; + // if (label === 'Disease') return '#B91C1C'; + // if (label === 'Drug') return '#047857'; + // if (label === 'Check') return '#1D4ED8'; // 橙 + // if (label === 'Symptom') return '#B45309'; + // return '#40999b'; + // }, labelText: (d) => d.data.name, labelPlacement: 'center', labelWordWrap: true, @@ -921,16 +1030,16 @@ export default { edge: { style: { labelText: (d) => d.data.relationship.properties.label, - stroke: (d) => { - // 获取 target 节点的 label - const targetLabel = this._nodeLabelMap.get(d.source); // d.target 是目标节点 ID - // 根据 target 节点类型返回对应浅色 - if (targetLabel === 'Disease') return 'rgba(239,68,68,0.5)'; - if (targetLabel === 'Drug') return 'rgba(145,204,117,0.5)'; - if (targetLabel === 'Symptom') return 'rgba(250,200,88,0.5)'; - if (targetLabel === 'Check') return 'rgba(51,110,238,0.5)'; // 橙 - return 'rgba(89,209,212,0.5)'; // default - }, + // stroke: (d) => { + // // 获取 target 节点的 label + // const targetLabel = this._nodeLabelMap.get(d.source); // d.target 是目标节点 ID + // // 根据 target 节点类型返回对应浅色 + // if (targetLabel === 'Disease') return 'rgba(239,68,68,0.5)'; + // if (targetLabel === 'Drug') return 'rgba(145,204,117,0.5)'; + // if (targetLabel === 'Symptom') return 'rgba(250,200,88,0.5)'; + // if (targetLabel === 'Check') return 'rgba(51,110,238,0.5)'; // 橙 + // return 'rgba(89,209,212,0.5)'; // default + // }, // labelFill: (d) => { // // 获取 target 节点的 label // const targetLabel = this._nodeLabelMap.get(d.target); // d.target 是目标节点 ID diff --git a/vue/src/system/GraphStyle.vue b/vue/src/system/GraphStyle.vue index 90b4659..09ead30 100644 --- a/vue/src/system/GraphStyle.vue +++ b/vue/src/system/GraphStyle.vue @@ -32,7 +32,7 @@