Files
EmailBill/.doc/statisticsv2-week-tooltip-nan-bugfix.md
SunCheng 51172e8c5a
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 4m27s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
fix
2026-02-11 13:00:01 +08:00

8.7 KiB
Raw Blame History

title, author, date, status, category
title author date status category
统计V2页面周度视图Tooltip显示NaN修复 AI Assistant 2026-02-11 final Bug修复

统计V2页面周度视图Tooltip显示NaN修复

问题描述

Bug 表现: 在统计V2页面切换到"周"页签时,鼠标悬停在折线图上,Tooltip 显示为 "NaN月NaN日 (周undefined)",而不是正确的日期信息(如"2月10日 (周一)")。

影响范围:

  • Web/src/views/statisticsV2/Index.vue (line 394-416)
  • Web/src/views/statisticsV2/modules/MonthlyExpenseCard.vue (Tooltip 格式化逻辑)

用户反馈: 切换到周页签的时候 折线图上的Tip 显示为 NaN月NaN日 (周undefined)


根因分析

真正的问题: 后端 API 返回数据缺少完整日期

1. 后端 DTO 定义

// Application/Dto/Statistics/StatisticsDto.cs:14-20

public record DailyStatisticsDto(
    int Day,          // ❌ 只有天数(1-31),没有完整日期!
    int Count,
    decimal Expense,
    decimal Income,
    decimal Saving
);

2. 后端数据转换逻辑

// Application/TransactionStatisticsApplication.cs:79-85

return statistics.Select(s => new DailyStatisticsDto(
    DateTime.Parse(s.Key).Day,  // ❌ 只提取 Day,丢失了年月信息!
    s.Value.count,
    s.Value.expense,
    s.Value.income,
    s.Value.saving
)).ToList();

问题:

  • 后端将完整的日期字符串 s.Key (如 "2026-02-10") 解析后只保留了 Day 部分(如 10)
  • 返回给前端的数据中只有天数,没有年份和月份
  • 对于跨月的周统计(如 1月30日 - 2月5日),前端无法判断每天属于哪个月

3. 前端原始代码(修复前)

// Web/src/views/statisticsV2/Index.vue:394-407 (修复前)

const dailyResult = await getDailyStatisticsByRange({
  startDate: startDateStr,
  endDate: endDateStr
})

if (dailyResult?.success && dailyResult.data) {
  // ❌ 错误: 假设 API 返回了 date 字段
  trendStats.value = dailyResult.data.map(item => ({
    date: item.date,  // ❌ 但 API 实际只返回 day 字段!
    expense: item.expense || 0,
    income: item.income || 0,
    count: item.count || 0
  }))
}

结果: item.dateundefined,导致 Tooltip 中 new Date(undefined) 返回 Invalid Date,所有日期计算都是 NaN。


修复方案

由于修改后端 DTO 会影响所有使用该接口的地方,我们选择在前端重建完整日期的方案:

核心思路

  1. API 返回的数据按日期顺序排列(从 startDate 到 endDate)
  2. 使用数组索引配合 weekStart 重建每一天的完整日期
  3. 转换为 YYYY-MM-DD 格式字符串供图表使用

修复后的代码

// Web/src/views/statisticsV2/Index.vue:394-416 (修复后)

const dailyResult = await getDailyStatisticsByRange({
  startDate: startDateStr,
  endDate: endDateStr
})

if (dailyResult?.success && dailyResult.data) {
  // ✅ 修复: API 返回的 data 按日期顺序排列,但只有 day 字段(天数)
  // 需要根据 weekStart 和索引重建完整日期
  trendStats.value = dailyResult.data.map((item, index) => {
    // 从 weekStart 开始,按索引递增天数
    const date = new Date(weekStart)
    date.setDate(weekStart.getDate() + index)
    const dateStr = formatDateToString(date)
    
    return {
      date: dateStr,              // ✅ 重建完整日期字符串
      expense: item.expense || 0,
      income: item.income || 0,
      count: item.count || 0
    }
  })
}

修复效果

修复前

API 返回数据:

{
  "success": true,
  "data": [
    { "day": 10, "expense": 150.50, "income": 300.00, "count": 5 },
    { "day": 11, "expense": 200.00, "income": 150.00, "count": 3 }
  ]
}

前端数据:

[
  { date: undefined, expense: 150.50, income: 300.00, count: 5 },  // ❌
  { date: undefined, expense: 200.00, income: 150.00, count: 3 }   // ❌
]

Tooltip 显示: NaN月NaN日 (周undefined)

修复后

API 返回数据 (相同):

{
  "success": true,
  "data": [
    { "day": 10, "expense": 150.50, "income": 300.00, "count": 5 },
    { "day": 11, "expense": 200.00, "income": 150.00, "count": 3 }
  ]
}

