Browse Source

all

hanyuqing
hanyuqing 3 months ago
parent
commit
8996d95fa2
  1. 56
      controller/GraphController.py
  2. 5
      service/GraphService.py
  3. 42
      util/neo4j_utils.py
  4. 20
      vue/src/api/graph.js
  5. 133
      vue/src/system/GraphDemo.vue
  6. 2
      vue/src/system/KGData.vue

56
controller/GraphController.py

@ -11,7 +11,7 @@ from robyn import Robyn, jsonify, Response
from typing import Optional, List, Any, Dict
from service.GraphService import build_g6_subgraph_by_props, get_drug_names_from_neo4j, get_group_key, \
get_check_names_from_neo4j
get_check_names_from_neo4j, get_disease_names_from_neo4j
from util.neo4j_utils import Neo4jUtil
from util.neo4j_utils import neo4j_client
from util.redis_utils import set as redis_set, get as redis_get # 使用你已有的模块级 Redis 工
@ -19,7 +19,7 @@ from util.redis_utils import set as redis_set, get as redis_get # 使用你已
# 缓存键
DRUG_TREE_KEY = "cache:drug_tree"
CHECK_TREE_KEY = "cache:check_tree"
DISEASE_TREE_KEY = "cache:disease_tree" # 👈 新增
# ========================
# 🔥 启动时预加载数据(在 app 启动前执行)
# ========================
@ -41,7 +41,7 @@ def preload_data():
for key in all_keys:
if key in groups:
children = [{"label": name, "type": "Drug"} for name in sorted(groups[key])]
tree_data.append({"label": key, "type": "Drug", "children": children})
tree_data.append({"label": key, "children": children})
redis_set(DRUG_TREE_KEY, json.dumps(tree_data, ensure_ascii=False), ex=None)
@ -56,10 +56,23 @@ def preload_data():
for key in all_keys:
if key in groups:
children = [{"label": name, "type": "Check"} for name in sorted(groups[key])]
tree_data.append({"label": key, "type": "Check", "children": children})
tree_data.append({"label": key, "children": children})
redis_set(CHECK_TREE_KEY, json.dumps(tree_data, ensure_ascii=False), ex=None)
names = get_disease_names_from_neo4j()
groups = {}
for name in names:
key = get_group_key(name)
groups.setdefault(key, []).append(name)
tree_data = []
for key in all_keys:
if key in groups:
children = [{"label": name, "type": "Disease"} for name in sorted(groups[key])]
tree_data.append({"label": key, "children": children})
redis_set(DISEASE_TREE_KEY, json.dumps(tree_data, ensure_ascii=False), ex=None)
print("✅ 预加载完成!数据已写入 Redis 缓存。")
except Exception as e:
print(f"❌ 预加载失败: {e}", file=sys.stderr)
@ -229,8 +242,39 @@ def get_check_tree():
# description=json.dumps({"error": str(e)}, ensure_ascii=False),
# headers={"Content-Type": "application/json; charset=utf-8"}
# )
@app.get("/api/disease-tree")
def get_disease_tree():
return Response(
status_code=200,
description=redis_get(DISEASE_TREE_KEY),
headers={"Content-Type": "application/json; charset=utf-8"}
)
@app.get("/api/disease-depart-tree")
def get_department_disease_tree():
try:
tree_data = neo4j_client.get_department_disease_tree()
return Response(
status_code=200,
description=jsonify(tree_data),
headers={"Content-Type": "text/plain; charset=utf-8"}
)
except Exception as e:
error_trace = traceback.format_exc()
print("Error in /api/tree:", error_trace)
return jsonify({"error": str(e)}), 500
@app.get("/api/drug-subject-tree")
def get_drug_subject_tree():
try:
tree_data = neo4j_client.get_subject_drug_tree()
return Response(
status_code=200,
description=jsonify(tree_data),
headers={"Content-Type": "text/plain; charset=utf-8"}
)
except Exception as e:
error_trace = traceback.format_exc()
print("Error in /api/tree:", error_trace)
return jsonify({"error": str(e)}), 500
@app.get("/health")
def health():
print(redis_get(DRUG_TREE_KEY))

5
service/GraphService.py

