Browse Source

all

yangrongze
hanyuqing 4 months ago
parent
commit
fbb21e4433
  1. 80
      controller/GraphController.py
  2. 7
      util/neo4j_utils.py
  3. 2
      vue/src/App.vue
  4. 6
      vue/src/api/builder.js
  5. BIN
      vue/src/assets/logo1.png
  6. 87
      vue/src/components/Menu.vue
  7. 178
      vue/src/system/GraphBuilder.vue
  8. 451
      vue/src/system/GraphDemo.vue
  9. 85
      vue/src/system/Login.vue
  10. 3
      vue/src/system/Profile.vue
  11. 2
      vue/src/utils/request.js

80
controller/GraphController.py

@ -209,31 +209,75 @@ def health():
return {"status": "ok", "drug_cached": redis_get(DRUG_TREE_KEY) is not None}
# @app.post("/api/analyze")
# async def analyze(request):
# # 假设前端传入 JSON: {"text": "病例文本..."}
# body = request.json()
# input_text = body.get("text", "").strip()
#
# if not input_text:
# return jsonify({"error": "缺少 text 字段"}), 400
# client = httpx.AsyncClient(base_url="http://192.168.50.113:8088")
# # 调用实体关系抽取服务
# response = await client.post(
# "/extract_entities_and_relations",
# json={"text": input_text}
# )
# print(response)
# if response.status_code == 200:
# result = response.json()
# return jsonify(result)
# else:
# return jsonify({
# "error": "实体抽取服务返回错误",
# "status": response.status_code,
# "detail": response.text
# }), response.status_code
# 全局 client(可复用)
client = httpx.AsyncClient(base_url="http://192.168.50.113:8088")
@app.post("/api/analyze")
async def analyze(request):
body = request.json()
input_text = body.get("text", "").strip()
if not input_text:
return jsonify({"error": "缺少 text 字段"}), 400
try:
# 假设前端传入 JSON: {"text": "病例文本..."}
body = request.json()
input_text = body.get("text", "").strip()
if not input_text:
return jsonify({"error": "缺少 text 字段"}), 400
client = httpx.AsyncClient(base_url="http://192.168.50.113:8088")
# 调用实体关系抽取服务
response = await client.post(
# 直接转发到大模型服务(假设它返回 { "task_id": "xxx" })
resp = await client.post(
"/extract_entities_and_relations",
json={"text": input_text}
json={"text": input_text},
timeout=1800.0 # 30分钟
)
print(response)
if response.status_code == 200:
result = response.json()
return jsonify(result)
print(resp)
if resp.status_code == 202 or resp.status_code == 200:
return Response(
status_code=200,
description=jsonify(resp.json()),
headers={"Content-Type": "text/plain; charset=utf-8"}
)
else:
return jsonify({
"error": "实体抽取服务返回错误",
"status": response.status_code,
"detail": response.text
}), response.status_code
"error": "提交失败",
"detail": resp.text
}), resp.status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.get("/api/analyze/status")
async def get_status():
try:
# 直接转发状态查询到大模型服务
resp = await client.get(
"/status",
timeout=5.0
)
return jsonify(resp.json()), resp.status_code
except httpx.ReadTimeout:
return jsonify({"error": "状态查询超时"}), 500
except Exception as e:
return jsonify({"error": str(e)}), 500

7
util/neo4j_utils.py

@ -320,10 +320,8 @@ class Neo4jUtil:
raise RuntimeError(
f"查询邻居节点时发生数据库错误: {str(e)}"
) from e # 使用 'from e' 保留原始异常链
print(raw_results)
neighbors = []
print("Ssssssssss")
print(len(raw_results))
for row in raw_results:
source = dict(row["sourceProps"])
source.update({"id": row["sourceId"], "label": row["sourceLabel"]})
@ -339,8 +337,7 @@ class Neo4jUtil:
"target": target,
"relationship": relationship
})
print(relationship)
print(neighbors)
return neighbors
# def find_neighbors_with_relationships(
# self,

