fix(analytics): 修复环比 NaN%/Infinity% 显示问题

根因: chainRatio 函数只防 undefined/0, 未防 null/NaN/Infinity
- 后端 Long 字段返回 null(无上期数据)时, curr=null/prev=null 算出 NaN
- curr=有效值/prev=null 时算出 Infinity, StatCard 渲染 '↓ NaN%'/'↓ Infinity%'

修复:
- chainRatio 用 Number.isFinite 统一拦截 null/undefined/NaN/Infinity
- StatCard 模板加 Number.isFinite 二次保险
- merchantRank.proportion / channelSuccess.rate 加 Number.isFinite 兜底
This commit is contained in:
DaxPay Dev
2026-07-19 11:43:44 +08:00
parent 10989dc9c2
commit ae07f8a533
2 changed files with 13 additions and 6 deletions

View File

@@ -57,7 +57,7 @@
</div>
<div class="mt-1 flex items-center text-xs">
<span
v-if="chainRatio !== null"
v-if="chainRatio !== null && Number.isFinite(chainRatio)"
:class="chainRatio >= 0 ? 'text-red-500' : 'text-emerald-500'"
>
<!-- 涨跌色遵循中国惯例涨红跌绿 -->

View File

@@ -49,11 +49,16 @@ function fenToYuan(fen?: number): number {
/**
* 环比百分比计算
*
* 防护策略:后端 Long 字段可能返回 null(无上期数据),或前端派生计算产生 NaN/Infinity。
* Number.isFinite 一次性拦截 null/undefined/NaN/Infinity 四类无效输入,
* prev=0 时无法计算环比(除零),统一返回 null(显示 "—")。
*
* @returns null 表示无法计算(prev 缺数据或为 0)
*/
function chainRatio(curr?: number, prev?: number): null | number {
if (curr === undefined || prev === undefined || prev === 0) return null;
return Math.round(((curr - prev) / prev) * 1000) / 10;
function chainRatio(curr?: null | number, prev?: null | number): null | number {
if (!Number.isFinite(curr) || !Number.isFinite(prev) || prev === 0) return null;
return Math.round(((curr as number - (prev as number)) / (prev as number)) * 1000) / 10;
}
/**
@@ -267,7 +272,8 @@ function buildAnalyticsData(input: {
// ===== channelSuccess(渠道成功率) =====
const channelSuccess = (providerSuccessRes ?? []).map((i) => ({
name: providerLabel(i.provider),
rate: i.rate ?? 0,
// 后端 NULLIF 防除零, 这里 Number.isFinite 二次兜底防异常值
rate: Number.isFinite(i.rate) ? (i.rate as number) : 0,
}));
// ===== hourlyDist(24 小时时段, 取 count 数组) =====
@@ -285,7 +291,8 @@ function buildAnalyticsData(input: {
amount: fenToYuan(i.amount),
merchantName: i.merchantName ?? '-',
orders: i.orders ?? 0,
proportion: i.proportion ?? 0,
// 后端 computeProportion 已防除零, 这里 Number.isFinite 二次兜底防异常值
proportion: Number.isFinite(i.proportion) ? (i.proportion as number) : 0,
}));
return { amountRange, channelSuccess, channelVolume, hourlyDist, merchantRank, overview, payMethod, refundTrend, tradeTrend };