From b593e0383f8b3468bf664973b807d56ac70c940c Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Sun, 26 Jul 2026 18:01:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20O=E2=86=92KR=E2=86=92KPI=20Phase=200=20?= =?UTF-8?q?-=20KR=20sub-nodes=20on=20canvas,=20KR=20edit=20dialog,=20data?= =?UTF-8?q?=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MapNode.vue: Add collapsible KR sub-list with progress bars under each O node - MapCanvasDialogs.vue: Add KPI selector per KR, editing mode (skip template step, pre-fill data), validation (max 5 KR, target value required) - StrategyLayer.vue: Pass through edit-kr/add-kr events - MapCanvas.vue: Data migration (old kpis[]→krs[]), KR add/edit handlers, pass editingObj/allKpis to dialog --- .../map-canvas/MapCanvasDialogs.vue | 81 ++++++- .../src/components/strategy-map/MapNode.vue | 221 +++++++++++++++++- .../components/strategy-map/StrategyLayer.vue | 6 +- frontend/src/views/MapCanvas.vue | 62 ++++- 4 files changed, 356 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/map-canvas/MapCanvasDialogs.vue b/frontend/src/components/map-canvas/MapCanvasDialogs.vue index 8372cc92..b5e87436 100644 --- a/frontend/src/components/map-canvas/MapCanvasDialogs.vue +++ b/frontend/src/components/map-canvas/MapCanvasDialogs.vue @@ -41,7 +41,7 @@ 第二步:编辑 O + KR ========================================================== -->
-
步骤 2/2 — 编辑目标与关键结果
+
{{ isEditing ? '编辑目标与关键结果' : '步骤 2/2 — 编辑目标与关键结果' }}
@@ -66,7 +66,7 @@
- +
@@ -85,6 +85,15 @@
+
+ + +
⚠️ 权重合计 {{ weightSum }}%,应为100%
@@ -100,7 +109,7 @@ @@ -222,6 +231,7 @@ diff --git a/frontend/src/components/strategy-map/StrategyLayer.vue b/frontend/src/components/strategy-map/StrategyLayer.vue index b7dd1501..a521122d 100644 --- a/frontend/src/components/strategy-map/StrategyLayer.vue +++ b/frontend/src/components/strategy-map/StrategyLayer.vue @@ -50,7 +50,9 @@ @link-drag-start="(e: MouseEvent) => $emit('link-drag-start', e, layerKey, index, element)" @kpi-click="(code: string) => $emit('kpi-click', code)" @plan-click="$emit('show-plan-list', { dimKey: layerKey, idx: index, obj: element })" - /> + @edit-kr="(p: any) => $emit('edit-kr', { dimKey: layerKey, idx: index, obj: element, ki: p.index, kr: p.kr })" + @add-kr="$emit('add-kr', { dimKey: layerKey, idx: index, obj: element })" + /> @@ -107,6 +109,8 @@ const emit = defineEmits<{ (e: 'update:label', val: string): void (e: 'update:color', val: string): void (e: 'update:icon', val: string): void + (e: 'edit-kr', payload: { dimKey: string; idx: number; obj: any; ki: number; kr: any }): void + (e: 'add-kr', payload: { dimKey: string; idx: number; obj: any }): void }>() const bodyRef = ref(null) diff --git a/frontend/src/views/MapCanvas.vue b/frontend/src/views/MapCanvas.vue index bbacf79c..6e723f77 100644 --- a/frontend/src/views/MapCanvas.vue +++ b/frontend/src/views/MapCanvas.vue @@ -76,6 +76,8 @@ @show-plan-list="(p: any) => showPlanList(p.dimKey, p.idx, p.obj)" @node-click="(dk: string, oi: number, obj: any) => onNodeClick(dk, oi, obj)" @reorder="(dk: string) => onLayerReorder(dk)" + @edit-kr="(p: any) => onEditKr(p.dimKey, p.idx, p.obj, p.ki, p.kr)" + @add-kr="(p: any) => onAddKr(p.dimKey, p.idx, p.obj)" /> @@ -121,6 +123,8 @@ :plan-popup-obj-name="planPopupObjName" :plan-popup-list="planPopupList" :editing-layer-key="editingLayerKey" + :editing-obj="editingNodeData" + :all-kpis="allKpis" @update:show-node-dialog="v => showNodeDialog = v" @update:show-causality-dialog="v => showCausalityDialog = v" @update:show-versions="v => showVersions = v" @@ -357,6 +361,8 @@ function loadCanvas() { } // 确保四层都存在 ensureFourLayers() + // 数据迁移:旧格式 kpis[] → krs[] + migrateOldKpiFormat() connections.value = map?.canvas_data?.connections || [] loading.value = false nextTick(() => { @@ -534,6 +540,11 @@ function openEditDialog(dimKey: string, oi: number, obj: any) { owner: obj.owner || '', isLeading: obj.isLeading ?? obj.is_leading ?? false, description: obj.description || '', + // O+KR 扩展字段(传递给弹窗预填) + krs: obj.krs || [], + o_name: obj.name, + o_description: obj.description || '', + _rawObjective: obj, // 保留原始引用以便直接修改KR } // 保存编辑索引以便更新 editingNodeData.value._dimKey = dimKey @@ -567,11 +578,15 @@ function onNodeSave(data: any) { // 编辑已有节点 const idx = editingNodeData.value._index if (dim.objectives[idx]) { - // 保留原有kpis等额外字段 + // 保留原有kpis/krs等额外字段(当新数据未提供时) const extra = { kpis: dim.objectives[idx].kpis || [], icon: dim.objectives[idx].icon || 'target', } + // 如果新数据没有提供krs,保留旧krs + if (!data.krs || data.krs.length === 0) { + node.krs = dim.objectives[idx].krs || [] + } dim.objectives[idx] = { ...node, ...extra } ElMessage.success("节点已更新") } @@ -604,6 +619,51 @@ async function deleteNode(dimKey: string, oi: number) { } catch { /* cancel */ } } +// ── KR操作 ── +function onEditKr(dimKey: string, oi: number, obj: any, ki: number, kr: any) { + // 打开O编辑弹窗并定位到对应KR + openEditDialog(dimKey, oi, obj) +} + +function onAddKr(dimKey: string, oi: number, obj: any) { + const dim = dimensions.find((d: any) => d.key === dimKey) + if (!dim || !dim.objectives[oi]) return + const target = dim.objectives[oi] + if (!target.krs) target.krs = [] + if (target.krs.length >= 5) { + ElMessage.warning('每个目标最多5个关键结果') + return + } + target.krs.push({ + name: "", + target_value: "", + weight: 33, + kpi_code: null, + sort_order: target.krs.length + 1, + }) + ElMessage.success("已添加空白KR,请编辑完善") + debouncedSaveCanvas() +} + +// ── 数据迁移:旧格式 kpis[] 自动转为 krs[] ── +function migrateOldKpiFormat() { + for (const dim of dimensions) { + for (const obj of dim.objectives || []) { + // 仅当有 kpis 数组 且 没有 krs 时执行迁移 + if (obj.kpis && Array.isArray(obj.kpis) && obj.kpis.length > 0 && (!obj.krs || obj.krs.length === 0)) { + obj.krs = obj.kpis.map((kpiCode: string, idx: number) => ({ + name: kpiNameMap[kpiCode] || kpiCode, + target_value: "", + weight: Math.round(100 / obj.kpis.length), + kpi_code: kpiCode, + sort_order: idx + 1, + })) + console.log(`[迁移] 目标「${obj.name}」: ${obj.kpis.length} 条KPI已转为KR`) + } + } + } +} + // ── 保存 ── let saveTimer: ReturnType | null = null let autoSaveTimer: ReturnType | null = null