You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

502 lines
14 KiB

4 months ago
<template>
<div style="display: flex;
height: 100vh;">
<Menu
:initial-active="2"
/>
4 months ago
<div class="medical-qa-container">
<!-- 标题 -->
4 months ago
<div style="display: flex;align-items: center;">
<div class="title-before">
</div>
<h2 class="title">医疗问答</h2>
</div>
4 months ago
<!-- 搜索框 -->
<div class="search-box">
<el-input
v-model="query"
placeholder="请输入问题..."
clearable
/>
<el-button type="primary" @click="handleSearch" style=" background: #114FE6;">查询</el-button>
</div>
<!-- 主内容区 -->
<div class="content-wrapper">
<!-- 左侧问答结果 -->
<div class="answer-list">
4 months ago
<div v-if="queryRecord">
<h2 class="section-title" style="margin-bottom: 10px;margin-top: 0px">问答提问</h2>
<div class="answer-item" style=" background-color: #165DFF;
border-color: #0066cc;
color: #fff; height: auto;max-height: 3vw">
{{queryRecord}}
</div>
</div>
<h2 class="section-title">问答结果</h2>
4 months ago
<div v-if="answers.length === 0" class="empty-state">
请输入问题进行查询...
</div>
<div v-else class="answer-items">
<div
v-for="(item, index) in answers"
:key="index"
class="answer-item"
4 months ago
@click="selectGraph(index)"
:class="{ 'highlight': selected === index }"
4 months ago
>
4 months ago
{{ item.answer }}
4 months ago
</div>
</div>
</div>
<!-- 右侧知识图谱 -->
<div class="knowledge-graph">
4 months ago
<div ref="graphContainer" class="graph-container" id="container"></div>
4 months ago
</div>
</div>
</div>
4 months ago
</div>
</template>
4 months ago
<script>
4 months ago
import Menu from "@/components/Menu.vue";
4 months ago
import {qaAnalyze} from "@/api/qa";
4 months ago
import {Graph} from "@antv/g6";
import {getGraph} from "@/api/graph";
4 months ago
export default {
name: 'GraghQA',
components: {Menu},
data() {
return {
query:"",
4 months ago
answers:[],
4 months ago
selected:0,
4 months ago
// 节点样式
nodeShowLabel: true,
nodeFontSize: 12,
nodeFontColor: '#fff',
nodeShape: 'circle',
nodeSize: 50,
nodeFill: '#9FD5FF',
nodeStroke: '#5B8FF9',
nodeLineWidth: 2,
nodeFontFamily: 'Microsoft YaHei, sans-serif',
// 边样式
edgeShowLabel: true,
edgeFontSize: 10,
edgeFontColor: '#666666',
edgeType: 'quadratic',
edgeLineWidth: 2,
edgeStroke: '#b6b2b2',
edgeEndArrow: true,
edgeFontFamily: 'Microsoft YaHei, sans-serif',
queryRecord:""
4 months ago
4 months ago
};
},
methods: {
4 months ago
selectGraph(index){
this.selected=index
4 months ago
this.formatData(this.answers[index].result)
4 months ago
},
4 months ago
handleSearch(){
4 months ago
this.answers=[]
4 months ago
let data={
text:this.query
}
4 months ago
this.queryRecord=this.query
4 months ago
this.query=""
4 months ago
// 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)
4 months ago
qaAnalyze(data).then(res=>{
4 months ago
this.answers=res
if(this.answers.length>0){
this.initGraph(this.answers[0].result)
}
4 months ago
})
4 months ago
},
formatData(data){
this._graph.stopLayout();
this.clearGraphState();
const updatedEdges = data.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 updatedNodes = data.nodes.map(node => ({
...node,
type: this.nodeShape,
style:{
size: this.nodeSize,
lineWidth: this.nodeLineWidth,
label: this.nodeShowLabel,
labelFontSize: this.nodeFontSize,
labelFontFamily: this.nodeFontFamily,
labelFill: this.nodeFontColor,
opacity: 1,
}
}))
const updatedData = {
nodes: updatedNodes,
edges: updatedEdges
}
this.updateGraph(updatedData)
},
updateGraph(data) {
if (!this._graph) return
this._graph.setData(data)
this._graph.render()
},
initGraph(data) {
if (this._graph!=null){
this._graph.destroy()
this._graph = null;
}
const updatedEdges = data.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 updatedNodes = data.nodes.map(node => ({
...node,
type: this.nodeShape,
style:{
size: this.nodeSize,
lineWidth: this.nodeLineWidth,
label: this.nodeShowLabel,
labelFontSize: this.nodeFontSize,
labelFontFamily: this.nodeFontFamily,
labelFill: this.nodeFontColor,
opacity: 1,
}
}))
const updatedData = {
nodes: updatedNodes,
edges: updatedEdges
}
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,
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: {
fill: (d) => {
const label = d?.type;
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?.type;
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.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
}
},
},
edge: {
style: {
labelText: (d) => d.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
},
// 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
}
},
},
data:updatedData,
});
graph.render();
this._graph = graph
this._graph?.fitView()
}
},
4 months ago
},
};
4 months ago
</script>
<style scoped>
4 months ago
.medical-qa-container {
padding: 25px;
background-color: #F6F9FF;
flex: 1;
overflow: auto;
height: 100vh;
margin: 0 auto;
}
.title {
4 months ago
font-size: 22px;
4 months ago
margin-bottom: 20px;
position: relative;
display: flex;
align-items: center;
color: #165DFF;
4 months ago
margin-left: 11px;
4 months ago
}
4 months ago
.title-before {
4 months ago
content: '';
4 months ago
width: 8px;
height: 22px;
4 months ago
background-color: #165DFF;
4 months ago
border-radius: 5px;
4 months ago
}
.search-box {
display: flex;
gap: 20px;
margin-bottom: 30px;
align-items: center;
}
.search-box .el-input {
flex: 1;
max-width: 94%;
margin-right: 5px;
}
.content-wrapper {
display: flex;
gap: 20px;
height: 37vw;
}
.answer-list {
flex: 1;
background-color: white;
border-radius: 33px;
padding: 25px;
box-shadow: 3px 1px 12px 7px #ECF2FF;
max-width: 32%;
}
.section-title {
font-size: 16px;
color: #165DFF;
margin-bottom: 15px;
padding-left: 12px;
position: relative;
display: flex;
align-items: center;
font-weight: 900;
}
.section-title::before {
content: '';
width: 6px;
4 months ago
height: 16px;
4 months ago
background-color: #165DFF;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
4 months ago
border-radius: 5px;
4 months ago
}
.answer-items {
4 months ago
max-height: 25vw;
4 months ago
overflow-y: auto;
padding-right: 10px;
4 months ago
cursor: pointer;
4 months ago
}
.answer-items::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
.answer-items::-webkit-scrollbar-track {
background: #fff; /* 轨道背景 */
border-radius: 4px;
}
.answer-items::-webkit-scrollbar-thumb {
background: #B8C2D9; /* 滑块颜色 */
border-radius: 4px;
transition: background 0.2s ease;
}
.answer-items::-webkit-scrollbar-thumb:hover {
background: #B8C2D9; /* 悬停时滑块颜色 */
}
.answer-item {
padding: 12px;
border: 1px solid #165DFF;
border-radius: 15px;
background-color: #f9f9f9;
line-height: 1.5;
word-wrap: break-word;
color: #a2a5a7;
font-weight: 400;
font-size: 13px;
text-align: left;
height: 4.5vw;
margin-bottom: 12px;
}
.answer-item.highlight {
background-color: #165DFF;
border-color: #0066cc;
color: #fff;
}
4 months ago
.answer-item:hover {
background-color: #165DFF;
border-color: #0066cc;
color: #fff;
}
4 months ago
.empty-state {
text-align: center;
color: #fff;
padding: 40px 0;
font-size: 14px;
}
.knowledge-graph {
flex: 1;
background-color: white;
border-radius: 16px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
}
/deep/.el-input__wrapper{
background-color: #EFF4FF;
border: 1px solid #165DFF;
border-radius: 8px;
}
4 months ago
</style>