5 changed files with 1080 additions and 145 deletions
@ -0,0 +1,272 @@ |
|||
<template> |
|||
<div style="display: flex; |
|||
height: 100vh;"> |
|||
<Menu |
|||
:initial-active="1" |
|||
/> |
|||
<div class="qwen-chat"> |
|||
<!-- 聊天消息区域 --> |
|||
<div ref="messagesContainer" class="chat-messages"> |
|||
<div |
|||
v-for="(msg, index) in messages" |
|||
:key="index" |
|||
:class="['message', msg.role]" |
|||
> |
|||
<div class="bubble">{{ msg.content }}</div> |
|||
</div> |
|||
</div> |
|||
<!-- 输入区域(Qwen 风格) --> |
|||
<div class="input-area"> |
|||
<textarea |
|||
v-model="inputText" |
|||
placeholder="向千问提问" |
|||
class="input-box" |
|||
@keyup.enter.exact.prevent="sendMessage" |
|||
rows="1" |
|||
></textarea> |
|||
|
|||
<div class="action-buttons"> |
|||
<button class="btn" @click="insertPrefix('深度思考:')"> |
|||
<span>☰</span> 深度思考 |
|||
</button> |
|||
<button class="btn" @click="insertPrefix('深度研究:')"> |
|||
<span>🔍</span> 深度研究 |
|||
</button> |
|||
<button class="btn" @click="insertPrefix('```代码\n')"> |
|||
<span><></span> 代码 |
|||
</button> |
|||
<button class="btn" @click="insertPrefix('生成一张图片:')"> |
|||
<span>🖼️</span> 图像 |
|||
</button> |
|||
<button class="btn" @click="insertPrefix('翻译成英文:')"> |
|||
<span>🌐</span> 翻译 |
|||
</button> |
|||
<button class="btn more-btn">...</button> |
|||
</div> |
|||
|
|||
<div class="icon-group"> |
|||
<span class="icon" title="切换语言">🌍</span> |
|||
<span class="icon" title="编辑模式">✏️</span> |
|||
<button class="send-btn" @click="sendMessage" title="发送"> |
|||
<span>📤</span> |
|||
</button> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
import Menu from "@/components/Menu.vue"; |
|||
|
|||
export default { |
|||
name: 'QwenChat', |
|||
components: {Menu}, |
|||
data() { |
|||
return { |
|||
inputText: '', |
|||
messages: [ |
|||
{ role: 'assistant', content: '你好!我是 Qwen,有什么可以帮你的吗?' } |
|||
] |
|||
}; |
|||
}, |
|||
methods: { |
|||
sendMessage() { |
|||
const text = this.inputText.trim(); |
|||
if (!text) return; |
|||
|
|||
// 添加用户消息 |
|||
this.messages.push({ role: 'user', content: text }); |
|||
this.inputText = ''; |
|||
|
|||
// 模拟 AI 回复(可替换为真实 API) |
|||
setTimeout(() => { |
|||
this.messages.push({ |
|||
role: 'assistant', |
|||
content: `你刚才说:“${text}” —— 这是一个模拟回复。` |
|||
}); |
|||
}, 600); |
|||
}, |
|||
insertPrefix(prefix) { |
|||
this.inputText += prefix; |
|||
this.$nextTick(() => { |
|||
// 自动聚焦到 textarea 末尾(简单处理) |
|||
const el = this.$el.querySelector('.input-box'); |
|||
el.focus(); |
|||
}); |
|||
}, |
|||
scrollToBottom() { |
|||
this.$nextTick(() => { |
|||
const container = this.$refs.messagesContainer; |
|||
if (container) { |
|||
container.scrollTop = container.scrollHeight; |
|||
} |
|||
}); |
|||
} |
|||
}, |
|||
watch: { |
|||
messages() { |
|||
this.scrollToBottom(); |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.scrollToBottom(); |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.qwen-chat { |
|||
display: flex; |
|||
flex-direction: column; |
|||
height: 100vh; |
|||
max-width: 800px; |
|||
margin: 0 auto; |
|||
border: 1px solid #e0e0e0; |
|||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); |
|||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; |
|||
} |
|||
|
|||
/* === 消息区域 === */ |
|||
.chat-messages { |
|||
flex: 1; |
|||
padding: 16px; |
|||
overflow-y: auto; |
|||
background-color: #f9f9f9; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.message { |
|||
display: flex; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.message.user { |
|||
justify-content: flex-end; |
|||
} |
|||
|
|||
.message.assistant { |
|||
justify-content: flex-start; |
|||
} |
|||
|
|||
.bubble { |
|||
padding: 10px 14px; |
|||
border-radius: 18px; |
|||
max-width: 75%; |
|||
word-break: break-word; |
|||
line-height: 1.5; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.message.user .bubble { |
|||
background-color: #007bff; |
|||
color: white; |
|||
border-bottom-right-radius: 4px; |
|||
} |
|||
|
|||
.message.assistant .bubble { |
|||
background-color: #e9ecef; |
|||
color: #333; |
|||
border-bottom-left-radius: 4px; |
|||
} |
|||
|
|||
/* === 输入区域 === */ |
|||
.input-area { |
|||
padding: 16px; |
|||
background: white; |
|||
border-top: 1px solid #eee; |
|||
} |
|||
|
|||
.input-box { |
|||
width: 100%; |
|||
padding: 12px 16px; |
|||
border: 1px solid #d9d9d9; |
|||
border-radius: 12px; |
|||
font-size: 14px; |
|||
line-height: 1.5; |
|||
resize: none; |
|||
outline: none; |
|||
transition: border-color 0.2s ease; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.input-box:focus { |
|||
border-color: #007bff; |
|||
} |
|||
|
|||
.action-buttons { |
|||
display: flex; |
|||
gap: 8px; |
|||
margin: 12px 0; |
|||
overflow-x: auto; |
|||
white-space: nowrap; |
|||
padding: 0 4px; |
|||
} |
|||
|
|||
.btn { |
|||
padding: 6px 12px; |
|||
background: #f5f5f5; |
|||
border: 1px solid #ddd; |
|||
border-radius: 12px; |
|||
font-size: 12px; |
|||
color: #333; |
|||
cursor: pointer; |
|||
display: flex; |
|||
align-items: center; |
|||
gap: 4px; |
|||
} |
|||
|
|||
.btn:hover { |
|||
background: #e9e9e9; |
|||
} |
|||
|
|||
.btn span { |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.more-btn { |
|||
background: transparent; |
|||
border: 1px dashed #ccc; |
|||
color: #666; |
|||
} |
|||
|
|||
.icon-group { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
align-items: center; |
|||
gap: 12px; |
|||
margin-top: 8px; |
|||
} |
|||
|
|||
.icon, |
|||
.send-btn span { |
|||
font-size: 16px; |
|||
cursor: pointer; |
|||
color: #999; |
|||
} |
|||
|
|||
.icon:hover, |
|||
.send-btn:hover span { |
|||
color: #333; |
|||
} |
|||
|
|||
.send-btn { |
|||
background: #9b68ff; |
|||
border: none; |
|||
width: 32px; |
|||
height: 32px; |
|||
border-radius: 50%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: white; |
|||
cursor: pointer; |
|||
transition: background-color 0.2s; |
|||
} |
|||
|
|||
.send-btn:hover { |
|||
background: #8a53e0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,788 @@ |
|||
<template> |
|||
<div class="knowledge-graph-container"> |
|||
<Menu |
|||
:initial-active="5" |
|||
/> |
|||
<div class="control-panel"> |
|||
<!-- 节点控制 --> |
|||
<div class="section"> |
|||
<h5>节点控制</h5> |
|||
<label style="text-align: left;margin: 10px 0;display: flex;align-items: center;font-size: 12px;"> |
|||
<input v-model="nodeShowLabel" type="checkbox" /> |
|||
显示节点标签 |
|||
</label> |
|||
<div class="form-group"> |
|||
<label>字体名称:</label> |
|||
<select v-model="nodeFontFamily"> |
|||
<!-- 中文常用字体 --> |
|||
<option value="Microsoft YaHei, sans-serif">微软雅黑</option> |
|||
<option value="SimSun, serif">宋体(SimSun)</option> |
|||
<option value="SimHei, sans-serif">黑体(SimHei)</option> |
|||
<option value="KaiTi, serif">楷体(KaiTi)</option> |
|||
<option value="FangSong, serif">仿宋(FangSong)</option> |
|||
<option value="PingFang SC, Helvetica Neue, sans-serif">苹方(PingFang SC)</option> |
|||
|
|||
<!-- 英文/通用字体 --> |
|||
<option value="Arial, sans-serif">Arial</option> |
|||
<option value="Helvetica, sans-serif">Helvetica</option> |
|||
<option value="Times New Roman, serif">Times New Roman</option> |
|||
<option value="Georgia, serif">Georgia</option> |
|||
<option value="Courier New, monospace">Courier New(等宽)</option> |
|||
<option value="Verdana, sans-serif">Verdana</option> |
|||
<option value="Tahoma, sans-serif">Tahoma</option> |
|||
<option value="Impact, sans-serif">Impact</option> |
|||
</select> |
|||
</div> |
|||
<div class="slider-group"> |
|||
<span>字体大小: {{ nodeFontSize }}px</span> |
|||
<input |
|||
v-model.number="nodeFontSize" |
|||
type="range" |
|||
min="10" |
|||
max="24" |
|||
step="1" |
|||
/> |
|||
</div> |
|||
<div class="color-picker"> |
|||
<label>字体颜色:</label> |
|||
<input v-model="nodeFontColor" type="color" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>图形:</label> |
|||
<select v-model="nodeShape"> |
|||
<option value="circle">圆形</option> |
|||
<option value="diamond">菱形</option> |
|||
<option value="triangle">三角形</option> |
|||
<option value="rect">矩形</option> |
|||
<option value="star">星形</option> |
|||
<option value="hexagon">六边形</option> |
|||
</select> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>尺寸:</label> |
|||
<input v-model.number="nodeSize" type="number" min="30" max="100" /> |
|||
</div> |
|||
<div class="color-picker"> |
|||
<label>填充颜色:</label> |
|||
<input v-model="nodeFill" type="color" /> |
|||
</div> |
|||
<div class="color-picker"> |
|||
<label>边框颜色:</label> |
|||
<input v-model="nodeStroke" type="color" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>边框尺寸:</label> |
|||
<input v-model.number="nodeLineWidth" type="number" min="1" max="5" /> |
|||
</div> |
|||
</div> |
|||
<!-- 连边控制 --> |
|||
<div class="section"> |
|||
<h5>连边控制</h5> |
|||
<label style="text-align: left;margin: 10px 0;display: flex;align-items: center;font-size: 12px;"> |
|||
<input v-model="edgeShowLabel" type="checkbox" /> |
|||
显示连边标签 |
|||
<input v-model="edgeEndArrow" type="checkbox" style="margin-left: 10px"/> |
|||
显示端点箭头 |
|||
</label> |
|||
<div class="form-group"> |
|||
<label>字体名称:</label> |
|||
<select v-model="edgeFontFamily"> |
|||
<option value="Microsoft YaHei, sans-serif">微软雅黑</option> |
|||
<option value="SimSun, serif">宋体</option> |
|||
<option value="SimHei, sans-serif">黑体</option> |
|||
<option value="Arial, sans-serif">Arial</option> |
|||
<option value="sans-serif">无衬线(默认)</option> |
|||
</select> |
|||
</div> |
|||
<div class="slider-group"> |
|||
<span>字体大小: {{ edgeFontSize }}px</span> |
|||
<input |
|||
v-model.number="edgeFontSize" |
|||
type="range" |
|||
min="8" |
|||
max="16" |
|||
step="1" |
|||
/> |
|||
</div> |
|||
<div class="color-picker"> |
|||
<label>字体颜色:</label> |
|||
<input v-model="edgeFontColor" type="color" /> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>连边类型:</label> |
|||
<select v-model="edgeType"> |
|||
<option value="line">直线</option> |
|||
<option value="polyline">折线边</option> |
|||
<option value="cubic">三次贝塞尔曲线边</option> |
|||
<option value="cubic-horizontal">水平三次贝塞尔曲线边</option> |
|||
<option value="cubic-vertical">垂直三次贝塞尔曲线边</option> |
|||
<option value="quadratic">二次贝塞尔曲线边</option> |
|||
</select> |
|||
</div> |
|||
<div class="form-group"> |
|||
<label>线粗细:</label> |
|||
<input v-model.number="edgeLineWidth" type="number" min="1" max="5" /> |
|||
</div> |
|||
<div class="color-picker"> |
|||
<label>线条颜色:</label> |
|||
<input v-model="edgeStroke" type="color" /> |
|||
</div> |
|||
<!-- <label>--> |
|||
<!-- <input v-model="edgeEndArrow" type="checkbox" />--> |
|||
<!-- 显示端点箭头--> |
|||
<!-- </label>--> |
|||
</div> |
|||
<button @click="resetView">重置视图</button> |
|||
<button @click="resetStyle">重置样式</button> |
|||
<!-- 操作按钮 --> |
|||
</div> |
|||
<!-- 图谱容器 --> |
|||
<div ref="graphContainer" class="graph-container" id="container"></div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { getTestGraphData } from "@/api/graph" |
|||
import { Graph } from '@antv/g6'; |
|||
import Menu from "@/components/Menu.vue"; |
|||
|
|||
export default { |
|||
name: 'GraphDemo', |
|||
components: {Menu}, |
|||
data() { |
|||
return { |
|||
G6: null, // 添加这个 |
|||
// 节点样式 |
|||
nodeShowLabel: true, |
|||
nodeFontSize: 12, |
|||
nodeFontColor: '#fff', |
|||
nodeShape: 'circle', |
|||
nodeSize: 60, |
|||
nodeFill: '#9FD5FF', |
|||
nodeStroke: '#5B8FF9', |
|||
nodeLineWidth: 2, |
|||
nodeFontFamily: 'Microsoft YaHei, sans-serif', |
|||
|
|||
// 边样式 |
|||
edgeShowLabel: true, |
|||
edgeFontSize: 10, |
|||
edgeFontColor: '#666666', |
|||
edgeType: 'line', |
|||
edgeLineWidth: 2, |
|||
edgeStroke: '#F00', |
|||
edgeEndArrow: true, |
|||
edgeFontFamily: 'Microsoft YaHei, sans-serif', |
|||
|
|||
defaultData: { |
|||
"nodes": [ |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"data": { |
|||
"name": "霍乱", |
|||
"diagnosisCode": "A00.900", |
|||
"nodeId": 6978, |
|||
"label": "Disease" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:9461", |
|||
"data": { |
|||
"name": "腹泻", |
|||
"nodeId": 9464, |
|||
"label": "Symptom" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:5992", |
|||
"data": { |
|||
"name": "脱水", |
|||
"nodeId": 5995, |
|||
"label": "Disease" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:1277", |
|||
"data": { |
|||
"name": "呕吐", |
|||
"nodeId": 1280, |
|||
"label": "Disease" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:26775", |
|||
"data": { |
|||
"name": "由霍乱弧菌感染所致", |
|||
"nodeId": 26778, |
|||
"label": "Cause" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:48981", |
|||
"data": { |
|||
"name": "复方磺胺甲噁唑", |
|||
"nodeId": 48984, |
|||
"label": "Drug" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:17392", |
|||
"data": { |
|||
"name": "消化系统", |
|||
"nodeId": 17395, |
|||
"label": "DiseaseSite" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:18534", |
|||
"data": { |
|||
"name": "传染科", |
|||
"nodeId": 18537, |
|||
"label": "Department" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:413", |
|||
"data": { |
|||
"name": "代谢性 酸中毒", |
|||
"nodeId": 416, |
|||
"label": "Disease" |
|||
} |
|||
}, |
|||
{ |
|||
"id": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:2990", |
|||
"data": { |
|||
"name": "急性肾衰竭", |
|||
"nodeId": 2993, |
|||
"label": "Disease" |
|||
} |
|||
} |
|||
], |
|||
"edges": [ |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:9461", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "symptomAndSign", |
|||
"properties": { "label": "症状与体征" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:5992", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "complication", |
|||
"properties": { "label": "并发症" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:1277", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "complication", |
|||
"properties": { "label": "并发症" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:26775", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "cause", |
|||
"properties": { "label": "病因" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:48981", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "treatmentPrograms", |
|||
"properties": { "label": "治疗方案" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:17392", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "diseaseSite", |
|||
"properties": { "label": "病变部位" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:18534", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "department", |
|||
"properties": { "label": "科室" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:413", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "complication", |
|||
"properties": { "label": "并发症" } |
|||
} |
|||
} |
|||
}, |
|||
{ |
|||
"source": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:6975", |
|||
"target": "4:bc93ccaa-d618-48f0-b787-2d9496417bcd:2990", |
|||
"type": "line", |
|||
"data": { |
|||
"relationship": { |
|||
"type": "complication", |
|||
"properties": { "label": "并发症" } |
|||
} |
|||
} |
|||
} |
|||
] |
|||
}, |
|||
children: 'children', |
|||
label: 'title' // 虽然用插槽,但 el-tree 内部仍会读取,可留空或任意 |
|||
} |
|||
}, |
|||
|
|||
async mounted() { |
|||
await this.$nextTick(); |
|||
try { |
|||
const updatedNodes = this.defaultData.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 = this.defaultData.edges.map(edge => ({ |
|||
...edge, |
|||
type: this.edgeType, |
|||
id: edge.data.relationship.id, |
|||
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(); |
|||
window.addEventListener('resize', this.handleResize); |
|||
}, 1000); |
|||
} catch (error) { |
|||
console.error('加载图谱数据失败:', error); |
|||
} |
|||
}, |
|||
beforeUnmount() { |
|||
this._graph.destroy() |
|||
this._graph = null; |
|||
window.removeEventListener('resize', this.handleResize); |
|||
}, |
|||
watch: { |
|||
// 节点相关 |
|||
nodeShowLabel: 'updateAllNodes', |
|||
nodeFontSize: 'updateAllNodes', |
|||
nodeFontColor: 'updateAllNodes', |
|||
nodeShape: 'updateAllNodes', |
|||
nodeSize: 'updateAllNodes', |
|||
nodeFill: 'updateAllNodes', |
|||
nodeStroke: 'updateAllNodes', |
|||
nodeLineWidth: 'updateAllNodes', |
|||
nodeFontFamily: 'updateAllNodes', |
|||
|
|||
// 边相关 |
|||
edgeShowLabel: 'updateAllEdges', |
|||
edgeFontSize: 'updateAllEdges', |
|||
edgeFontColor: 'updateAllEdges', |
|||
edgeType: 'updateAllEdges', |
|||
edgeLineWidth: 'updateAllEdges', |
|||
edgeStroke: 'updateAllEdges', |
|||
edgeEndArrow: 'updateAllEdges', |
|||
edgeFontFamily: 'updateAllEdges', |
|||
}, |
|||
methods: { |
|||
buildNodeLabelMap(nodes) { |
|||
this._nodeLabelMap = new Map(); |
|||
nodes.forEach(node => { |
|||
this._nodeLabelMap.set(node.id, node.data?.label || 'default'); |
|||
}); |
|||
}, |
|||
initGraph() { |
|||
|
|||
if (this._graph!=null){ |
|||
this._graph.destroy() |
|||
this._graph = null; |
|||
} |
|||
if (!this._nodeLabelMap) { |
|||
this.buildNodeLabelMap(this.defaultData.nodes); |
|||
} |
|||
const container = this.$refs.graphContainer; |
|||
const width = container.clientWidth || 800; |
|||
const height = container.clientHeight || 600; |
|||
|
|||
const graph = new Graph({ |
|||
container, |
|||
width, |
|||
height, |
|||
layout: { |
|||
type: 'radial', |
|||
nodeSize: 32, |
|||
unitRadius: 100, |
|||
linkDistance: 200, |
|||
}, |
|||
behaviors: [ 'zoom-canvas', 'drag-element','click-select','focus-element', |
|||
{ |
|||
type: 'hover-activate', |
|||
degree: 1, // 👈🏻 Activate relations. |
|||
}, |
|||
{ |
|||
type: 'drag-canvas', |
|||
enable: (event) => event.shiftKey === false, |
|||
}, |
|||
{ |
|||
type: 'brush-select', |
|||
}, |
|||
], |
|||
|
|||
node: { |
|||
style: { |
|||
labelText: (d) => d.data.name, |
|||
labelPlacement: 'center', |
|||
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 '#59d1d4'; // 橙 |
|||
return '#336eee'; // 默认灰蓝 |
|||
}, |
|||
stroke: (d) => { |
|||
const label = d.data?.label; |
|||
if (label === 'Disease') return '#B91C1C'; |
|||
if (label === 'Drug') return '#047857'; |
|||
if (label === 'Check') return '#40999b'; // 橙 |
|||
if (label === 'Symptom') return '#B45309'; |
|||
return '#1D4ED8'; |
|||
}, |
|||
labelWordWrap: true, |
|||
labelMaxWidth: '150%', |
|||
labelMaxLines: 3, |
|||
labelTextOverflow: 'ellipsis', |
|||
labelTextAlign: 'center', |
|||
}, |
|||
state: { |
|||
|
|||
highlight: { |
|||
stroke: '#FF6A00', |
|||
lineWidth: 3, |
|||
}, |
|||
disabled: { |
|||
fill: '#ECECEC', |
|||
stroke: '#BFBFBF', |
|||
opacity: 0.5, |
|||
}, |
|||
}, |
|||
}, |
|||
edge: { |
|||
style: { |
|||
labelText: (d) => d.data.relationship.properties.label, |
|||
stroke: (d) => { |
|||
// 获取 target 节点的 label |
|||
const targetLabel = this._nodeLabelMap.get(d.target); // 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(89,209,212,0.5)'; // 橙 |
|||
return 'rgba(51,110,238,0.5)'; // default |
|||
}, |
|||
}, |
|||
state: { |
|||
selected: { |
|||
stroke: '#1890FF', |
|||
lineWidth: 2, |
|||
}, |
|||
highlight: { |
|||
stroke: '#FF6A00', |
|||
lineWidth: 3, |
|||
}, |
|||
}, |
|||
|
|||
}, |
|||
data:this.defaultData |
|||
}); |
|||
|
|||
graph.render(); |
|||
this._graph = graph |
|||
this._graph?.fitView() |
|||
}, |
|||
|
|||
updateGraph(data) { |
|||
if (!this._graph) return |
|||
this._graph.setData(data) |
|||
this._graph.render() |
|||
}, |
|||
|
|||
updateAllNodes() { |
|||
if (!this._graph) return |
|||
const updatedNodes = this.defaultData.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, |
|||
labelFontColor: this.nodeFontColor, |
|||
} |
|||
})) |
|||
const updatedData = { |
|||
nodes: updatedNodes, |
|||
edges: this.defaultData.edges |
|||
} |
|||
this.defaultData = updatedData |
|||
this.updateGraph(updatedData) |
|||
}, |
|||
|
|||
updateAllEdges() { |
|||
if (!this._graph) return |
|||
const updatedEdges = this.defaultData.edges.map(edge => ({ |
|||
...edge, |
|||
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: this.defaultData.nodes, |
|||
edges: updatedEdges |
|||
} |
|||
this.defaultData = updatedData |
|||
this.updateGraph(updatedData) |
|||
}, |
|||
|
|||
clearAllStates() { |
|||
if (!this._graph) return |
|||
|
|||
// 清除所有节点的状态 |
|||
this._graph.getNodes().forEach(node => { |
|||
this._graph.clearItemStates(node) |
|||
}) |
|||
|
|||
// 清除所有边的状态 |
|||
this._graph.getEdges().forEach(edge => { |
|||
this._graph.clearItemStates(edge) |
|||
}) |
|||
}, |
|||
|
|||
handleResize() { |
|||
const container = this.$refs.graphContainer |
|||
if (this._graph && container) { |
|||
this._graph.resize(container.offsetWidth, container.offsetHeight) |
|||
} |
|||
}, |
|||
|
|||
resetView() { |
|||
this._graph?.fitView() |
|||
}, |
|||
|
|||
resetStyle() { |
|||
Object.assign(this, { |
|||
nodeShowLabel: true, |
|||
nodeFontSize: 12, |
|||
nodeFontColor: '#000000', |
|||
nodeShape: 'circle', |
|||
nodeSize: 60, |
|||
nodeFill: '#9FD5FF', |
|||
nodeStroke: '#5B8FF9', |
|||
nodeLineWidth: 2, |
|||
nodeFontFamily: 'Microsoft YaHei, sans-serif', |
|||
|
|||
edgeShowLabel: true, |
|||
edgeFontSize: 10, |
|||
edgeFontColor: '#666666', |
|||
edgeType: 'line', |
|||
edgeLineWidth: 2, |
|||
edgeStroke: '#F00', |
|||
edgeEndArrow: true, |
|||
edgeFontFamily: 'Microsoft YaHei, sans-serif' |
|||
}) |
|||
}, |
|||
|
|||
|
|||
handleNodeClick(data) { |
|||
console.log('点击节点:', data) |
|||
// 可用于显示详情、复制 code 等 |
|||
this.$message.info(`已选中: ${data.title}`) |
|||
}, |
|||
|
|||
getLevelLabel(level) { |
|||
const map = { |
|||
chapter: '章', |
|||
section: '节', |
|||
category: '类目', |
|||
subcategory: '亚目', |
|||
diagnosis: '条目' |
|||
} |
|||
return map[level] || level |
|||
}, |
|||
|
|||
getTagType(level) { |
|||
const map = { |
|||
chapter: 'primary', |
|||
section: 'success', |
|||
category: 'warning', |
|||
subcategory: 'info', |
|||
diagnosis: '' |
|||
} |
|||
return map[level] || '' |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.knowledge-graph-container { |
|||
display: flex; |
|||
height: 100vh; |
|||
} |
|||
|
|||
.control-panel { |
|||
width: 280px; |
|||
background: #f5f7fa; |
|||
padding: 16px; |
|||
border-right: 1px solid #ddd; |
|||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1); |
|||
font-family: 'Microsoft YaHei'; |
|||
} |
|||
|
|||
.section { |
|||
margin-bottom: 10px; |
|||
padding: 10px; |
|||
background: white; |
|||
border-radius: 8px; |
|||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.section h5 { |
|||
margin-top: 0; |
|||
color: #333; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.slider-group { |
|||
margin: 10px 0; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.slider-group span { |
|||
margin-right: 10px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.form-group { |
|||
margin: 10px 0; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.form-group label { |
|||
margin-right: 8px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.form-group select, |
|||
.form-group input[type="number"] { |
|||
padding: 4px; |
|||
border: 1px solid #ccc; |
|||
border-radius: 4px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.color-picker { |
|||
margin: 10px 0; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.color-picker label { |
|||
margin-right: 8px; |
|||
font-size: 12px; |
|||
} |
|||
|
|||
.color-picker input[type="color"] { |
|||
width: 40px; |
|||
height: 20px; |
|||
border: none; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
button { |
|||
width: 45%; |
|||
padding: 10px; |
|||
background: #007bff; |
|||
color: white; |
|||
border: none; |
|||
border-radius: 6px; |
|||
font-size: 14px; |
|||
cursor: pointer; |
|||
margin-right: 2%; |
|||
} |
|||
|
|||
button:hover { |
|||
background: #0056b3; |
|||
} |
|||
|
|||
.graph-container { |
|||
flex: 1; |
|||
border: 1px solid #eee; |
|||
background: #fff; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue