From d5edcefb48a3ebc35c1e5ff361054d838dd7a0fb Mon Sep 17 00:00:00 2001 From: Hermes CI Fix Date: Thu, 20 Aug 2026 15:56:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20KPI=E6=96=B0=E5=BB=BA=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=BC=95=E5=AF=BC(=E4=BA=94=E6=AD=A5=E6=B3=95=C2=B7=E4=BA=94?= =?UTF-8?q?=E8=A7=84=E5=88=99)=20=E2=80=94=20KPIList=E6=96=B0=E5=BB=BA?= =?UTF-8?q?=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建KPI时显示设计引导折叠面板: 五步法逐步引导(职责/客户/需求/成果/五因素) - 五规则实时校验清单: 名称/维度/单位/目标值/频率/负责人 + 本维度KPI数量≤6提示 - 提交门禁(仅新建): 名称+维度必填, 单位+至少一个目标值必填 - 对应 docs/kpi-design-rule.md 方法论落地 --- frontend/src/views/KPIList.vue | 153 ++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/KPIList.vue b/frontend/src/views/KPIList.vue index 7d781ea6..23f928bd 100644 --- a/frontend/src/views/KPIList.vue +++ b/frontend/src/views/KPIList.vue @@ -5,7 +5,7 @@

KPI字典

📋 从模板创建 - + 新建KPI + + 新建KPI
@@ -170,6 +170,34 @@ + +
+ + + +
+
+ {{ i + 1 }} +
+
{{ s.title }}
+
{{ s.desc }}
+ +
+
+
+
+
五规则实时校验
+
+ {{ c.pass ? '✅' : '❌' }} + {{ c.label }} + {{ c.note }} +
+
+
+
+
@@ -432,6 +460,41 @@ const editMode = ref(false) const form = ref({}) const saving = ref(false) +// 新建入口:重置表单 + 设计引导草稿 +function openCreate() { + editMode.value = false + form.value = {} + designDraft.value = {} + guideOpen.value = ['guide'] + showForm.value = true +} + +// ── KPI设计引导(五步法·五规则,对应 docs/kpi-design-rule.md)── +const guideOpen = ref(['guide']) +const guideSteps = [ + { field: 'duty', title: '① 明确岗位职责', desc: '这个KPI考核谁的什么岗位?岗位存在的价值是什么?', placeholder: '如:销售岗——负责新客户开拓与回款' }, + { field: 'customer', title: '② 分析客户是谁', desc: '服务对象:内部客户(上级/下游部门)+ 外部客户(终端/渠道)', placeholder: '如:外部客户(新签/续约)+ 内部(交付/财务)' }, + { field: 'requirement', title: '③ 分析客户需求', desc: '客户对这个岗位的期望标准是什么?', placeholder: '如:快速响应、按时交付、回款可控' }, + { field: 'output', title: '④ 确定成果输出', desc: '岗位需要产出什么可交付成果?(即本KPI,填入上方表单)', placeholder: '如:新签客户数、签约金额' }, + { field: 'factors', title: '⑤ 五因素设计指标', desc: '时间/数量/质量/成本/风险——对应下方表单字段', placeholder: '如:数量=新签数(目标值)、时间=频率(月度)、质量=满意度(阈值)' }, +] +const designDraft = ref>({}) + +const designChecks = computed(() => { + const f = form.value || {} + const dimCount = kpis.value.filter((k: any) => k.dimension === f.dimension).length + const hasTarget = f.target_monthly != null || f.target_quarterly != null || f.target_yearly != null || f.target_value != null + return [ + { label: 'KPI名称已填(SMART·具体)', pass: !!f.kpi_name }, + { label: '已选维度(BSC四维平衡)', pass: !!f.dimension }, + { label: '已填单位(可衡量)', pass: !!f.unit }, + { label: '有目标值(可衡量·五因素数量)', pass: hasTarget }, + { label: '已选频率(SMART·有时限)', pass: !!f.frequency }, + { label: '已选负责人(责任到人)', pass: !!f.responsible_user }, + { label: '本维度KPI数量≤6(规则①)', pass: dimCount < 6, note: dimCount ? `当前${DIM_MAP[f.dimension] || ''}维度已有${dimCount}个,建议聚焦` : '' }, + ] +}) + // ── 编辑入口:列表行 → 弹窗编辑(与新建一致)── function openEdit(row: any) { editMode.value = true @@ -667,6 +730,17 @@ function onDimensionChange(val: string) { async function saveKPI() { if (saving.value) return + // KPI设计规范·基础门禁(仅新建模式;五步法成果输出 + SMART可衡量) + if (!editMode.value) { + if (!form.value.kpi_name || !form.value.dimension) { + ElMessage.warning('请先填写 KPI名称 和 维度(KPI设计规范·五步法)') + return + } + if (!form.value.unit || (form.value.target_monthly == null && form.value.target_quarterly == null && form.value.target_yearly == null && form.value.target_value == null)) { + ElMessage.warning('请补充 单位 和 至少一个目标值(KPI设计规范·SMART可衡量)') + return + } + } saving.value = true try { // 多粒度目标同步:按frequency将对应周期目标写入target_value(兼容评分/阈值等旧逻辑) @@ -801,6 +875,83 @@ onMounted(() => { display: flex; flex-direction: column; } + +/* ── KPI设计引导(五步法·五规则)── */ +.design-guide { + margin-bottom: 12px; + border: 1px solid #d9e8ff; + border-radius: 8px; + background: #f8fbff; + overflow: hidden; +} +.guide-title { + font-weight: 600; + font-size: 13px; + color: #1f4e9c; +} +.guide-steps { + display: flex; + flex-direction: column; + gap: 8px; + padding: 4px 8px 10px; +} +.guide-step { + display: flex; + gap: 10px; + align-items: flex-start; +} +.step-no { + width: 22px; + height: 22px; + border-radius: 50%; + background: #409EFF; + color: #fff; + font-size: 12px; + font-weight: 600; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + margin-top: 2px; +} +.step-body { + flex: 1; +} +.step-title { + font-size: 13px; + font-weight: 600; + color: #303133; +} +.step-desc { + font-size: 12px; + color: #909399; + margin: 2px 0 4px; +} +.rule-checks { + border-top: 1px dashed #d9e8ff; + padding: 10px 8px 4px; + display: flex; + flex-direction: column; + gap: 6px; +} +.rule-title { + font-size: 13px; + font-weight: 600; + color: #1f4e9c; +} +.rule-check { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: #303133; +} +.rule-check .ok { color: #67C23A; } +.rule-check .fail { color: #F56C6C; } +.rule-note { + color: #E6A23C; + font-size: 12px; +} .page-header { display: flex; justify-content: space-between;