|
|
|
@ -79,23 +79,21 @@ |
|
|
|
|
|
|
|
<script> |
|
|
|
import Menu from "@/components/Menu.vue"; |
|
|
|
import {qaAnalyze} from "@/api/qa"; |
|
|
|
import {Graph} from "@antv/g6"; |
|
|
|
import {getGraph} from "@/api/graph"; |
|
|
|
import {getGraphStyleActive} from "@/api/style"; |
|
|
|
import { qaAnalyze } from "@/api/qa"; |
|
|
|
import { Graph } from "@antv/g6"; |
|
|
|
import { getGraphStyleActive } from "@/api/style"; |
|
|
|
import GraphToolbar from '@/components/GraphToolbar.vue'; |
|
|
|
|
|
|
|
|
|
|
|
export default { |
|
|
|
name: 'GraghQA', |
|
|
|
components: {Menu,GraphToolbar}, |
|
|
|
components: { Menu, GraphToolbar }, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
_graph: null, |
|
|
|
query:"", |
|
|
|
answers:[], |
|
|
|
selected:0, |
|
|
|
// 节点样式 |
|
|
|
query: "", |
|
|
|
answers: [], |
|
|
|
selected: 0, |
|
|
|
// 节点基础样式 |
|
|
|
nodeShowLabel: true, |
|
|
|
nodeFontSize: 12, |
|
|
|
nodeFontColor: '#fff', |
|
|
|
@ -106,7 +104,7 @@ export default { |
|
|
|
nodeLineWidth: 2, |
|
|
|
nodeFontFamily: 'Microsoft YaHei, sans-serif', |
|
|
|
|
|
|
|
// 边样式 |
|
|
|
// 边基础样式 |
|
|
|
edgeShowLabel: true, |
|
|
|
edgeFontSize: 10, |
|
|
|
edgeFontColor: '#666666', |
|
|
|
@ -116,11 +114,10 @@ export default { |
|
|
|
edgeEndArrow: true, |
|
|
|
edgeFontFamily: 'Microsoft YaHei, sans-serif', |
|
|
|
|
|
|
|
|
|
|
|
queryRecord:"", |
|
|
|
isSending:false, |
|
|
|
configs:[], |
|
|
|
parsedStyles:{}, |
|
|
|
queryRecord: "", |
|
|
|
isSending: false, |
|
|
|
configs: [], |
|
|
|
parsedStyles: {}, |
|
|
|
enToZhLabelMap: { |
|
|
|
Disease: '疾病', |
|
|
|
Drug: '药品', |
|
|
|
@ -128,41 +125,31 @@ export default { |
|
|
|
Symptom: '症状', |
|
|
|
Other: '其他' |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
}, |
|
|
|
// =============== 👇【新增】组件内路由守卫:离开当前路由时触发 =============== |
|
|
|
|
|
|
|
beforeRouteLeave(to, from, next) { |
|
|
|
this.saveDataToLocalStorage(); |
|
|
|
next(); // 允许导航 |
|
|
|
next(); |
|
|
|
}, |
|
|
|
// ======================================================================= |
|
|
|
|
|
|
|
async mounted() { |
|
|
|
await this.getDefault() |
|
|
|
// =============== 👇【新增】页面加载时从 localStorage 恢复数据 =============== |
|
|
|
await this.getDefault(); |
|
|
|
this.restoreDataFromLocalStorage(); |
|
|
|
// ======================================================================= |
|
|
|
// this.answers=[] |
|
|
|
// 如果有初始数据,可以初始化图谱(可选) |
|
|
|
if (this.answers.length > 0) { |
|
|
|
this.initGraph(this.answers[0].result); |
|
|
|
// console.log(this.answers[0].result) |
|
|
|
this.initGraph(this.answers[this.selected].result); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
beforeUnmount() { |
|
|
|
// =============== 👇【新增】组件销毁前也保存一次(兼容非路由跳转场景)============== |
|
|
|
this.saveDataToLocalStorage(); |
|
|
|
// 移除页面卸载监听(如果用了 beforeunload) |
|
|
|
window.removeEventListener('beforeunload', this.handleBeforeUnload); |
|
|
|
// ======================================================================= |
|
|
|
}, |
|
|
|
|
|
|
|
created() { |
|
|
|
// =============== 👇【新增】监听页面刷新/关闭事件 =============== |
|
|
|
window.addEventListener('beforeunload', this.handleBeforeUnload); |
|
|
|
// ======================================================================= |
|
|
|
}, |
|
|
|
|
|
|
|
methods: { |
|
|
|
safeParseStyles(stylesStr) { |
|
|
|
try { |
|
|
|
@ -172,7 +159,8 @@ export default { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
}, |
|
|
|
async getDefault(){ |
|
|
|
|
|
|
|
async getDefault() { |
|
|
|
const response = await getGraphStyleActive(); |
|
|
|
const data = response.data; |
|
|
|
if (!Array.isArray(data) || data.length === 0) { |
|
|
|
@ -180,27 +168,23 @@ export default { |
|
|
|
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) |
|
|
|
}, |
|
|
|
|
|
|
|
buildNodeLabelMap(nodes) { |
|
|
|
this._nodeLabelMap = new Map(); |
|
|
|
nodes.forEach(node => { |
|
|
|
this._nodeLabelMap.set(node.id, node.data?.type || 'default'); |
|
|
|
}); |
|
|
|
}, |
|
|
|
// =============== 👇【新增】统一的数据保存方法 =============== |
|
|
|
|
|
|
|
saveDataToLocalStorage() { |
|
|
|
try { |
|
|
|
localStorage.setItem('graphQA_queryRecord', this.queryRecord); |
|
|
|
@ -210,72 +194,53 @@ export default { |
|
|
|
console.warn('⚠️ 无法保存到 localStorage:', e); |
|
|
|
} |
|
|
|
}, |
|
|
|
// ======================================================================= |
|
|
|
|
|
|
|
// =============== 👇【新增】统一的数据恢复方法 =============== |
|
|
|
restoreDataFromLocalStorage() { |
|
|
|
try { |
|
|
|
const savedQuery = localStorage.getItem('graphQA_queryRecord'); |
|
|
|
const savedAnswers = localStorage.getItem('graphQA_answers'); |
|
|
|
|
|
|
|
if (savedQuery !== null) { |
|
|
|
this.queryRecord = savedQuery; |
|
|
|
} |
|
|
|
if (savedQuery !== null) this.queryRecord = savedQuery; |
|
|
|
if (savedAnswers !== null) { |
|
|
|
this.answers = JSON.parse(savedAnswers); |
|
|
|
// 确保 selected 不越界 |
|
|
|
if (this.answers.length > 0) { |
|
|
|
this.selected = Math.min(this.selected, this.answers.length - 1); |
|
|
|
} |
|
|
|
} |
|
|
|
console.log('✅ 数据已从 localStorage 恢复'); |
|
|
|
} catch (e) { |
|
|
|
console.warn('⚠️ 无法从 localStorage 恢复数据:', e); |
|
|
|
// 出错时清空(避免脏数据) |
|
|
|
localStorage.removeItem('graphQA_queryRecord'); |
|
|
|
localStorage.removeItem('graphQA_answers'); |
|
|
|
} |
|
|
|
}, |
|
|
|
// ======================================================================= |
|
|
|
|
|
|
|
// =============== 👇【新增】处理页面关闭/刷新的兜底保存 =============== |
|
|
|
handleBeforeUnload(event) { |
|
|
|
this.saveDataToLocalStorage(); |
|
|
|
// 注意:现代浏览器通常不显示自定义消息 |
|
|
|
event.preventDefault(); |
|
|
|
event.returnValue = ''; // 必须设置才能触发提示(但实际可能不显示) |
|
|
|
event.returnValue = ''; |
|
|
|
}, |
|
|
|
selectGraph(index){ |
|
|
|
this.selected=index |
|
|
|
if(this.answers.length>0){ |
|
|
|
this.formatData(this.answers[index].result) |
|
|
|
} |
|
|
|
|
|
|
|
selectGraph(index) { |
|
|
|
this.selected = index; |
|
|
|
if (this.answers.length > 0) { |
|
|
|
this.formatData(this.answers[index].result); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleSearch() { |
|
|
|
alert('方法触发成功!'); |
|
|
|
console.log('--- 1. 发起搜索,参数为:', this.query); |
|
|
|
this.isSending = true; |
|
|
|
this.answers = []; |
|
|
|
|
|
|
|
if (this._graph) { |
|
|
|
this._graph.clear(); |
|
|
|
} |
|
|
|
if (this._graph) this._graph.clear(); |
|
|
|
|
|
|
|
let data = { text: this.query }; |
|
|
|
this.queryRecord = this.query; |
|
|
|
this.query = ""; |
|
|
|
|
|
|
|
// 1. 调用接口 |
|
|
|
qaAnalyze(data).then(res => { |
|
|
|
console.log('--- 2. 接口响应成功 ---'); |
|
|
|
this.answers = res; |
|
|
|
if (this.answers && this.answers.length > 0) { |
|
|
|
this.initGraph(this.answers[0].result); |
|
|
|
} |
|
|
|
this.isSending = false; |
|
|
|
}).catch(err => { |
|
|
|
console.error('--- 2. 接口失败,启动保底方案 ---', err); |
|
|
|
console.error('接口失败,启动保底方案', err); |
|
|
|
const mockData = { |
|
|
|
nodes: [ |
|
|
|
{ id: "node1", label: "霍乱", data: { type: "疾病" } }, |
|
|
|
@ -284,7 +249,6 @@ export default { |
|
|
|
{ id: "node4", label: "呕吐", data: { type: "疾病" } }, |
|
|
|
{ id: "node5", label: "霍乱弧菌", data: { type: "病因" } }, |
|
|
|
{ id: "node6", label: "复方磺胺", data: { type: "药品" } }, |
|
|
|
|
|
|
|
], |
|
|
|
edges: [ |
|
|
|
{ id: "e1", source: "node1", target: "node2", data: { label: "典型症状" } }, |
|
|
|
@ -294,38 +258,46 @@ export default { |
|
|
|
{ id: "e5", source: "node1", target: "node6", data: { label: "推荐用药" } }, |
|
|
|
] |
|
|
|
}; |
|
|
|
|
|
|
|
// 伪造一个和接口返回格式一样的数组 |
|
|
|
this.answers = [{ |
|
|
|
answer: "后端接口连接失败,当前显示的是预览版图谱。", |
|
|
|
result: mockData |
|
|
|
}]; |
|
|
|
|
|
|
|
// 强制执行初始化,让工具栏出来 |
|
|
|
this.answers = [{ answer: "连接失败,显示预览版。", result: mockData }]; |
|
|
|
this.initGraph(this.answers[0].result); |
|
|
|
|
|
|
|
this.isSending = false; |
|
|
|
this.$message.warning("已切换至离线预览模式"); |
|
|
|
}); |
|
|
|
}, |
|
|
|
formatData(data){ |
|
|
|
// this._graph.stopLayout(); |
|
|
|
// this.clearGraphState(); |
|
|
|
// === 1. 构建 nodeId → label 映射 === |
|
|
|
const nodeIdToEnLabel = {}; |
|
|
|
|
|
|
|
formatData(data) { |
|
|
|
const typeMap = { '疾病': 'Disease', '药品': 'Drug', '药物': 'Drug', '症状': 'Symptom', '检查': 'Check', '病因': 'Cause' }; |
|
|
|
const getStandardLabel = (rawType) => typeMap[rawType] || rawType; |
|
|
|
|
|
|
|
const nodeIdToData = {}; |
|
|
|
data.nodes.forEach(node => { |
|
|
|
nodeIdToEnLabel[node.id] = node.data.type; // e.g. "Disease" |
|
|
|
nodeIdToData[node.id] = { |
|
|
|
enLabel: getStandardLabel(node.data.type), |
|
|
|
rawType: node.data.type |
|
|
|
}; |
|
|
|
}); |
|
|
|
// === 2. 处理节点:根据自身 label 设置样式 === |
|
|
|
|
|
|
|
const updatedNodes = data.nodes.map(node => { |
|
|
|
const enLabel = node.data.type; |
|
|
|
const enLabel = getStandardLabel(node.data.type); |
|
|
|
const styleConf = this.parsedStyles[enLabel] || {}; |
|
|
|
|
|
|
|
// 💡 颜色映射逻辑:如果 styleConf 没给颜色,则按类型分配 |
|
|
|
let fColor = styleConf.nodeFill; |
|
|
|
if (!fColor) { |
|
|
|
if (node.data.type === '疾病') fColor = '#EF4444'; |
|
|
|
else if (node.data.type === '药品' || node.data.type === '药物') fColor = '#91cc75'; |
|
|
|
else if (node.data.type === '症状') fColor = '#fac858'; |
|
|
|
else if (node.data.type === '检查') fColor = '#336eee'; |
|
|
|
else fColor = this.nodeFill; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...node, |
|
|
|
type: styleConf.nodeShape || this.nodeShape, |
|
|
|
data: { ...node.data, label: enLabel, name: node.label }, |
|
|
|
style: { |
|
|
|
...node.style, |
|
|
|
size: styleConf.nodeSize || this.nodeSize, |
|
|
|
fill: styleConf.nodeFill || this.nodeFill, |
|
|
|
fill: fColor, |
|
|
|
stroke: styleConf.nodeStroke || this.nodeStroke, |
|
|
|
lineWidth: styleConf.nodeLineWidth || this.nodeLineWidth, |
|
|
|
label: styleConf.nodeShowLabel !== undefined ? styleConf.nodeShowLabel : true, |
|
|
|
@ -336,55 +308,53 @@ export default { |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 3. 处理边:根据 source 节点的 label 设置样式 === |
|
|
|
const updatedEdges = data.edges.map(edge => { |
|
|
|
console.log(edge) |
|
|
|
const sourceEnLabel = nodeIdToEnLabel[edge.source]; // e.g. "Disease" |
|
|
|
const styleConf = this.parsedStyles[sourceEnLabel] || {}; |
|
|
|
const sourceInfo = nodeIdToData[edge.source]; |
|
|
|
const styleConf = this.parsedStyles[sourceInfo.enLabel] || {}; |
|
|
|
|
|
|
|
// 💡 边颜色逻辑:跟随源节点类型 |
|
|
|
let eStroke = styleConf.edgeStroke; |
|
|
|
if (!eStroke) { |
|
|
|
if (sourceInfo.rawType === '疾病') eStroke = 'rgba(239, 68, 68, 0.4)'; |
|
|
|
else if (sourceInfo.rawType === '药品' || sourceInfo.rawType === '药物') eStroke = 'rgba(145, 204, 117, 0.4)'; |
|
|
|
else if (sourceInfo.rawType === '症状') eStroke = 'rgba(250, 200, 88, 0.4)'; |
|
|
|
else eStroke = this.edgeStroke; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...edge, |
|
|
|
id: edge.data?.relationship?.id || edge.id, |
|
|
|
type: styleConf.edgeType ||this.edgeType, |
|
|
|
type: styleConf.edgeType || this.edgeType, |
|
|
|
style: { |
|
|
|
...edge.style, |
|
|
|
endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, |
|
|
|
stroke: styleConf.edgeStroke || this.edgeStroke, |
|
|
|
stroke: eStroke, |
|
|
|
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 |
|
|
|
} |
|
|
|
}, |
|
|
|
data: { ...edge.data, label: edge.data?.label || "" }, |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 4. 更新图数据 === |
|
|
|
let updatedData = { |
|
|
|
nodes: updatedNodes, |
|
|
|
edges: updatedEdges |
|
|
|
}; |
|
|
|
|
|
|
|
this.updateGraph(updatedData) |
|
|
|
const pureData = JSON.parse(JSON.stringify({ nodes: updatedNodes, edges: updatedEdges })); |
|
|
|
this.updateGraph(pureData); |
|
|
|
}, |
|
|
|
updateGraph(data) { |
|
|
|
if (!this._graph) return |
|
|
|
|
|
|
|
this._graph.setData(data) |
|
|
|
this._graph.render() |
|
|
|
updateGraph(data) { |
|
|
|
if (!this._graph) return; |
|
|
|
this._graph.setData(data); |
|
|
|
this._graph.render(); |
|
|
|
}, |
|
|
|
|
|
|
|
localResetGraph() { |
|
|
|
if (!this._graph) return; |
|
|
|
|
|
|
|
// 1. 获取当前选中的数据快照 |
|
|
|
const currentResult = this.answers[this.selected]?.result; |
|
|
|
if (!currentResult) return; |
|
|
|
|
|
|
|
// 2. 彻底销毁当前图谱实例(这是清理 EventBoundary 报错的终极手段) |
|
|
|
this._graph.destroy(); |
|
|
|
this._graph = null; |
|
|
|
|
|
|
|
// 3. 重新调用 initGraph,相当于重新加载页面时的纯净状态 |
|
|
|
// 这种方式不会闪烁,因为 initGraph 内部包含了一套完整的 render 流程 |
|
|
|
this.$nextTick(() => { |
|
|
|
this.initGraph(currentResult); |
|
|
|
this.$message.success("图谱已重置"); |
|
|
|
@ -392,27 +362,43 @@ export default { |
|
|
|
}, |
|
|
|
|
|
|
|
initGraph(data) { |
|
|
|
if (this._graph!=null){ |
|
|
|
this._graph.destroy() |
|
|
|
const typeMap = { '疾病': 'Disease', '药品': 'Drug', '药物': 'Drug', '症状': 'Symptom', '检查': 'Check', '病因': 'Cause' }; |
|
|
|
const getStandardLabel = (rawType) => typeMap[rawType] || rawType; |
|
|
|
|
|
|
|
if (this._graph != null) { |
|
|
|
this._graph.destroy(); |
|
|
|
this._graph = null; |
|
|
|
} |
|
|
|
console.log(data) |
|
|
|
// === 1. 构建 nodeId → label 映射 === |
|
|
|
const nodeIdToEnLabel = {}; |
|
|
|
|
|
|
|
const nodeIdToData = {}; |
|
|
|
data.nodes.forEach(node => { |
|
|
|
nodeIdToEnLabel[node.id] = node.data.type; // e.g. "Disease" |
|
|
|
nodeIdToData[node.id] = { |
|
|
|
enLabel: getStandardLabel(node.data.type), |
|
|
|
rawType: node.data.type |
|
|
|
}; |
|
|
|
}); |
|
|
|
console.log(nodeIdToEnLabel) |
|
|
|
// === 2. 处理节点:根据自身 label 设置样式 === |
|
|
|
|
|
|
|
const updatedNodes = data.nodes.map(node => { |
|
|
|
const enLabel = node.data.type; |
|
|
|
const enLabel = getStandardLabel(node.data.type); |
|
|
|
const styleConf = this.parsedStyles[enLabel] || {}; |
|
|
|
|
|
|
|
let fColor = styleConf.nodeFill; |
|
|
|
if (!fColor) { |
|
|
|
if (node.data.type === '疾病') fColor = '#EF4444'; |
|
|
|
else if (node.data.type === '药品' || node.data.type === '药物') fColor = '#91cc75'; |
|
|
|
else if (node.data.type === '症状') fColor = '#fac858'; |
|
|
|
else if (node.data.type === '检查') fColor = '#336eee'; |
|
|
|
else fColor = this.nodeFill; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...node, |
|
|
|
type: styleConf.nodeShape || this.nodeShape, |
|
|
|
data: { ...node.data, label: enLabel, name: node.label }, |
|
|
|
style: { |
|
|
|
...node.style, |
|
|
|
size: styleConf.nodeSize || this.nodeSize, |
|
|
|
fill: styleConf.nodeFill || this.nodeFill, |
|
|
|
fill: fColor, |
|
|
|
stroke: styleConf.nodeStroke || this.nodeStroke, |
|
|
|
lineWidth: styleConf.nodeLineWidth || this.nodeLineWidth, |
|
|
|
label: styleConf.nodeShowLabel !== undefined ? styleConf.nodeShowLabel : true, |
|
|
|
@ -423,19 +409,27 @@ export default { |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 3. 处理边:根据 source 节点的 label 设置样式 === |
|
|
|
const updatedEdges = data.edges.map(edge => { |
|
|
|
console.log(edge) |
|
|
|
const sourceEnLabel = nodeIdToEnLabel[edge.source]; // e.g. "Disease" |
|
|
|
const styleConf = this.parsedStyles[sourceEnLabel] || {}; |
|
|
|
const sourceInfo = nodeIdToData[edge.source]; |
|
|
|
const styleConf = this.parsedStyles[sourceInfo.enLabel] || {}; |
|
|
|
|
|
|
|
let eStroke = styleConf.edgeStroke; |
|
|
|
if (!eStroke) { |
|
|
|
if (sourceInfo.rawType === '疾病') eStroke = 'rgba(239, 68, 68, 0.4)'; |
|
|
|
else if (sourceInfo.rawType === '药品' || sourceInfo.rawType === '药物') eStroke = 'rgba(145, 204, 117, 0.4)'; |
|
|
|
else if (sourceInfo.rawType === '症状') eStroke = 'rgba(250, 200, 88, 0.4)'; |
|
|
|
else eStroke = this.edgeStroke; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...edge, |
|
|
|
id: edge.data?.relationship?.id || edge.id, |
|
|
|
type: styleConf.edgeType ||this.edgeType, |
|
|
|
type: styleConf.edgeType || this.edgeType, |
|
|
|
data: { ...edge.data, label: edge.data?.label || "" }, |
|
|
|
style: { |
|
|
|
...edge.style, |
|
|
|
endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, |
|
|
|
stroke: styleConf.edgeStroke || this.edgeStroke, |
|
|
|
stroke: eStroke, |
|
|
|
lineWidth: styleConf.edgeLineWidth || this.edgeLineWidth, |
|
|
|
label: styleConf.edgeShowLabel !== undefined ? styleConf.edgeShowLabel : false, |
|
|
|
labelFontSize: styleConf.edgeFontSize || this.edgeFontSize, |
|
|
|
@ -445,19 +439,18 @@ export default { |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 4. 更新图数据 === |
|
|
|
let updatedData = { |
|
|
|
const finalData = JSON.parse(JSON.stringify({ |
|
|
|
nodes: updatedNodes, |
|
|
|
edges: updatedEdges |
|
|
|
}; |
|
|
|
this.buildNodeLabelMap(updatedNodes); |
|
|
|
})); |
|
|
|
|
|
|
|
this.buildNodeLabelMap(finalData.nodes); |
|
|
|
const container = this.$refs.graphContainer; |
|
|
|
console.log(container) |
|
|
|
if (container!=null){ |
|
|
|
|
|
|
|
if (container != null) { |
|
|
|
const width = container.clientWidth || 800; |
|
|
|
const height = container.clientHeight || 600; |
|
|
|
console.log(width) |
|
|
|
console.log(height) |
|
|
|
|
|
|
|
const graph = new Graph({ |
|
|
|
container, |
|
|
|
width, |
|
|
|
@ -467,73 +460,47 @@ export default { |
|
|
|
type: 'toolbar', |
|
|
|
key: 'g6-toolbar', |
|
|
|
onClick: (id) => { |
|
|
|
if (id === 'reset') { |
|
|
|
this.localResetGraph(); // 如果你有重置方法 |
|
|
|
} else if (this.$refs.toolbarRef) { |
|
|
|
// 调用 GraphToolbar.vue 组件内部的方法执行 放大/缩小/导出 |
|
|
|
this.$refs.toolbarRef.handleToolbarAction(id); |
|
|
|
} |
|
|
|
}, |
|
|
|
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: '导出图谱' }, |
|
|
|
]; |
|
|
|
if (id === 'reset') this.localResetGraph(); |
|
|
|
else if (this.$refs.toolbarRef) this.$refs.toolbarRef.handleToolbarAction(id); |
|
|
|
}, |
|
|
|
getItems: () => [ |
|
|
|
{ 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: '导出图谱' }, |
|
|
|
], |
|
|
|
}, |
|
|
|
{ type: 'history', key: 'history' }, // 撤销重做必须 |
|
|
|
{ type: 'history', key: 'history' }, |
|
|
|
], |
|
|
|
layout: { |
|
|
|
type: 'force', // 力导向布局 |
|
|
|
gravity: 0.3, // 重力系数,控制节点聚集程度 |
|
|
|
repulsion: 500, // 排斥力 |
|
|
|
attraction: 20, // 吸引力 |
|
|
|
preventOverlap: true // 防止节点重叠 |
|
|
|
|
|
|
|
|
|
|
|
type: 'force', |
|
|
|
gravity: 0.3, |
|
|
|
repulsion: 500, |
|
|
|
attraction: 20, |
|
|
|
preventOverlap: true |
|
|
|
}, |
|
|
|
behaviors: [ 'zoom-canvas', 'drag-element', |
|
|
|
'click-select','focus-element', { |
|
|
|
behaviors: [ |
|
|
|
'zoom-canvas', 'drag-element', 'click-select', 'focus-element', |
|
|
|
{ |
|
|
|
type: 'hover-activate', |
|
|
|
degree: 1, |
|
|
|
enable: (e) => |
|
|
|
{ |
|
|
|
return e.target && e.target.id && e.action !== 'drag'; |
|
|
|
} |
|
|
|
enable: (e) => e.target && e.target.id && e.action !== 'drag' |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: 'drag-canvas', |
|
|
|
enable: (event) => event.shiftKey === false, |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: 'brush-select', |
|
|
|
}, |
|
|
|
{ type: 'brush-select' }, |
|
|
|
], |
|
|
|
|
|
|
|
node: { |
|
|
|
style: { |
|
|
|
// fill: (d) => { |
|
|
|
// |
|
|
|
// const label = d.data?.type; |
|
|
|
// if (label === '疾病') return '#EF4444'; // 红 |
|
|
|
// if (label === '药品'||label === '药物') return '#91cc75'; // 绿 |
|
|
|
// if (label === '症状') return '#fac858'; // 橙 |
|
|
|
// if (label === '检查') return '#336eee'; // 橙 |
|
|
|
// return '#59d1d4'; // 默认灰蓝 |
|
|
|
// }, |
|
|
|
// stroke: (d) => { |
|
|
|
// const label = d.data?.type; |
|
|
|
// if (label === '疾病') return '#B91C1C'; |
|
|
|
// if (label === '药品'||label === '药物') return '#047857'; |
|
|
|
// if (label === '检查') return '#1D4ED8'; // 橙 |
|
|
|
// if (label === '症状') return '#B45309'; |
|
|
|
// return '#40999b'; |
|
|
|
// }, |
|
|
|
fill: (d) => d.style?.fill, |
|
|
|
stroke: (d) => d.style?.stroke, |
|
|
|
size: (d) => d.style?.size, |
|
|
|
lineWidth: (d) => d.style?.lineWidth, |
|
|
|
labelText: (d) => d.label, |
|
|
|
labelPlacement: 'center', |
|
|
|
labelWordWrap: true, |
|
|
|
@ -541,55 +508,37 @@ export default { |
|
|
|
labelMaxLines: 3, |
|
|
|
labelTextOverflow: 'ellipsis', |
|
|
|
labelTextAlign: 'center', |
|
|
|
labelFill: (d) => d.style?.labelFill, |
|
|
|
labelFontSize: (d) => d.style?.labelFontSize, |
|
|
|
labelFontFamily: (d) => d.style?.labelFontFamily, |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
state: { |
|
|
|
active: { |
|
|
|
lineWidth: 2, |
|
|
|
shadowColor: '#ffffff', |
|
|
|
shadowBlur: 10, |
|
|
|
fill: (d) => d.style?.fill, |
|
|
|
stroke: (d) => d.style?.stroke, |
|
|
|
lineWidth: 3, |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
inactive: { |
|
|
|
opacity: 0.3 |
|
|
|
}, |
|
|
|
normal:{ |
|
|
|
inactive: { opacity: 0.3 }, |
|
|
|
normal: { |
|
|
|
fill: (d) => d.style?.fill, |
|
|
|
stroke: (d) => d.style?.stroke, |
|
|
|
opacity: 1 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
|
|
|
|
edge: { |
|
|
|
style: { |
|
|
|
labelText: (d) => { |
|
|
|
return d.data.label}, |
|
|
|
// stroke: (d) => { |
|
|
|
// const targetLabel = this._nodeLabelMap.get(d.source); // d.target 是目标节点 ID |
|
|
|
// if (targetLabel === '疾病') return 'rgba(239,68,68,0.5)'; |
|
|
|
// if (targetLabel === '药品'||targetLabel === '药物') return 'rgba(145,204,117,0.5)'; |
|
|
|
// if (targetLabel === '症状') return 'rgba(250,200,88,0.5)'; |
|
|
|
// if (targetLabel === '检查') 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 |
|
|
|
// // 根据 target 节点类型返回对应浅色 |
|
|
|
// |
|
|
|
// if (targetLabel === 'Disease') return '#ff4444'; |
|
|
|
// if (targetLabel === 'Drug') return '#2f9b70'; |
|
|
|
// if (targetLabel === 'Symptom') return '#f89775'; |
|
|
|
// return '#6b91ff'; // default |
|
|
|
// } |
|
|
|
|
|
|
|
stroke: (d) => d.style?.stroke, |
|
|
|
lineWidth: (d) => d.style?.lineWidth, |
|
|
|
endArrow: (d) => d.style?.endArrow, |
|
|
|
labelText: (d) => d.data?.label, |
|
|
|
labelFill: (d) => d.style?.labelFill, |
|
|
|
labelFontSize: (d) => d.style?.labelFontSize, |
|
|
|
}, |
|
|
|
state: { |
|
|
|
selected: { |
|
|
|
stroke: '#1890FF', |
|
|
|
lineWidth: 2, |
|
|
|
}, |
|
|
|
selected: { stroke: '#1890FF', lineWidth: 2 }, |
|
|
|
highlight: { |
|
|
|
halo: true, |
|
|
|
haloStroke: '#1890FF', |
|
|
|
@ -598,31 +547,19 @@ export default { |
|
|
|
lineWidth: 3, |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
inactive: { |
|
|
|
opacity: 0.3 |
|
|
|
}, |
|
|
|
normal:{ |
|
|
|
opacity: 1 |
|
|
|
} |
|
|
|
|
|
|
|
inactive: { opacity: 0.3 }, |
|
|
|
normal: { opacity: 1 } |
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
data:updatedData, |
|
|
|
|
|
|
|
|
|
|
|
data: finalData, |
|
|
|
}); |
|
|
|
|
|
|
|
graph.render(); |
|
|
|
this._graph = graph |
|
|
|
this._graph?.fitView() |
|
|
|
this._graph = graph; |
|
|
|
this._graph?.fitView(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}; |
|
|
|
</script> |
|
|
|
<style scoped> |
|
|
|
|