## Context **当前状态**: - 后端 `BudgetStatsService` 已正确计算 `Description` (HTML格式明细) 和 `Trend` (每日累计金额数组) - Service 层的 `BudgetStatsDto` 包含这两个字段 - **问题**: Application 层在映射 DTO 时丢失了这两个字段,导致 API 响应不完整 - 前端使用 fallback 逻辑(线性估算)来弥补缺失数据,导致燃尽图显示为直线 **约束**: - 修复必须向后兼容,不能破坏现有 API 契约 - 优先修复 Bug #4 和 #5(高优先级),因为修复简单且影响大 - 前端已有完整的数据处理逻辑,只需后端提供正确数据 ## Goals / Non-Goals **Goals:** - 修复 `BudgetStatsDetail` DTO 定义,添加 `Description` 和 `Trend` 字段 - 修复 `BudgetApplication.GetCategoryStatsAsync` 中的 DTO 映射逻辑 - 修复前端路由配置和 Vant 组件注册问题 - 分析并修复账单删除功能和金额不一致问题 - 添加单元测试覆盖修复场景 **Non-Goals:** - 不重构 `BudgetStatsService` 的计算逻辑(已验证正确) - 不改变前端图表组件的渲染逻辑(已有完整支持) - 不修改 API 路由或版本化(向后兼容) ## Decisions ### 决策 1: 使用 `init` 关键字而非 `set` 来定义新字段 **理由**: `BudgetStatsDetail` 是 `record` 类型,遵循不可变对象模式。使用 `init` 确保字段只能在对象初始化时设置。 **替代方案**: - 改用 `class` + `set` → 违背现有代码风格,且失去 record 的值语义 - 保持 `record` + `set` → C# 9+ 允许,但不符合不可变设计原则 ### 决策 2: 在 Application 层映射时直接赋值,不做转换 **理由**: Service 层的 `BudgetStatsDto.Trend` 和 `Description` 已经是目标格式(`List` 和 `string`),无需额外处理。 **实现**: ```csharp Month = new BudgetStatsDetail { Limit = stats.Month.Limit, Current = stats.Month.Current, Remaining = stats.Month.Remaining, UsagePercentage = stats.Month.UsagePercentage, Trend = stats.Month.Trend, // ⬅️ 新增 Description = stats.Month.Description // ⬅️ 新增 } ``` ### 决策 3: Bug #1 (路由跳转) - 修改底部导航配置而非路由定义 **理由**: 经验证,`/statistics-v2` 路由已存在且正常工作。问题出在底部导航组件中硬编码了错误的路由路径。 **定位策略**: 1. 搜索底部导航组件代码 (通常包含 `van-tabbar` 或 `router-link`) 2. 检查"统计"标签的 `to` 属性或 `path` 配置 3. 修改为 `/statistics-v2` ### 决策 4: Bug #2 (删除功能) - 添加确认对话框而非直接删除 **理由**: 删除操作是破坏性的,应符合最佳实践要求用户确认。 **实现**: ```vue const handleDelete = async () => { const confirmed = await showConfirmDialog({ title: '确认删除', message: '确定要删除这条账单吗?此操作无法撤销。' }) if (confirmed) { await deleteBill(billId) closePopup() } } ``` ### 决策 5: Bug #3 (组件警告) - 按需导入而非全局注册 **理由**: Vant 推荐使用按需导入,减少打包体积。全局注册可能是遗漏导致的警告。 **验证步骤**: 1. 检查 `main.ts` 或全局插件文件是否有 `DatetimePicker` 导入 2. 如果缺失,添加 `import { DatetimePicker } from 'vant'; app.use(DatetimePicker);` 3. 或在使用组件的文件中局部导入 ### 决策 6: Bug #6 (金额不一致) - 先验证是否虚拟消耗导致 **理由**: Bug-handoff 文档指出硬性预算 (📌标记) 会产生虚拟消耗,这是设计行为而非 bug。 **验证逻辑**: 1. 检查不一致的预算是否标记为硬性预算 2. 检查 `BudgetService.GetPeriodRange` 返回的日期范围是否与 `periodStart/periodEnd` 一致 3. 如果是虚拟消耗:在前端账单列表中添加提示说明 4. 如果是日期范围问题:修复 `BudgetResult` 的赋值逻辑 ## Risks / Trade-offs ### 风险 1: API 响应体积增大 **问题**: 新增 `Trend` 数组(月度31个元素,年度12个元素)会增加响应大小。 **缓解措施**: - `Trend` 数据是前端绘制图表必需的,体积增长合理 - 考虑后续添加 gzip 压缩到 API 响应(可选优化) ### 风险 2: 前端可能依赖旧的 fallback 逻辑 **问题**: 如果前端代码中有显式检查 `trend.length === 0` 的逻辑,可能会在修复后仍执行 fallback。 **缓解措施**: - 在修复后端后,验证前端 `BudgetChartAnalysis.vue:603` 和 `629` 行的条件是否正确处理非空 trend - 如果逻辑有问题,修改为 `if (!trend || trend.length === 0)` ### 风险 3: 测试覆盖不足可能导致回归 **问题**: 现有测试可能未覆盖 DTO 映射场景。 **缓解措施**: - 在 `WebApi.Test` 中添加针对 `BudgetApplication.GetCategoryStatsAsync` 的单元测试 - 验证返回的 DTO 包含非空的 `Description` 和 `Trend` - 使用 `FluentAssertions` 编写清晰的断言 ## Migration Plan **部署步骤**: 1. 部署后端更新(向后兼容,前端可继续使用旧逻辑) 2. 验证 API 响应包含新字段 (使用 Swagger 或浏览器开发工具) 3. 前端无需额外部署(已支持新字段,会自动切换到真实数据) 4. 清除浏览器缓存以确保使用最新前端代码 **回滚策略**: - 如果新版本出现问题,回滚到上一个 commit - API 是向后兼容的(只添加字段),旧版前端仍可正常工作 **验证清单**: - [ ] 预算明细弹窗显示完整的 HTML 表格 - [ ] 燃尽图显示波动曲线而非直线 - [ ] 底部导航"统计"按钮正常跳转 - [ ] 删除账单功能弹出确认对话框并正常工作 - [ ] 控制台无 `van-datetime-picker` 警告 - [ ] 金额不一致问题已分析并修复或说明 ## Open Questions 1. **Bug #6 金额不一致的根本原因**: 需要在测试环境中验证是否为虚拟消耗导致,还是日期范围计算错误。 2. **前端 fallback 逻辑是否需要移除**: 当前 fallback 作为容错机制保留是否合理?还是应在有真实数据时完全禁用? 3. **是否需要添加 E2E 测试**: 当前只计划单元测试,是否需要添加端到端测试覆盖完整流程?