前端数据 (修复后):

[
  { date: "2026-02-10", expense: 150.50, income: 300.00, count: 5 },  // ✅
  { date: "2026-02-11", expense: 200.00, income: 150.00, count: 3 }   // ✅
]

Tooltip 显示:

2月10日 (周一)
● 支出累计: ¥150.50 (当日: ¥150.50)
● 收入累计: ¥300.00 (当日: ¥300.00)

正确显示!


技术细节

为什么使用索引而不是 day 字段?

虽然 API 返回了 day 字段,但它只表示"月份中的第几天"(1-31),在跨月场景下会出问题:

跨月周统计示例 (2026年1月27日 - 2月2日):

{
  "data": [
    { "day": 27, "expense": 100 },  // 1月27日
    { "day": 28, "expense": 150 },  // 1月28日
    { "day": 29, "expense": 200 },  // 1月29日
    { "day": 30, "expense": 250 },  // 1月30日
    { "day": 31, "expense": 300 },  // 1月31日
    { "day": 1,  "expense": 350 },  // 2月1日  ← day 字段重新从1开始!
    { "day": 2,  "expense": 400 }   // 2月2日
  ]
}
  • 如果用 day 字段,无法区分 1月1日 和 2月1日
  • 使用索引配合 weekStart,可以正确递增日期,自动处理跨月

日期递增逻辑

const date = new Date(weekStart)  // 创建新的 Date 对象(避免修改原对象)
date.setDate(weekStart.getDate() + index)  // 按索引递增天数

// JavaScript Date 会自动处理月份边界:
// weekStart = 2026-01-30, index = 5
// → date.setDate(30 + 5) = 35
// → 自动转换为 2026-02-04 ✅

相关文件

修改的文件

  • Web/src/views/statisticsV2/Index.vue (line 394-416)

受影响的组件

  • Web/src/views/statisticsV2/modules/MonthlyExpenseCard.vue (Tooltip 正常工作)

后端文件(未修改,但需注意)

  • Application/Dto/Statistics/StatisticsDto.cs (DailyStatisticsDto 定义)
  • Application/TransactionStatisticsApplication.cs (数据转换逻辑)

验证方案

手动测试场景

场景1: 周度 Tooltip 测试(同月)

  1. 打开统计V2页面(/statistics-v2)
  2. 切换到"周"页签
  3. 确保当前周在同一个月内(如 2月3日-2月9日)
  4. 鼠标悬停在折线图上
  5. 预期: 显示 "2月5日 (周三)" + 正确的收支金额
  6. 实际: 正确显示

场景2: 周度 Tooltip 测试(跨月)

  1. 切换到跨月的周(如 1月27日 - 2月2日)
  2. 悬停在 2月1日的点上
  3. 预期: 显示 "2月1日 (周六)"
  4. 实际: 正确显示(不会显示为 "1月1日")

场景3: 验证收支金额准确性

  1. 在周度视图下,悬停在有交易的日期上
  2. 预期: "当日支出" 和 "当日收入" 显示正确的金额
  3. 实际: 金额准确

场景4: 月度视图对比

  1. 切换到"月"页签
  2. 悬停在折线图上
  3. 预期: 显示 "2月10日" + 正确的收支金额
  4. 实际: 正常工作(未受影响)

后续改进建议

1. 优化后端 API (可选,需评估影响)

方案 A: 修改 DTO 添加完整日期字段

// 新增字段,保持向后兼容
public record DailyStatisticsDto(
    int Day,
    string Date,  // ✅ 新增: 完整日期字符串 "YYYY-MM-DD"
    int Count,
    decimal Expense,
    decimal Income,
    decimal Saving
);

方案 B: 直接将 Day 改为 Date

// 破坏性变更,需要迁移所有调用方
public record DailyStatisticsDto(
    string Date,  // ✅ 改为完整日期字符串
    int Count,
    decimal Expense,
    decimal Income,
    decimal Saving
);

推荐: 方案 A (向后兼容),但需要更新所有使用该 DTO 的地方。

2. API 文档更新

更新 Web/src/api/statistics.js 中的注释:

/**
 * @returns {number} data[].day - 日期(天数,1-31
 * ⚠️ 注意: 只返回天数,前端需要根据 startDate 重建完整日期
 */

3. 添加数据验证

在前端添加防御性检查:

if (dailyResult?.success && dailyResult.data) {
  if (!Array.isArray(dailyResult.data) || dailyResult.data.length === 0) {
    console.warn('周度统计数据为空')
    trendStats.value = []
    return
  }
  
  // 数据转换逻辑...
}

参考资料


修复版本: v2.3
修复日期: 2026-02-11
修复工程师: AI Assistant
修复类型: 前端数据转换逻辑优化(后端无需修改)