fix(maps): 画布因果连线零绘制(节点双键注册) + 战略回顾 total_hold 未初始化 500
ci/woodpecker/push/main Pipeline was successful

- StrategyLayer.vue: setNodeRef 双键注册(渲染键 layer-index 与数据键 layer:目标名),$el 归一化为 DOM,卸载时清理数据键
- maps.py: total_hold 初始化 0 + summary.hold 透出(修复 review?level=strategic 500 UnboundLocalError)
- tests: 新增 test_maps_review_hold_20261022.py(待核/普通/空/NULL/无level 五例,负对照已验证会失败)

任务: project-20261022-cma-map-canvas-connections-not-drawn-fullstack
阶段A候选件;未 push、未部署(等阶段B放行)
This commit is contained in:
wecom-fullstack
2026-10-22 16:50:10 +08:00
committed by Hermes CI Fix
parent 4cad2b73c4
commit 986dec516e
3 changed files with 175 additions and 3 deletions
@@ -42,7 +42,7 @@
<div class="layer-node-wrapper">
<MapNode
:key="`${layerKey}-${index}`"
:ref="el => setNodeRef(`${layerKey}-${index}`, el)"
:ref="el => setNodeRef(`${layerKey}-${index}`, el, element)"
:objective="element"
:node-key="`${layerKey}-${index}`"
:index="index"
@@ -144,8 +144,41 @@ const bodyRef = ref<HTMLElement | null>(null)
// Node DOM refs for line calculation (reactive so parent can aggregate)
const nodeRefs = reactive<Record<string, any>>({})
function setNodeRef(key: string, el: any) {
if (el) nodeRefs[key] = el
/**
* 双键注册:`${layerKey}-${index}`(渲染键,历史契约)+ `${layerKey}:${目标名}`(数据键)。
* canvas_data.connections / map_connections 里的节点标识形如 `learning:贡献率思维全员培训`,
* 只注册渲染键会导致 ConnectionLines 全部取不到 DOM → 一条线都不画(P2 缺陷)。
* 同层同名目标:先到先得(与数据侧 `dim:name` 本身的歧义语义保持一致)。
* 卸载(el=null)时按 key 反查清理自己注册的数据键,避免残留指向已卸载 DOM(线画到 0,0)。
*/
const dataKeyOwner: Record<string, string> = {} // dataKey → 渲染键(谁注册的)
const ownerDataKey: Record<string, string> = {} // 渲染键 → dataKey
function setNodeRef(key: string, el: any, element?: any) {
// 组件 ref 给的是实例(不是 DOM),ConnectionLines 直接调 getBoundingClientRect
// ⇒ 统一归一化为 DOM 元素($el);取不到 DOM 视同卸载,走清理分支。
const dom: HTMLElement | null = el
? (el.nodeType === 1 ? el : (el.$el && el.$el.nodeType === 1 ? el.$el : null))
: null
if (!dom) {
const dk = ownerDataKey[key]
if (dk && dataKeyOwner[dk] === key) {
delete nodeRefs[dk]
delete dataKeyOwner[dk]
}
delete ownerDataKey[key]
delete nodeRefs[key]
return
}
nodeRefs[key] = dom
const nm = typeof element?.name === 'string' ? element.name : ''
if (nm) {
const dataKey = `${props.layerKey}:${nm}`
if (!nodeRefs[dataKey]) {
nodeRefs[dataKey] = dom
dataKeyOwner[dataKey] = key
ownerDataKey[key] = dataKey
}
}
}
// Expose nodeRefs to parent for connection line calculation