feat: KPI新建设计引导(五步法·五规则) — KPIList新建弹窗

- 新建KPI时显示设计引导折叠面板: 五步法逐步引导(职责/客户/需求/成果/五因素)
- 五规则实时校验清单: 名称/维度/单位/目标值/频率/负责人 + 本维度KPI数量≤6提示
- 提交门禁(仅新建): 名称+维度必填, 单位+至少一个目标值必填
- 对应 docs/kpi-design-rule.md 方法论落地
This commit is contained in:
Hermes CI Fix
2026-08-20 15:56:56 +08:00
parent a9845eb2cf
commit d5edcefb48
+152 -1
View File
@@ -5,7 +5,7 @@
<h3>KPI字典</h3>
<div class="header-actions">
<el-button @click="openTemplateDialog">📋 从模板创建</el-button>
<el-button type="primary" @click="showForm=true; form={}; editMode=false">+ 新建KPI</el-button>
<el-button type="primary" @click="openCreate">+ 新建KPI</el-button>
</div>
</div>
@@ -170,6 +170,34 @@
<!-- 新建/编辑对话框 -->
<MyDialog v-model="showForm" :title="editMode ? '编辑KPI' : '新建KPI'" :width="640">
<!-- KPI设计引导(五步法·五规则,新建时显示) -->
<div v-if="!editMode" class="design-guide">
<el-collapse v-model="guideOpen">
<el-collapse-item name="guide">
<template #title>
<span class="guide-title">📐 KPI设计引导(五步法 · 五规则)</span>
</template>
<div class="guide-steps">
<div v-for="(s, i) in guideSteps" :key="i" class="guide-step">
<span class="step-no">{{ i + 1 }}</span>
<div class="step-body">
<div class="step-title">{{ s.title }}</div>
<div class="step-desc">{{ s.desc }}</div>
<el-input v-model="designDraft[s.field]" :placeholder="s.placeholder" size="small" />
</div>
</div>
</div>
<div class="rule-checks">
<div class="rule-title">五规则实时校验</div>
<div v-for="c in designChecks" :key="c.label" class="rule-check">
<span :class="c.pass ? 'ok' : 'fail'">{{ c.pass ? '✅' : '❌' }}</span>
<span>{{ c.label }}</span>
<span v-if="c.note" class="rule-note">{{ c.note }}</span>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
<el-form :model="form" label-width="110px" size="small">
<el-row :gutter="20">
<el-col :span="12">
@@ -432,6 +460,41 @@ const editMode = ref(false)
const form = ref<any>({})
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<Record<string, string>>({})
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;