fix(cma): my-dashboard KPI period caliber (monthly period priority) + as-of/stale marks
my_dashboard picked the row with max calculated_at, so batch backfills made stale periods win (revenue/net-profit showed 2026-07, rebate rate 2026-04). Now prefer the latest monthly period, fall back to calculated_at. Frontend adds data-as-of label and stale tag; not deployed yet (push gated by another pending commit).
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
<div class="section-header">
|
||||
<span class="section-icon">📊</span>
|
||||
<span class="section-title">我的KPI</span>
|
||||
<span v-if="dataAsOf" class="section-asof">数据截至 {{ dataAsOf }}</span>
|
||||
<span class="section-action" @click="$router.push('/kpis')">查看全部 →</span>
|
||||
</div>
|
||||
<div v-if="kpis.length === 0" class="empty-card">
|
||||
@@ -70,10 +71,12 @@
|
||||
<span class="kpi-tag" :class="'tag-' + k.level">{{ k.level === 'green' ? '达标' : k.level === 'yellow' ? '预警' : k.level === 'red' ? '危险' : '未评' }}</span>
|
||||
</div>
|
||||
<div class="kpi-value-row">
|
||||
<div class="kpi-actual" :class="{ 'kpi-actual-empty': k.actual_value == null }">
|
||||
<div class="kpi-actual" :class="{ 'kpi-actual-empty': k.actual_value == null, 'kpi-actual-stale': isStale(k) }">
|
||||
{{ k.actual_value == null ? '暂无数据' : periodActual(k) }}
|
||||
</div>
|
||||
<div class="kpi-target">{{ targetLabel(k) }} {{ k.target_value ?? '-' }}</div>
|
||||
<div class="kpi-target">
|
||||
<span v-if="isStale(k)" class="kpi-stale-tag">非当期</span>{{ targetLabel(k) }} {{ k.target_value ?? '-' }}
|
||||
</div>
|
||||
</div>
|
||||
<el-progress
|
||||
:percentage="k.target_value ? Math.min(100, Math.round((k.actual_value || 0) / k.target_value * 100)) : 0"
|
||||
@@ -174,6 +177,23 @@ function targetLabel(k: any) {
|
||||
return '目标(月)'
|
||||
}
|
||||
|
||||
// 2026-09-18 口径小改(工作台期间缺口):月期间正则 + 页面最新可得月
|
||||
const MONTH_PERIOD_RE = /^\d{4}-(0[1-9]|1[0-2])$/
|
||||
const latestMonth = computed(() => {
|
||||
const months = kpis.value
|
||||
.map((k: any) => String(k.period || ''))
|
||||
.filter((p: string) => MONTH_PERIOD_RE.test(p))
|
||||
.sort()
|
||||
return months.length ? months[months.length - 1] : ''
|
||||
})
|
||||
// 页头「数据截至 X 月」= 本页各卡中最新的月期间(无月期间时不出该文案,不编造时点)
|
||||
const dataAsOf = computed(() => (latestMonth.value ? fmtPeriod(latestMonth.value) : ''))
|
||||
// 非当期 = 该卡的值期间早于页面最新可得月(仍保留原期间标签,只加灰标提示,不隐藏)
|
||||
function isStale(k: any): boolean {
|
||||
const p = String(k.period || '')
|
||||
return MONTH_PERIOD_RE.test(p) && !!latestMonth.value && p < latestMonth.value
|
||||
}
|
||||
|
||||
function planIcon(p: any) {
|
||||
if (p.status === 'completed') return '✅'
|
||||
if (p.overdue) return '🔴'
|
||||
@@ -262,6 +282,8 @@ onMounted(async () => {
|
||||
}
|
||||
.section-action { font-size: 13px; color: #409eff; cursor: pointer; }
|
||||
.section-action:hover { text-decoration: underline; }
|
||||
/* 数据截至 X 月(2026-09-18):说明本页各卡的最新可得期间,不改渲染结构 */
|
||||
.section-asof { font-size: 12px; color: #999; margin-right: 10px; }
|
||||
.loading-wrap { padding: 40px; }
|
||||
|
||||
/* ── 空状态 ── */
|
||||
@@ -310,6 +332,12 @@ onMounted(async () => {
|
||||
.kpi-value-row { display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px; }
|
||||
.kpi-actual { font-size: 24px; font-weight: 700; }
|
||||
.kpi-actual-empty { font-size: 15px; font-weight: 500; color: #999; }
|
||||
/* 非当期灰标(2026-09-18):值期间落后于页面最新可得月时置灰,期间标签照旧显示 */
|
||||
.kpi-actual-stale { color: #999; }
|
||||
.kpi-stale-tag {
|
||||
font-size: 11px; color: #999; background: #f0f0f0;
|
||||
padding: 0 6px; border-radius: 4px; margin-right: 6px;
|
||||
}
|
||||
.kpi-target { font-size: 12px; color: #999; }
|
||||
|
||||
/* ── 改善行动 ── */
|
||||
|
||||
Reference in New Issue
Block a user