feat: O→KR→KPI Phase 0 - KR sub-nodes on canvas, KR edit dialog, data migration
- 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
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
第二步:编辑 O + KR
|
||||
========================================================== -->
|
||||
<div v-if="dialogStep === 2" class="mc-okr-step">
|
||||
<div class="mc-step-hint">步骤 2/2 — 编辑目标与关键结果</div>
|
||||
<div class="mc-step-hint">{{ isEditing ? '编辑目标与关键结果' : '步骤 2/2 — 编辑目标与关键结果' }}</div>
|
||||
|
||||
<div class="mc-form-item">
|
||||
<label>🎯 目标名称 <span class="mc-required">*</span></label>
|
||||
@@ -66,7 +66,7 @@
|
||||
</div>
|
||||
<div class="mc-kr-row-bottom">
|
||||
<div class="mc-kr-field">
|
||||
<label>目标值</label>
|
||||
<label>目标值 <span class="mc-required">*</span></label>
|
||||
<input v-model="kr.target_value" class="mc-input mc-kr-input" placeholder="输入数字,回车自动加%" @keydown.enter.prevent="formatTargetValue(ki)" />
|
||||
</div>
|
||||
<div class="mc-kr-field">
|
||||
@@ -85,6 +85,15 @@
|
||||
<option value="100">100%</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mc-kr-field">
|
||||
<label>关联KPI</label>
|
||||
<select v-model="kr.kpi_code" class="mc-input mc-kr-input">
|
||||
<option value="">— 不关联 —</option>
|
||||
<option v-for="kpi in kpiOptions" :key="kpi.kpi_code" :value="kpi.kpi_code">
|
||||
{{ kpi.kpi_name || kpi.kpi_code }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="weightSum !== null && weightSum !== 100" class="mc-kr-warning">⚠️ 权重合计 {{ weightSum }}%,应为100%</div>
|
||||
</div>
|
||||
@@ -100,7 +109,7 @@
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="dialogStep === 2" class="mc-footer-buttons">
|
||||
<el-button size="small" @click="prevStep">上一步</el-button>
|
||||
<el-button v-if="!isEditing" size="small" @click="prevStep">上一步</el-button>
|
||||
<el-button size="small" @click="onCancel">取消</el-button>
|
||||
<el-button size="small" type="primary" @click="onSave">保存</el-button>
|
||||
</div>
|
||||
@@ -222,6 +231,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from "vue"
|
||||
import { ElMessage } from "element-plus"
|
||||
|
||||
// ── Props ──
|
||||
const props = defineProps<{
|
||||
@@ -240,6 +250,8 @@ const props = defineProps<{
|
||||
planPopupObjName: string
|
||||
planPopupList: any[]
|
||||
editingLayerKey?: string // 当前编辑的维度key
|
||||
editingObj?: any // 正在编辑的目标对象(含krs)
|
||||
allKpis?: any[] // KPI字典数据(用于KR关联KPI)
|
||||
}>()
|
||||
|
||||
// ── Emits ──
|
||||
@@ -426,7 +438,7 @@ const usedTemplate = ref<any | null>(null)
|
||||
const form = ref<{
|
||||
o_name: string
|
||||
o_description: string
|
||||
krs: { name: string; target_value: string; weight: string }[]
|
||||
krs: { name: string; target_value: string; weight: string; kpi_code: string }[]
|
||||
}>({
|
||||
o_name: "",
|
||||
o_description: "",
|
||||
@@ -448,6 +460,10 @@ const dialogTitle = computed(() => {
|
||||
return props.isEditing ? "编辑目标" : "添加目标"
|
||||
})
|
||||
|
||||
const kpiOptions = computed(() => {
|
||||
return props.allKpis || []
|
||||
})
|
||||
|
||||
// ── 方法 ──
|
||||
function resetForm() {
|
||||
dialogStep.value = 1
|
||||
@@ -468,9 +484,9 @@ function skipTemplate() {
|
||||
o_name: "",
|
||||
o_description: "",
|
||||
krs: [
|
||||
{ name: "", target_value: "", weight: "33" },
|
||||
{ name: "", target_value: "", weight: "33" },
|
||||
{ name: "", target_value: "", weight: "34" },
|
||||
{ name: "", target_value: "", weight: "33", kpi_code: "" },
|
||||
{ name: "", target_value: "", weight: "33", kpi_code: "" },
|
||||
{ name: "", target_value: "", weight: "34", kpi_code: "" },
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -488,6 +504,7 @@ function nextStep() {
|
||||
name: kr.name,
|
||||
target_value: kr.target_value,
|
||||
weight: String(kr.weight),
|
||||
kpi_code: kr.kpi_code || "",
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -502,7 +519,11 @@ const weightSum = computed(() => {
|
||||
})
|
||||
|
||||
function addKr() {
|
||||
form.value.krs.push({ name: "", target_value: "", weight: "33" })
|
||||
if (form.value.krs.length >= 5) {
|
||||
ElMessage.warning('每个目标最多5个关键结果')
|
||||
return
|
||||
}
|
||||
form.value.krs.push({ name: "", target_value: "", weight: "33", kpi_code: "" })
|
||||
}
|
||||
|
||||
function removeKr(idx: number) {
|
||||
@@ -528,8 +549,26 @@ function checkWeightSum() {
|
||||
}
|
||||
|
||||
function onSave() {
|
||||
// 验证目标名称
|
||||
if (!form.value.o_name?.trim()) {
|
||||
// 简单提示 — 父组件会检测
|
||||
ElMessage.warning('请输入目标名称')
|
||||
return
|
||||
}
|
||||
// 验证KR
|
||||
for (let i = 0; i < form.value.krs.length; i++) {
|
||||
const kr = form.value.krs[i]
|
||||
if (!kr.name?.trim()) {
|
||||
ElMessage.warning(`第 ${i + 1} 条KR名称不能为空`)
|
||||
return
|
||||
}
|
||||
if (!kr.target_value?.trim()) {
|
||||
ElMessage.warning(`第 ${i + 1} 条KR目标值不能为空`)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 验证KR数量
|
||||
if (form.value.krs.length > 5) {
|
||||
ElMessage.warning('每个目标最多5个关键结果')
|
||||
return
|
||||
}
|
||||
// 构造完整数据发给父组件
|
||||
@@ -542,6 +581,7 @@ function onSave() {
|
||||
name: kr.name,
|
||||
target_value: kr.target_value,
|
||||
weight: parseInt(kr.weight) || 33,
|
||||
kpi_code: kr.kpi_code || null,
|
||||
sort_order: i + 1,
|
||||
})),
|
||||
// 兼容旧字段(父组件 onNodeSave 需要 name)
|
||||
@@ -559,7 +599,28 @@ function onCancel() {
|
||||
watch(
|
||||
() => props.showNodeDialog,
|
||||
(val) => {
|
||||
if (!val) {
|
||||
if (val) {
|
||||
// 编辑模式:跳过模板步骤,直接进入O+KR编辑,预填数据
|
||||
if (props.isEditing && props.editingObj) {
|
||||
dialogStep.value = 2
|
||||
selectedTemplateIdx.value = null
|
||||
usedTemplate.value = null
|
||||
const obj = props.editingObj
|
||||
const existingKrs = (obj.krs || []).map((kr: any) => ({
|
||||
name: kr.name || "",
|
||||
target_value: kr.target_value || "",
|
||||
weight: String(kr.weight || 33),
|
||||
kpi_code: kr.kpi_code || "",
|
||||
}))
|
||||
form.value = {
|
||||
o_name: obj.name || obj.o_name || "",
|
||||
o_description: obj.description || obj.o_description || "",
|
||||
krs: existingKrs.length > 0 ? existingKrs : [{ name: "", target_value: "", weight: "33", kpi_code: "" }],
|
||||
}
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
} else {
|
||||
resetForm()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user