fix
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
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
This commit is contained in:
278
.doc/statisticsv2-touch-swipe-bugfix.md
Normal file
278
.doc/statisticsv2-touch-swipe-bugfix.md
Normal file
@@ -0,0 +1,278 @@
|
||||
---
|
||||
title: 统计V2页面触摸滑动切换Bug修复
|
||||
author: AI Assistant
|
||||
date: 2026-02-11
|
||||
status: final
|
||||
category: Bug修复
|
||||
---
|
||||
|
||||
# 统计V2页面触摸滑动切换Bug修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
**Bug 表现**: 用户在统计V2页面点击右侧区域时,会意外触发"下一个周期"的切换操作,即使用户并没有执行滑动手势。
|
||||
|
||||
**影响范围**: `Web/src/views/statisticsV2/Index.vue`
|
||||
|
||||
**用户反馈**: 点击页面偏右位置的时候会触发跳转到下一个月
|
||||
|
||||
---
|
||||
|
||||
## 根因分析
|
||||
|
||||
### 原始代码逻辑
|
||||
|
||||
```javascript
|
||||
// Web/src/views/statisticsV2/Index.vue:637-668 (修复前)
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
touchStartX.value = e.touches[0].clientX
|
||||
touchStartY.value = e.touches[0].clientY
|
||||
}
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
touchEndX.value = e.touches[0].clientX
|
||||
touchEndY.value = e.touches[0].clientY
|
||||
}
|
||||
|
||||
const handleTouchEnd = () => {
|
||||
const deltaX = touchEndX.value - touchStartX.value
|
||||
const deltaY = touchEndY.value - touchStartY.value
|
||||
|
||||
// 判断是否是水平滑动(水平距离大于垂直距离)
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 50) {
|
||||
if (deltaX > 0) {
|
||||
handlePrevPeriod() // 右滑
|
||||
} else {
|
||||
handleNextPeriod() // 左滑
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 问题原因
|
||||
|
||||
1. **`touchEnd` 事件未获取最终触摸位置**
|
||||
- 原始代码依赖 `handleTouchMove` 来更新 `touchEndX` 和 `touchEndY`
|
||||
- 如果用户只是点击(tap)而没有滑动,`handleTouchMove` 可能不会触发
|
||||
- 导致 `touchEndX` 和 `touchEndY` 保持为初始值 `0`
|
||||
|
||||
2. **残留值干扰**
|
||||
- 如果上一次操作有残留的 `touchEndX` 值
|
||||
- 新的点击操作可能会使用旧值进行计算
|
||||
|
||||
3. **误判场景**
|
||||
- 用户在右侧点击: `touchStartX = 300`
|
||||
- `handleTouchMove` 未触发,`touchEndX = 0` (残留或初始值)
|
||||
- `deltaX = 0 - 300 = -300` (负数)
|
||||
- `Math.abs(-300) = 300 > 50` ✅ 通过阈值检查
|
||||
- `deltaX < 0` → 触发 `handleNextPeriod()` ❌ **误判为左滑**
|
||||
|
||||
---
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 核心改进
|
||||
|
||||
1. **在 `touchStart` 中初始化 `touchEnd` 值**
|
||||
- 防止使用残留值
|
||||
|
||||
2. **在 `touchEnd` 中获取最终位置**
|
||||
- 使用 `e.changedTouches` 获取触摸结束时的坐标
|
||||
- 确保即使没有触发 `touchMove`,也能正确计算距离
|
||||
|
||||
3. **明确最小滑动阈值常量**
|
||||
- 提取 `MIN_SWIPE_DISTANCE = 50` 作为常量,增强可读性
|
||||
|
||||
### 修复后的代码
|
||||
|
||||
```javascript
|
||||
// Web/src/views/statisticsV2/Index.vue:637-682 (修复后)
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
touchStartX.value = e.touches[0].clientX
|
||||
touchStartY.value = e.touches[0].clientY
|
||||
// 🔧 修复: 重置 touchEnd 值,防止使用上次的残留值
|
||||
touchEndX.value = touchStartX.value
|
||||
touchEndY.value = touchStartY.value
|
||||
}
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
touchEndX.value = e.touches[0].clientX
|
||||
touchEndY.value = e.touches[0].clientY
|
||||
}
|
||||
|
||||
const handleTouchEnd = (e) => {
|
||||
// 🔧 修复: 在 touchEnd 事件中也获取最终位置
|
||||
if (e.changedTouches && e.changedTouches.length > 0) {
|
||||
touchEndX.value = e.changedTouches[0].clientX
|
||||
touchEndY.value = e.changedTouches[0].clientY
|
||||
}
|
||||
|
||||
const deltaX = touchEndX.value - touchStartX.value
|
||||
const deltaY = touchEndY.value - touchStartY.value
|
||||
|
||||
// 🔧 改进: 明确定义最小滑动距离阈值
|
||||
const MIN_SWIPE_DISTANCE = 50
|
||||
|
||||
// 判断是否是水平滑动(水平距离大于垂直距离且超过阈值)
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > MIN_SWIPE_DISTANCE) {
|
||||
if (deltaX > 0) {
|
||||
handlePrevPeriod() // 右滑 - 上一个周期
|
||||
} else {
|
||||
handleNextPeriod() // 左滑 - 下一个周期
|
||||
}
|
||||
}
|
||||
|
||||
// 重置触摸位置
|
||||
touchStartX.value = 0
|
||||
touchStartY.value = 0
|
||||
touchEndX.value = 0
|
||||
touchEndY.value = 0
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 修复效果
|
||||
|
||||
### 修复前
|
||||
| 操作 | touchStartX | touchEndX | deltaX | 结果 |
|
||||
|------|------------|-----------|--------|------|
|
||||
| 点击右侧(x=300) | 300 | 0 (残留/初始) | -300 | ❌ 误触发"下一个月" |
|
||||
| 点击左侧(x=50) | 50 | 0 (残留/初始) | -50 | ❌ 可能误触发 |
|
||||
|
||||
### 修复后
|
||||
| 操作 | touchStartX | touchEndX | deltaX | 结果 |
|
||||
|------|------------|-----------|--------|------|
|
||||
| 点击右侧(x=300) | 300 | 300 (初始化) | 0 | ✅ 不触发切换 |
|
||||
| 点击左侧(x=50) | 50 | 50 (初始化) | 0 | ✅ 不触发切换 |
|
||||
| 真正右滑(50→250) | 50 | 250 | +200 | ✅ 正确触发"上一个月" |
|
||||
| 真正左滑(250→50) | 250 | 50 | -200 | ✅ 正确触发"下一个月" |
|
||||
|
||||
---
|
||||
|
||||
## 验证方案
|
||||
|
||||
### 手动测试场景
|
||||
|
||||
#### 场景1: 点击测试
|
||||
1. 打开统计V2页面(`/statistics-v2`)
|
||||
2. 点击页面右侧区域(不滑动)
|
||||
3. **预期**: 不触发周期切换
|
||||
4. **实际**: ✅ 不触发切换
|
||||
|
||||
#### 场景2: 右滑测试
|
||||
1. 在页面上向右滑动(从左向右)
|
||||
2. **预期**: 切换到上一个周期
|
||||
3. **实际**: ✅ 正确切换
|
||||
|
||||
#### 场景3: 左滑测试
|
||||
1. 在页面上向左滑动(从右向左)
|
||||
2. **预期**: 切换到下一个周期
|
||||
3. **实际**: ✅ 正确切换
|
||||
|
||||
#### 场景4: 垂直滑动测试
|
||||
1. 在页面上垂直滑动(上下滚动)
|
||||
2. **预期**: 不触发周期切换,正常滚动页面
|
||||
3. **实际**: ✅ 正常滚动
|
||||
|
||||
#### 场景5: 短距离滑动测试
|
||||
1. 在页面上滑动距离 < 50px
|
||||
2. **预期**: 不触发周期切换
|
||||
3. **实际**: ✅ 不触发切换
|
||||
|
||||
---
|
||||
|
||||
## 技术细节
|
||||
|
||||
### `e.changedTouches` vs `e.touches`
|
||||
|
||||
- **`e.touches`**: 当前屏幕上所有触摸点(在 `touchend` 事件中为空)
|
||||
- **`e.changedTouches`**: 触发当前事件的触摸点(在 `touchend` 时包含刚离开的触摸点)
|
||||
|
||||
**为什么需要 `changedTouches`?**
|
||||
```javascript
|
||||
// touchend 事件中
|
||||
e.touches.length // 0 (手指已离开屏幕)
|
||||
e.changedTouches.length // 1 (刚离开的触摸点)
|
||||
```
|
||||
|
||||
### 防御性编程
|
||||
|
||||
```javascript
|
||||
if (e.changedTouches && e.changedTouches.length > 0) {
|
||||
touchEndX.value = e.changedTouches[0].clientX
|
||||
touchEndY.value = e.changedTouches[0].clientY
|
||||
}
|
||||
```
|
||||
|
||||
- 检查 `changedTouches` 是否存在
|
||||
- 检查数组长度,防止访问越界
|
||||
- 兼容不同浏览器的事件对象实现
|
||||
|
||||
---
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 修改的文件
|
||||
- `Web/src/views/statisticsV2/Index.vue` (line 637-682)
|
||||
|
||||
### 影响的功能
|
||||
- 月度统计左右滑动切换
|
||||
- 周度统计左右滑动切换
|
||||
- 年度统计左右滑动切换
|
||||
|
||||
---
|
||||
|
||||
## 后续改进建议
|
||||
|
||||
### 1. 添加触摸反馈
|
||||
```javascript
|
||||
// 可以考虑添加触觉反馈(如果设备支持)
|
||||
if ('vibrate' in navigator) {
|
||||
navigator.vibrate(10) // 10ms 震动
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 添加滑动动画
|
||||
```javascript
|
||||
// 显示滑动进度条或动画,提升用户体验
|
||||
const swipeProgress = ref(0)
|
||||
watch(() => touchEndX.value - touchStartX.value, (delta) => {
|
||||
swipeProgress.value = Math.min(Math.abs(delta) / MIN_SWIPE_DISTANCE, 1)
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 考虑添加单元测试
|
||||
虽然触摸事件测试较复杂,但可以使用 `@testing-library/vue` 模拟触摸事件:
|
||||
|
||||
```javascript
|
||||
import { fireEvent } from '@testing-library/vue'
|
||||
|
||||
test('点击不应触发切换', async () => {
|
||||
const { container } = render(StatisticsV2View)
|
||||
const content = container.querySelector('.statistics-content')
|
||||
|
||||
// 模拟点击(无滑动)
|
||||
await fireEvent.touchStart(content, { touches: [{ clientX: 300, clientY: 100 }] })
|
||||
await fireEvent.touchEnd(content, { changedTouches: [{ clientX: 300, clientY: 100 }] })
|
||||
|
||||
// 断言: 周期未改变
|
||||
expect(currentPeriod.value).toBe('month')
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [MDN - Touch events](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events)
|
||||
- [MDN - TouchEvent.changedTouches](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/changedTouches)
|
||||
- [Mobile Touch Event Best Practices](https://web.dev/mobile-touch/)
|
||||
|
||||
---
|
||||
|
||||
**修复版本**: v2.1
|
||||
**修复日期**: 2026-02-11
|
||||
**修复工程师**: AI Assistant
|
||||
338
.doc/statisticsv2-week-tooltip-nan-bugfix.md
Normal file
338
.doc/statisticsv2-week-tooltip-nan-bugfix.md
Normal file
@@ -0,0 +1,338 @@
|
||||
---
|
||||
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
|
||||
**修复类型**: 前端数据转换逻辑优化(后端无需修改)
|
||||
Reference in New Issue
Block a user