@ -149,6 +149,11 @@ def get_check_names_from_neo4j():
names.append(name)
print(f"[DEBUG] Loaded {len(names)} check names from Neo4j") # 打印实际数量
return names
def get_disease_names_from_neo4j():
cypher = "MATCH (d:Disease) RETURN d.name AS name"
results = neo4j_client.execute_read(cypher)
return [record["name"] for record in results if record.get("name")]
def get_group_key(name: str) -> str:
if not name or not isinstance(name, str):
return "其他"

42
util/neo4j_utils.py

@ -650,6 +650,48 @@ class Neo4jUtil:
conditions.append(f"{var}.`{k}` = ${param_key}")
params[param_key] = v
return " AND ".join(conditions), params
def get_department_disease_tree(self):
"""
查询所有科室及其关联的疾病返回 el-tree 格式数据
"""
cypher = """
MATCH (d:Department)--(dis:Disease)
RETURN d.name AS dept_name, collect(dis.name) AS diseases
ORDER BY d.name
"""
results = self.execute_read(cypher)
tree = []
for record in results:
dept_node = {
"label": record["dept_name"],
"type": "Department",
"children": [{"label": name,"type": "Disease"} for name in record["diseases"]]
}
tree.append(dept_node)
return tree
def get_subject_drug_tree(self):
"""
查询所有药物分类Subject及其关联的药物Drug返回 el-tree 格式数据
"""
cypher = """
MATCH (s:Subject)--(d:Drug)
RETURN s.name AS subject_name, collect(d.name) AS drugs
ORDER BY s.name
"""
results = self.execute_read(cypher)
tree = []
for record in results:
subject_node = {
"label": record["subject_name"],
"type": "Subject",
"children": [{"label": name, "type": "Drug"} for name in record["drugs"]]
}
tree.append(subject_node)
return tree
# ==================== 全局单例实例(自动初始化)====================
neo4j_client = Neo4jUtil(
uri=NEO4J_URI,

20
vue/src/api/graph.js

@ -47,3 +47,23 @@ export function getCheckTree() {
method: 'get'
});
}
export function getDiseaseDepartTree() {
return request({
url: '/api/disease-depart-tree',
method: 'get'
});
}
export function getDiseaseTree() {
return request({
url: '/api/disease-tree',
method: 'get'
});
}
export function getDrugSubjectTree() {
return request({
url: '/api/drug-subject-tree',
method: 'get'
});
}

133
vue/src/system/GraphDemo.vue

