|
|
|
@ -94,7 +94,7 @@ export default { |
|
|
|
query:"", |
|
|
|
answers:[], |
|
|
|
selected:0, |
|
|
|
// 节点样式 |
|
|
|
// 默认节点样式 |
|
|
|
nodeShowLabel: true, |
|
|
|
nodeFontSize: 12, |
|
|
|
nodeFontColor: '#fff', |
|
|
|
@ -105,7 +105,7 @@ export default { |
|
|
|
nodeLineWidth: 2, |
|
|
|
nodeFontFamily: 'Microsoft YaHei, sans-serif', |
|
|
|
|
|
|
|
// 边样式 |
|
|
|
// 默认边样式 |
|
|
|
edgeShowLabel: true, |
|
|
|
edgeFontSize: 10, |
|
|
|
edgeFontColor: '#666666', |
|
|
|
@ -115,7 +115,6 @@ export default { |
|
|
|
edgeEndArrow: true, |
|
|
|
edgeFontFamily: 'Microsoft YaHei, sans-serif', |
|
|
|
|
|
|
|
|
|
|
|
queryRecord:"", |
|
|
|
isSending:false, |
|
|
|
configs:[], |
|
|
|
@ -127,41 +126,37 @@ export default { |
|
|
|
Symptom: '症状', |
|
|
|
Other: '其他' |
|
|
|
} |
|
|
|
|
|
|
|
}; |
|
|
|
}, |
|
|
|
|
|
|
|
// =============== 👇【新增】组件内路由守卫:离开当前路由时触发 =============== |
|
|
|
beforeRouteLeave(to, from, next) { |
|
|
|
this.saveDataToLocalStorage(); |
|
|
|
next(); // 允许导航 |
|
|
|
next();// 允许导航 |
|
|
|
|
|
|
|
}, |
|
|
|
// ======================================================================= |
|
|
|
|
|
|
|
async mounted() { |
|
|
|
await this.getDefault() |
|
|
|
await this.getDefault(); |
|
|
|
// =============== 👇【新增】页面加载时从 localStorage 恢复数据 =============== |
|
|
|
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 { |
|
|
|
@ -171,6 +166,7 @@ export default { |
|
|
|
return {}; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
async getDefault(){ |
|
|
|
const response = await getGraphStyleActive(); |
|
|
|
const data = response.data; |
|
|
|
@ -179,7 +175,6 @@ export default { |
|
|
|
this.parsedStyles = {}; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 只取第一个(即 is_active=1 的组) |
|
|
|
const activeGroup = data[0]; |
|
|
|
this.configs = Array.isArray(activeGroup.configs) ? activeGroup.configs : []; |
|
|
|
@ -187,91 +182,76 @@ export default { |
|
|
|
// 构建 label -> style 映射 |
|
|
|
const styleMap = {}; |
|
|
|
this.configs.forEach(config => { |
|
|
|
// 重要:这里的 current_label 通常是中文(疾病、药品) |
|
|
|
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); |
|
|
|
localStorage.setItem('graphQA_answers', JSON.stringify(this.answers)); |
|
|
|
console.log('✅ 数据已保存到 localStorage'); |
|
|
|
} catch (e) { |
|
|
|
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) |
|
|
|
this.selected = index; |
|
|
|
if(this.answers.length > 0){ |
|
|
|
this.formatData(this.answers[index].result); |
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
handleSearch() { |
|
|
|
this.isSending = true |
|
|
|
this.answers = [] |
|
|
|
if (this._graph) { |
|
|
|
this._graph.clear() |
|
|
|
} |
|
|
|
let data = { |
|
|
|
text: this.query |
|
|
|
} |
|
|
|
this.queryRecord = this.query |
|
|
|
this.query = "" |
|
|
|
// this.answers=[{"answer":"糖尿病患者应避免高糖食物,如糖果、甜点、含糖饮料等,以防止血糖波动。", |
|
|
|
// "result":{"nodes":[{"id":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","label":"糖尿病","type":"疾病"},{"id":"89f9498b-7a83-4361-889b-f96dfdfb802c","label":"血糖波动","type":"症状"},{"id":"03201620-ba9f-4957-b7d3-f89c5a115e37","label":"高糖食物","type":"其他类型"},{"id":"b0bace0a-eedc-485c-90d3-0a5378dc5556","label":"糖果","type":"其他类型"},{"id":"ffc12d7b-60e5-4ffa-a945-a0769f6e1047","label":"甜点","type":"其他类型"},{"id":"a7e94ee7-072b-456f-bc0c-354545851c38","label":"含糖饮料","type":"其他类型"}],"edges":[{"source":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","target":"03201620-ba9f-4957-b7d3-f89c5a115e37","label":"关联"},{"source":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","target":"b0bace0a-eedc-485c-90d3-0a5378dc5556","label":"关联"},{"source":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","target":"ffc12d7b-60e5-4ffa-a945-a0769f6e1047","label":"关联"},{"source":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","target":"a7e94ee7-072b-456f-bc0c-354545851c38","label":"关联"},{"source":"e0a8410b-c5ee-47d4-acbe-b7ae5f111fd4","target":"89f9498b-7a83-4361-889b-f96dfdfb802c","label":"导致"}]}}] |
|
|
|
// this.initGraph(this.answers[0].result) |
|
|
|
// this.formatData(this.answers[0].result) |
|
|
|
this.isSending = true; |
|
|
|
this.answers = []; |
|
|
|
if (this._graph) this._graph.clear(); |
|
|
|
let data = { text: this.query }; |
|
|
|
this.queryRecord = this.query; |
|
|
|
this.query = ""; |
|
|
|
|
|
|
|
qaAnalyze(data).then(res => { |
|
|
|
this.answers = res |
|
|
|
this.answers = res; |
|
|
|
if (this.answers.length > 0) { |
|
|
|
this.initGraph(this.answers[0].result) |
|
|
|
this.initGraph(this.answers[0].result); |
|
|
|
} |
|
|
|
this.isSending = false |
|
|
|
this.isSending = false; |
|
|
|
}).catch(err => { |
|
|
|
console.error('接口失败,启动保底方案', err); |
|
|
|
const mockData = { |
|
|
|
@ -296,36 +276,36 @@ export default { |
|
|
|
this.isSending = false; |
|
|
|
}); |
|
|
|
}, |
|
|
|
formatData(data){ |
|
|
|
// this._graph.stopLayout(); |
|
|
|
// this.clearGraphState(); |
|
|
|
const typeMap = { '疾病': 'Disease', '药品': 'Drug', '药物': 'Drug', '症状': 'Symptom', '检查': 'Check', '病因': 'Cause' }; |
|
|
|
const getStandardLabel = (rawType) => typeMap[rawType] || rawType; |
|
|
|
|
|
|
|
// 抽离统一的样式处理函数,供 initGraph 和 formatData 使用 |
|
|
|
processGraphData(data) { |
|
|
|
// 1. 获取节点类型映射 |
|
|
|
const nodeIdToData = {}; |
|
|
|
data.nodes.forEach(node => { |
|
|
|
nodeIdToData[node.id] = { |
|
|
|
enLabel: getStandardLabel(node.data.type), |
|
|
|
rawType: node.data.type |
|
|
|
}; |
|
|
|
nodeIdToData[node.id] = { rawType: node.data.type || '其他' }; |
|
|
|
}); |
|
|
|
// === 2. 处理节点:根据自身 label 设置样式 === |
|
|
|
|
|
|
|
// 2. 处理节点 |
|
|
|
const updatedNodes = data.nodes.map(node => { |
|
|
|
const enLabel = getStandardLabel(node.data.type); |
|
|
|
const styleConf = this.parsedStyles[enLabel] || {}; |
|
|
|
// 💡 颜色映射逻辑:如果 styleConf 没给颜色,则按类型分配 |
|
|
|
const zhLabel = node.data.type || '其他'; |
|
|
|
// 直接用中文 Label 去匹配样式配置 |
|
|
|
const styleConf = this.parsedStyles[zhLabel] || {}; |
|
|
|
|
|
|
|
// 颜色优先级:配置 > 硬编码 > 默认 |
|
|
|
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'; |
|
|
|
if (zhLabel === '疾病') fColor = '#EF4444'; |
|
|
|
else if (zhLabel === '药品' || zhLabel === '药物') fColor = '#91cc75'; |
|
|
|
else if (zhLabel === '症状') fColor = '#fac858'; |
|
|
|
else if (zhLabel === '检查') fColor = '#336eee'; |
|
|
|
else fColor = this.nodeFill; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
...node, |
|
|
|
type: styleConf.nodeShape || this.nodeShape, |
|
|
|
data: { ...node.data, label: enLabel, name: node.label }, |
|
|
|
// 保持 data 原样,但确保 G6 渲染需要的字段 |
|
|
|
data: { ...node.data, name: node.label }, |
|
|
|
style: { |
|
|
|
...node.style, |
|
|
|
size: styleConf.nodeSize || this.nodeSize, |
|
|
|
@ -335,17 +315,17 @@ export default { |
|
|
|
label: styleConf.nodeShowLabel !== undefined ? styleConf.nodeShowLabel : true, |
|
|
|
labelFontSize: styleConf.nodeFontSize || this.nodeFontSize, |
|
|
|
labelFontFamily: styleConf.nodeFontFamily || this.nodeFontFamily, |
|
|
|
labelFill: styleConf.nodeFontColor || this.nodeFontColor |
|
|
|
labelFill: styleConf.nodeFontColor || this.nodeFontColor, |
|
|
|
labelText: node.label // 显式传入文本防止丢失 |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 3. 处理边:根据 source 节点的 label 设置样式 === |
|
|
|
// 3. 处理边 |
|
|
|
const updatedEdges = data.edges.map(edge => { |
|
|
|
const sourceInfo = nodeIdToData[edge.source]; |
|
|
|
const styleConf = this.parsedStyles[sourceInfo.enLabel] || {}; |
|
|
|
const sourceInfo = nodeIdToData[edge.source] || { rawType: '其他' }; |
|
|
|
const styleConf = this.parsedStyles[sourceInfo.rawType] || {}; |
|
|
|
|
|
|
|
// 💡 边颜色逻辑:跟随源节点类型 |
|
|
|
let eStroke = styleConf.edgeStroke; |
|
|
|
if (!eStroke) { |
|
|
|
if (sourceInfo.rawType === '疾病') eStroke = 'rgba(239, 68, 68, 0.4)'; |
|
|
|
@ -357,267 +337,121 @@ export default { |
|
|
|
return { |
|
|
|
...edge, |
|
|
|
id: edge.data?.relationship?.id || edge.id, |
|
|
|
type: styleConf.edgeType ||this.edgeType, |
|
|
|
data: { ...edge.data, label: edge.data?.label || "" }, |
|
|
|
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 |
|
|
|
labelFill: styleConf.edgeFontColor || this.edgeFontColor, |
|
|
|
labelText: edge.data?.label || "" |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 4. 更新图数据 === |
|
|
|
const pureData = JSON.parse(JSON.stringify({ nodes: updatedNodes, edges: updatedEdges })); |
|
|
|
this.updateGraph(pureData); |
|
|
|
return { nodes: updatedNodes, edges: updatedEdges }; |
|
|
|
}, |
|
|
|
|
|
|
|
formatData(data){ |
|
|
|
const processed = this.processGraphData(data); |
|
|
|
this.buildNodeLabelMap(processed.nodes); |
|
|
|
this.updateGraph(processed); |
|
|
|
}, |
|
|
|
|
|
|
|
updateGraph(data) { |
|
|
|
if (!this._graph) return; |
|
|
|
this._graph.setData(data); |
|
|
|
this._graph.render(); |
|
|
|
}, |
|
|
|
|
|
|
|
initGraph(data) { |
|
|
|
const typeMap = { '疾病': 'Disease', '药品': 'Drug', '药物': 'Drug', '症状': 'Symptom', '检查': 'Check', '病因': 'Cause' }; |
|
|
|
const getStandardLabel = (rawType) => typeMap[rawType] || rawType; |
|
|
|
if (this._graph!=null){ |
|
|
|
this._graph.destroy() |
|
|
|
if (this._graph != null){ |
|
|
|
this._graph.destroy(); |
|
|
|
this._graph = null; |
|
|
|
} |
|
|
|
// === 1. 构建 nodeId → label 映射 === |
|
|
|
const nodeIdToData = {}; |
|
|
|
data.nodes.forEach(node => { |
|
|
|
nodeIdToData[node.id] = { |
|
|
|
enLabel: getStandardLabel(node.data.type), |
|
|
|
rawType: node.data.type |
|
|
|
}; |
|
|
|
}); |
|
|
|
// === 2. 处理节点:根据自身 label 设置样式 === |
|
|
|
const updatedNodes = data.nodes.map(node => { |
|
|
|
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: fColor, |
|
|
|
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 = data.edges.map(edge => { |
|
|
|
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; |
|
|
|
} |
|
|
|
const updatedData = this.processGraphData(data); |
|
|
|
this.buildNodeLabelMap(updatedData.nodes); |
|
|
|
|
|
|
|
return { |
|
|
|
...edge, |
|
|
|
id: edge.data?.relationship?.id || edge.id, |
|
|
|
type: styleConf.edgeType ||this.edgeType, |
|
|
|
data: { ...edge.data, label: edge.data?.label || "" }, |
|
|
|
style: { |
|
|
|
...edge.style, |
|
|
|
endArrow: styleConf.edgeEndArrow !== undefined ? styleConf.edgeEndArrow : true, |
|
|
|
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 |
|
|
|
} |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
// === 4. 更新图数据 === |
|
|
|
let updatedData = { |
|
|
|
nodes: updatedNodes, |
|
|
|
edges: updatedEdges |
|
|
|
}; |
|
|
|
this.buildNodeLabelMap(updatedNodes); |
|
|
|
const container = this.$refs.graphContainer; |
|
|
|
console.log(container) |
|
|
|
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, |
|
|
|
height, |
|
|
|
plugins: [ |
|
|
|
{ |
|
|
|
type: 'toolbar', |
|
|
|
key: 'g6-toolbar', |
|
|
|
onClick: (id) => { |
|
|
|
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: 'auto-fit', value: 'auto-fit', title: '聚焦' }, |
|
|
|
{ id: 'export', value: 'export', title: '导出图谱' }, |
|
|
|
], |
|
|
|
if (!container) return; |
|
|
|
|
|
|
|
const width = container.clientWidth || 800; |
|
|
|
const height = container.clientHeight || 600; |
|
|
|
|
|
|
|
const graph = new Graph({ |
|
|
|
container, |
|
|
|
width, |
|
|
|
height, |
|
|
|
plugins: [ |
|
|
|
{ |
|
|
|
type: 'toolbar', |
|
|
|
key: 'g6-toolbar', |
|
|
|
onClick: (id) => { |
|
|
|
if (this.$refs.toolbarRef) this.$refs.toolbarRef.handleToolbarAction(id); |
|
|
|
}, |
|
|
|
], |
|
|
|
layout: { |
|
|
|
type: 'force', // 力导向布局 |
|
|
|
gravity: 0.3, // 重力系数,控制节点聚集程度 |
|
|
|
repulsion: 500, // 排斥力 |
|
|
|
attraction: 20, // 吸引力 |
|
|
|
preventOverlap: true // 防止节点重叠 |
|
|
|
getItems: () => [ |
|
|
|
{ id: 'zoom-in', value: 'zoom-in', title: '放大' }, |
|
|
|
{ id: 'zoom-out', value: 'zoom-out', title: '缩小' }, |
|
|
|
{ id: 'auto-fit', value: 'auto-fit', title: '聚焦' }, |
|
|
|
{ id: 'export', value: 'export', title: '导出图谱' }, |
|
|
|
], |
|
|
|
}, |
|
|
|
behaviors: [ 'zoom-canvas', 'drag-element', |
|
|
|
'click-select','focus-element', { |
|
|
|
type: 'hover-activate', |
|
|
|
degree: 1, |
|
|
|
}, |
|
|
|
{ |
|
|
|
type: 'drag-canvas', |
|
|
|
enable: (event) => event.shiftKey === false, |
|
|
|
}, |
|
|
|
{ |
|
|
|
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'; |
|
|
|
// }, |
|
|
|
labelText: (d) => d.label, |
|
|
|
labelPlacement: 'center', |
|
|
|
labelWordWrap: true, |
|
|
|
labelMaxWidth: '150%', |
|
|
|
labelMaxLines: 3, |
|
|
|
labelTextOverflow: 'ellipsis', |
|
|
|
labelTextAlign: 'center', |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
state: { |
|
|
|
active: { |
|
|
|
lineWidth: 2, |
|
|
|
shadowColor: '#ffffff', |
|
|
|
shadowBlur: 10, |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
inactive: { |
|
|
|
opacity: 0.3 |
|
|
|
}, |
|
|
|
normal:{ |
|
|
|
opacity: 1 |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, |
|
|
|
], |
|
|
|
layout: { |
|
|
|
type: 'force', |
|
|
|
gravity: 0.3, |
|
|
|
repulsion: 500, |
|
|
|
attraction: 20, |
|
|
|
preventOverlap: true |
|
|
|
}, |
|
|
|
behaviors: [ |
|
|
|
'zoom-canvas', 'drag-element', 'click-select', 'focus-element', |
|
|
|
{ type: 'hover-activate', degree: 1 }, |
|
|
|
{ type: 'drag-canvas', enable: (event) => event.shiftKey === false }, |
|
|
|
{ type: 'brush-select' } |
|
|
|
], |
|
|
|
node: { |
|
|
|
style: { |
|
|
|
labelPlacement: 'center', |
|
|
|
labelWordWrap: true, |
|
|
|
labelMaxWidth: '150%', |
|
|
|
labelMaxLines: 3, |
|
|
|
labelTextOverflow: 'ellipsis', |
|
|
|
labelTextAlign: 'center', |
|
|
|
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 |
|
|
|
// } |
|
|
|
|
|
|
|
}, |
|
|
|
state: { |
|
|
|
selected: { |
|
|
|
stroke: '#1890FF', |
|
|
|
lineWidth: 2, |
|
|
|
}, |
|
|
|
highlight: { |
|
|
|
halo: true, |
|
|
|
haloStroke: '#1890FF', |
|
|
|
haloLineWidth: 6, |
|
|
|
haloStrokeOpacity: 0.3, |
|
|
|
lineWidth: 3, |
|
|
|
opacity: 1 |
|
|
|
}, |
|
|
|
inactive: { |
|
|
|
opacity: 0.3 |
|
|
|
}, |
|
|
|
normal:{ |
|
|
|
opacity: 1 |
|
|
|
} |
|
|
|
|
|
|
|
state: { |
|
|
|
active: { lineWidth: 2, shadowColor: '#ffffff', shadowBlur: 10, opacity: 1 }, |
|
|
|
inactive: { opacity: 0.3 }, |
|
|
|
normal: { opacity: 1 } |
|
|
|
}, |
|
|
|
}, |
|
|
|
edge: { |
|
|
|
style: { |
|
|
|
// 默认配置会在 updatedData 的 style 里被覆盖 |
|
|
|
}, |
|
|
|
state: { |
|
|
|
selected: { stroke: '#1890FF', lineWidth: 2 }, |
|
|
|
highlight: { |
|
|
|
halo: true, haloStroke: '#1890FF', haloLineWidth: 6, |
|
|
|
haloStrokeOpacity: 0.3, lineWidth: 3, opacity: 1 |
|
|
|
}, |
|
|
|
|
|
|
|
inactive: { opacity: 0.3 }, |
|
|
|
normal: { opacity: 1 } |
|
|
|
}, |
|
|
|
data:updatedData, |
|
|
|
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
graph.render(); |
|
|
|
this._graph = graph |
|
|
|
this._graph?.fitView() |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
data: updatedData, |
|
|
|
}); |
|
|
|
|
|
|
|
graph.render(); |
|
|
|
this._graph = graph; |
|
|
|
this._graph.fitView(); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
</script> |
|
|
|
<style scoped> |
|
|
|
|