diff --git a/backend/app/api/kpis.py b/backend/app/api/kpis.py
index 61f203b6..3645fb5a 100644
--- a/backend/app/api/kpis.py
+++ b/backend/app/api/kpis.py
@@ -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
diff --git a/frontend/src/views/KPIDetail.vue b/frontend/src/views/KPIDetail.vue
index 24387383..61df0f08 100644
--- a/frontend/src/views/KPIDetail.vue
+++ b/frontend/src/views/KPIDetail.vue
@@ -147,12 +147,33 @@
-
-
-
-
+
+
+
+
+ {{ row.actual_value != null ? formatValue(row.actual_value) : '--' }}{{ row.unit || '' }}
+
+
+
+
+ {{ row.target_value != null ? formatValue(row.target_value) + (row.unit || '') : '--' }}
+
+
+ {{ row.deviation_pct > 0 ? '+' : '' }}{{ row.deviation_pct }}%--
+
+
+
+ 绿
+ 黄
+ 红
+ —
+
+
+
+
{{ row.data_status }}
+
@@ -304,6 +325,14 @@ import { useFormGuard } from '../composables/useFormGuard'
const route = useRoute()
const kpi = ref(null)
const values = ref([])
+
+// 数值格式化(历史数据对齐显示用)
+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([])
const activeTab = ref('basic')