--- title: 统计V2页面周度视图Tooltip显示NaN修复 author: AI Assistant date: 2026-02-11 status: final category: 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 定义 ```csharp // Application/Dto/Statistics/StatisticsDto.cs:14-20 public record DailyStatisticsDto( int Day, // ❌ 只有天数(1-31),没有完整日期! int Count, decimal Expense, decimal Income, decimal Saving ); ``` #### 2. 后端数据转换逻辑 ```csharp // 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. 前端原始代码(修复前) ```javascript // 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.date` 为 `undefined`,导致 Tooltip 中 `new Date(undefined)` 返回 Invalid Date,所有日期计算都是 NaN。 --- ## 修复方案 由于修改后端 DTO 会影响所有使用该接口的地方,我们选择**在前端重建完整日期**的方案: ### 核心思路 1. API 返回的数据按日期顺序排列(从 startDate 到 endDate) 2. 使用数组索引配合 `weekStart` 重建每一天的完整日期 3. 转换为 `YYYY-MM-DD` 格式字符串供图表使用 ### 修复后的代码 ```javascript // 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 返回数据**: ```json { "success": true, "data": [ { "day": 10, "expense": 150.50, "income": 300.00, "count": 5 }, { "day": 11, "expense": 200.00, "income": 150.00, "count": 3 } ] } ``` **前端数据**: ```javascript [ { 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 返回数据** (相同): ```json { "success": true, "data": [ { "day": 10, "expense": 150.50, "income": 300.00, "count": 5 }, { "day": 11, "expense": 200.00, "income": 150.00, "count": 3 } ] } ``` **前端数据** (修复后): ```javascript [ { 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日): ```json { "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`,可以正确递增日期,自动处理跨月 ### 日期递增逻辑 ```javascript 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 添加完整日期字段 ```csharp // 新增字段,保持向后兼容 public record DailyStatisticsDto( int Day, string Date, // ✅ 新增: 完整日期字符串 "YYYY-MM-DD" int Count, decimal Expense, decimal Income, decimal Saving ); ``` **方案 B**: 直接将 `Day` 改为 `Date` ```csharp // 破坏性变更,需要迁移所有调用方 public record DailyStatisticsDto( string Date, // ✅ 改为完整日期字符串 int Count, decimal Expense, decimal Income, decimal Saving ); ``` **推荐**: 方案 A (向后兼容),但需要更新所有使用该 DTO 的地方。 ### 2. API 文档更新 更新 `Web/src/api/statistics.js` 中的注释: ```javascript /** * @returns {number} data[].day - 日期(天数,1-31) * ⚠️ 注意: 只返回天数,前端需要根据 startDate 重建完整日期 */ ``` ### 3. 添加数据验证 在前端添加防御性检查: ```javascript if (dailyResult?.success && dailyResult.data) { if (!Array.isArray(dailyResult.data) || dailyResult.data.length === 0) { console.warn('周度统计数据为空') trendStats.value = [] return } // 数据转换逻辑... } ``` --- ## 参考资料 - [MDN - Date.prototype.setDate()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate) - [JavaScript Date 跨月处理](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_boundaries) - [ECharts Tooltip Formatter](https://echarts.apache.org/en/option.html#tooltip.formatter) --- **修复版本**: v2.3 **修复日期**: 2026-02-11 **修复工程师**: AI Assistant **修复类型**: 前端数据转换逻辑优化(后端无需修改)