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

339 lines
8.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
**修复类型**: 前端数据转换逻辑优化(后端无需修改)