feat: 战略地图贝塞尔因果链+节点KPI可视化(P0+P1)

This commit is contained in:
Hermes CI Fix
2026-08-15 22:08:09 +08:00
parent 778a5d9c79
commit 603d6ae5cf
8 changed files with 645 additions and 183 deletions
+65 -2
View File
@@ -7,6 +7,7 @@
:view-direction="viewDirection"
:current-map="currentMap"
:linking-from="linkingFrom"
:chain-mode="chainMode"
@select-map="onSelectMap"
@update:view-direction="v => viewDirection = v"
@zoom-in="zoomIn"
@@ -20,8 +21,15 @@
@show-versions="showVersions = true; loadVersions()"
@go-alignment="goAlignment"
@cancel-link="cancelLink"
@toggle-chain-mode="toggleChainMode"
/>
<!-- 因果链查看模式提示 -->
<div v-if="chainMode" class="chain-mode-bar">
<span>🔍 因果链查看模式 — 点击节点聚焦其相关因果链(再次点击取消),其余节点自动变淡</span>
<el-button size="small" type="warning" @click="toggleChainMode">退出模式</el-button>
</div>
<!-- 连线模式提示 -->
<div v-if="linkingFrom" class="linking-bar">
<span>🔗 从「{{ linkingFrom.obj.name }}」连线 — 点击另一个目标完成连线</span>
@@ -67,6 +75,7 @@
:all-kpis="allKpis"
:get-node-level="getNodeLevel"
:get-node-progress="getNodeProgress"
:chain-node-keys="chainNodeKeys"
@add-objective="openAddDialog"
@edit-objective="(p: any) => openEditDialog(p.dimKey, p.idx, p.obj)"
@delete-objective="(p: any) => deleteNode(p.dimKey, p.idx)"
@@ -102,8 +111,13 @@
:temp-line="tempLine"
:container-key="recalcTrigger"
:view-direction="viewDirection"
:chain-mode="chainMode"
:chain-focus-key="chainFocusKey"
@select-connection="onSelectConnection"
@delete-connection="onDeleteConnection"
@chain-hover="onChainHover"
@chain-focus="onChainFocus"
@update-connection="onUpdateConnection"
/>
<!-- 所有弹窗统一管理 -->
@@ -179,6 +193,38 @@ const tempLine = ref<{ x1: number; y1: number; x2: number; y2: number } | null>(
const dragConnectTarget = ref<string | null>(null)
const selectedConnIdx = ref<number | null>(null)
// ── 因果链查看模式 ──
const chainMode = ref(false)
const chainFocusKey = ref<string | null>(null)
const chainHoverNodes = ref<string[] | null>(null)
const chainFocusNodes = ref<string[] | null>(null)
/** 当前因果链涉及的节点 key 集合(悬停链 ∪ 聚焦链),用于节点变淡 */
const chainNodeKeys = computed<string[]>(() => {
const set = new Set<string>()
chainHoverNodes.value?.forEach(k => set.add(k))
chainFocusNodes.value?.forEach(k => set.add(k))
return [...set]
})
function toggleChainMode() {
chainMode.value = !chainMode.value
chainFocusKey.value = null
chainHoverNodes.value = null
chainFocusNodes.value = null
if (!chainMode.value) selectedConnIdx.value = null
}
function onChainHover(nodes: string[] | null) { chainHoverNodes.value = nodes }
function onChainFocus(nodes: string[] | null) { chainFocusNodes.value = nodes }
/** 更新连线语义(effect/label),落库 */
function onUpdateConnection(idx: number, patch: any) {
if (!connections.value[idx]) return
Object.assign(connections.value[idx], patch)
saveConnections()
}
// 缩放
const zoomLevel = ref(1)
const scrollWrapRef = ref<HTMLElement | null>(null)
@@ -342,6 +388,10 @@ function loadCanvas() {
connections.value = []
linkingFrom.value = null
selectedConnIdx.value = null
chainMode.value = false
chainFocusKey.value = null
chainHoverNodes.value = null
chainFocusNodes.value = null
loading.value = true
api.get("/maps").then((r: any) => {
@@ -404,6 +454,11 @@ function onNodeClick(dimKey: string, oi: number, obj: any) {
completeConnection(linkingFrom.value.key, key)
return
}
// 因果链查看模式:点击节点聚焦其相关因果链,再次点击取消
if (chainMode.value) {
chainFocusKey.value = chainFocusKey.value === key ? null : key
return
}
// 有KPI则展示详情
if (kpiProgressMap[key]?.kpiList?.length > 0) {
showKpiDetailModal(dimKey, oi, obj)
@@ -433,12 +488,12 @@ function completeConnection(fromKey: string, toKey: string) {
from: fromKey, to: toKey,
}).then(() => {
ElMessage.success("连线已添加")
connections.value.push({ from: fromKey, to: toKey, style: "solid" })
connections.value.push({ from: fromKey, to: toKey, style: "solid", effect: "positive", label: "" })
cancelLink()
nextTick(() => { triggerRecalc(); saveConnections() })
}).catch((e: any) => {
if (e?.response?.status !== 400) {
connections.value.push({ from: fromKey, to: toKey, style: "solid" })
connections.value.push({ from: fromKey, to: toKey, style: "solid", effect: "positive", label: "" })
ElMessage.success("连线已添加本地")
} else {
ElMessage.warning(e?.response?.data?.detail || "连线失败")
@@ -976,6 +1031,14 @@ onUnmounted(() => {
border-radius: 8px; margin-bottom: 8px; font-size: 14px; flex-shrink: 0;
}
.chain-mode-bar {
display: flex; align-items: center; gap: 12px;
padding: 8px 16px; background: #fdf6ec; border: 1px solid #f5dab1;
border-radius: 8px; margin-bottom: 8px; font-size: 13px; flex-shrink: 0;
color: #b88230;
}
.chain-mode-bar span { flex: 1; }
.empty-state {
flex: 1; display: flex; align-items: center; justify-content: center;
}