2
vue/src/App.vue

@ -10,10 +10,10 @@ export default {
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
</style>

6
vue/src/api/builder.js

@ -13,3 +13,9 @@ export function analyze(data) {
data
});
}
export function getAIStatus() {
return request({
url: '/api/analyze/status',
method: 'get',
});
}

BIN
vue/src/assets/logo1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

87
vue/src/components/Menu.vue

@ -1,10 +1,13 @@
<template>
<div class="admin-layout">
<aside class="sidebar" :class="{ 'is-collapsed': isCollapsed }">
<div class="admin-layout" :class="{
'is-collapsed': isCollapsed,
'is-expanded': !isCollapsed
}">
<aside class="sidebar">
<div class="sidebar-header">
<div class="header-content">
<div class="header-icon-wrap">
<img src="@/assets/logo.png" class="logo-img"/>
<img src="../assets/logo1.png" class="logo-img"/>
</div>
<div v-if="!isCollapsed" class="header-text-wrap">
<div class="title-line">面向疾病预测的知识图谱</div>
@ -37,11 +40,14 @@
</div>
</nav>
<div class="sidebar-footer">
<div class="sidebar-footer"
:style="{
'border-top': isCollapsed ? 'none' : '2px solid rgba(255, 255, 255, 0.1)'
}">
<div v-if="!isCollapsed" class="user-block">
<div class="avatar">用户</div>
<div class="avatar" @click="handleProfile">用户</div>
<div class="info">
<div class="name">用户名字</div>
<div class="name" @click="handleProfile">用户名字</div>
<div class="id">8866990099</div>
</div>
<div class="exit-wrap">
@ -92,30 +98,39 @@ const menuItems = [
{name: '知识图谱构建', path: '/kg-builder', icon: i2},
{name: '知识图谱问答', path: '/kg-qa', icon: i3},
{name: '知识图谱数据', path: '/kg-data', icon: i4},
{name: '知识图谱数据', path: '/kg-data', icon: i4}
{name: '图谱样式工具', path: '/kg-style', icon: i4}
];
const handleMenuClick = (i) => {
activeIndex.value = i;
router.push(menuItems[i].path);
};
const handleLogout = () => console.log('logout');
const handleProfile = () => {
// 使Vue Router
router.push('/profile');
};
const handleLogout = () => {
// 使Vue Router
router.push('/login');
};
</script>
<style scoped>
.admin-layout {
display: flex;
width: 200px;
height: 100vh;
background: #fff;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
-webkit-font-smoothing: antialiased;
z-index: 100;
box-shadow: 2px -1px 10px 8px rgb(97 99 100 / 22%);
width: 12%;
}
/* --- 侧边栏 --- */
.sidebar {
width: 200px;
width: 100%;
height: 100%;
background: #0a2463;
flex-shrink: 0;
@ -126,13 +141,15 @@ const handleLogout = () => console.log('logout');
z-index: 100;
}
.sidebar.is-collapsed {
width: 64px;
.admin-layout.is-collapsed {
width: 4%;
}
.admin-layout.is-expanded {
width: 12%;
}
.sidebar-header {
padding: 30px 0 20px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
padding: 25px 0px 18px 0;
border-bottom: 2px solid rgba(255, 255, 255, 0.1);
display: flex;
justify-content: center;
}
@ -140,14 +157,13 @@ const handleLogout = () => console.log('logout');
.header-content {
display: flex;
align-items: center;
gap: 10px;
gap: 7px;
width: fit-content;
}
.header-icon-wrap {
width: 32px;
height: 32px;
background: #165dff;
border-radius: 8px;
display: flex;
align-items: center;
@ -157,8 +173,8 @@ const handleLogout = () => console.log('logout');
}
.logo-img {
width: 22px;
height: 22px;
width: 29px;
height: 30px;
object-fit: contain;
}
@ -170,10 +186,11 @@ const handleLogout = () => console.log('logout');
.title-line {
color: #fff;
font-size: 12px;
font-weight: 600;
font-size: 11px;
line-height: 1.25;
white-space: nowrap;
font-weight: 600;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
/* --- 菜单导航 --- */
@ -209,7 +226,7 @@ const handleLogout = () => console.log('logout');
}
.menu-item:hover .highlight-box {
background: rgba(255, 255, 255, 0.08);
background: #133189;
}
.menu-content-fixed {
@ -247,9 +264,10 @@ const handleLogout = () => console.log('logout');
.menu-text {
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
margin-left: 10px;
font-weight: 400;
font-weight: 500;
font-size: 12px;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
.active-tag {
@ -273,7 +291,7 @@ const handleLogout = () => console.log('logout');
/* --- 用户区域 (重点修改) --- */
.sidebar-footer {
padding: 12px 0 20px 0;
border-top: 1px solid rgba(255, 255, 255, 0.1);
border-top: 2px solid rgba(255, 255, 255, 0.1);
}
.user-block {
@ -283,8 +301,8 @@ const handleLogout = () => console.log('logout');
}
.avatar {
width: 42px;
height: 42px;
width: 36px;
height: 36px;
background: rgba(255, 255, 255, 0.15);
border-radius: 50%;
font-size: 12px;
@ -293,26 +311,30 @@ const handleLogout = () => console.log('logout');
align-items: center;
justify-content: center;
flex-shrink: 0;
cursor: pointer;
}
.info {
margin-left: 12px;
margin-left: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.info .name {
font-size: 12px;
font-size: 11px;
color: #fff;
line-height: 1;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
cursor: pointer;
}
.info .id {
font-size: 9px;
color: rgba(255, 255, 255, 0.35);
color: rgba(255, 255, 255, 0.5);
line-height: 1;
margin-top: 8px; /* 通过增加上边距让id向下移动 */
margin-top: 6px; /* 通过增加上边距让id向下移动 */
}
.exit-wrap {
@ -322,7 +344,7 @@ const handleLogout = () => console.log('logout');
}
.exit-icon {
width: 16px;
width: 11px;
cursor: pointer;
opacity: 0.8;
}
@ -330,7 +352,6 @@ const handleLogout = () => console.log('logout');
.expand-handle-circle {
display: flex;
justify-content: center;
padding-bottom: 20px;
cursor: pointer;
}

178
vue/src/system/GraphBuilder.vue

@ -12,7 +12,46 @@
:key="index"
:class="['message', msg.role]"
>
<div class="bubble">{{ msg.content }}</div>
<div v-if="msg.role === 'user'" class="bubble">
{{ msg.content }}
</div>
<div v-else-if="msg.role === 'assistant'">
<div v-if="msg.isKG" class="kg-card">
<h4>🔬 病历结构化分析结果</h4>
<div v-if="msg.content.entities?.length" class="kg-section">
<h5>识别出的实体</h5>
<div class="entity-list">
<span
v-for="(ent, i) in msg.content.entities"
:key="i"
class="entity-tag"
:class="'tag-' + ent.t"
>
{{ ent.n }}<small>({{ ent.t }})</small>
</span>
</div>
</div>
<div v-if="msg.content.relations?.length" class="kg-section">
<h5>识别出的关系</h5>
<ul class="relation-list">
<li v-for="(rel, i) in msg.content.relations" :key="i">
<span class="rel-subject">{{ rel.e1 }}</span>
<span class="rel-predicate"> {{ rel.r }} </span>
<span class="rel-object">{{ rel.e2 }}</span>
</li>
</ul>
</div>
<div v-if="!msg.content.entities?.length && !msg.content.relations?.length" class="empty-state">
未提取到有效医学实体或关系
</div>
</div>
<div v-else class="bubble assistant-text">
{{ msg.content }}
</div>
</div>
</div>
</div>
<!-- 输入区域Qwen 风格 -->
@ -60,7 +99,7 @@
<script>
import Menu from "@/components/Menu.vue";
import axios from "axios";
import {analyze} from "@/api/builder";
import {analyze, getAIStatus} from "@/api/builder";
export default {
name: 'QwenChat',
@ -69,28 +108,44 @@ export default {
return {
inputText: '',
messages: [
{ role: 'assistant', content: '你好!我是 Qwen,有什么可以帮你的吗?' }
]
{ role: 'assistant', content: '你好!我是图谱构建助手,有什么可以帮你的吗?',isKG:false }
],
status:"wait"
};
},
methods: {
pollAIStatus() {
if (this.status !== "wait") return; //
try {
const data = getAIStatus();
if (data.status === "ok") {
this.status = "ok";
} else if (data.status === "error") {
this.status = "error";
} else {
setTimeout(() => this.pollAIStatus(), 3000);
}
} catch (error) {
console.error("轮询出错:", error);
this.status = "error";
alert("查询状态失败,请重试");
}
},
sendMessage() {
const text = this.inputText.trim();
if (!text) return;
let data={
"text":text
}
analyze(data)
// this.pollAIStatus()
//
this.messages.push({ role: 'user', content: text });
this.inputText = '';
// AI API
setTimeout(() => {
this.messages.push({
role: 'assistant',
content: `你刚才说:“${text}” —— 这是一个模拟回复。`
});
}, 600);
analyze(data).then(res=>{
console.log(res)
this.messages.push({ role: 'assistant',
content: res,entities:res.entities,relations:res.relations,isKG:true });
})
},
insertPrefix(prefix) {
this.inputText += prefix;
@ -125,7 +180,6 @@ export default {
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);
@ -162,6 +216,7 @@ export default {
word-break: break-word;
line-height: 1.5;
font-size: 14px;
text-align: left;
}
.message.user .bubble {
@ -273,4 +328,101 @@ export default {
.send-btn:hover {
background: #8a53e0;
}
/* === 自定义助手 KG 卡片 === */
.kg-card {
background: white;
border: 1px solid #e0e0e0;
border-radius: 16px;
padding: 16px;
width: 100%;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
font-size: 14px;
color: #333;
}
.kg-card h4 {
margin: 0 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
color: #1a73e8;
font-weight: 600;
}
.kg-section {
margin-bottom: 16px;
}
.kg-section h5 {
margin: 0 0 8px 0;
color: #555;
font-size: 14px;
font-weight: 600;
}
/* 实体标签 */
.entity-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.entity-tag {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 20px;
font-size: 13px;
background: #f0f8ff;
color: #1e88e5;
border: 1px solid #bbdefb;
}
.entity-tag small {
margin-left: 6px;
opacity: 0.8;
font-weight: normal;
}
/* 按类型着色(可扩展) */
.tag-疾病 { background: #ffebee; color: #c62828; border-color: #ffcdd2; }
.tag-症状 { background: #e8f5e9; color: #2e7d32; border-color: #c8e6c9; }
.tag-检查 { background: #fff8e1; color: #ff8f00; border-color: #ffecb3; }
.tag-药物 { background: #f3e5f5; color: #7b1fa2; border-color: #ce93d8; }
/* 关系列表 */
.relation-list {
list-style: none;
padding: 0;
margin: 0;
}
.relation-list li {
padding: 6px 0;
border-bottom: 1px dashed #eee;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.relation-list li:last-child {
border-bottom: none;
}
.rel-subject { font-weight: 600; color: #1a237e; }
.rel-predicate { color: #d32f2f; font-weight: 500; }
.rel-object { font-weight: 600; color: #1b5e20; }
/* 空状态 */
.empty-state {
color: #999;
font-style: italic;
padding: 12px 0;
}
/* 普通助手文本(非 KG) */
.assistant-text {
background-color: #e9ecef;
color: #333;
border-bottom-left-radius: 4px;
}
</style>

451
vue/src/system/GraphDemo.vue

@ -20,7 +20,7 @@
class="color-block"
@click="toggleCategory(item.key)"
></div>
<span>{{ item.label }}</span>
<span style=" font-size: 12px;color: rgb(0, 0, 0);font-weight: 600;">{{ item.label }}</span>
</div>
</div>
</header>
@ -187,8 +187,12 @@ export default {
},
beforeUnmount() {
this._graph.destroy()
this._graph = null;
if (this._graph!=null){
this._graph.stopLayout();
this.clearGraphState();
this._graph.destroy()
this._graph = null;
}
window.removeEventListener('resize', this.handleResize);
},
watch: {
@ -216,20 +220,23 @@ export default {
methods: {
buildCategoryIndex() {
const index = {};
const nodes = this._graph.getNodeData() //
nodes.forEach(node => {
console.log(node.data.label)
const category = node.data.label; // label
if(category=='Drug'||category=='Symptom'||
category=='Disease'||category=='Check'){
if (!index[category]) index[category] = [];
index[category].push(node.id);
}else{
if (!index["Other"]) index["Other"] = [];
index["Other"].push(node.id);
}
});
this.categoryToNodeIds = index;
if (this._graph!=null){
const nodes = this._graph.getNodeData() //
nodes.forEach(node => {
console.log(node.data.label)
const category = node.data.label; // label
if(category=='Drug'||category=='Symptom'||
category=='Disease'||category=='Check'){
if (!index[category]) index[category] = [];
index[category].push(node.id);
}else{
if (!index["Other"]) index["Other"] = [];
index["Other"].push(node.id);
}
});
this.categoryToNodeIds = index;
}
},
//
toggleCategory (key){
@ -433,207 +440,211 @@ export default {
console.log(this.defaultData)
console.log(this._nodeLabelMap)
const container = this.$refs.graphContainer;
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 //
// type: 'radial',
// preventOverlap: true,
// unitRadius: 200,
// maxPreventOverlapIteration:100
// type: 'force-atlas2',
// preventOverlap: true,
// kr: 1000,
// center: [250, 250],
// barnesHut:true,
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 //
// type: 'radial',
// preventOverlap: true,
// unitRadius: 200,
// maxPreventOverlapIteration:100
// type: 'force-atlas2',
// preventOverlap: true,
// kr: 1000,
// center: [250, 250],
// barnesHut: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.data?.label;
if (label === 'Disease') return '#EF4444'; //
if (label === 'Drug') return '#91cc75'; // 绿
if (label === 'Symptom') return '#fac858'; //
if (label === 'Check') return '#59d1d4'; //
return '#336eee'; //
behaviors: [ 'zoom-canvas', 'drag-element',
'click-select','focus-element', {
type: 'hover-activate',
degree: 1,
},
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';
{
type: 'drag-canvas',
enable: (event) => event.shiftKey === false,
},
labelText: (d) => d.data.name,
labelPlacement: 'center',
labelWordWrap: true,
labelMaxWidth: '150%',
labelMaxLines: 3,
labelTextOverflow: 'ellipsis',
labelTextAlign: 'center',
opacity: 1
},
state: {
active: {
lineWidth: 2,
shadowColor: '#ffffff',
shadowBlur: 10,
opacity: 1
{
type: 'brush-select',
},
inactive: {
opacity: 0.3
},
normal:{
],
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 '#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';
},
labelText: (d) => d.data.name,
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.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
},
// 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
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
},
// 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
// }
},
inactive: {
opacity: 0.3
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
}
},
normal:{
opacity: 1
}
},
data:this.defaultData,
});
graph.render();
// graph.on('node:pointerover', (evt) => {
// const nodeItem = evt.target; //
// const relatedEdges = graph.getRelatedEdgesData(nodeItem.id);
// const relatedEdgeIds = relatedEdges.map(edge => edge.id);
//
// // 3. 'highlight'
// relatedEdgeIds.forEach(edgeId => {
// graph.setElementState(edgeId, 'highlight', true);
// });
// graph.setElementState(nodeItem.id, 'active',true);
//
// // 2. ID
// const neighborNodeIds = new Set();
// relatedEdges.forEach(edge => {
// if (edge.source !== nodeItem.id) neighborNodeIds.add(edge.source);
// if (edge.target !== nodeItem.id) neighborNodeIds.add(edge.target);
// });
//
// neighborNodeIds.forEach(id => {
// graph.setElementState(id, 'active', true);
// });
//
// graph.getEdgeData().forEach(edge => {
// if (!relatedEdgeIds.includes(edge.id)) {
// graph.setElementState(edge.id, 'inactive', true);
// }
// });
// graph.getNodeData().forEach(node => {
// if (node.id !== nodeItem.id && !neighborNodeIds.has(node.id)) {
// graph.setElementState(node.id, 'inactive',true);
// }
// });
// });
// graph.on('node:pointerleave', (evt) => {
// graph.getEdgeData().forEach(edge => {
// graph.setElementState(edge.id, 'highlight', false);
// graph.setElementState(edge.id, 'inactive', false);
// graph.setElementState(edge.id, 'normal', true);
// });
// graph.getNodeData().forEach(node => {
// graph.setElementState(node.id, 'active', false);
// graph.setElementState(node.id, 'inactive', false);
// graph.setElementState(node.id, 'normal', true);
// });
// });
graph.on('node:click', (evt) => {
const nodeItem = evt.target.id; //
let node=graph.getNodeData(nodeItem).data
let data={
label:node.name,
type:node.label
}
getGraph(data).then(response=>{
console.log(response)
this.formatData(response)
}); // Promise
});
this._graph = graph
this._graph?.fitView()
}
},
data:this.defaultData,
});
graph.render();
// graph.on('node:pointerover', (evt) => {
// const nodeItem = evt.target; //
// const relatedEdges = graph.getRelatedEdgesData(nodeItem.id);
// const relatedEdgeIds = relatedEdges.map(edge => edge.id);
//
// // 3. 'highlight'
// relatedEdgeIds.forEach(edgeId => {
// graph.setElementState(edgeId, 'highlight', true);
// });
// graph.setElementState(nodeItem.id, 'active',true);
//
// // 2. ID
// const neighborNodeIds = new Set();
// relatedEdges.forEach(edge => {
// if (edge.source !== nodeItem.id) neighborNodeIds.add(edge.source);
// if (edge.target !== nodeItem.id) neighborNodeIds.add(edge.target);
// });
//
// neighborNodeIds.forEach(id => {
// graph.setElementState(id, 'active', true);
// });
//
// graph.getEdgeData().forEach(edge => {
// if (!relatedEdgeIds.includes(edge.id)) {
// graph.setElementState(edge.id, 'inactive', true);
// }
// });
// graph.getNodeData().forEach(node => {
// if (node.id !== nodeItem.id && !neighborNodeIds.has(node.id)) {
// graph.setElementState(node.id, 'inactive',true);
// }
// });
// });
// graph.on('node:pointerleave', (evt) => {
// graph.getEdgeData().forEach(edge => {
// graph.setElementState(edge.id, 'highlight', false);
// graph.setElementState(edge.id, 'inactive', false);
// graph.setElementState(edge.id, 'normal', true);
// });
// graph.getNodeData().forEach(node => {
// graph.setElementState(node.id, 'active', false);
// graph.setElementState(node.id, 'inactive', false);
// graph.setElementState(node.id, 'normal', true);
// });
// });
graph.on('node:click', (evt) => {
const nodeItem = evt.target.id; //
let node=graph.getNodeData(nodeItem).data
let data={
label:node.name,
type:node.label
}
getGraph(data).then(response=>{
console.log(response)
this.formatData(response)
}); // Promise
});
this._graph = graph
this._graph?.fitView()
},
@ -850,15 +861,15 @@ button:hover {
.legend-item {
display: flex;
align-items: center;
gap: 5px;
gap: 7px;
cursor: pointer;
user-select: none;
}
.color-block {
width: 16px;
height: 16px;
border-radius: 4px;
width: 30px;
height: 15px;
border-radius: 18px;
transition: all 0.2s ease;
}
@ -885,13 +896,14 @@ button:hover {
}
.search-container {
width: 260px;
height: 36px;
background: #f5f7fa;
width: 330px;
height: 30px;
background: #fff;
border-radius: 18px;
display: flex;
align-items: center;
padding: 0 15px;
box-shadow: 4px 2px 9px 1px rgb(97 99 100 / 19%);
}
.search-container input {
@ -937,6 +949,7 @@ button:hover {
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
}
.disease-header {
@ -945,12 +958,24 @@ button:hover {
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 15px;
justify-content: flex-start;
}
.d-title{
display: flex;
align-items: center;
font-size: 13px;
}
.d-count{
font-size: 9px;
background-color: #5989F0;
border-radius: 7px;
padding: 0px 4px;
margin-left: 7px;
}
.d-icon {
width: 18px;
width: 21px;
margin-right: 8px;
}
@ -970,6 +995,7 @@ button:hover {
align-items: center;
width: 100%;
overflow: hidden;
font-weight: 500;
}
.code {
@ -985,6 +1011,7 @@ button:hover {
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
font-size: 12px;
}
.disease-body{
width: 360px;

85
vue/src/system/Login.vue

@ -2,7 +2,7 @@
<div class="login-container">
<!-- 左上角Logo和标题 -->
<div class="logo-header">
<img src="@/assets/logo.png" alt="Logo" class="logo">
<img src="../assets/logo.png" alt="Logo" class="logo">
<h1 class="login-title">面向疾病预测的知识图谱应用系统</h1>
</div>
@ -12,8 +12,9 @@
</div>
<div class="login-form">
<h2 class="form-title">登录</h2>
<p class="form-description">请输入您的电子邮件地址和密码以访问账户</p>
<div class="form-title">登录</div>
<div class="form-description">请输入您的电子邮件地址和密码以访问账户</div>
<form class="form" @submit.prevent="handleLogin">
<!-- 错误信息显示 -->
@ -35,7 +36,7 @@
<div class="form-group">
<div class="password-header">
<label for="password" class="form-label">密码</label>
<a href="#" class="forgot-password">忘记密码?</a>
<!-- <a href="#" class="forgot-password">忘记密码?</a>-->
</div>
<input
type="password"
@ -67,9 +68,9 @@
</button>
</form>
<div class="social-login">
<p class="social-text">使用其他方式登录</p>
</div>
<!-- <div class="social-login">-->
<!-- <p class="social-text">使用其他方式登录</p>-->
<!-- </div>-->
<div class="register-link">
<p>还没有账户? <a href="#" class="register"> 立即注册</a></p>
@ -170,7 +171,6 @@ body, html {
height: 100vh;
overflow: hidden;
flex-direction: row;
font-family: 'SimSun', '宋体', serif;
}
/* 左上角Logo和标题样式 */
@ -191,8 +191,7 @@ body, html {
.login-title {
font-size: 17px;
font-weight: 900;
font-family: 'SimSun Bold', '宋体', serif;
font-weight: 600;
color: #1f2937;
margin: 0;
white-space: nowrap;
@ -212,10 +211,8 @@ body, html {
}
.login-header {
margin-bottom: 1.5rem;
width: 100%;
max-width: 24rem;
margin-top: 20px;
}
.login-form {
@ -226,24 +223,19 @@ body, html {
.form-title {
font-size: 18px;
font-weight: 900;
font-weight: 600;
color: #333333;
margin-top: -7px;
margin-bottom: 10px;
margin-left: 13px;
margin-bottom: 6px;
text-shadow: 0.2px 0.2px 0 #1f2937;
text-align: left;
font-family: 'SimSun', '宋体', serif;
}
.form-description {
color: #B5B5B5;
color: #A3A3A3;
margin-bottom: 2rem;
margin-left: 13px;
text-align: left;
font-size: 11px;
font-weight: bold;
font-family: 'SimSun', '宋体', serif;
}
.form {
@ -272,24 +264,24 @@ body, html {
.form-label {
display: block;
font-size: 11px;
font-size: 12px;
font-weight: 700;
color: #374151;
margin-bottom: 0.3rem;
color: #757575;
margin-bottom: 8px;
text-align: left;
font-family:'STSong', '宋体', serif;
}
.form-input {
width: 100%;
padding: 0.6rem 0.8rem;
border-radius: 0.5rem;
padding: 0.4rem 0.7rem;
border-radius: 7px;
border: 2px solid #A3A3A3;
transition: all 0.2s;
font-size: 9px;
font-size: 11px;
box-sizing: border-box;
font-family: 'SimSun', '宋体', serif;
background-color: #FFFFFF;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
font-weight: 600;
}
.form-input:focus {
@ -310,9 +302,8 @@ body, html {
.forgot-password {
font-size: 9px;
color: #B5B5B5;
color: #A3A3A3;
text-decoration: none;
font-family: 'SimSun', '宋体', serif;
}
.forgot-password:hover {
@ -339,17 +330,15 @@ body, html {
font-size: 11px;
color: #444040ba;
font-weight: bold;
font-family: 'SimSun', '宋体', serif;
}
.login-button {
width: 100%;
background-color: #409EFF;
background-color: #175EFF;
color: white;
font-weight: 500;
font-size: 11px;
padding: 0.6rem 0.8rem;
border-radius: 0;
font-size: 12px;
padding: 0.4rem 0.8rem;
border-radius: 3px;
border: none;
cursor: pointer;
transition: background-color 0.2s;
@ -357,14 +346,14 @@ body, html {
align-items: center;
justify-content: center;
box-sizing: border-box;
font-family: 'SimSun', '宋体', 'STSong', '华文宋体', serif;
box-shadow: 3px 3px 5px rgba(0, 0, 0, 0.2);
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
.login-icon {
height: 0.9rem;
height: 14px;
width: auto;
margin-right: 8px;
margin-right: 6px;
}
.login-button:hover {
@ -380,7 +369,6 @@ body, html {
display: flex;
align-items: center;
margin: 1.5rem 0;
font-family: 'SimSun', '宋体', serif;
}
.divider::before,
@ -394,8 +382,7 @@ body, html {
.divider span {
padding: 0 1rem;
font-size: 11px;
color: #B5B5B5;
font-family: 'SimSun', '宋体', serif;
color: #A3A3A3;
}
.social-login {
@ -404,11 +391,10 @@ body, html {
.social-text {
text-align: center;
color: #B5B5B5;
color: #A3A3A3;
margin-bottom: 1rem;
font-size: 11px;
font-weight: bold;
font-family: 'SimSun', '宋体', serif;
}
.social-icons {
@ -445,18 +431,14 @@ body, html {
}
.register-link p {
color: #B5B5B5;
color: #A3A3A3;
font-size: 11px;
font-weight: bold;
font-family: 'SimSun', '宋体', serif;
}
.register {
color: #B5B5B5;
font-weight: 500;
color: #A3A3A3;
text-decoration: none;
font-weight: bold;
font-family: 'SimSun', '宋体', serif;
}
.register:hover {
@ -511,7 +493,6 @@ body, html {
align-items: center;
justify-content: center;
height: 100%;
font-family: 'SimSun', '宋体', serif;
}
.graph-wrapper {
@ -533,7 +514,7 @@ body, html {
}
.login-form-container {
padding: 2rem;
padding: 1.5rem;
}
.graph-container {

3
vue/src/system/Profile.vue

@ -1,8 +1,7 @@
<template>
<div class="app-container">
<!-- 引入侧边栏组件 -->
<Menu
:initial-active="0"
<Menu
@menu-click="handleSidebarClick"
/>

2
vue/src/utils/request.js

@ -2,7 +2,7 @@ import axios from 'axios';
const service = axios.create({
baseURL: '/',
timeout: 10000,
timeout: 18000000,
});
service.interceptors.request.use(

Loading…
Cancel
Save