fix(cma): 缺失值 NULL 语义——同环比/执行率/进度不把缺失当 0
ci/woodpecker/push/main Pipeline failed

财务裁定 2026-09-21(博海 2026-08 批次 §2):前端对缺失值须显式 NULL 语义,
同环比不得按 0 参与。

- ProfitBreakdown: revChg/profitChg/rateChg 上期缺失→null(原 return 0 显示为持平 0.0%),
  去掉 prev || 0.01 假分母;模板 null 显示「缺上期数据」
- MyDashboard: 进度条 actual 缺失不再显示 0%,改显 —
- DeviationDashboard: 趋势图 actual/budget 保留 null,barHeight 对 null 不画柱
- BudgetManagement: 执行率/偏差额 actual 缺失→null(表格显示 -),平均执行率只对非空取平均
This commit is contained in:
fullstack-bot
2026-09-21 16:07:03 +08:00
parent f88ed94c15
commit e89629228e
4 changed files with 28 additions and 18 deletions
@@ -61,21 +61,24 @@
<div v-if="prevRevenue" class="pb-compare">
<div class="pb-compare-item">
<span class="pb-compare-label">营收较上期</span>
<span :class="revChg >= 0 ? 'pb-pos' : 'pb-neg'">
<span v-if="revChg != null" :class="revChg >= 0 ? 'pb-pos' : 'pb-neg'">
{{ revChg >= 0 ? '↑' : '↓' }} {{ (Math.abs(revChg) * 100).toFixed(1) }}%
</span>
<span v-else style="color:#909399">缺上期数据</span>
</div>
<div class="pb-compare-item">
<span class="pb-compare-label">净利润较上期</span>
<span :class="profitChg >= 0 ? 'pb-pos' : 'pb-neg'">
<span v-if="profitChg != null" :class="profitChg >= 0 ? 'pb-pos' : 'pb-neg'">
{{ profitChg >= 0 ? '↑' : '↓' }} {{ (Math.abs(profitChg) * 100).toFixed(1) }}%
</span>
<span v-else style="color:#909399">缺上期数据</span>
</div>
<div class="pb-compare-item">
<span class="pb-compare-label">净利率较上期</span>
<span :class="rateChg >= 0 ? 'pb-pos' : 'pb-neg'">
<span v-if="rateChg != null" :class="rateChg >= 0 ? 'pb-pos' : 'pb-neg'">
{{ rateChg >= 0 ? '↑' : '↓' }} {{ (Math.abs(rateChg) * 100).toFixed(1) }}%
</span>
<span v-else style="color:#909399">缺上期数据</span>
</div>
</div>
@@ -129,19 +132,22 @@ const costRate = computed(() => {
return (props.cost / props.revenue * 100).toFixed(1) + '%'
})
const revChg = computed(() => {
if (!props.prevRevenue) return 0
// 缺失值 NULL 语义(财务裁定 2026-09-21 §2):上期缺失/为 0 时返回 null,
// 不得回落 0(0 会被显示成「持平」,把「无数据」当真实 0 参与同环比)。
const revChg = computed<number | null>(() => {
if (props.prevRevenue == null || props.prevRevenue === 0) return null
return (props.revenue - props.prevRevenue) / props.prevRevenue
})
const profitChg = computed(() => {
if (!props.prevProfit) return 0
const profitChg = computed<number | null>(() => {
if (props.prevProfit == null || props.prevProfit === 0) return null
return (props.profit - props.prevProfit) / Math.abs(props.prevProfit)
})
const rateChg = computed(() => {
if (!props.prevRevenue || !props.prevProfit) return 0
const rateChg = computed<number | null>(() => {
if (props.prevRevenue == null || props.prevProfit == null || props.prevRevenue === 0) return null
const cur = props.profit / props.revenue
const prev = props.prevProfit / props.prevRevenue
return (cur - prev) / Math.abs(prev || 0.01)
if (prev === 0) return null
return (cur - prev) / Math.abs(prev)
})
const detailItems = computed(() => {