|
|
@ -218,8 +218,7 @@ |
|
|
size="small" |
|
|
size="small" |
|
|
type="info" |
|
|
type="info" |
|
|
disabled |
|
|
disabled |
|
|
plain |
|
|
plain>已应用 |
|
|
@click.stop>已应用 |
|
|
|
|
|
</el-button> |
|
|
</el-button> |
|
|
|
|
|
|
|
|
<i class="el-icon-delete group-del" @click.stop="deleteGroup(group.id)"></i> |
|
|
<i class="el-icon-delete group-del" @click.stop="deleteGroup(group.id)"></i> |
|
|
@ -236,10 +235,10 @@ |
|
|
<div |
|
|
<div |
|
|
class="config-card" |
|
|
class="config-card" |
|
|
:class="{ |
|
|
:class="{ |
|
|
'card-using': usingConfigIds.includes(item.id), |
|
|
'card-using': item.id === editingConfigId, |
|
|
'card-checked': checkedConfigIds.includes(item.id) |
|
|
'card-checked': checkedConfigIds.includes(item.id) |
|
|
}" |
|
|
}" |
|
|
@click="toggleApplyConfig(item)" |
|
|
@click="handleEditConfig(item)" |
|
|
> |
|
|
> |
|
|
<div class="card-left"> |
|
|
<div class="card-left"> |
|
|
<div class="checkbox-wrapper"> |
|
|
<div class="checkbox-wrapper"> |
|
|
@ -248,7 +247,7 @@ |
|
|
<div class="card-info"> |
|
|
<div class="card-info"> |
|
|
<div class="card-title-row"> |
|
|
<div class="card-title-row"> |
|
|
<span class="card-name">{{ item.canvas_name }}</span> |
|
|
<span class="card-name">{{ item.canvas_name }}</span> |
|
|
<span v-if="usingConfigIds.includes(item.id)" class="status-badge">已应用</span> |
|
|
<span v-if="item.id === editingConfigId" class="status-badge">编辑中</span> |
|
|
</div> |
|
|
</div> |
|
|
<span class="card-tag">标签: {{ item.current_label }}</span> |
|
|
<span class="card-tag">标签: {{ item.current_label }}</span> |
|
|
</div> |
|
|
</div> |
|
|
@ -318,7 +317,7 @@ |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script> |
|
|
<script> |
|
|
import {Graph} from '@antv/g6'; |
|
|
import { Graph } from '@antv/g6'; |
|
|
import Menu from "@/components/Menu.vue"; |
|
|
import Menu from "@/components/Menu.vue"; |
|
|
import { |
|
|
import { |
|
|
saveGraphStyle, |
|
|
saveGraphStyle, |
|
|
@ -329,8 +328,8 @@ import { |
|
|
deleteGraphStyleGroup, |
|
|
deleteGraphStyleGroup, |
|
|
applyGraphStyleGroup, getGraphStyleActive |
|
|
applyGraphStyleGroup, getGraphStyleActive |
|
|
} from '@/api/style'; |
|
|
} from '@/api/style'; |
|
|
import {ElMessageBox, ElMessage} from 'element-plus'; |
|
|
import { ElMessageBox, ElMessage } from 'element-plus'; |
|
|
import {markRaw} from 'vue'; |
|
|
import { markRaw } from 'vue'; |
|
|
|
|
|
|
|
|
const tagToLabelMap = { |
|
|
const tagToLabelMap = { |
|
|
'疾病': 'Disease', '症状': 'Symptom', '病因': 'Cause', '药品': 'Drug', '科室': 'Department', '检查': 'Check', '其他': 'Other' |
|
|
'疾病': 'Disease', '症状': 'Symptom', '病因': 'Cause', '药品': 'Drug', '科室': 'Department', '检查': 'Check', '其他': 'Other' |
|
|
@ -349,7 +348,7 @@ const INITIAL_STROKE_MAP = { |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: 'GraphDemo', |
|
|
name: 'GraphDemo', |
|
|
components: {Menu}, |
|
|
components: { Menu }, |
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
activeTags: '疾病', |
|
|
activeTags: '疾病', |
|
|
@ -359,8 +358,10 @@ export default { |
|
|
checkedConfigIds: [], |
|
|
checkedConfigIds: [], |
|
|
checkedGroupIds: [], |
|
|
checkedGroupIds: [], |
|
|
usingConfigIds: [], |
|
|
usingConfigIds: [], |
|
|
|
|
|
editingConfigId: null, |
|
|
|
|
|
editingConfigLabel: '', |
|
|
saveDialogVisible: false, |
|
|
saveDialogVisible: false, |
|
|
saveForm: {group_name: '', canvas_name: ''}, |
|
|
saveForm: { group_name: '', canvas_name: '' }, |
|
|
tagStyles: { |
|
|
tagStyles: { |
|
|
'Disease': this.getInitialTagParams('Disease'), |
|
|
'Disease': this.getInitialTagParams('Disease'), |
|
|
'Symptom': this.getInitialTagParams('Symptom'), |
|
|
'Symptom': this.getInitialTagParams('Symptom'), |
|
|
@ -376,7 +377,7 @@ export default { |
|
|
nodeFontSize: 12, |
|
|
nodeFontSize: 12, |
|
|
nodeFontColor: '#ffffff', |
|
|
nodeFontColor: '#ffffff', |
|
|
nodeShape: 'circle', |
|
|
nodeShape: 'circle', |
|
|
nodeSize: 50, |
|
|
nodeSize: 60, |
|
|
nodeFill: '#EF4444', |
|
|
nodeFill: '#EF4444', |
|
|
nodeStroke: '#B91C1C', |
|
|
nodeStroke: '#B91C1C', |
|
|
nodeLineWidth: 2, |
|
|
nodeLineWidth: 2, |
|
|
@ -390,31 +391,33 @@ export default { |
|
|
edgeStroke: '#EF4444', |
|
|
edgeStroke: '#EF4444', |
|
|
defaultData: { |
|
|
defaultData: { |
|
|
nodes: [ |
|
|
nodes: [ |
|
|
{id: "node1", data: {name: "霍乱", label: "Disease"}}, |
|
|
{ id: "node1", data: { name: "霍乱", label: "Disease" } }, |
|
|
{id: "node2", data: {name: "腹泻", label: "Symptom"}}, |
|
|
{ id: "node2", data: { name: "腹泻", label: "Symptom" } }, |
|
|
{id: "node3", data: {name: "脱水", label: "Disease"}}, |
|
|
{ id: "node3", data: { name: "脱水", label: "Disease" } }, |
|
|
{id: "node4", data: {name: "呕吐", label: "Disease"}}, |
|
|
{ id: "node4", data: { name: "呕吐", label: "Disease" } }, |
|
|
{id: "node5", data: {name: "由霍乱弧菌感染所致", label: "Cause"}}, |
|
|
{ id: "node5", data: { name: "由霍乱弧菌感染所致", label: "Cause" } }, |
|
|
{id: "node6", data: {name: "复方磺胺甲噁唑", label: "Drug"}}, |
|
|
{ id: "node6", data: { name: "复方磺胺甲噁唑", label: "Drug" } }, |
|
|
{id: "node7", data: {name: "消化系统", label: "DiseaseSite"}}, |
|
|
{ id: "node7", data: { name: "消化系统", label: "DiseaseSite" } }, |
|
|
{id: "node8", data: {name: "传染科", label: "Department"}}, |
|
|
{ id: "node8", data: { name: "传染科", label: "Department" } }, |
|
|
{id: "node9", data: {name: "代谢性 酸中毒", label: "Disease"}}, |
|
|
{ id: "node9", data: { name: "代谢性 酸中毒", label: "Disease" } }, |
|
|
{id: "node10", data: {name: "急性肾衰竭", label: "Disease"}}, |
|
|
{ id: "node10", data: { name: "急性肾衰竭", label: "Disease" } }, |
|
|
{id: "node11", data: {name: "检查项目", label: "Check"}} |
|
|
{ id: "node11", data: { name: "检查项目", label: "Check" } } |
|
|
], |
|
|
], |
|
|
edges: [ |
|
|
edges: [ |
|
|
{id: "e1", source: "node1", target: "node2", data: {relationship: {properties: {label: "症状与体征"}}}}, |
|
|
{ id: "e1", source: "node1", target: "node2", data: { relationship: { properties: { label: "症状与体征" } } } }, |
|
|
{id: "e2", source: "node1", target: "node3", data: {relationship: {properties: {label: "并发症"}}}}, |
|
|
{ id: "e2", source: "node1", target: "node3", data: { relationship: { properties: { label: "并发症" } } } }, |
|
|
{id: "e3", source: "node1", target: "node4", data: {relationship: {properties: {label: "并发症"}}}}, |
|
|
{ id: "e3", source: "node1", target: "node4", data: { relationship: { properties: { label: "并发症" } } } }, |
|
|
{id: "e4", source: "node1", target: "node5", data: {relationship: {properties: {label: "病因"}}}}, |
|
|
{ id: "e4", source: "node1", target: "node5", data: { relationship: { properties: { label: "病因" } } } }, |
|
|
{id: "e5", source: "node1", target: "node6", data: {relationship: {properties: {label: "治疗方案"}}}}, |
|
|
{ id: "e5", source: "node1", target: "node6", data: { relationship: { properties: { label: "治疗方案" } } } }, |
|
|
{id: "e6", source: "node1", target: "node7", data: {relationship: {properties: {label: "病变部位"}}}}, |
|
|
{ id: "e6", source: "node1", target: "node7", data: { relationship: { properties: { label: "病变部位" } } } }, |
|
|
{id: "e7", source: "node1", target: "node8", data: {relationship: {properties: {label: "科室"}}}}, |
|
|
{ id: "e7", source: "node1", target: "node8", data: { relationship: { properties: { label: "科室" } } } }, |
|
|
{id: "e8", source: "node1", target: "node9", data: {relationship: {properties: {label: "并发症"}}}}, |
|
|
{ id: "e8", source: "node1", target: "node9", data: { relationship: { properties: { label: "并发症" } } } }, |
|
|
{id: "e9", source: "node1", target: "node10", data: {relationship: {properties: {label: "并发症"}}}}, |
|
|
{ id: "e9", source: "node1", target: "node10", data: { relationship: { properties: { label: "并发症" } } } }, |
|
|
{id: "e10", source: "node1", target: "node11", data: {relationship: {properties: {label: "检查"}}}} |
|
|
{ id: "e10", source: "node1", target: "node11", data: { relationship: { properties: { label: "检查" } } } } |
|
|
] |
|
|
] |
|
|
} |
|
|
}, |
|
|
|
|
|
saveTimer: null, |
|
|
|
|
|
isInitialEcho: false |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
watch: { |
|
|
watch: { |
|
|
@ -459,40 +462,141 @@ export default { |
|
|
this._graph = null; |
|
|
this._graph = null; |
|
|
} |
|
|
} |
|
|
window.removeEventListener('resize', this.handleResize); |
|
|
window.removeEventListener('resize', this.handleResize); |
|
|
|
|
|
if (this.saveTimer) clearTimeout(this.saveTimer); |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
handleEditConfig(item) { |
|
|
|
|
|
if (this.saveTimer) clearTimeout(this.saveTimer); |
|
|
|
|
|
this.isInitialEcho = true; |
|
|
|
|
|
|
|
|
|
|
|
this.editingConfigId = item.id; |
|
|
|
|
|
this.editingConfigLabel = item.current_label; |
|
|
|
|
|
this.activeTags = item.current_label; |
|
|
|
|
|
|
|
|
|
|
|
const s = item.styles; |
|
|
|
|
|
if (!s) { |
|
|
|
|
|
this.isInitialEcho = false; |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.nodeShowLabel = s.nodeShowLabel; |
|
|
|
|
|
this.nodeFontFamily = s.nodeFontFamily; |
|
|
|
|
|
this.nodeFontSize = s.nodeFontSize; |
|
|
|
|
|
this.nodeFontColor = s.nodeFontColor; |
|
|
|
|
|
this.nodeShape = s.nodeShape; |
|
|
|
|
|
this.nodeSize = s.nodeSize; |
|
|
|
|
|
this.nodeFill = s.nodeFill; |
|
|
|
|
|
this.nodeStroke = s.nodeStroke; |
|
|
|
|
|
this.nodeLineWidth = s.nodeLineWidth; |
|
|
|
|
|
this.edgeShowLabel = s.edgeShowLabel; |
|
|
|
|
|
this.edgeEndArrow = s.edgeEndArrow; |
|
|
|
|
|
this.edgeFontFamily = s.edgeFontFamily; |
|
|
|
|
|
this.edgeFontSize = s.edgeFontSize; |
|
|
|
|
|
this.edgeFontColor = s.edgeFontColor; |
|
|
|
|
|
this.edgeType = s.edgeType; |
|
|
|
|
|
this.edgeLineWidth = s.edgeLineWidth; |
|
|
|
|
|
this.edgeStroke = s.edgeStroke; |
|
|
|
|
|
|
|
|
|
|
|
const labelEn = tagToLabelMap[item.current_label]; |
|
|
|
|
|
if (labelEn) this.tagStyles[labelEn] = JSON.parse(JSON.stringify(s)); |
|
|
|
|
|
|
|
|
|
|
|
if (!this.usingConfigIds.includes(item.id)) { |
|
|
|
|
|
this.styleGroups.forEach(g => { |
|
|
|
|
|
g.configs.forEach(c => { |
|
|
|
|
|
if (this.usingConfigIds.includes(c.id) && c.current_label === item.current_label) { |
|
|
|
|
|
this.usingConfigIds = this.usingConfigIds.filter(id => id !== c.id); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
this.usingConfigIds.push(item.id); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.updateAllElements(); |
|
|
|
|
|
this.$nextTick(() => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
this.isInitialEcho = false; |
|
|
|
|
|
}, 100); |
|
|
|
|
|
}); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
syncAndRefresh() { |
|
|
|
|
|
if (this.isInitialEcho) return; |
|
|
|
|
|
|
|
|
|
|
|
const labelEn = tagToLabelMap[this.activeTags]; |
|
|
|
|
|
if (!labelEn) return; |
|
|
|
|
|
|
|
|
|
|
|
const currentStyle = { |
|
|
|
|
|
nodeShowLabel: this.nodeShowLabel, nodeFontFamily: this.nodeFontFamily, nodeFontSize: this.nodeFontSize, |
|
|
|
|
|
nodeFontColor: this.nodeFontColor, nodeShape: this.nodeShape, nodeSize: this.nodeSize, |
|
|
|
|
|
nodeFill: this.nodeFill, nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth, |
|
|
|
|
|
edgeShowLabel: this.edgeShowLabel, edgeEndArrow: this.edgeEndArrow, edgeFontFamily: this.edgeFontFamily, |
|
|
|
|
|
edgeFontSize: this.edgeFontSize, edgeFontColor: this.edgeFontColor, edgeType: this.edgeType, |
|
|
|
|
|
edgeLineWidth: this.edgeLineWidth, edgeStroke: this.edgeStroke |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
this.tagStyles[labelEn] = currentStyle; |
|
|
|
|
|
this.updateAllElements(); |
|
|
|
|
|
|
|
|
|
|
|
if (this.editingConfigId) { |
|
|
|
|
|
if (this.saveTimer) clearTimeout(this.saveTimer); |
|
|
|
|
|
const currentEditId = this.editingConfigId; |
|
|
|
|
|
this.saveTimer = setTimeout(async () => { |
|
|
|
|
|
try { |
|
|
|
|
|
let targetConf = null; |
|
|
|
|
|
let groupName = null; |
|
|
|
|
|
for (const group of this.styleGroups) { |
|
|
|
|
|
targetConf = group.configs.find(c => c.id === currentEditId); |
|
|
|
|
|
if (targetConf) { |
|
|
|
|
|
groupName = group.group_name; |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (targetConf) { |
|
|
|
|
|
const payload = { |
|
|
|
|
|
id: currentEditId, |
|
|
|
|
|
canvas_name: targetConf.canvas_name, |
|
|
|
|
|
current_label: targetConf.current_label, |
|
|
|
|
|
group_name: groupName, |
|
|
|
|
|
styles: currentStyle, |
|
|
|
|
|
is_auto_save: true |
|
|
|
|
|
}; |
|
|
|
|
|
await saveGraphStyle(payload); |
|
|
|
|
|
targetConf.styles = JSON.parse(JSON.stringify(currentStyle)); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
console.error("同步失败:", err); |
|
|
|
|
|
} |
|
|
|
|
|
}, 800); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
validateNodeSize(event) { |
|
|
validateNodeSize(event) { |
|
|
const inputVal = event.target.value; |
|
|
const val = parseInt(event.target.value); |
|
|
const val = parseInt(inputVal); |
|
|
|
|
|
if (isNaN(val) || val < 30 || val > 100) { |
|
|
if (isNaN(val) || val < 30 || val > 100) { |
|
|
ElMessage({ message: `节点尺寸请输入 30 到 100 之间的数字`, type: 'warning', duration: 1500 }); |
|
|
ElMessage({ message: `请输入 30 到 100`, type: 'warning' }); |
|
|
event.target.value = this.nodeSize; |
|
|
event.target.value = this.nodeSize; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
this.nodeSize = val; |
|
|
this.nodeSize = val; |
|
|
this.syncAndRefresh(); |
|
|
|
|
|
}, |
|
|
}, |
|
|
validateNodeLineWidth(event) { |
|
|
validateNodeLineWidth(event) { |
|
|
const inputVal = event.target.value; |
|
|
const val = parseInt(event.target.value); |
|
|
const val = parseInt(inputVal); |
|
|
|
|
|
if (isNaN(val) || val < 1 || val > 5) { |
|
|
if (isNaN(val) || val < 1 || val > 5) { |
|
|
ElMessage({ message: `边框尺寸请输入 1 到 5 之间的数字`, type: 'warning', duration: 1500 }); |
|
|
ElMessage({ message: `请输入 1 到 5`, type: 'warning' }); |
|
|
event.target.value = this.nodeLineWidth; |
|
|
event.target.value = this.nodeLineWidth; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
this.nodeLineWidth = val; |
|
|
this.nodeLineWidth = val; |
|
|
this.syncAndRefresh(); |
|
|
|
|
|
}, |
|
|
}, |
|
|
validateEdgeLineWidth(event) { |
|
|
validateEdgeLineWidth(event) { |
|
|
const inputVal = event.target.value; |
|
|
const val = parseInt(event.target.value); |
|
|
const val = parseInt(inputVal); |
|
|
|
|
|
if (isNaN(val) || val < 1 || val > 5) { |
|
|
if (isNaN(val) || val < 1 || val > 5) { |
|
|
ElMessage({ message: `线条粗细请输入 1 到 5 之间的数字`, type: 'warning', duration: 1500 }); |
|
|
ElMessage({ message: `请输入 1 到 5`, type: 'warning' }); |
|
|
event.target.value = this.edgeLineWidth; |
|
|
event.target.value = this.edgeLineWidth; |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
this.edgeLineWidth = val; |
|
|
this.edgeLineWidth = val; |
|
|
this.syncAndRefresh(); |
|
|
|
|
|
}, |
|
|
}, |
|
|
getInitialTagParams(label) { |
|
|
getInitialTagParams(label) { |
|
|
const fill = INITIAL_FILL_MAP[label] || '#59d1d4'; |
|
|
const fill = INITIAL_FILL_MAP[label] || '#59d1d4'; |
|
|
@ -504,27 +608,35 @@ export default { |
|
|
edgeLineWidth: 2, edgeStroke: fill |
|
|
edgeLineWidth: 2, edgeStroke: fill |
|
|
}; |
|
|
}; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
handleTagClick(tag) { |
|
|
handleTagClick(tag) { |
|
|
this.activeTags = tag; |
|
|
if (this.editingConfigId && this.editingConfigLabel !== tag) { |
|
|
const label = tagToLabelMap[tag]; |
|
|
ElMessageBox.confirm( |
|
|
const style = this.tagStyles[label]; |
|
|
`当前正在编辑【${this.editingConfigLabel}】的存储配置,切换到【${tag}】将退出编辑模式,未保存的修改可能丢失。`, |
|
|
if (style) Object.assign(this, style); |
|
|
'提示', |
|
|
|
|
|
{ confirmButtonText: '确定切换', cancelButtonText: '取消', type: 'warning' } |
|
|
|
|
|
).then(() => { |
|
|
|
|
|
this.editingConfigId = null; |
|
|
|
|
|
this.editingConfigLabel = ''; |
|
|
|
|
|
this.performTagSwitch(tag); |
|
|
|
|
|
}).catch(() => {}); |
|
|
|
|
|
} else { |
|
|
|
|
|
this.performTagSwitch(tag); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
syncAndRefresh() { |
|
|
|
|
|
const label = tagToLabelMap[this.activeTags]; |
|
|
performTagSwitch(tag) { |
|
|
console.log(this.nodeFill) |
|
|
this.activeTags = tag; |
|
|
if (label) { |
|
|
const labelEn = tagToLabelMap[tag]; |
|
|
this.tagStyles[label] = { |
|
|
const style = this.tagStyles[labelEn]; |
|
|
nodeShowLabel: this.nodeShowLabel, nodeFontFamily: this.nodeFontFamily, nodeFontSize: this.nodeFontSize, |
|
|
if (style) { |
|
|
nodeFontColor: this.nodeFontColor, nodeShape: this.nodeShape, nodeSize: this.nodeSize, |
|
|
this.isInitialEcho = true; |
|
|
nodeFill: this.nodeFill, nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth, |
|
|
Object.assign(this, style); |
|
|
edgeShowLabel: this.edgeShowLabel, edgeEndArrow: this.edgeEndArrow, edgeFontFamily: this.edgeFontFamily, |
|
|
this.$nextTick(() => { this.isInitialEcho = false; }); |
|
|
edgeFontSize: this.edgeFontSize, edgeFontColor: this.edgeFontColor, edgeType: this.edgeType, |
|
|
|
|
|
edgeLineWidth: this.edgeLineWidth, edgeStroke: this.edgeStroke |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
} |
|
|
this.updateAllElements(); |
|
|
this.updateAllElements(); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
initGraph() { |
|
|
initGraph() { |
|
|
const container = this.$refs.graphContainer; |
|
|
const container = this.$refs.graphContainer; |
|
|
if (!container || container.clientWidth === 0) return; |
|
|
if (!container || container.clientWidth === 0) return; |
|
|
@ -532,9 +644,9 @@ export default { |
|
|
if (this._graph) this._graph.destroy(); |
|
|
if (this._graph) this._graph.destroy(); |
|
|
const graph = new Graph({ |
|
|
const graph = new Graph({ |
|
|
container, width: container.clientWidth, height: container.clientHeight || 600, |
|
|
container, width: container.clientWidth, height: container.clientHeight || 600, |
|
|
layout: {type: 'radial', unitRadius: 100, preventOverlap: true, nodeSpacing: 50}, |
|
|
layout: { type: 'radial', unitRadius: 100, preventOverlap: true, nodeSpacing: 50 }, |
|
|
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element', 'hover-activate'], |
|
|
behaviors: ['zoom-canvas', 'drag-canvas', 'drag-element', 'hover-activate'], |
|
|
autoFit: 'center', animation: true |
|
|
autoFit: 'center', animation: false |
|
|
}); |
|
|
}); |
|
|
this._graph = markRaw(graph); |
|
|
this._graph = markRaw(graph); |
|
|
this.updateAllElements(); |
|
|
this.updateAllElements(); |
|
|
@ -545,6 +657,7 @@ export default { |
|
|
updateAllElements() { |
|
|
updateAllElements() { |
|
|
if (!this._graph) return; |
|
|
if (!this._graph) return; |
|
|
|
|
|
|
|
|
|
|
|
const currentActiveLabelEn = tagToLabelMap[this.activeTags]; |
|
|
const labelToAppliedConfigMap = {}; |
|
|
const labelToAppliedConfigMap = {}; |
|
|
this.styleGroups.forEach(group => { |
|
|
this.styleGroups.forEach(group => { |
|
|
group.configs.forEach(conf => { |
|
|
group.configs.forEach(conf => { |
|
|
@ -565,7 +678,18 @@ export default { |
|
|
const nodes = this.defaultData.nodes.map(node => { |
|
|
const nodes = this.defaultData.nodes.map(node => { |
|
|
const rawLabel = node.data?.label || ''; |
|
|
const rawLabel = node.data?.label || ''; |
|
|
const effectiveKey = this.getEffectiveStyleKey(rawLabel); |
|
|
const effectiveKey = this.getEffectiveStyleKey(rawLabel); |
|
|
const s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey]; |
|
|
|
|
|
|
|
|
let s; |
|
|
|
|
|
if (effectiveKey === currentActiveLabelEn) { |
|
|
|
|
|
s = { |
|
|
|
|
|
nodeShape: this.nodeShape, nodeSize: this.nodeSize, nodeFill: this.nodeFill, |
|
|
|
|
|
nodeStroke: this.nodeStroke, nodeLineWidth: this.nodeLineWidth, |
|
|
|
|
|
nodeShowLabel: this.nodeShowLabel, nodeFontColor: this.nodeFontColor, |
|
|
|
|
|
nodeFontSize: this.nodeFontSize, nodeFontFamily: this.nodeFontFamily |
|
|
|
|
|
}; |
|
|
|
|
|
} else { |
|
|
|
|
|
s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
return { |
|
|
...node, type: s?.nodeShape || 'circle', |
|
|
...node, type: s?.nodeShape || 'circle', |
|
|
@ -582,27 +706,31 @@ export default { |
|
|
const edges = this.defaultData.edges.map(edge => { |
|
|
const edges = this.defaultData.edges.map(edge => { |
|
|
const sRawLabel = this._nodeLabelMap.get(edge.source); |
|
|
const sRawLabel = this._nodeLabelMap.get(edge.source); |
|
|
const effectiveKey = this.getEffectiveStyleKey(sRawLabel); |
|
|
const effectiveKey = this.getEffectiveStyleKey(sRawLabel); |
|
|
const s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey] || this; |
|
|
|
|
|
|
|
|
|
|
|
const strokeColor = hexToRgba(s.edgeStroke, 0.6); |
|
|
let s; |
|
|
|
|
|
if (effectiveKey === currentActiveLabelEn) { |
|
|
|
|
|
s = { |
|
|
|
|
|
edgeType: this.edgeType, edgeStroke: this.edgeStroke, edgeLineWidth: this.edgeLineWidth, |
|
|
|
|
|
edgeEndArrow: this.edgeEndArrow, edgeShowLabel: this.edgeShowLabel, |
|
|
|
|
|
edgeFontColor: this.edgeFontColor, edgeFontSize: this.edgeFontSize, edgeFontFamily: this.edgeFontFamily |
|
|
|
|
|
}; |
|
|
|
|
|
} else { |
|
|
|
|
|
s = labelToAppliedConfigMap[effectiveKey] || this.tagStyles[effectiveKey]; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const strokeColor = hexToRgba(s?.edgeStroke || '#EF4444', 0.6); |
|
|
return { |
|
|
return { |
|
|
...edge, type: s.edgeType || 'line', |
|
|
...edge, type: s?.edgeType || 'line', |
|
|
style: { |
|
|
style: { |
|
|
stroke: strokeColor, |
|
|
stroke: strokeColor, lineWidth: this.safeNum(s?.edgeLineWidth, 2), endArrow: s?.edgeEndArrow, |
|
|
lineWidth: this.safeNum(s.edgeLineWidth, 2), |
|
|
labelText: s?.edgeShowLabel ? (edge.data?.relationship?.properties?.label || '') : '', |
|
|
endArrow: s.edgeEndArrow, |
|
|
labelFill: s?.edgeFontColor || '#666', labelFontSize: this.safeNum(s?.edgeFontSize, 10), |
|
|
labelText: s.edgeShowLabel ? (edge.data?.relationship?.properties?.label || '') : '', |
|
|
labelFontFamily: s?.edgeFontFamily || 'Microsoft YaHei', labelBackground: true, |
|
|
labelFill: s.edgeFontColor || '#666', |
|
|
labelBackgroundFill: '#fff', labelBackgroundOpacity: 0.7 |
|
|
labelFontSize: this.safeNum(s.edgeFontSize, 10), |
|
|
|
|
|
labelFontFamily: s.edgeFontFamily || 'Microsoft YaHei', |
|
|
|
|
|
labelBackground: true, |
|
|
|
|
|
labelBackgroundFill: '#fff', |
|
|
|
|
|
labelBackgroundOpacity: 0.7 |
|
|
|
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
}); |
|
|
}); |
|
|
|
|
|
this._graph.setData({ nodes, edges }); |
|
|
this._graph.setData({nodes, edges}); |
|
|
|
|
|
this._graph.render(); |
|
|
this._graph.render(); |
|
|
}, |
|
|
}, |
|
|
safeNum(val, defaultVal = 1) { |
|
|
safeNum(val, defaultVal = 1) { |
|
|
@ -613,66 +741,37 @@ export default { |
|
|
try { |
|
|
try { |
|
|
const res = await getGroupedGraphStyleList(); |
|
|
const res = await getGroupedGraphStyleList(); |
|
|
if (res.code === 200) { |
|
|
if (res.code === 200) { |
|
|
// 1. 数据处理与【强制去重】 |
|
|
|
|
|
// 这里的逻辑确保了同一个配置 ID 在同一个分组内不会出现两次 |
|
|
|
|
|
this.styleGroups = res.data.map(group => { |
|
|
this.styleGroups = res.data.map(group => { |
|
|
const idSet = new Set(); |
|
|
const uniqueConfigs = (group.configs || []).map(conf => ({ |
|
|
const uniqueConfigs = []; |
|
|
...conf, |
|
|
|
|
|
styles: typeof conf.styles === 'string' ? JSON.parse(conf.styles) : conf.styles |
|
|
(group.configs || []).forEach(conf => { |
|
|
})); |
|
|
if (!idSet.has(conf.id)) { |
|
|
return { ...group, configs: uniqueConfigs }; |
|
|
idSet.add(conf.id); |
|
|
|
|
|
uniqueConfigs.push({ |
|
|
|
|
|
...conf, |
|
|
|
|
|
// 安全解析 styles 字符串 |
|
|
|
|
|
styles: typeof conf.styles === 'string' ? JSON.parse(conf.styles) : conf.styles |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
console.warn(`检测到重复 ID: ${conf.id},已在前端过滤`); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
...group, |
|
|
|
|
|
configs: uniqueConfigs |
|
|
|
|
|
}; |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 2. 初始加载时的应用逻辑(带排他性同步) |
|
|
const activeGroup = this.styleGroups.find(g => g.is_active); |
|
|
if (this.usingConfigIds.length === 0) { |
|
|
if (activeGroup) { |
|
|
const tempUsingIds = []; |
|
|
this.usingConfigIds = activeGroup.configs.map(c => c.id); |
|
|
const seenLabels = new Set(); // 记录已处理的标签(如:疾病、药品) |
|
|
activeGroup.configs.forEach(conf => { |
|
|
|
|
|
const labelEn = tagToLabelMap[conf.current_label]; |
|
|
this.styleGroups.forEach(group => { |
|
|
if (labelEn) { |
|
|
// 只处理后端标记为 active 的方案组 |
|
|
this.tagStyles[labelEn] = JSON.parse(JSON.stringify(conf.styles)); |
|
|
if (group.is_active) { |
|
|
|
|
|
// 自动展开激活的折叠面板 |
|
|
|
|
|
if (!this.activeCollapseNames.includes(group.id)) { |
|
|
|
|
|
this.activeCollapseNames.push(group.id); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 遍历组内配置进行标签去重 |
|
|
|
|
|
group.configs.forEach(conf => { |
|
|
|
|
|
if (!seenLabels.has(conf.current_label)) { |
|
|
|
|
|
tempUsingIds.push(conf.id); |
|
|
|
|
|
seenLabels.add(conf.current_label); |
|
|
|
|
|
} else { |
|
|
|
|
|
console.warn(`标签冲突拦截:方案【${group.group_name}】中的配置【${conf.canvas_name}】因标签【${conf.current_label}】已存在而被忽略`); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 最终赋值 |
|
|
const currentActiveConf = activeGroup.configs.find(c => c.current_label === this.activeTags); |
|
|
this.usingConfigIds = tempUsingIds; |
|
|
if (currentActiveConf) { |
|
|
|
|
|
this.isInitialEcho = true; |
|
|
|
|
|
Object.assign(this, currentActiveConf.styles); |
|
|
|
|
|
this.editingConfigId = currentActiveConf.id; |
|
|
|
|
|
this.editingConfigLabel = currentActiveConf.current_label; |
|
|
|
|
|
this.$nextTick(() => { this.isInitialEcho = false; }); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 3. 驱动图谱重新渲染 |
|
|
|
|
|
this.updateAllElements(); |
|
|
this.updateAllElements(); |
|
|
} |
|
|
} |
|
|
} catch (err) { |
|
|
} catch (err) { |
|
|
console.error("加载配置失败:", err); |
|
|
console.error("加载配置失败:", err); |
|
|
ElMessage.error("获取方案列表失败"); |
|
|
|
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
async fetchGroupNames() { |
|
|
async fetchGroupNames() { |
|
|
@ -683,117 +782,68 @@ export default { |
|
|
const idx = this.usingConfigIds.indexOf(item.id); |
|
|
const idx = this.usingConfigIds.indexOf(item.id); |
|
|
if (idx > -1) { |
|
|
if (idx > -1) { |
|
|
this.usingConfigIds.splice(idx, 1); |
|
|
this.usingConfigIds.splice(idx, 1); |
|
|
|
|
|
if (this.editingConfigId === item.id) { |
|
|
|
|
|
this.editingConfigId = null; |
|
|
|
|
|
this.editingConfigLabel = ''; |
|
|
|
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
// 同类标签排他性应用 |
|
|
this.handleEditConfig(item); |
|
|
this.styleGroups.forEach(g => { |
|
|
|
|
|
g.configs.forEach(c => { |
|
|
|
|
|
if (this.usingConfigIds.includes(c.id) && c.current_label === item.current_label) { |
|
|
|
|
|
this.usingConfigIds = this.usingConfigIds.filter(id => id !== c.id); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
this.usingConfigIds.push(item.id); |
|
|
|
|
|
} |
|
|
} |
|
|
this.updateAllElements(); |
|
|
this.updateAllElements(); |
|
|
}, |
|
|
}, |
|
|
/** |
|
|
|
|
|
* 新增功能:将配置移动到另一个方案 |
|
|
// 修改 validateGroupConstraint 函数 |
|
|
*/ |
|
|
validateGroupConstraint(groupName, labelName, excludeId = null) { |
|
|
|
|
|
const group = this.styleGroups.find(g => g.group_name === groupName); |
|
|
|
|
|
if (!group) return true; |
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查标签是否重复 |
|
|
|
|
|
const isLabelExist = group.configs.some(c => c.current_label === labelName && c.id !== excludeId); |
|
|
|
|
|
if (isLabelExist) { |
|
|
|
|
|
// 使用 alert 而不是 confirm,因为它不需要处理“取消”逻辑,且必须加 .catch() 规避报错 |
|
|
|
|
|
ElMessageBox.alert( |
|
|
|
|
|
`方案【${groupName}】中已存在【${labelName}】标签的配置,请先删除旧配置或选择其他方案。`, |
|
|
|
|
|
'校验失败', |
|
|
|
|
|
{ type: 'error' } |
|
|
|
|
|
).catch(() => {}); // 捕获关闭弹窗时的异常 |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查总量是否已达5个 |
|
|
|
|
|
if (group.configs.length >= 5 && !group.configs.some(c => c.id === excludeId)) { |
|
|
|
|
|
ElMessageBox.alert( |
|
|
|
|
|
`方案【${groupName}】的配置已满(上限5个),无法添加。`, |
|
|
|
|
|
'校验失败', |
|
|
|
|
|
{ type: 'error' } |
|
|
|
|
|
).catch(() => {}); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
async moveConfigToGroup(config, targetGroup) { |
|
|
async moveConfigToGroup(config, targetGroup) { |
|
|
|
|
|
// 移动时的校验 |
|
|
|
|
|
if (!this.validateGroupConstraint(targetGroup.group_name, config.current_label, config.id)) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 1. 构建更新负载 |
|
|
|
|
|
const payload = { |
|
|
const payload = { |
|
|
id: config.id, // 确保有 ID,后端才会执行更新而非新增 |
|
|
id: config.id, |
|
|
canvas_name: config.canvas_name, |
|
|
canvas_name: config.canvas_name, |
|
|
group_name: targetGroup.group_name, // 目标方案名 |
|
|
group_name: targetGroup.group_name, |
|
|
current_label: config.current_label, |
|
|
current_label: config.current_label, |
|
|
styles: config.styles |
|
|
styles: config.styles, |
|
|
|
|
|
is_auto_save: false |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const res = await saveGraphStyle(payload); |
|
|
const res = await saveGraphStyle(payload); |
|
|
|
|
|
|
|
|
if (res.code === 200) { |
|
|
if (res.code === 200) { |
|
|
ElMessage.success(`已成功移动至【${targetGroup.group_name}】`); |
|
|
ElMessage.success(`已移动至【${targetGroup.group_name}】`); |
|
|
|
|
|
|
|
|
// 2. 【核心修复】手动清理本地 styleGroups 中的旧引用,避免 fetchConfigs 响应延迟导致的视觉重复 |
|
|
|
|
|
this.styleGroups = this.styleGroups.map(group => { |
|
|
|
|
|
return { |
|
|
|
|
|
...group, |
|
|
|
|
|
// 过滤掉所有组里 ID 等于当前移动配置 ID 的项 |
|
|
|
|
|
configs: group.configs.filter(c => c.id !== config.id) |
|
|
|
|
|
}; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 重新获取最新的服务器状态 |
|
|
|
|
|
await this.fetchConfigs(); |
|
|
await this.fetchConfigs(); |
|
|
} |
|
|
} |
|
|
} catch (err) { |
|
|
} catch (err) { |
|
|
console.error("移动配置失败:", err); |
|
|
ElMessage.error("操作失败"); |
|
|
ElMessage.error("移动操作失败,请重试"); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
/** |
|
|
|
|
|
* 修改后的应用全案方法 |
|
|
|
|
|
* 逻辑:保留当前已手动选中的配置,新方案中冲突的配置不予应用 |
|
|
|
|
|
*/ |
|
|
|
|
|
async applyWholeGroup(group) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 1. 【新增】定义必须包含的 5 个核心标签 |
|
|
|
|
|
const REQUIRED_TAGS = ['疾病', '症状', '药品', '检查', '其他']; |
|
|
|
|
|
|
|
|
|
|
|
// 2. 【新增】获取当前方案组里已有的标签 |
|
|
|
|
|
const currentLabels = group.configs.map(conf => conf.current_label); |
|
|
|
|
|
const hasTags = new Set(currentLabels); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 【新增】找出缺失的标签 |
|
|
|
|
|
const missingTags = REQUIRED_TAGS.filter(tag => !hasTags.has(tag)); |
|
|
|
|
|
|
|
|
|
|
|
// 4. 【新增】拦截逻辑:如果标签不全,弹出提示并返回 |
|
|
|
|
|
if (missingTags.length > 0) { |
|
|
|
|
|
return ElMessage.warning('该方案配置不完整,无法应用。目前缺少:'+missingTags.join('、')); |
|
|
|
|
|
} |
|
|
|
|
|
// 获取当前正在使用的所有标签名(用于外部排他) |
|
|
|
|
|
const currentlyUsingLabels = []; |
|
|
|
|
|
this.styleGroups.forEach(g => { |
|
|
|
|
|
g.configs.forEach(c => { |
|
|
|
|
|
if (this.usingConfigIds.includes(c.id)) { |
|
|
|
|
|
currentlyUsingLabels.push(c.current_label); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 对新方案内部进行去重:如果方案内有多个同标签配置,只取第一个 |
|
|
|
|
|
const uniqueNewConfigs = []; |
|
|
|
|
|
const seenLabelsInNewGroup = new Set(); |
|
|
|
|
|
|
|
|
|
|
|
group.configs.forEach(conf => { |
|
|
|
|
|
if (!seenLabelsInNewGroup.has(conf.current_label)) { |
|
|
|
|
|
uniqueNewConfigs.push(conf); |
|
|
|
|
|
seenLabelsInNewGroup.add(conf.current_label); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 过滤掉与当前已选标签冲突的配置(外部排他) |
|
|
|
|
|
const filteredNewConfigIds = uniqueNewConfigs |
|
|
|
|
|
.filter(newConf => !currentlyUsingLabels.includes(newConf.current_label)) |
|
|
|
|
|
.map(newConf => newConf.id); |
|
|
|
|
|
|
|
|
|
|
|
if (filteredNewConfigIds.length === 0) { |
|
|
|
|
|
return ElMessage.info("该方案中的标签配置已存在,无需重复应用"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 追加 ID |
|
|
|
|
|
this.usingConfigIds = [...this.usingConfigIds, ...filteredNewConfigIds]; |
|
|
|
|
|
|
|
|
|
|
|
// 调用后端接口更新状态 |
|
|
|
|
|
const res = await applyGraphStyleGroup(group.id); |
|
|
|
|
|
if (res.code === 200) { |
|
|
|
|
|
await this.fetchConfigs(); |
|
|
|
|
|
ElMessage.success(`方案【${group.group_name}】已应用,已自动过滤重复标签`); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
console.error(err); |
|
|
|
|
|
ElMessage.error("应用全案失败"); |
|
|
|
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
@ -802,142 +852,117 @@ export default { |
|
|
this.saveForm.canvas_name = `${this.activeTags}_${Date.now()}`; |
|
|
this.saveForm.canvas_name = `${this.activeTags}_${Date.now()}`; |
|
|
this.saveDialogVisible = true; |
|
|
this.saveDialogVisible = true; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
async confirmSave() { |
|
|
async confirmSave() { |
|
|
if (!this.saveForm.group_name || !this.saveForm.group_name.trim()) { |
|
|
if (!this.saveForm.group_name?.trim() || !this.saveForm.canvas_name?.trim()) { |
|
|
return ElMessage.warning("请选择或输入方案名称"); |
|
|
return ElMessage.warning("请完善名称"); |
|
|
} |
|
|
} |
|
|
if (!this.saveForm.canvas_name || !this.saveForm.canvas_name.trim()) { |
|
|
|
|
|
return ElMessage.warning("请输入配置名称"); |
|
|
// 保存时的校验 |
|
|
|
|
|
if (!this.validateGroupConstraint(this.saveForm.group_name.trim(), this.activeTags)) { |
|
|
|
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const labelEn = tagToLabelMap[this.activeTags]; |
|
|
const payload = { |
|
|
const payload = { |
|
|
canvas_name: this.saveForm.canvas_name.trim(), |
|
|
canvas_name: this.saveForm.canvas_name.trim(), |
|
|
group_name: this.saveForm.group_name.trim(), |
|
|
group_name: this.saveForm.group_name.trim(), |
|
|
current_label: this.activeTags, |
|
|
current_label: this.activeTags, |
|
|
styles: {...this.tagStyles[tagToLabelMap[this.activeTags]]} |
|
|
styles: { ...this.tagStyles[labelEn] }, |
|
|
|
|
|
is_auto_save: false |
|
|
}; |
|
|
}; |
|
|
const res = await saveGraphStyle(payload); |
|
|
const res = await saveGraphStyle(payload); |
|
|
if (res.code === 200) { |
|
|
if (res.code === 200) { |
|
|
ElMessage.success("保存成功"); |
|
|
ElMessage.success("保存成功"); |
|
|
this.saveDialogVisible = false; |
|
|
this.saveDialogVisible = false; |
|
|
this.saveForm.canvas_name = ''; |
|
|
await this.fetchConfigs(); |
|
|
this.saveForm.group_name = ''; |
|
|
|
|
|
this.resetAllTagsToDefault(); |
|
|
|
|
|
this.fetchConfigs(); |
|
|
|
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
resetAllTagsToDefault() { |
|
|
// =========================================================================== |
|
|
Object.keys(this.tagStyles).forEach(labelKey => { |
|
|
|
|
|
this.tagStyles[labelKey] = this.getInitialTagParams(labelKey); |
|
|
async applyWholeGroup(group) { |
|
|
}); |
|
|
if (this.saveTimer) clearTimeout(this.saveTimer); |
|
|
this.activeTags = '疾病'; |
|
|
this.isInitialEcho = true; |
|
|
const diseaseInitial = this.getInitialTagParams('Disease'); |
|
|
this.editingConfigId = null; |
|
|
Object.assign(this, diseaseInitial); |
|
|
this.editingConfigLabel = ''; |
|
|
this.updateAllElements(); |
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
const REQUIRED_TAGS = ['疾病', '症状', '药品', '检查', '其他']; |
|
|
|
|
|
const currentLabels = group.configs.map(conf => conf.current_label); |
|
|
|
|
|
const hasTags = new Set(currentLabels); |
|
|
|
|
|
const missingTags = REQUIRED_TAGS.filter(tag => !hasTags.has(tag)); |
|
|
|
|
|
|
|
|
|
|
|
if (missingTags.length > 0) { |
|
|
|
|
|
this.isInitialEcho = false; |
|
|
|
|
|
return ElMessageBox.alert( |
|
|
|
|
|
`该方案配置不完整,无法应用。<br/>缺失:<b style="color: #f56c6c">${missingTags.join('、')}</b>`, |
|
|
|
|
|
'提示', { dangerouslyUseHTMLString: true, type: 'warning' } |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.usingConfigIds = group.configs.map(c => c.id); |
|
|
|
|
|
const res = await applyGraphStyleGroup(group.id); |
|
|
|
|
|
if (res.code === 200) { |
|
|
|
|
|
await this.fetchConfigs(); |
|
|
|
|
|
if (group.configs.length > 0) { |
|
|
|
|
|
this.handleEditConfig(group.configs[0]); |
|
|
|
|
|
} |
|
|
|
|
|
ElMessage.success(`已应用方案【${group.group_name}】`); |
|
|
|
|
|
} |
|
|
|
|
|
} catch (err) { |
|
|
|
|
|
this.isInitialEcho = false; |
|
|
|
|
|
ElMessage.error("切换失败"); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
resetStyle() { |
|
|
resetStyle() { |
|
|
const labelEn = tagToLabelMap[this.activeTags]; |
|
|
const labelEn = tagToLabelMap[this.activeTags]; |
|
|
const initial = this.getInitialTagParams(labelEn); |
|
|
const initial = this.getInitialTagParams(labelEn); |
|
|
this.tagStyles[labelEn] = initial; |
|
|
this.tagStyles[labelEn] = initial; |
|
|
|
|
|
this.isInitialEcho = true; |
|
|
Object.assign(this, initial); |
|
|
Object.assign(this, initial); |
|
|
this.updateAllElements(); |
|
|
this.$nextTick(() => { |
|
|
ElMessage.info(`已重置【${this.activeTags}】样式`); |
|
|
this.isInitialEcho = false; |
|
|
|
|
|
this.syncAndRefresh(); |
|
|
|
|
|
}); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// --- 修改点:单个配置删除增加判断 --- |
|
|
|
|
|
async deleteSingleConfig(id) { |
|
|
async deleteSingleConfig(id) { |
|
|
if (this.usingConfigIds.includes(id)) { |
|
|
if (this.usingConfigIds.includes(id)) return ElMessage.error("应用中无法删除"); |
|
|
return ElMessage.error("该配置正在应用中,请取消应用或切换方案后再删除"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await ElMessageBox.confirm('确定删除此配置吗?', '提示'); |
|
|
await ElMessageBox.confirm('确定删除吗?', '提示'); |
|
|
const res = await deleteGraphStyle(id); |
|
|
const res = await deleteGraphStyle(id); |
|
|
if (res.code === 200) { |
|
|
if (res.code === 200) { |
|
|
ElMessage.success("删除成功"); |
|
|
ElMessage.success("删除成功"); |
|
|
this.fetchConfigs(); |
|
|
this.fetchConfigs(); |
|
|
} |
|
|
} |
|
|
} catch (err) {} |
|
|
} catch (err) { } |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// --- 修改点:方案组删除增加判断 --- |
|
|
|
|
|
async deleteGroup(groupId) { |
|
|
async deleteGroup(groupId) { |
|
|
const group = this.styleGroups.find(g => g.id === groupId); |
|
|
const group = this.styleGroups.find(g => g.id === groupId); |
|
|
if (!group) return; |
|
|
if (!group || group.is_active) return ElMessage.error("应用中无法删除"); |
|
|
|
|
|
|
|
|
// 1. 判断是否正在应用 |
|
|
|
|
|
const isGroupUsing = group.configs.some(c => this.usingConfigIds.includes(c.id)); |
|
|
|
|
|
if (isGroupUsing || group.is_active) { |
|
|
|
|
|
return ElMessage.error("该方案中包含正在应用的配置,无法删除"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 判断是否是最后一个方案 |
|
|
|
|
|
if (this.styleGroups.length <= 1) { |
|
|
|
|
|
return ElMessage.error("系统至少需保留一个方案,无法全部删除"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await ElMessageBox.confirm('确定删除整个方案吗?', '提示'); |
|
|
await ElMessageBox.confirm('确定删除全案吗?', '提示'); |
|
|
const res = await deleteGraphStyleGroup(groupId); |
|
|
const res = await deleteGraphStyleGroup(groupId); |
|
|
if (res.code === 200) { |
|
|
if (res.code === 200) { |
|
|
ElMessage.success("方案已删除"); |
|
|
ElMessage.success("已删除"); |
|
|
this.fetchConfigs(); |
|
|
this.fetchConfigs(); |
|
|
} |
|
|
} |
|
|
} catch (err) {} |
|
|
} catch (err) { } |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// --- 修改点:批量删除增加核心判断逻辑 --- |
|
|
|
|
|
async handleUnifiedBatchDelete() { |
|
|
async handleUnifiedBatchDelete() { |
|
|
// 1. 基础判断 |
|
|
if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) return; |
|
|
if (this.checkedConfigIds.length === 0 && this.checkedGroupIds.length === 0) { |
|
|
|
|
|
return ElMessage.warning("请先勾选要删除的项目"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 正在应用状态判断 |
|
|
|
|
|
const isAnyCheckedConfigUsing = this.checkedConfigIds.some(id => this.usingConfigIds.includes(id)); |
|
|
|
|
|
const isAnyCheckedGroupUsing = this.styleGroups |
|
|
|
|
|
.filter(g => this.checkedGroupIds.includes(g.id)) |
|
|
|
|
|
.some(g => g.configs.some(c => this.usingConfigIds.includes(c.id))); |
|
|
|
|
|
|
|
|
|
|
|
if (isAnyCheckedConfigUsing || isAnyCheckedGroupUsing) { |
|
|
|
|
|
return ElMessage.warning('选中的项目中包含“正在应用”的配置,请先取消应用后再执行删除操作。'); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. 最小保留数判断 (针对方案组) |
|
|
|
|
|
if (this.checkedGroupIds.length >= this.styleGroups.length && this.styleGroups.length > 0) { |
|
|
|
|
|
return ElMessage.error("系统至少需要保留一个方案,请勿全部勾选删除"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
await ElMessageBox.confirm( |
|
|
await ElMessageBox.confirm('确定批量删除吗?', '提示'); |
|
|
'确定执行批量删除吗?此操作不可恢复。', |
|
|
|
|
|
'批量删除确认', |
|
|
|
|
|
{ |
|
|
|
|
|
confirmButtonText: '确定删除', |
|
|
|
|
|
cancelButtonText: '取消', |
|
|
|
|
|
type: 'warning' |
|
|
|
|
|
} |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
// 执行删除 |
|
|
|
|
|
if (this.checkedGroupIds.length > 0) { |
|
|
if (this.checkedGroupIds.length > 0) { |
|
|
for (const gid of this.checkedGroupIds) { |
|
|
for (const gid of this.checkedGroupIds) await deleteGraphStyleGroup(gid); |
|
|
await deleteGraphStyleGroup(gid); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (this.checkedConfigIds.length > 0) { |
|
|
if (this.checkedConfigIds.length > 0) { |
|
|
await batchDeleteGraphStyle({ids: this.checkedConfigIds}); |
|
|
await batchDeleteGraphStyle({ ids: this.checkedConfigIds }); |
|
|
} |
|
|
} |
|
|
|
|
|
ElMessage.success("成功"); |
|
|
ElMessage.success("批量删除成功"); |
|
|
|
|
|
this.clearSelection(); |
|
|
this.clearSelection(); |
|
|
this.fetchConfigs(); |
|
|
this.fetchConfigs(); |
|
|
this.updateAllElements(); |
|
|
} catch (e) { } |
|
|
} catch (e) { |
|
|
|
|
|
console.log("用户取消或删除失败", e); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
clearSelection() { |
|
|
clearSelection() { |
|
|
this.checkedConfigIds = []; |
|
|
this.checkedConfigIds = []; |
|
|
this.checkedGroupIds = []; |
|
|
this.checkedGroupIds = []; |
|
|
|