feat(KR): 行动挂KR完成 — action_plans.kr_id字段+API+前端目标/KR选择+KR中文名自动填+地图krs端点
- action_plans表加kr_id字段(关联krs.id),create/update/list支持kr_id读写
- 创建行动选KR时自动继承objective_id;列表附kr_title
- 前端行动方案库加'所属目标/KR'两级联动选择,选中KR自动带出KPI
- OKR详情页KR卡片下显示达成行动列表(O→KR→行动三层)
- MapCanvasDialogs onKpiChange选中KPI自动填kr.name=kpi.kpi_name(中文)
- 编辑回填英文code KR名转中文显示兜底
- maps.py 新增GET /{map_id}/krs端点(MapReview战略回顾会KR进度)
- 数据修复: krs表9条英文code title→中文名(按kpi_definitions映射)
- 数据迁移: 高置信行动挂KR(费用率→kr10,应收→kr14); 地图32空KR清理
- okr list的kr_summary补metric_kpi_id(前端联动用)
- 新增6个kr_id测试(test_action_plans),全量547通过
This commit is contained in:
@@ -71,6 +71,12 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="所属KR" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.kr_title" style="font-size:12px;">{{ row.kr_title }}</span>
|
||||
<span v-else style="font-size:12px;color:#ccc;">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="维度" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="dimTagType(row.kpi_dimension)">{{ dimLabel(row.kpi_dimension) }}</el-tag>
|
||||
@@ -157,6 +163,16 @@
|
||||
<el-option v-for="k in kpiOptions" :key="k.id" :label="`${k.kpi_code} ${k.kpi_name}`" :value="k.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属目标">
|
||||
<el-select v-model="form.objective_id" placeholder="选择OKR目标(可选)" clearable filterable style="width:100%;" :teleported="false" @change="onObjectiveChange">
|
||||
<el-option v-for="o in objectiveOptions" :key="o.id" :label="`${o.title}(${o.kr_count || 0} KR)`" :value="o.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属KR">
|
||||
<el-select v-model="form.kr_id" placeholder="选择KR(行动是KR的达成手段)" clearable filterable style="width:100%;" :teleported="false" :disabled="!form.objective_id" @change="onKrChange">
|
||||
<el-option v-for="kr in currentKrs" :key="kr.id" :label="`${kr.title}(${kr.progress || 0}%)`" :value="kr.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="详细描述(可选)" />
|
||||
</el-form-item>
|
||||
@@ -331,7 +347,7 @@
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { actionPlanApi, kpiApi, userApi } from '../api/index'
|
||||
import { actionPlanApi, kpiApi, userApi, okrApi } from '../api/index'
|
||||
import MyDialog from '../components/MyDialog.vue'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -352,6 +368,7 @@ const selectedRows = ref<any[]>([])
|
||||
|
||||
const kpiOptions = ref<any[]>([])
|
||||
const userOptions = ref<string[]>([])
|
||||
const objectiveOptions = ref<any[]>([])
|
||||
|
||||
// ── 筛选 ──
|
||||
const filterStatus = ref('')
|
||||
@@ -450,6 +467,33 @@ async function loadUsers() {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// ── 目标/KR加载(行动挂KR 2026-08-27) ──
|
||||
async function loadObjectives() {
|
||||
try {
|
||||
const r: any = await okrApi.list({})
|
||||
objectiveOptions.value = r.items || r.data || []
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// 当前目标下的KR(两级联动)
|
||||
const currentKrs = computed(() => {
|
||||
const obj = objectiveOptions.value.find((o: any) => o.id === form.value.objective_id)
|
||||
return obj?.key_results || []
|
||||
})
|
||||
|
||||
function onObjectiveChange() {
|
||||
form.value.kr_id = null
|
||||
}
|
||||
|
||||
function onKrChange() {
|
||||
const kr = currentKrs.value.find((k: any) => k.id === form.value.kr_id)
|
||||
if (!kr) return
|
||||
// 选中KR自动带出KPI(若尚未选择)
|
||||
if (kr.metric_kpi_id && !form.value.kpi_id) {
|
||||
form.value.kpi_id = kr.metric_kpi_id
|
||||
}
|
||||
}
|
||||
|
||||
// ── 新建/编辑 ──
|
||||
const showForm = ref(false)
|
||||
const formTitle = ref('')
|
||||
@@ -459,6 +503,8 @@ const form = ref({
|
||||
title: '',
|
||||
target_value: '',
|
||||
kpi_id: null as number | null,
|
||||
objective_id: null as number | null,
|
||||
kr_id: null as number | null,
|
||||
description: '',
|
||||
assignee: '',
|
||||
priority: 'medium',
|
||||
@@ -471,7 +517,7 @@ const form = ref({
|
||||
function openCreate() {
|
||||
editingId.value = null
|
||||
formTitle.value = '新建行动方案'
|
||||
form.value = { title: '', target_value: '', kpi_id: null, description: '', assignee: '', priority: 'medium', due_date: null, status: 'pending', progress: 0, result: '' }
|
||||
form.value = { title: '', target_value: '', kpi_id: null, objective_id: null, kr_id: null, description: '', assignee: '', priority: 'medium', due_date: null, status: 'pending', progress: 0, result: '' }
|
||||
// 如果路由携带 kpi_id 参数,自动填入
|
||||
if (route.query.kpi_id) {
|
||||
form.value.kpi_id = Number(route.query.kpi_id)
|
||||
@@ -486,6 +532,8 @@ function editPlan(row: any) {
|
||||
title: row.title,
|
||||
target_value: row.target_value || '',
|
||||
kpi_id: row.kpi_id,
|
||||
objective_id: row.objective_id || null,
|
||||
kr_id: row.kr_id || null,
|
||||
description: row.description || '',
|
||||
assignee: row.assignee || '',
|
||||
priority: row.priority || 'medium',
|
||||
@@ -613,6 +661,7 @@ onMounted(() => {
|
||||
loadData()
|
||||
loadKpis()
|
||||
loadUsers()
|
||||
loadObjectives()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user