@ -51,6 +51,37 @@
:class="{'radio-check': typeRadio === 'Check'}"
>检查</el-radio>
</el-radio-group>
</div>
<div v-if="typeRadio === 'Disease'">
<el-radio-group v-model="DiseaseRadio" @change="changeTree">
<el-radio
value="ICD10"
:class="{'radio-disease': typeRadio === 'Disease'}"
>ICD10</el-radio>
<el-radio
value="Department"
:class="{'radio-disease': typeRadio === 'Disease'}"
>科室</el-radio>
<el-radio
value="SZM"
:class="{'radio-disease': typeRadio === 'Disease'}"
>首字母</el-radio>
</el-radio-group>
</div>
<div v-if="typeRadio === 'Drug'">
<el-radio-group v-model="DrugRadio" @change="changeTree">
<el-radio
value="Subject"
:class="{'radio-drug': typeRadio === 'Drug'}"
>药物分类</el-radio>
<el-radio
value="SZM"
:class="{'radio-drug': typeRadio === 'Drug'}"
>首字母</el-radio>
</el-radio-group>
</div>
<div class="disease-body">
@ -85,7 +116,15 @@
</template>
<script>
import {getCheckTree, getCount, getDrugTree, getGraph, getTestGraphData} from "@/api/graph"
import {
getCheckTree,
getCount,
getDiseaseDepartTree,
getDiseaseTree, getDrugSubjectTree,
getDrugTree,
getGraph,
getTestGraphData
} from "@/api/graph"
import {Graph, Tooltip} from '@antv/g6';
import Menu from "@/components/Menu.vue";
import {a} from "vue-router/dist/devtools-EWN81iOl.mjs";
@ -135,8 +174,11 @@ export default {
label: 'title' // el-tree
},
typeRadio:"Disease",
DiseaseRadio:"ICD10",
drugTree:[],
diseaseTree:[],
diseaseDepartTree:[],
diseaseICD10Tree:[],
diseaseSZMTree:[],
checkTree:[],
legendItems: [
{ key: 'Disease', label: '疾病', color: '#EF4444' },
@ -155,7 +197,9 @@ export default {
nodes: [],
edges: []
},
searchKeyword:""
searchKeyword:"",
drugSubjectTree:[],
DrugRadio:"Subject"
}
},
computed: {
@ -214,12 +258,16 @@ export default {
// #790800
},
async mounted() {
this.visibleCategories = new Set(this.legendItems.map(i => i.key));
await this.loadDiseaseTreeData()
await this.loadDiseaseICD10TreeData()
this.loadDiseaseDepartTreeData()
this.loadDiseaseSZMTreeData()
this.getCount()
this.loadDrugTreeData()
this.loadCheckTreeData()
this.treeData=this.diseaseTree
this.loadDrugSubjectTreeData()
this.treeData=this.diseaseICD10Tree
await this.$nextTick();
try {
const response = await getTestGraphData(); // Promise
@ -532,53 +580,59 @@ export default {
changeTree(){
if(this.typeRadio=="Disease"){
this.treeData=this.diseaseTree
if(this.DiseaseRadio=="ICD10") {
this.treeData=this.diseaseICD10Tree
}
if(this.DiseaseRadio=="Department") {
this.treeData=this.diseaseDepartTree
}
if(this.DiseaseRadio=="SZM") {
this.treeData=this.diseaseSZMTree
}
}
if(this.typeRadio=="Drug") {
if(this.DrugRadio=="Subject") {
this.treeData=this.drugSubjectTree
}
if(this.DrugRadio=="SZM") {
this.treeData=this.drugTree
}
}
if(this.typeRadio=="Check") {
this.treeData=this.checkTree
}
},
loadTreeNode(node, resolve, data) {
//
let apiCall;
if (this.typeRadio === 'Disease') {
// ICD-10 level chapter, section
// code id
apiCall = () => this.loadDiseaseTreeData();
} else if (this.typeRadio === 'Drug') {
apiCall = () => this.loadDrugTreeData(); // API
} else if (this.typeRadio === 'Check') {
apiCall = () => this.loadCheckTreeData();
}
if (apiCall) {
apiCall()
.then(children => {
// children labelcode/id
// []
resolve(children || []);
})
.catch(err => {
console.error('加载子节点失败:', err);
resolve([]); //
});
} else {
resolve([]);
}
},
async loadDiseaseTreeData() {
async loadDiseaseICD10TreeData() {
try {
const res = await fetch('/icd10.json')
if (!res.ok) throw new Error('Failed to load JSON')
this.diseaseTree = await res.json()
console.log(this.treeData)
this.diseaseICD10Tree = await res.json()
} catch (error) {
console.error('加载 ICD-10 数据失败:', error)
this.$message.error('加载编码数据失败,请检查文件路径')
}
},
async loadDiseaseDepartTreeData() {
try {
const res = await getDiseaseDepartTree()
this.diseaseDepartTree = res
} catch (error) {
}
},
async loadDiseaseSZMTreeData() {
try {
const res = await getDiseaseTree()
this.diseaseSZMTree = res
} catch (error) {
}
},
async loadDrugSubjectTreeData() {
try {
const res = await getDrugSubjectTree()
this.drugSubjectTree = res
} catch (error) {
}
},
async loadDrugTreeData() {
try {
const res = await getDrugTree()
@ -612,6 +666,11 @@ export default {
const response = await getGraph(data); // Promise
this.formatData(response)
}
if(data.type === "Disease"){
data.type="Disease"
const response = await getGraph(data); // Promise
this.formatData(response)
}
},
buildNodeLabelMap(nodes) {
this._nodeLabelMap = new Map();
@ -1263,7 +1322,7 @@ button:hover {
border: 1px solid #f0f0f0;
border-radius: 12px;
overflow: hidden;
max-height: 85vh;
}
.disease-header {

2
vue/src/system/KGData.vue

@ -333,7 +333,7 @@ const CHINESE_TO_ENGLISH_LABEL = {
"辅助治疗": "AdjuvantTherapy",
"不良反应": "adverseReactions",
"检查": "Check",
"部门": "Department",
"科室": "Department",
"疾病部位": "DiseaseSite",
"相关疾病": "RelatedDisease",
"相关症状": "RelatedSymptom",

Loading…
Cancel
Save