feat: KPI详情历史数据对齐元数据 — 目标值/偏差%/红黄绿判定/单位/数据批次
- 后端: get_kpi的values附带target_value/deviation_pct/level/score/unit 判定对齐工作台语义: 正向≥0.9绿/≥0.7黄/否则红; 反向≤目标绿/≤1.1倍黄/否则红 - 前端: 历史数据表格新增目标值/偏差/判定列, 实际值带单位+红黄着色, 新增数据批次列 - 验证: 渠补率2月84%=黄(超5%), 5月88.3%=红(超10.4%), 收入全部red(远低于1200万目标)
This commit is contained in:
+36
-5
@@ -485,21 +485,52 @@ def get_kpi(kpi_id: int, db: Session = Depends(get_db), entity_id: int = Depends
|
||||
result = kpi_to_dict(kpi)
|
||||
# 附带历史数据(前端KPI详情"历史数据"Tab依赖)
|
||||
# 2026-08-26修复: 原实现只返回定义信息,values缺失导致历史数据Tab空白
|
||||
# 2026-08-26增强: values对齐KPI元数据(目标值/偏差/红黄绿判定/单位)
|
||||
vals = db.query(KPIValue).filter(
|
||||
KPIValue.kpi_id == kpi_id,
|
||||
KPIValue.actual_value.isnot(None),
|
||||
).order_by(KPIValue.period.asc()).all()
|
||||
result["values"] = [
|
||||
{
|
||||
is_reverse = kpi.kpi_code in REVERSE_INDICATORS
|
||||
target = kpi.target_value
|
||||
result["values"] = []
|
||||
for v in vals:
|
||||
# 判定红黄绿(对齐工作台语义:正向≥0.9绿/≥0.7黄/否则红;反向≤目标绿/≤1.1倍黄/否则红)
|
||||
level = "info"
|
||||
score = None
|
||||
if target and v.actual_value is not None:
|
||||
if is_reverse:
|
||||
if v.actual_value <= target:
|
||||
level = "green"
|
||||
elif v.actual_value <= target * 1.1:
|
||||
level = "yellow"
|
||||
else:
|
||||
level = "red"
|
||||
else:
|
||||
ratio = v.actual_value / target
|
||||
if ratio >= 0.9:
|
||||
level = "green"
|
||||
elif ratio >= 0.7:
|
||||
level = "yellow"
|
||||
else:
|
||||
level = "red"
|
||||
score, _ = _calc_five_tier_score(v.actual_value, target, is_reverse=is_reverse)
|
||||
# 偏差率(相对目标)
|
||||
deviation = None
|
||||
if target and target != 0 and v.actual_value is not None:
|
||||
deviation = round((v.actual_value - target) / target * 100, 1)
|
||||
result["values"].append({
|
||||
"id": v.id,
|
||||
"period": v.period,
|
||||
"actual_value": v.actual_value,
|
||||
"target_value": target,
|
||||
"unit": kpi.unit or "",
|
||||
"deviation_pct": deviation,
|
||||
"level": level,
|
||||
"score": score,
|
||||
"source_type": v.source_type,
|
||||
"data_status": v.data_status,
|
||||
"source_batch": v.source_batch,
|
||||
}
|
||||
for v in vals
|
||||
]
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -147,12 +147,33 @@
|
||||
<el-tab-pane label="历史数据" name="history">
|
||||
<v-chart :option="chartOption" autoresize class="full-width-chart" />
|
||||
<el-table :data="values" size="small" class="data-table">
|
||||
<el-table-column prop="period" label="期间" width="120" />
|
||||
<el-table-column prop="actual_value" label="实际值" width="120" />
|
||||
<el-table-column prop="source_type" label="来源" width="80" />
|
||||
<el-table-column prop="data_status" label="状态" width="80">
|
||||
<el-table-column prop="period" label="期间" width="100" />
|
||||
<el-table-column label="实际值" width="130">
|
||||
<template #default="{ row }">
|
||||
<span :style="row.level === 'red' ? 'color:#f56c6c;font-weight:600' : (row.level === 'yellow' ? 'color:#e6a23c;font-weight:600' : '')">
|
||||
{{ row.actual_value != null ? formatValue(row.actual_value) : '--' }}{{ row.unit || '' }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标值" width="120">
|
||||
<template #default="{ row }">{{ row.target_value != null ? formatValue(row.target_value) + (row.unit || '') : '--' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="偏差" width="90">
|
||||
<template #default="{ row }"><span v-if="row.deviation_pct != null" :style="row.level === 'red' ? 'color:#f56c6c' : (row.level === 'yellow' ? 'color:#e6a23c' : 'color:#67c23a')">{{ row.deviation_pct > 0 ? '+' : '' }}{{ row.deviation_pct }}%</span><span v-else>--</span></template>
|
||||
</el-table-column>
|
||||
<el-table-column label="判定" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.level === 'green'" type="success" size="small">绿</el-tag>
|
||||
<el-tag v-else-if="row.level === 'yellow'" type="warning" size="small">黄</el-tag>
|
||||
<el-tag v-else-if="row.level === 'red'" type="danger" size="small">红</el-tag>
|
||||
<el-tag v-else type="info" size="small">—</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source_type" label="来源" width="90" />
|
||||
<el-table-column prop="data_status" label="状态" width="90">
|
||||
<template #default="{ row }"><el-tag :type="row.data_status==='verified'?'success':'warning'" size="small">{{ row.data_status }}</el-tag></template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="source_batch" label="数据批次" min-width="160" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
@@ -304,6 +325,14 @@ import { useFormGuard } from '../composables/useFormGuard'
|
||||
const route = useRoute()
|
||||
const kpi = ref<any>(null)
|
||||
const values = ref<any[]>([])
|
||||
|
||||
// 数值格式化(历史数据对齐显示用)
|
||||
function formatValue(v: any) {
|
||||
if (v === null || v === undefined || isNaN(Number(v))) return '--'
|
||||
const n = Number(v)
|
||||
// 整数不带小数,非整数保留2位
|
||||
return n.toLocaleString('zh-CN', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
||||
}
|
||||
const mapOptions = ref<any[]>([])
|
||||
const activeTab = ref('basic')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user