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