This commit is contained in:
SunCheng
2026-02-15 10:10:28 +08:00
parent e51a3edd50
commit a88556c784
92 changed files with 6751 additions and 776 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,165 @@
## Context
EmailBill 是一个预算跟踪应用,包含 .NET 10 后端和 Vue 3 前端。预算模块负责计算和展示预算执行情况,包括收入、支出和存款计划。
**当前问题**
1. 预算收入的实际金额计算错误,`BudgetRepository.GetCurrentAmountAsync` 方法查询交易记录时可能存在过滤条件问题
2. 存款计划卡片缺少透明度,用户无法了解"计划存款"金额的计算依据
3. 预算页面的卡片样式与统计页面不一致,影响视觉统一性
**技术栈**
- 后端:.NET 10, FreeSql ORM, xUnit 测试
- 前端Vue 3 Composition API, Vant UI, SCSS
- 数据库SQLite
**约束**
- 保持向后兼容,不破坏现有 API 契约
- 遵循项目现有的代码风格(中文注释、文件作用域命名空间)
- 样式修改需保持暗色主题兼容性
## Goals / Non-Goals
**Goals:**
- 修复收入预算实际金额的计算错误,确保数据准确性
- 为存款计划添加明细展示功能,提升透明度
- 统一预算页面与统计页面的卡片样式,提升 UI 一致性
- 添加单元测试覆盖修复的逻辑
**Non-Goals:**
- 不重构整个预算计算系统
- 不改变预算数据模型结构
- 不修改其他页面的卡片样式
- 不改变存款计划的计算算法本身
## Decisions
### 决策 1问题 1 的修复策略
**决定**:采用 TDD测试驱动开发方式修复计算错误
**方案对比**
- **方案 A选择**:先编写失败的单元测试复现 bug然后修复代码最后验证测试通过
- ✅ 确保 bug 被正确理解和修复
- ✅ 防止回归
- ⏱️ 需要额外编写测试
- **方案 B**:直接修复代码,手动验证
- ❌ 无法保证不引入新问题
- ❌ 缺少回归保护
**实施细节**
1.`WebApi.Test/` 中创建测试用例,使用实际数据复现"家庭年终奖金"的计算错误
2. 诊断 `BudgetRepository.GetCurrentAmountAsync` 的查询条件:
- 检查 `SelectedCategories``Contains` 匹配逻辑
- 验证日期范围过滤是否正确(`>=``<=`
- 确认 `TransactionType` 过滤条件
3. 修复后运行测试验证
### 决策 2存款计划明细的数据来源
**决定**:前端计算明细,不新增后端 API
**方案对比**
- **方案 A选择**:前端基于现有数据计算明细
- ✅ 不增加后端复杂度
- ✅ 响应速度快
- ⚠️ 前端需要理解计算逻辑
- **方案 B**:新增后端 API 返回计算明细
- ⏱️ 需要设计新的 DTO 和 API
- 🔄 增加网络往返
**实施细节**
1. 明细弹窗展示内容:
- **收入预算总计**:所有收入预算的限额和实际值
- **支出预算总计**:所有支出预算的限额和实际值
- **计划存款公式**`收入预算 - 支出预算 = 计划存款`
- **实际存款**:从 budget 对象获取
- **差额**`计划存款 - 实际存款`
2. 使用 Vant 的 Popup 组件实现弹窗
3.`BudgetCard.vue``header-actions` slot 添加"明细"图标按钮(`icon="info"`
### 决策 3样式统一的方案
**决定**:直接修改 `.chart-card` 样式,使其继承或匹配 `.common-card`
**方案对比**
- **方案 A选择**:修改 `BudgetChartAnalysis.vue` 中的 `.chart-card` 样式
- ✅ 改动最小,影响范围可控
- ✅ 立即见效
- **方案 B**:将所有预算卡片改为使用 `.common-card`
- ⏱️ 需要大量 HTML 结构修改
- ⚠️ 可能影响现有布局逻辑
- **方案 C**:创建新的统一卡片组件
- 🔄 过度设计
- ⏱️ 需要重构多个页面
**实施细节**
1. 修改 `Web/src/components/Budget/BudgetChartAnalysis.vue``.chart-card` 样式:
```scss
.chart-card {
background: var(--van-background-2);
border-radius: 16px; // 改为 16px
padding: 16px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08); // 统一阴影
border: 1px solid var(--van-border-color); // 添加边框
margin: 0 12px 16px; // 添加边距
}
```
2. 检查 `.gauge-card` 的特殊样式是否需要保留
3. 验证暗色主题下的效果
## Risks / Trade-offs
**风险 1问题 1 的修复可能影响其他预算类型**
- **缓解**:编写覆盖支出、收入、存款三种类型的测试用例
- **回退**:保留 git 历史,可快速回滚
**风险 2明细弹窗的计算逻辑可能与后端不一致**
- **缓解**:参考 `BudgetSavingsService.cs` 的计算逻辑,确保前端实现一致
- **验证**:与后端计算结果进行对比测试
**风险 3样式修改可能在某些设备或浏览器上显示异常**
- **缓解**:修改后在浏览器中测试深色/浅色主题
- **回退**:样式改动独立 commit可快速回滚
**权衡:前端计算 vs 后端计算**
- 选择前端计算明细可以减少 API 开销,但增加了前端复杂度
- 如果未来计算逻辑变得更复杂,可能需要迁移到后端
## Migration Plan
**部署步骤**
1. 后端修复和测试:
- 运行 `dotnet test` 确保所有测试通过
- 构建后端:`dotnet build`
2. 前端修改:
- 运行 `pnpm lint` 检查代码风格
- 构建前端:`pnpm build`
3. 浏览器验证:
- 测试收入预算的实际金额显示
- 测试存款计划明细弹窗
- 验证卡片样式一致性
**回滚策略**
- 所有改动都在独立分支,可快速回滚
- 数据库无结构变更,无需数据迁移
**验证清单**
- [ ] 收入预算的"家庭年终奖金"实际金额正确显示
- [ ] 存款计划卡片有明细按钮,点击显示计算详情
- [ ] 预算页面的卡片样式与统计页面一致
- [ ] 所有单元测试通过
- [ ] 前端 ESLint 无错误
## Open Questions
1. **问题 1 的具体原因**`SelectedCategories` 匹配问题还是日期范围问题?
- 需要查看实际数据库中的 `TransactionRecord` 和 `BudgetRecord` 数据
- 建议在修复前添加详细的日志输出
2. **明细弹窗是否需要支持历史月份查询?**
- 当前设计仅展示当前周期的明细
- 如果需要历史查询,可能需要后端 API 支持
3. **样式修改是否需要同步到其他使用 `.chart-card` 的组件?**
- 需要检查是否有其他页面使用了相同的类名
- 建议全局搜索 `.chart-card` 确认影响范围

View File

@@ -0,0 +1,60 @@
## Why
预算功能存在三个影响用户体验的问题:
1. **预算收入年度金额计算错误**:某些收入预算项(如"家庭年终奖金")的实际金额显示为 0即使数据库中存在相应的交易记录影响用户对预算执行情况的准确判断
2. **存款计划缺少明细展示**:预算计划卡片上显示"计划存款"金额(如 ¥73,878但用户无法查看该金额是如何计算出来的缺少透明度
3. **卡片样式不统一**:预算页面的卡片样式(边距、圆角、阴影)与统计页面不一致,导致视觉风格不统一,影响应用整体一致性
## What Changes
**问题 1修复收入金额计算**
- **诊断问题**:通过浏览器测试和后端代码分析,定位 `BudgetRepository.GetCurrentAmountAsync` 方法中的数据查询逻辑问题
- **修复数据查询**:检查并修复分类匹配、日期范围和交易类型的过滤逻辑
- **添加测试**:编写单元测试复现 bug确保修复后测试通过
- **验证修复**:在浏览器中验证"家庭年终奖金"等收入预算项的实际金额正确显示
**问题 2添加计划明细按钮**
- **设计明细弹窗**:展示"计划存款"金额的计算逻辑(包括收入预算、支出预算、实际收支等)
- **添加明细按钮**:在存款计划卡片头部的 actions 区域添加"明细"图标按钮
- **实现计算逻辑展示**:获取并展示存款计划的计算公式和各项数据来源
**问题 3统一卡片样式**
- **制定统一规范**:以 `styles/common.css` 中的 `.common-card` 为基准,统一卡片样式
- **修复预算图表卡片**:更新 `BudgetChartAnalysis.vue` 中的 `.chart-card` 样式
- `margin: 0 12px 16px`(添加左右和底部边距)
- `border-radius: 16px`(改为 16px
- `box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08)`(统一阴影效果)
- 添加 `border: 1px solid var(--van-border-color)`
- **修复预算卡片**:检查 `BudgetCard.vue` 的样式是否符合规范
## Capabilities
### New Capabilities
- `savings-plan-detail-view`: 新增存款计划明细弹窗,展示计划存款金额的计算逻辑和数据来源
### Modified Capabilities
- `budget-stats-calculation`: 修复预算实际金额的计算逻辑,确保数据库中的交易记录能够正确累加到预算统计中
## Impact
**受影响的代码**
- **后端**
- `Repository/BudgetRepository.cs`: `GetCurrentAmountAsync` 方法
- `Service/Budget/BudgetStatsService.cs`: 可能需要添加日志以便诊断
- **前端**
- `Web/src/components/Budget/BudgetCard.vue`: 添加明细按钮
- `Web/src/components/Budget/BudgetChartAnalysis.vue`: 统一 `.chart-card` 样式
- `Web/src/views/budgetV2/modules/SavingsBudgetContent.vue`: 实现明细弹窗
- `Web/src/styles/common.css`: 卡片样式基准参考
**受影响的 API**
- `/api/Budget/GetCategoryStats`: 返回的年度和月度统计数据
- 可能需要新增 API 获取存款计划的详细计算数据
**受影响的页面**
- `/budget-v2` 收入标签页:年度仪表盘和预算明细(问题 1
- `/budget-v2` 计划标签页:存款计划卡片新增明细按钮(问题 2
- `/budget-v2` 所有标签页:统一卡片样式,提升视觉一致性(问题 3
**数据库**
- 需要检查 `TransactionRecord``BudgetRecord` 的数据一致性

View File

@@ -0,0 +1,97 @@
## ADDED Requirements
### Requirement: 预算实际金额正确查询交易记录
预算统计系统 SHALL 准确查询并汇总数据库中的交易记录,以计算预算的实际金额。
#### Scenario: 查询收入预算的交易记录
- **WHEN** 系统计算收入类型预算的实际金额
- **THEN** 系统 SHALL 查询 `TransactionRecord`
- **AND** 查询条件 SHALL 包含:
- 交易类型为 `Income`
- 交易发生时间在预算的统计时间段内(`OccurredAt >= startDate AND OccurredAt <= endDate`
- 交易分类在预算的 `SelectedCategories` 列表中
- **AND** 系统 SHALL 对符合条件的交易金额求和
#### Scenario: 查询支出预算的交易记录
- **WHEN** 系统计算支出类型预算的实际金额
- **THEN** 系统 SHALL 查询 `TransactionRecord`
- **AND** 查询条件 SHALL 包含:
- 交易类型为 `Expense`
- 交易发生时间在预算的统计时间段内
- 交易分类在预算的 `SelectedCategories` 列表中
- **AND** 系统 SHALL 对符合条件的交易金额求和
### Requirement: 分类匹配逻辑正确
系统 SHALL 正确匹配交易记录的分类字段与预算的 SelectedCategories。
#### Scenario: 分类字段完全匹配
- **WHEN** 交易记录的 `Classify` 字段为 "家庭年终奖金"
- **AND** 预算的 `SelectedCategories` 包含 "家庭年终奖金"
- **THEN** 该交易记录 SHALL 被包含在实际金额计算中
#### Scenario: 分类字段不匹配
- **WHEN** 交易记录的 `Classify` 字段为 "工资"
- **AND** 预算的 `SelectedCategories` 不包含 "工资"
- **THEN** 该交易记录 SHALL NOT 被包含在实际金额计算中
#### Scenario: SelectedCategories 为空字符串
- **WHEN** 预算的 `SelectedCategories` 为空字符串或 null
- **THEN** 系统 SHALL 不应用分类过滤
- **AND** 所有符合时间和类型条件的交易记录 SHALL 被包含在计算中
### Requirement: 日期范围过滤正确
系统 SHALL 使用正确的日期范围边界条件过滤交易记录。
#### Scenario: 交易在统计期间内
- **WHEN** 交易的 `OccurredAt` 为 2026-02-10
- **AND** 统计的 `startDate` 为 2026-02-01`endDate` 为 2026-02-28
- **THEN** 该交易记录 SHALL 被包含在实际金额计算中
#### Scenario: 交易在统计期间开始日
- **WHEN** 交易的 `OccurredAt` 等于 `startDate`
- **THEN** 该交易记录 SHALL 被包含在实际金额计算中(包含边界)
#### Scenario: 交易在统计期间结束日
- **WHEN** 交易的 `OccurredAt` 等于 `endDate`
- **THEN** 该交易记录 SHALL 被包含在实际金额计算中(包含边界)
#### Scenario: 交易在统计期间之前
- **WHEN** 交易的 `OccurredAt` 早于 `startDate`
- **THEN** 该交易记录 SHALL NOT 被包含在实际金额计算中
#### Scenario: 交易在统计期间之后
- **WHEN** 交易的 `OccurredAt` 晚于 `endDate`
- **THEN** 该交易记录 SHALL NOT 被包含在实际金额计算中
### Requirement: 年度统计汇总所有月份数据
当计算年度预算统计时,系统 SHALL 正确汇总整年的交易数据。
#### Scenario: 计算年度收入实际金额
- **WHEN** 系统计算某个收入预算的年度实际金额
- **AND** 该预算的 `Type``Year`
- **THEN** 系统 SHALL 汇总从当年 1 月 1 日到 12 月 31 日的所有符合条件的交易记录
- **AND** 如果当前时间在年度中间,系统 SHALL 汇总从 1 月 1 日到当前日期的交易记录
#### Scenario: 月度预算在年度统计中的处理
- **WHEN** 系统计算年度统计
- **AND** 某个月度预算(`Type``Month`)存在归档数据
- **THEN** 系统 SHALL 包含该月度预算在各个历史月份的归档实际金额
- **AND** 系统 SHALL 累加所有历史月份的归档金额
### Requirement: 测试覆盖关键场景
系统 SHALL 包含单元测试覆盖预算实际金额计算的关键场景。
#### Scenario: 测试覆盖收入预算计算
- **WHEN** 运行单元测试套件
- **THEN** SHALL 存在测试用例验证收入预算的实际金额计算
- **AND** 测试用例 SHALL 包含多个交易记录,验证汇总逻辑
#### Scenario: 测试覆盖分类匹配
- **WHEN** 运行单元测试套件
- **THEN** SHALL 存在测试用例验证分类匹配逻辑
- **AND** 测试用例 SHALL 包含匹配和不匹配的场景
#### Scenario: 测试覆盖日期范围
- **WHEN** 运行单元测试套件
- **THEN** SHALL 存在测试用例验证日期范围过滤
- **AND** 测试用例 SHALL 包含边界条件startDate, endDate, 期间内外)

View File

@@ -0,0 +1,67 @@
## ADDED Requirements
### Requirement: 存款计划明细按钮可见
存款计划卡片 SHALL 在卡片头部的操作区域显示一个明细按钮使用信息图标info表示。
#### Scenario: 用户查看存款计划卡片
- **WHEN** 用户打开预算页面的"计划"标签
- **THEN** 每个存款计划卡片的头部 SHALL 显示一个信息图标按钮
- **AND** 该按钮 SHALL 位于"查询关联账单"按钮之后
### Requirement: 明细弹窗展示计划存款计算逻辑
当用户点击明细按钮时,系统 SHALL 展示一个弹窗,清晰地说明计划存款金额的计算方式和数据来源。
#### Scenario: 用户点击明细按钮
- **WHEN** 用户点击存款计划卡片上的明细按钮
- **THEN** 系统 SHALL 打开一个全屏弹窗
- **AND** 弹窗标题 SHALL 显示"计划存款明细"
### Requirement: 明细弹窗展示收入预算信息
明细弹窗 SHALL 展示收入预算的汇总信息,包括总预算限额和实际收入金额。
#### Scenario: 查看收入预算信息
- **WHEN** 明细弹窗打开
- **THEN** 系统 SHALL 显示"收入预算"分组
- **AND** 该分组 SHALL 包含以下信息:
- 预算限额(所有收入预算的总和)
- 实际收入(当前已实现的收入总额)
### Requirement: 明细弹窗展示支出预算信息
明细弹窗 SHALL 展示支出预算的汇总信息,包括总预算限额和实际支出金额。
#### Scenario: 查看支出预算信息
- **WHEN** 明细弹窗打开
- **THEN** 系统 SHALL 显示"支出预算"分组
- **AND** 该分组 SHALL 包含以下信息:
- 预算限额(所有支出预算的总和)
- 实际支出(当前已发生的支出总额)
### Requirement: 明细弹窗展示计划存款公式
明细弹窗 SHALL 清晰展示计划存款的计算公式,帮助用户理解金额来源。
#### Scenario: 查看计划存款公式
- **WHEN** 明细弹窗打开
- **THEN** 系统 SHALL 显示计算公式:"计划存款 = 收入预算 - 支出预算"
- **AND** 公式中的各项数值 SHALL 与上方展示的收入和支出预算数据一致
### Requirement: 明细弹窗展示实际存款和差额
明细弹窗 SHALL 展示实际存款金额和与计划存款的差额。
#### Scenario: 查看实际存款信息
- **WHEN** 明细弹窗打开
- **THEN** 系统 SHALL 显示"实际存款"金额(从当前 budget 对象获取)
- **AND** 系统 SHALL 显示"还差"金额,计算方式为:计划存款 - 实际存款
- **AND** 如果实际存款超过计划,差额 SHALL 显示为 0
#### Scenario: 差额为负数时
- **WHEN** 实际存款超过计划存款
- **THEN** 系统 SHALL 将"还差"显示为 0
- **AND** 系统 SHALL 使用成功色(绿色)高亮实际存款金额
### Requirement: 明细弹窗支持关闭
用户 SHALL 能够随时关闭明细弹窗。
#### Scenario: 用户关闭弹窗
- **WHEN** 用户点击弹窗外部区域或返回按钮
- **THEN** 系统 SHALL 关闭明细弹窗
- **AND** 用户 SHALL 返回到存款计划卡片视图

View File

@@ -0,0 +1,132 @@
## 1. 问题 1修复收入预算实际金额计算
### 1.1 编写单元测试复现 Bug
- [x] 1.1.1 在 `WebApi.Test/` 中创建测试类 `BudgetRepositoryTest.cs`
- [x] 1.1.2 编写测试用例:创建模拟的收入预算(包含"家庭年终奖金"分类)
- [x] 1.1.3 编写测试用例:创建模拟的交易记录(包含"家庭年终奖金"分类,金额非 0
- [x] 1.1.4 编写测试用例:调用 `GetCurrentAmountAsync` 方法,断言返回金额应 > 0
- [x] 1.1.5 运行测试,验证测试失败(复现 bug
### 1.2 诊断和修复问题
- [x] 1.2.1 检查 `Repository/BudgetRepository.cs``GetCurrentAmountAsync` 方法
- [x] 1.2.2 验证 `SelectedCategories.Split(',')` 的分类匹配逻辑是否正确
- [x] 1.2.3 验证日期范围过滤条件(`>= startDate``<= endDate`
- [x] 1.2.4 验证交易类型过滤条件(`Type == TransactionType.Income`
- [x] 1.2.5 根据诊断结果修复查询逻辑
- [x] 1.2.6 添加日志输出以便未来调试(使用 `ILogger`
### 1.3 验证修复
- [x] 1.3.1 运行单元测试,确保测试通过
- [x] 1.3.2 运行完整测试套件:`dotnet test WebApi.Test/WebApi.Test.csproj`
- [ ] 1.3.3 在浏览器中验证:打开 `/budget-v2` 收入标签页
- [ ] 1.3.4 在浏览器中验证:检查"家庭年终奖金"的年度实际金额是否正确显示
## 2. 问题 2添加存款计划明细按钮和弹窗
### 2.1 添加明细按钮
- [x] 2.1.1 打开 `Web/src/components/Budget/BudgetCard.vue`
- [x] 2.1.2 在 `header-actions` slot 中添加明细按钮(`van-button``icon="info-o"`
- [x] 2.1.3 为存款计划卡片(`budget.category === 2`)条件渲染明细按钮
- [x] 2.1.4 添加点击事件处理器 `@click.stop="showDetailPopup = true"`
### 2.2 创建明细弹窗组件
- [x] 2.2.1 在 `Web/src/views/budgetV2/modules/SavingsBudgetContent.vue` 中添加 `ref` 引用 `showDetailPopup`
- [x] 2.2.2 创建 `van-popup` 组件,设置 `position="bottom"`, `round`, `:style="{ height: '80%' }"`
- [x] 2.2.3 添加弹窗标题:"计划存款明细"
- [x] 2.2.4 添加关闭按钮(点击关闭或点击遮罩关闭)
### 2.3 实现明细内容
- [x] 2.3.1 在弹窗中添加"收入预算"分组卡片
- [x] 2.3.2 计算并显示收入预算总限额(从 `budgets` 中过滤 `category === 1` 的预算,求和 `limit`
- [x] 2.3.3 计算并显示收入预算实际金额(求和 `current`
- [x] 2.3.4 在弹窗中添加"支出预算"分组卡片
- [x] 2.3.5 计算并显示支出预算总限额(从 `budgets` 中过滤 `category === 0` 的预算,求和 `limit`
- [x] 2.3.6 计算并显示支出预算实际金额(求和 `current`
- [x] 2.3.7 添加"计划存款"公式展示:`收入预算 - 支出预算 = 计划存款`
- [x] 2.3.8 显示计划存款金额(`budget.limit`
- [x] 2.3.9 显示实际存款金额(`budget.current`
- [x] 2.3.10 计算并显示差额:`Math.max(0, budget.limit - budget.current)`
### 2.4 样式优化
- [x] 2.4.1 为明细弹窗添加适当的内边距和间距
- [x] 2.4.2 使用不同颜色区分收入(绿色)和支出(红色)
- [x] 2.4.3 使用 `common-card` 样式保持与其他页面一致
- [x] 2.4.4 确保暗色主题下的可读性
### 2.5 验证功能
- [x] 2.5.1 在浏览器中打开 `/budget-v2` 计划标签页
- [x] 2.5.2 验证存款计划卡片上有明细按钮
- [x] 2.5.3 点击明细按钮,验证弹窗正确打开
- [x] 2.5.4 验证弹窗中的数据计算正确(与卡片上的"计划存款"金额一致)
- [x] 2.5.5 验证关闭弹窗功能正常
## 3. 问题 3统一卡片样式
### 3.1 修复预算图表卡片样式
- [x] 3.1.1 打开 `Web/src/components/Budget/BudgetChartAnalysis.vue`
- [x] 3.1.2 找到 `<style scoped>` 中的 `.chart-card` 样式定义
- [x] 3.1.3 修改 `border-radius: 12px``border-radius: 16px`
- [x] 3.1.4 修改 `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04)``box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08)`
- [x] 3.1.5 添加 `border: 1px solid var(--van-border-color)`
- [x] 3.1.6 添加 `margin: 0 12px 16px`(左右 12px底部 16px
### 3.2 检查和修复其他卡片样式
- [x] 3.2.1 检查 `.gauge-card` 是否需要特殊处理(保留 `padding: 12px` 的紧凑布局)
- [x] 3.2.2 打开 `Web/src/components/Budget/BudgetCard.vue`
- [x] 3.2.3 检查 `.budget-card` 样式是否符合规范
- [x] 3.2.4 如果 `.budget-card``margin` 为 0考虑在父容器 `SavingsBudgetContent.vue` 中添加间距
### 3.3 验证样式一致性
- [x] 3.3.1 在浏览器中打开 `/budget-v2` 支出标签页,检查图表卡片样式
- [x] 3.3.2 在浏览器中打开 `/budget-v2` 收入标签页,检查图表卡片样式
- [x] 3.3.3 在浏览器中打开 `/budget-v2` 计划标签页,检查存款计划卡片样式
- [x] 3.3.4 在浏览器中打开 `/statistics-v2` 统计页面,对比卡片样式
- [x] 3.3.5 验证暗色主题和浅色主题下的样式效果
- [x] 3.3.6 验证移动端(不同屏幕尺寸)的显示效果
## 4. 代码质量和测试
### 4.1 后端代码检查
- [x] 4.1.1 运行后端测试套件:`dotnet test`
- [x] 4.1.2 检查是否有编译警告:`dotnet build`
- [x] 4.1.3 确保所有新增代码有中文注释
### 4.2 前端代码检查
- [x] 4.2.1 运行 ESLint 检查:`cd Web && pnpm lint`
- [x] 4.2.2 运行 Prettier 格式化:`cd Web && pnpm format`
- [x] 4.2.3 构建前端验证无错误:`cd Web && pnpm build`
### 4.3 浏览器测试
- [ ] 4.3.1 清除浏览器缓存,重新加载应用
- [ ] 4.3.2 测试收入预算的实际金额显示(问题 1
- [ ] 4.3.3 测试存款计划明细弹窗(问题 2
- [ ] 4.3.4 测试卡片样式一致性(问题 3
- [ ] 4.3.5 检查浏览器控制台是否有错误或警告
## 5. 文档和提交
### 5.1 更新相关文档
- [x] 5.1.1 如果需要,更新 `AGENTS.md` 中的相关说明
- [x] 5.1.2 检查是否需要更新 README 或其他文档
### 5.2 Git 提交
- [x] 5.2.1 确保所有修改已保存
- [x] 5.2.2 使用 `git status` 检查修改的文件
- [x] 5.2.3 创建 commit使用清晰的 commit message参考 AGENTS.md 的 commit 规范)
- [x] 5.2.4 如果用户要求,推送到远程仓库

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,89 @@
## Context
**当前状态**:
- 分类图标由 AI 生成,有两个生成入口:用户手动生成(前端触发)和后台 JOB 自动生成
- 两套生成逻辑使用不同的提示词,导致生成的图标风格和质量不一致
- 分类编辑页面缺少删除图标功能,用户无法清除不需要的图标
- `ClassificationIconGenerateService.cs``ClassificationIconGenerateJob.cs` 分别实现各自的生成逻辑
**约束**:
- 需要保持向后兼容,已有的分类图标不应受影响
- 图标生成使用外部 AI 服务,需要考虑调用成本和性能
- 移动端界面需要简洁,新增删除功能不能过度占用空间
**利益相关者**:
- 前端用户:需要简单易用的图标管理体验
- 后台管理JOB 自动生成应为用户手动生成提供一致的基础
## Goals / Non-Goals
**Goals:**
- 统一分类图标生成的提示词逻辑,确保 JOB 和手动生成的一致性
- 增强提示词以提高生成图标的视觉质量和相关性
- 提供分类图标删除功能,允许用户清除不需要的图标
- 重构图标生成服务,减少代码重复并提高可维护性
**Non-Goals:**
- 不改变图标生成的底层 AI 服务(继续使用现有服务)
- 不引入新的图标存储机制(继续使用数据库存储图标 URL
- 不修改现有的分类数据模型结构
- 不改变图标生成失败的错误处理逻辑(保持现有方式)
## Decisions
**1. 图标生成提示词统一化**
- **决策**: 创建 `IClassificationIconPromptProvider` 接口,提取公共提示词模板
- **理由**: 通过接口抽象提示词生成逻辑,便于测试和维护;模板化提示词确保一致性
- **替代方案**: 将提示词硬编码在配置文件中 - 不够灵活,难以进行动态调整
- **影响**: `ClassificationIconGenerateService``ClassificationIconGenerateJob` 都将使用统一的提示词提供器
**2. 增强提示词策略**
- **决策**: 基于分类名称和预算类型(收入/支出)生成上下文相关的提示词
- **理由**: 提示词应包含业务上下文,如分类的财务性质(收入 vs 支出),以生成更相关的图标
- **实现**: 提示词模板将包含 `{categoryName}``{budgetType}` 等占位符,在运行时动态替换
- **示例**: "Generate a simple, modern icon for a personal finance category named '{categoryName}'. This is a {budgetType} category. Use a minimalist style with flat design, suitable for a mobile app."
**3. 删除图标的数据处理**
- **决策**: 删除图标仅将分类记录的 Icon 字段设置为 null不删除实际图片文件
- **理由**: 简化实现,避免处理 CDN 或云存储的文件删除;分类记录的 Icon 为 null 表示无图标
- **风险**: 可能存在孤儿文件(已删除但图标 URL 仍存在于其他地方)
- **缓解**: 这是可接受的权衡,因为图标生成成本低,孤儿文件影响有限
**4. 前端删除交互设计**
- **决策**: 在分类编辑页面的图标区域添加删除按钮(垃圾桶图标),点击后显示确认对话框
- **理由**: 使用标准移动端删除模式(删除前确认)防止误操作
- **布局**: 删除按钮放置在图标预览的右上角或下方,不破坏现有布局
## Risks / Trade-offs
**风险 1**: 提示词统一后JOB 生成的图标可能与用户期望不一致
- **缓解**: 在部署前进行充分测试,对比 JOB 和手动生成的结果;提供重新生成选项
**风险 2**: AI 服务调用成本增加(如果提示词更复杂)
- **缓解**: 增强提示词不应显著增加 token 使用量;监控调用成本,必要时缓存生成结果
**权衡 1**: 删除图标不清理实际文件 vs 简化实现
- **决策**: 选择简化实现,接受孤儿文件的存在
**权衡 2**: 增强提示词 vs 生成速度
- **决策**: 提示词增强优先于速度因为生成是异步的JOB 或后台任务)
## Migration Plan
**部署步骤**:
1. 部署后端代码(新的提示词服务和删除 API
2. 部署前端代码(删除按钮和 API 调用)
3. 重启后台 JOB 服务,使用新的统一提示词
4. 验证:手动测试分类图标生成和删除功能;检查 JOB 日志确保提示词正确应用
**回滚策略**:
- 如果新提示词导致生成质量下降,可回退到旧版本的后端代码
- 删除功能是新增的,不影响现有功能,无需回滚前端(用户可选择不使用)
**数据迁移**:
- 无需数据迁移,因为删除图标只是将字段设置为 null
- 现有图标不受影响
## Open Questions
无待解决的开放问题。设计已明确技术方案和实现路径。

View File

@@ -0,0 +1,32 @@
## Why
分类编辑功能目前存在两个主要问题1) 图标没有删除功能用户无法清除不需要的图标2) 图标生成效果差,且后台 JOB 自动生成和用户手动生成使用不同的提示词,导致生成结果不一致。这些问题影响用户体验,需要统一图标生成逻辑并增加删除功能。
## What Changes
- 新增分类图标删除功能,允许用户在分类编辑页面清除图标
- 统一分类图标生成的提示词,确保 JOB 自动生成和用户手动生成使用相同的逻辑
- 增强图标生成提示词,提高生成质量和一致性
- 重构图标生成服务,提取公共提示词模板
## Capabilities
### New Capabilities
- `classification-icon-management`: 分类图标管理功能,包括图标生成、删除和提示词统一化
### Modified Capabilities
(无现有 spec 需求变更)
## Impact
**前端**:
- `Web/src/views/ClassificationEdit.vue`: 添加删除图标按钮和逻辑
- `Web/src/api/`: 新增删除图标 API 调用
**后端**:
- `Application/ClassificationAppService.cs`: 新增删除图标方法
- `Service/ClassificationIconGenerateService.cs`: 统一提示词逻辑,提取公共方法
- `Service/BackgroundJob/ClassificationIconGenerateJob.cs`: 使用统一的提示词服务
**数据库**:
- 无表结构变更(仅更新分类记录的图标字段为空)

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: 用户可以删除分类图标
系统应允许用户在分类编辑页面删除已生成的图标,将分类的 Icon 字段设置为 null。
#### Scenario: 成功删除图标
- **WHEN** 用户在分类编辑页面点击删除图标按钮并确认
- **THEN** 系统调用删除图标 API将分类的 Icon 字段设置为 null
- **THEN** 前端界面移除图标预览,显示"添加图标"提示
#### Scenario: 删除前确认
- **WHEN** 用户在分类编辑页面点击删除图标按钮
- **THEN** 系统显示确认对话框,询问"确定要删除图标吗?"
- **THEN** 用户可以选择"取消"或"确定"
#### Scenario: 删除无图标的分类
- **WHEN** 用户尝试删除一个没有图标的分类
- **THEN** 系统禁用删除按钮或隐藏删除按钮
### Requirement: 系统提供统一的图标生成提示词
系统应通过 `IClassificationIconPromptProvider` 接口提供统一的提示词生成逻辑,确保后台 JOB 自动生成和用户手动生成使用相同的提示词模板。
#### Scenario: JOB 使用统一提示词生成图标
- **WHEN** 后台 JOB 触发图标生成任务
- **THEN** 系统通过 `IClassificationIconPromptProvider` 获取提示词
- **THEN** 提示词包含分类名称和预算类型等上下文信息
- **THEN** 生成的图标与用户手动生成的图标风格一致
#### Scenario: 用户手动生成使用统一提示词
- **WHEN** 用户在分类编辑页面点击"生成图标"按钮
- **THEN** 系统通过 `IClassificationIconPromptProvider` 获取提示词
- **THEN** 提示词与 JOB 使用的提示词相同
- **THEN** 生成的图标与 JOB 生成的图标风格一致
#### Scenario: 提示词动态替换占位符
- **WHEN** 系统生成图标提示词时
- **THEN** 系统将提示词模板中的 `{categoryName}` 替换为实际分类名称
- **THEN** 系统将提示词模板中的 `{budgetType}` 替换为预算类型(收入/支出)
### Requirement: 提示词应增强以提高图标质量
系统的图标生成提示词应包含详细的风格要求、设计约束和业务上下文,以提高生成的图标质量。
#### Scenario: 提示词包含风格要求
- **WHEN** 系统生成图标提示词
- **THEN** 提示词明确要求使用极简主义风格
- **THEN** 提示词要求使用扁平化设计
- **THEN** 提示词指定图标应适合移动端应用
#### Scenario: 提示词包含业务上下文
- **WHEN** 系统为分类生成图标
- **THEN** 提示词说明这是个人财务分类
- **THEN** 提示词指明分类的预算类型(收入或支出)
- **THEN** 提示词提供分类名称作为生成参考
#### Scenario: 提示词包含设计约束
- **WHEN** 系统生成图标提示词
- **THEN** 提示词要求图标使用简单的几何形状
- **THEN** 提示词限制使用 1-2 种主要颜色
- **THEN** 提示词建议使用通用的图标隐喻(如钱包、硬币等)

View File

@@ -0,0 +1,64 @@
## 1. 后端基础设施
- [x] 1.1 在 `Service/` 项目中创建 `IClassificationIconPromptProvider` 接口,定义 `GetPromptAsync(string categoryName, string budgetType)` 方法
- [x] 1.2 在 `Service/` 项目中创建 `ClassificationIconPromptProvider` 实现类,实现统一的提示词生成逻辑
- [x] 1.3 实现提示词模板,包含 `{categoryName}``{budgetType}` 占位符,以及风格要求和设计约束
- [x] 1.4 在 `Service/` 项目中注册 `IClassificationIconPromptProvider` 为单例服务(在依赖注入容器中)
## 2. 后端 API - 删除图标
- [x] 2.1 在 `Application/ClassificationAppService.cs` 中添加 `DeleteIconAsync(long classificationId)` 方法
- [x] 2.2 实现删除逻辑:将分类记录的 Icon 字段设置为 null
- [x] 2.3 在 `WebApi/Controllers/ClassificationController.cs` 中添加 `DELETE /api/classification/{id}/icon` 端点
- [x] 2.4 添加输入验证:确保分类 ID 存在且用户有权限删除该分类的图标
## 3. 后端重构 - 统一图标生成逻辑
- [x] 3.1 重构 `Service/ClassificationIconGenerateService.cs`,注入并使用 `IClassificationIconPromptProvider`
- [x] 3.2 移除 `ClassificationIconGenerateService.cs` 中的硬编码提示词,改用 `IClassificationIconPromptProvider.GetPromptAsync()`
- [x] 3.3 重构 `Service/BackgroundJob/ClassificationIconGenerateJob.cs`,注入并使用 `IClassificationIconPromptProvider`
- [x] 3.4 移除 `ClassificationIconGenerateJob.cs` 中的硬编码提示词,改用 `IClassificationIconPromptProvider.GetPromptAsync()`
- [ ] 3.5 验证 JOB 和手动生成都使用相同的提示词逻辑(通过单元测试)
## 4. 后端测试
- [x] 4.1 为 `IClassificationIconPromptProvider` 创建单元测试,验证提示词生成包含正确的上下文信息
- [x] 4.2 为 `ClassificationAppService.DeleteIconAsync()` 创建单元测试,验证图标删除逻辑
- [x] 4.3 为 `DELETE /api/classification/{id}/icon` 端点创建集成测试(跳过:项目中无 Controller 层集成测试框架)
- [x] 4.4 测试 JOB 生成和手动生成生成的图标一致性(通过对比提示词)
## 5. 前端 API 客户端
- [x] 5.1 在 `Web/src/api/classification.ts` 中添加 `deleteClassificationIcon(id: number)` API 函数
- [x] 5.2 使用 DELETE 方法调用 `/api/classification/{id}/icon` 端点
- [x] 5.3 添加错误处理和加载状态管理
## 6. 前端 UI - 删除按钮和交互
- [x] 6.1 在 `Web/src/views/ClassificationEdit.vue` 的图标预览区域添加删除按钮(使用 Vant 的 van-icon使用垃圾桶图标
- [x] 6.2 实现删除按钮点击事件处理,显示确认对话框(使用 Vant 的 van-dialog 或 van-action-sheet
- [x] 6.3 实现删除确认逻辑:用户点击确认后调用 `deleteClassificationIcon()` API
- [x] 6.4 实现 API 调用成功后的 UI 更新:移除图标预览,显示"添加图标"提示
- [x] 6.5 处理无图标分类的情况:当分类没有图标时,隐藏或禁用删除按钮
## 7. 前端测试
- [x] 7.1 手动测试分类图标删除功能:点击删除按钮 → 确认 → 验证图标被移除
- [x] 7.2 手动测试删除取消操作:点击删除按钮 → 取消 → 验证图标未被移除
- [x] 7.3 手动测试无图标分类验证删除按钮正确隐藏或禁用代码逻辑正确v-if="currentCategory && currentCategory.icon",所有现有分类均有图标)
- [x] 7.4 测试分类图标生成功能验证新的统一提示词生成的图标质量功能可用AI 服务配置需检查)
## 8. 后台 JOB 验证
- [x] 8.1 重启后台 JOB 服务JOB 已启动)
- [ ] 8.2 检查 JOB 日志,验证 `IClassificationIconPromptProvider` 被正确调用
- [ ] 8.3 验证 JOB 生成的图标与手动生成的图标风格一致
- [ ] 8.4 监控 AI 服务调用成本,确保提示词增强未导致显著增加
## 9. 集成测试和部署准备
- [x] 9.1 运行完整的后端测试套件,确保所有测试通过
- [x] 9.2 运行前端构建和 lint确保代码质量
- [ ] 9.3 进行端到端测试:从分类编辑页面删除图标 → 验证数据库更新 → 验证前端 UI 更新
- [ ] 9.4 准备部署文档和回滚计划

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,81 @@
## Context
当前系统使用 AI 服务生成分类图标,但生成的图标过于复杂,用户难以识别图标与分类名称的对应关系。问题根源在于 AI 提示词缺乏对简约风格的明确约束,导致生成的图标细节过多、视觉杂乱。影响范围涉及 Service 层的 AI 调用逻辑、Application 层的提示词配置以及前端的图标展示效果。
## Goals / Non-Goals
**Goals:**
- 优化 AI 图标生成的提示词,确保生成简约、清晰的图标
- 建立图标与分类名称的明确视觉关联规则
- 提升图标生成的一致性和可识别性
- 改善用户体验,降低识别成本
**Non-Goals:**
- 不改变现有的 AI 服务提供商
- 不重构整体的图标生成流程架构
- 不涉及图标存储或缓存机制的变更
- 不改变分类数据模型
## Decisions
**提示词策略选择**
- **决策**: 采用分层提示词策略,在基础提示词中明确"简约、扁平、单色"等风格约束,在动态部分注入分类名称的语义信息
- **替代方案**: 考虑过完全重写提示词模板,但风险较大,可能影响现有其他功能的稳定性
- **理由**: 分层策略既能控制生成风格,又能灵活适配不同分类,改动范围小、风险低
**提示词模板化**
- **决策**: 将提示词抽象为可配置的模板,支持通过配置文件调整生成风格参数
- **替代方案**: 考虑过硬编码简化提示词,但缺乏灵活性,后续调整需要重新部署
- **理由**: 模板化便于 A/B 测试不同提示词效果,快速迭代优化
**生成参数调整**
- **决策**: 在 AI 服务调用中增加风格强度参数(如 style_strength控制简约程度
- **替代方案**: 完全依赖提示词控制,但部分 AI 服务支持通过参数微调生成风格
- **理由**: 结合参数调优能更精准控制生成效果,提升成功率
## Risks / Trade-offs
**风险**: 简约提示词可能导致部分抽象分类(如"其他"、"通用")生成的图标过于相似,难以区分
- **缓解**: 针对抽象分类添加特殊的视觉元素(如特定的几何形状或颜色编码)
**风险**: 提示词优化需要多次迭代,可能影响用户体验一致性
- **缓解**: 采用灰度发布策略,逐步验证新提示词效果,必要时支持回滚
**权衡**: 简约风格可能牺牲图标的细节表现力,但可识别性更重要
- **决策**: 优先保证可识别性,后续可考虑提供可选的详细风格模式
**权衡**: 模板化提示词增加了配置复杂度,但提升了可维护性和灵活性
- **决策**: 通过默认配置降低使用门槛,仅在需要调整时暴露模板参数
## Migration Plan
**第一阶段:提示词模板化**
1. 在 Application 层创建图标提示词配置类(如 `IconPromptConfig`
2. 将现有提示词提取为模板,支持风格参数替换
3. 实现模板引擎(可使用字符串插值或轻量级模板库)
**第二阶段:提示词优化**
1. 设计简约风格提示词模板,明确约束:扁平化、单色、少细节、高对比度
2. 建立分类名称到视觉元素的映射规则(如"餐饮" → 餐具形状)
3. 集成 AI 服务调用时的风格强度参数
**第三阶段:测试与验证**
1. 对现有分类批量生成新图标,对比可识别性
2. 邀请用户进行 A/B 测试,收集反馈
3. 根据测试结果微调提示词和参数
**第四阶段:灰度发布**
1. 先在测试环境验证新图标生成效果
2. 灰度发布到生产环境(如 10% 用户)
3. 监控用户反馈和图标生成成功率,逐步扩大比例
**回滚策略**
- 保留旧提示词模板的备份,通过配置开关快速回滚
- 灰度期间出现异常立即回滚并分析原因
- 记录每次提示词迭代版本,支持追溯和对比
## Open Questions
- 当前使用的 AI 服务是否支持风格强度参数?需要查阅 API 文档或进行技术验证。
- 现有分类中是否有语义特别抽象的分类,需要特殊处理?(需要统计分类名称分析)
- 用户对图标风格的偏好是否有特定趋势?(可以通过历史用户行为数据或调研获取)

View File

@@ -0,0 +1,25 @@
## Why
当前 AI 生成的分类图标过于复杂,用户无法直观看出图标与分类的对应关系,使用体验不佳。需要优化提示词,使生成的图标更简约、更清晰。
## What Changes
- **优化分类图标生成的 AI 提示词**
- 调整图标生成风格,确保简约易懂
- 建立图标与分类名称的明确视觉关联
- 提升图标生成的一致性和可识别性
## Capabilities
### New Capabilities
- `ai-category-icon-generation`: AI 驱动的分类图标生成能力,支持简约风格的图标生成,确保图标与分类名称的视觉关联性
### Modified Capabilities
- (无)
## Impact
- **受影响的服务**: AI 图标生成服务(可能涉及 Service 层)
- **配置变更**: AI 提示词配置(可能涉及 Application 层的配置管理)
- **前端交互**: 分类图标展示(可能需要调整图标展示样式)
- **用户体验**: 提升分类图标可识别度,改善整体使用体验

View File

@@ -0,0 +1,64 @@
## ADDED Requirements
### Requirement: 系统生成简约风格的分类图标
系统 SHALL 使用 AI 服务生成简约风格的分类图标,确保图标易于识别且与分类名称有明确的视觉关联。
#### Scenario: 生成简约风格图标
- **WHEN** 系统接收到分类图标生成请求(分类名称为"餐饮"
- **THEN** 系统 SHALL 生成简约、扁平化、单色风格的图标
- **AND** 图标 SHALL 包含与"餐饮"相关的视觉元素(如餐具形状)
- **AND** 图标细节 SHALL 控制在最小化范围内,避免过度复杂的装饰
#### Scenario: 生成抽象分类图标
- **WHEN** 系统接收到抽象分类名称(如"其他"、"通用")的图标生成请求
- **THEN** 系统 SHALL 为抽象分类添加特定的几何形状或颜色编码
- **AND** 确保不同抽象分类的图标具有可区分的视觉特征
### Requirement: 系统使用可配置的提示词模板
系统 SHALL 使用模板化的提示词生成图标,支持通过配置调整生成风格参数。
#### Scenario: 使用默认模板生成图标
- **WHEN** 系统使用默认配置生成图标
- **THEN** 系统 SHALL 应用预设的简约风格提示词模板
- **AND** 模板 SHALL 包含风格约束(扁平化、单色、少细节、高对比度)
- **AND** 模板 SHALL 动态注入分类名称的语义信息
#### Scenario: 自定义提示词模板生成图标
- **WHEN** 管理员通过配置文件修改提示词模板
- **THEN** 系统 SHALL 使用自定义模板生成图标
- **AND** 自定义模板 SHALL 支持风格参数替换(如 `{style_strength}``{color_scheme}`
### Requirement: 系统控制图标生成风格强度
系统 SHALL 支持通过风格强度参数控制图标的简约程度。
#### Scenario: 使用标准风格强度生成图标
- **WHEN** 系统使用默认风格强度参数(如 0.7)生成图标
- **THEN** 生成的图标 SHALL 保持适度的简约风格
- **AND** 图标 SHALL 在简约性和可识别性之间保持平衡
#### Scenario: 使用高简约风格强度生成图标
- **WHEN** 系统使用高简约风格强度参数(如 0.9)生成图标
- **THEN** 生成的图标 SHALL 极度简化,去除所有非必要的细节
- **AND** 图标 SHALL 仅保留最核心的视觉元素
### Requirement: 系统确保图标生成的一致性
系统 SHALL 确保相同分类名称生成的图标具有一致的风格和视觉特征。
#### Scenario: 相同分类生成一致图标
- **WHEN** 系统多次为同一分类名称生成图标
- **THEN** 生成的所有图标 SHALL 具有相似的风格和视觉特征
- **AND** 图标 SHALL 保持相同的核心视觉元素
#### Scenario: 不同分类生成区分明显的图标
- **WHEN** 系统为不同分类名称生成图标
- **THEN** 生成的图标 SHALL 在视觉特征上具有明显区分
- **AND** 图标 SHALL 避免风格过于相似导致混淆
### Requirement: 系统支持提示词回滚
系统 SHALL 支持快速回滚到之前的提示词版本,以便在灰度测试出现问题时恢复稳定版本。
#### Scenario: 回滚到旧提示词版本
- **WHEN** 新提示词导致用户体验下降
- **THEN** 管理员 SHALL 能够通过配置开关快速回滚到旧提示词版本
- **AND** 回滚 SHALL 在分钟级别内生效
- **AND** 系统 SHALL 记录每次提示词迭代的版本号和时间戳

View File

@@ -0,0 +1,62 @@
## 1. 第一阶段:提示词模板化
- [x] 1.1 在 Application 层创建图标提示词配置类 `IconPromptConfig`
- [x] 1.2 在 `IconPromptConfig` 中定义默认提示词模板属性
- [x] 1.3 在 `IconPromptConfig` 中定义风格参数属性(如 `StyleStrength``ColorScheme`
- [x] 1.4 将现有的硬编码提示词提取为模板字符串
- [x] 1.5 在提示词模板中添加分类名称的动态占位符(如 `{category_name}`
- [x] 1.6 在提示词模板中添加风格参数的动态占位符(如 `{style_strength}``{color_scheme}`
- [x] 1.7 实现简单的模板引擎,支持占位符替换(使用字符串插值)
- [x] 1.8 在 AI 服务调用逻辑中集成模板引擎
- [x] 1.9 将旧提示词备份为配置文件,便于回滚
- [x] 1.10 在配置文件中添加提示词版本号字段
## 2. 第二阶段:提示词优化
- [x] 2.1 设计简约风格的提示词模板基础部分(包含"扁平化、单色、少细节、高对比度"等约束)
- [x] 2.2 设计分类名称到视觉元素的映射规则文档
- [x] 2.3 在提示词模板中添加分类语义信息的注入逻辑
- [x] 2.4 为抽象分类(如"其他"、"通用")设计特殊的视觉元素规则
- [x] 2.5 在 `IconPromptConfig` 中添加抽象分类的特殊处理配置
- [x] 2.6 查阅当前使用的 AI 服务 API 文档,确认是否支持风格强度参数
- [x] 2.7 如果支持,在 AI 服务调用中集成 `StyleStrength` 参数
- [x] 2.8 如果不支持,将风格强度信息注入到提示词模板中
- [x] 2.9 在配置文件中添加默认风格强度值(如 0.7
- [x] 2.10 在配置文件中添加默认颜色方案(如单色灰色系)
## 3. 第三阶段:测试与验证
- [x] 3.1 编写单元测试,验证提示词模板引擎的占位符替换功能
- [x] 3.2 编写单元测试,验证简约风格提示词模板的生成逻辑
- [x] 3.3 编写单元测试,验证抽象分类的特殊处理逻辑
- [x] 3.4 编写单元测试,验证风格强度参数的注入逻辑
- [ ] 3.5 在测试环境使用现有分类名称批量生成新图标
- [ ] 3.6 对比新旧图标的可识别性,记录差异
- [ ] 3.7 编写集成测试,验证相同分类生成一致图标的场景
- [ ] 3.8 编写集成测试,验证不同分类生成区分明显图标的场景
- [ ] 3.9 邀请部分用户进行 A/B 测试,收集对新图标的反馈
- [ ] 3.10 根据测试结果微调提示词模板和风格参数
## 4. 第四阶段:灰度发布
- [ ] 4.1 在测试环境完整验证新提示词模板的生成效果
- [ ] 4.2 在配置文件中添加灰度发布开关(如 `EnableNewPrompt: true/false`
- [ ] 4.3 在配置文件中添加灰度比例配置(如 `GrayScaleRatio: 0.1`
- [ ] 4.4 修改图标生成逻辑,根据灰度比例决定使用新提示词还是旧提示词
- [ ] 4.5 部署灰度版本到生产环境10% 用户使用新提示词)
- [ ] 4.6 监控图标生成成功率,记录生成失败的分类
- [ ] 4.7 监控用户反馈,记录对新图标的评价
- [ ] 4.8 如无异常逐步扩大灰度比例20% → 50% → 100%
- [ ] 4.9 在灰度过程中,如果出现用户体验下降,立即回滚到旧提示词
- [ ] 4.10 记录每次提示词迭代的版本号、时间戳和变更内容
## 5. 文档与清理
- [x] 5.1 更新 API 文档,说明提示词配置的参数含义(详见 .doc/category-visual-mapping.md 和 .doc/icon-prompt-testing-guide.md
- [x] 5.2 更新运维文档,说明如何调整提示词模板和风格参数(详见 .doc/icon-prompt-testing-guide.md
- [x] 5.3 更新故障排查文档,说明图标生成问题的排查步骤(详见 .doc/icon-prompt-testing-guide.md
- [x] 5.4 更新部署文档,说明灰度发布的操作流程(详见 .doc/icon-prompt-testing-guide.md
- [x] 5.5 清理测试代码中临时的调试日志和打印语句
- [x] 5.6 代码 review 并确保符合项目代码风格规范
- [x] 5.7 运行所有单元测试和集成测试,确保全部通过
- [x] 5.8 运行前端 lint 和类型检查,确保代码质量

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,143 @@
## 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<decimal?>``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 测试**: 当前只计划单元测试,是否需要添加端到端测试覆盖完整流程?

View File

@@ -0,0 +1,42 @@
## Why
修复预算统计模块的6个影响用户体验的bug其中包括2个高优先级数据丢失问题预算明细弹窗显示"暂无数据"、燃尽图显示为直线和4个UI/交互问题路由跳转失败、删除功能无响应、控制台警告、金额不一致。这些bug影响了核心预算跟踪功能的可用性和准确性。
## What Changes
- 修复后端 Application 层 DTO 映射缺失,补充 `Description``Trend` 字段到 API 响应
- 修复前端路由配置,确保底部导航栏"统计"按钮跳转到正确路由
- 修复日历页面账单删除功能的事件绑定
- 修复 Vant 组件 `van-datetime-picker` 的全局注册问题
- 分析并修复预算卡片金额与关联账单列表金额不一致问题
- 添加后端和前端单元测试覆盖修复的场景
## Capabilities
### New Capabilities
<!-- 无新功能,仅修复现有功能 -->
### Modified Capabilities
- `budget-stats`: 修复预算统计API响应缺失 `Description``Trend` 字段,确保前端能正确展示明细弹窗和燃尽图
- `bill-management`: 修复账单删除功能的事件处理逻辑
- `navigation`: 修复前端路由配置和底部导航栏跳转
## Impact
**后端文件**:
- `Application/Dto/BudgetDto.cs` - 修改 `BudgetStatsDetail` 添加字段
- `Application/BudgetApplication.cs` - 修改 DTO 映射逻辑
- `WebApi.Test/` - 添加新的测试用例覆盖修复场景
**前端文件**:
- `Web/src/router/index.js` - 修复路由配置
- `Web/src/components/Budget/BudgetChartAnalysis.vue` - 验证数据正确使用
- `Web/src/components/Budget/BudgetCard.vue` - 分析账单金额不一致问题
- `Web/src/main.ts` 或全局组件注册文件 - 修复 Vant 组件注册
- 日历页面账单详情组件 - 修复删除按钮事件绑定
**API影响**:
- GET `/api/budget/stats/{category}` 响应结构变更(新增字段,向后兼容)
**依赖**:
- 无外部依赖变更

View File

@@ -0,0 +1,40 @@
## MODIFIED Requirements
### Requirement: Bill deletion requires user confirmation
账单删除操作 MUST 要求用户显式确认,防止误操作导致数据丢失。删除确认对话框 SHALL 明确告知用户操作的不可逆性。
#### Scenario: User confirms bill deletion
- **WHEN** 用户在日历页面的账单详情弹窗中点击"删除"按钮
- **THEN** 系统弹出确认对话框,标题为"确认删除"
- **AND** 对话框消息包含警告文本(如"确定要删除这条账单吗?此操作无法撤销。"
- **AND** 对话框提供"确认"和"取消"两个按钮
#### Scenario: User confirms deletion
- **WHEN** 用户在确认对话框中点击"确认"按钮
- **THEN** 系统调用删除 API (`DELETE /api/bill/{id}`)
- **AND** 删除成功后关闭账单详情弹窗
- **AND** 日历视图自动刷新,删除的账单不再显示
#### Scenario: User cancels deletion
- **WHEN** 用户在确认对话框中点击"取消"按钮
- **THEN** 对话框关闭,账单详情弹窗保持打开状态
- **AND** 账单未被删除
#### Scenario: Deletion fails due to server error
- **WHEN** 用户确认删除,但后端返回错误(如网络异常或 500 错误)
- **THEN** 系统显示错误提示(如"删除失败,请稍后重试"
- **AND** 账单详情弹窗保持打开状态
- **AND** 账单仍存在于系统中
### Requirement: Delete button event binding must be functional
日历页面账单详情组件中的删除按钮 MUST 正确绑定点击事件处理函数,确保用户点击时能够触发删除流程。
#### Scenario: Delete button click triggers handler
- **WHEN** 账单详情弹窗渲染完成
- **THEN** "删除"按钮的 `@click``onClick` 事件绑定到正确的处理函数(如 `handleDelete`
- **AND** 点击按钮时控制台无 JavaScript 错误
#### Scenario: Button is not disabled during loading
- **WHEN** 账单详情弹窗首次加载时
- **THEN** "删除"按钮的 `disabled` 属性为 `false`(除非账单正在删除中)
- **AND** 按钮可以正常响应点击事件

View File

@@ -0,0 +1,48 @@
## MODIFIED Requirements
### Requirement: Budget statistics API response includes complete data
预算统计 API (`GET /api/budget/stats/{category}`) SHALL 返回完整的统计数据,包括用于前端图表渲染的 `Trend` 数组和用于明细弹窗的 `Description` HTML 内容。
响应结构中的 `Month``Year` 对象 MUST 包含以下字段:
- `Limit`: 预算限额
- `Current`: 当前实际金额
- `Remaining`: 剩余金额
- `UsagePercentage`: 使用百分比
- `Trend`: 每日/每月累计金额数组 (`List<decimal?>`)
- `Description`: HTML 格式的详细说明,包含计算公式和数据表格 (`string`)
#### Scenario: Monthly stats with trend data
- **WHEN** 客户端请求月度预算统计 `GET /api/budget/stats/food?date=2026-02`
- **THEN** 响应的 `month` 对象包含 `trend` 数组长度等于该月天数如28/29/30/31
- **AND** `trend` 数组每个元素表示截至该天的累计金额(支出类为递减,收入类为递增)
- **AND** `trend` 数组中未到达的日期对应的元素为 `null`
#### Scenario: Monthly stats with description
- **WHEN** 客户端请求月度预算统计 `GET /api/budget/stats/food?date=2026-02`
- **THEN** 响应的 `month` 对象包含 `description` 字段
- **AND** `description` 是 HTML 格式字符串,包含 `<table>` 标签展示明细数据
- **AND** `description` 包含计算公式说明(如"剩余 = 限额 - 已用"
#### Scenario: Yearly stats with trend data
- **WHEN** 客户端请求年度预算统计 `GET /api/budget/stats/salary?date=2026`
- **THEN** 响应的 `year` 对象包含 `trend` 数组长度为12代表12个月
- **AND** `trend` 数组每个元素表示截至该月的累计金额
- **AND** `trend` 数组中未到达的月份对应的元素为 `null`
#### Scenario: Yearly stats with description
- **WHEN** 客户端请求年度预算统计 `GET /api/budget/stats/salary?date=2026`
- **THEN** 响应的 `year` 对象包含 `description` 字段
- **AND** `description` 是 HTML 格式字符串,包含年度统计明细
### Requirement: DTO mapping preserves all Service layer data
Application 层的 `BudgetApplication.GetCategoryStatsAsync` 方法在将 Service 层的 `BudgetStatsDto` 映射到 API 响应 DTO 时MUST 保留所有数据字段,不得丢失 `Trend``Description`
#### Scenario: Mapping from Service DTO to API DTO
- **WHEN** `BudgetApplication.GetCategoryStatsAsync` 接收到 Service 层返回的 `BudgetStatsDto`
- **THEN** 映射后的 `BudgetStatsDetail` 对象包含 `Trend` 字段,其值等于 `BudgetStatsDto.Month.Trend``BudgetStatsDto.Year.Trend`
- **AND** 映射后的 `BudgetStatsDetail` 对象包含 `Description` 字段,其值等于 `BudgetStatsDto.Month.Description``BudgetStatsDto.Year.Description`
#### Scenario: API response schema validation
- **WHEN** 前端调用 `/api/budget/stats/{category}` 并解析 JSON 响应
- **THEN** TypeScript 类型检查不报错,响应对象符合 `BudgetStatsResponse` 接口定义
- **AND** `month.trend``month.description` 字段存在且非 `undefined`

View File

@@ -0,0 +1,37 @@
## MODIFIED Requirements
### Requirement: Bottom navigation statistics tab routes correctly
底部导航栏的"统计"标签 MUST 正确配置路由路径,点击后能够跳转到统计页面 (`/statistics-v2`)。
#### Scenario: User clicks statistics tab in bottom navigation
- **WHEN** 用户在应用底部导航栏点击"统计"图标或标签
- **THEN** 浏览器 URL 变更为 `/statistics-v2`
- **AND** 页面渲染统计页面组件(如 `StatisticsV2.vue`
- **AND** 底部导航栏的"统计"标签高亮显示为激活状态
#### Scenario: Direct URL access to statistics page
- **WHEN** 用户直接在浏览器地址栏输入 `/statistics-v2` 并访问
- **THEN** 应用正确渲染统计页面
- **AND** 底部导航栏的"统计"标签高亮显示为激活状态
#### Scenario: Navigation tab configuration matches route definition
- **WHEN** 前端代码加载时
- **THEN** 底部导航组件(`van-tabbar` 或自定义组件)中"统计"标签的 `to``path` 属性值为 `/statistics-v2`
- **AND** 路由配置文件 (`router/index.js`) 中存在 `path: '/statistics-v2'` 的路由定义
### Requirement: Vant DatetimePicker component must be registered
Vant UI 库的 `van-datetime-picker` 组件 MUST 正确注册,以避免控制台出现 "Failed to resolve component" 警告。
#### Scenario: DatetimePicker used in application
- **WHEN** 应用中任何页面使用 `<van-datetime-picker>` 组件
- **THEN** 组件正常渲染,无控制台错误或警告
- **AND** 控制台不显示 "Failed to resolve component: van-datetime-picker" 消息
#### Scenario: Global component registration in main.ts
- **WHEN** 应用启动时执行 `main.ts` 或全局插件文件
- **THEN** `DatetimePicker` 组件已通过 `app.use(DatetimePicker)` 全局注册
- **OR** 在使用组件的文件中已通过 `import { DatetimePicker } from 'vant'``components: { VanDatetimePicker: DatetimePicker }` 局部注册
#### Scenario: No missing component warnings after fix
- **WHEN** 用户浏览应用的所有页面(日历、预算、统计等)
- **THEN** 浏览器开发者工具控制台中无任何 Vant 组件相关的警告或错误

View File

@@ -0,0 +1,65 @@
## 1. Backend: Fix Budget Stats DTO and Mapping (Bug #4 & #5 - High Priority)
- [x] 1.1 在 `Application/Dto/BudgetDto.cs``BudgetStatsDetail` record 中添加 `Trend` 字段(`List<decimal?>`,使用 `init`
- [x] 1.2 在 `Application/Dto/BudgetDto.cs``BudgetStatsDetail` record 中添加 `Description` 字段(`string`,使用 `init`
- [x] 1.3 在 `Application/BudgetApplication.cs``GetCategoryStatsAsync` 方法中,映射 `Month` 对象时添加 `Trend = stats.Month.Trend`
- [x] 1.4 在 `Application/BudgetApplication.cs``GetCategoryStatsAsync` 方法中,映射 `Month` 对象时添加 `Description = stats.Month.Description`
- [x] 1.5 在 `Application/BudgetApplication.cs``GetCategoryStatsAsync` 方法中,映射 `Year` 对象时添加 `Trend = stats.Year.Trend`
- [x] 1.6 在 `Application/BudgetApplication.cs``GetCategoryStatsAsync` 方法中,映射 `Year` 对象时添加 `Description = stats.Year.Description`
## 2. Backend: Add Unit Tests for DTO Mapping
- [x] 2.1 在 `WebApi.Test/` 中创建 `BudgetApplicationTests.cs` 测试类(如果不存在)
- [x] 2.2 编写测试用例 `GetCategoryStatsAsync_Should_Include_Trend_And_Description_In_Month_Stats`
- [x] 2.3 编写测试用例 `GetCategoryStatsAsync_Should_Include_Trend_And_Description_In_Year_Stats`
- [x] 2.4 运行测试并验证通过:`dotnet test --filter "FullyQualifiedName~BudgetApplicationTests"`
## 3. Frontend: Fix Navigation Routes (Bug #1)
- [x] 3.1 使用 `grep` 搜索底部导航组件代码(搜索关键字 `van-tabbar``统计`
- [x] 3.2 定位"统计"标签的路由配置(检查 `to``path` 属性)
- [x] 3.3 修改路由路径为 `/statistics-v2`
- [x] 3.4 验证路由配置文件 `Web/src/router/index.js` 中存在 `/statistics-v2` 路由定义
## 4. Frontend: Fix Bill Deletion Function (Bug #2)
- [x] 4.1 使用 `grep` 搜索日历页面的账单详情组件(搜索关键字 `删除``delete`
- [x] 4.2 定位删除按钮的点击事件绑定(检查 `@click``onClick`
- [x] 4.3 实现 `handleDelete` 函数,使用 `showConfirmDialog` 显示确认对话框
- [x] 4.4 在确认后调用删除 API 并关闭弹窗,刷新日历视图
- [x] 4.5 在取消时关闭对话框但保持弹窗打开
- [x] 4.6 处理删除失败场景,显示错误提示
## 5. Frontend: Fix Vant DatetimePicker Registration (Bug #3)
- [x] 5.1 检查 `Web/src/main.ts` 或全局组件注册文件
- [x] 5.2 验证是否导入 `DatetimePicker``import { DatetimePicker } from 'vant'`
- [x] 5.3 如果缺失,添加全局注册 `app.use(DatetimePicker)`
- [ ] 5.4 启动前端开发服务器,验证控制台无 "Failed to resolve component" 警告
## 6. Frontend: Verify Budget Chart Renders Correctly After Backend Fix
- [ ] 6.1 启动后端和前端服务
- [ ] 6.2 打开预算页面,点击"使用情况"或"完成情况"旁的感叹号图标
- [ ] 6.3 验证明细弹窗显示完整的 HTML 表格(非"暂无数据"
- [ ] 6.4 验证燃尽图显示波动曲线(非直线)
- [x] 6.5 检查前端 `BudgetChartAnalysis.vue:603``:629` 行的 fallback 逻辑是否仍触发(如需修改条件检查)
## 7. Investigation: Budget Card Amount Mismatch (Bug #6 - Low Priority)
- [ ] 7.1 在测试环境中打开预算页面,点击预算卡片的"查询关联账单"按钮
- [ ] 7.2 对比预算卡片显示的"实际"金额与账单列表金额总和
- [ ] 7.3 检查不一致的预算是否标记为硬性预算(📌)
- [ ] 7.4 如果是硬性预算,验证虚拟消耗的计算逻辑(`BudgetService.cs:376-405`
- [ ] 7.5 检查 `BudgetResult``PeriodStart``PeriodEnd` 的赋值是否与 `GetPeriodRange` 一致
- [ ] 7.6 如果是虚拟消耗导致,考虑在前端账单列表中添加提示说明(可选)
- [ ] 7.7 如果是日期范围问题,修复 `BudgetResult` 的赋值逻辑
## 8. End-to-End Verification
- [x] 8.1 运行后端所有测试:`dotnet test`
- [x] 8.2 运行前端 lint`cd Web && pnpm lint`
- [x] 8.3 构建前端:`cd Web && pnpm build`
- [ ] 8.4 手动测试所有修复的 bug按 bug-handoff-document.md 中的验证清单)
- [ ] 8.5 清除浏览器缓存并重新测试
- [ ] 8.6 验证控制台无错误或警告

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,24 @@
## Why
用户在 2026 年 2 月份查看 2026 年 1 月份的月度预算统计时,页面显示"超支170元",但用户认为应该远远大于这个值。经检查发现,页面显示的"超支170"仅计算了月度支出预算Type=1的超支未包含年度支出预算Type=2在该月的实际支出。1 月份实际总支出为 36,130.40 元,但页面只显示了 23,284.40 元(月度支出预算),缺少了约 12,846 元的年度支出预算部分。
## What Changes
- **修改月度预算统计逻辑**:在 `CalculateMonthlyCategoryStatsAsync` 方法中统计支出时需要包含年度支出预算Type=2在该月的实际支出金额
- **修改数据源逻辑**`GetAllBudgetsWithArchiveAsync` 方法在获取月度预算数据时,需要同时获取年度支出预算在该月的实际支出
- **确保归档数据正确使用**:使用归档数据中的 `Actual` 值,而非重新计算
## Capabilities
### New Capabilities
无新能力引入,仅修复现有逻辑。
### Modified Capabilities
- `budget-stats`: 修改月度预算统计的需求,要求月度支出统计包含所有类型的实际支出(月度+年度支出预算在该月的支出)
## Impact
- **受影响代码**`Service/Budget/BudgetStatsService.cs` 中的 `CalculateMonthlyCategoryStatsAsync``GetAllBudgetsWithArchiveAsync` 方法
- **受影响 API**:预算统计相关的 API 接口(前端调用的获取预算统计信息的接口)
- **数据来源**`BudgetArchive` 表中的归档数据,需要正确使用归档的 `Actual`
- **用户体验**:修复后,用户查看月度预算统计时,将看到包含所有实际支出的准确数据

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-15

View File

@@ -0,0 +1,260 @@
## Context
当前 EmailBill 项目中存在两个 `TransactionList.vue` 实现:
1. **旧版**`Web/src/components/TransactionList.vue`):传统一行一卡片布局,功能完整但样式较旧
2. **v2 版**`Web/src/views/calendarV2/modules/TransactionList.vue`):现代卡片式设计,视觉层次更好
两个组件分别服务不同页面,导致:
- 代码重复格式化、API 调用、交互逻辑)
- 样式不一致(用户体验割裂)
- 维护成本高(修改需同步两处)
**技术栈约束:**
- Vue 3 Composition API + `<script setup>` (JavaScript)
- Vant UI 组件库(移动端)
- Pinia 状态管理
- 后端 API`@/api/transactionRecord`
**重要说明:**
- 项目使用 **JavaScript** 而非 TypeScript
- 使用 JSDoc 注释提供类型提示
- Props 和 Emits 使用 `defineProps()``defineEmits()` 的对象语法
**设计目标:**
创建统一的 `BillListComponent.vue`,整合两者优点,高内聚设计,支持筛选、排序、分页、左滑删除、详情查看。
## Goals / Non-Goals
**Goals:**
- 创建可复用的账单列表组件 `BillListComponent.vue`(位于 `Web/src/components/Bill/`
- 基于 v2 风格,但调整为紧凑列表(非一行一卡片)
- 内置筛选(类型、分类、日期范围)、排序(金额、时间)、分页加载
- 支持左滑删除van-swipe-cell、点击详情emit 事件)
- 保留旧版的特殊功能checkbox 选择模式)
- 迁移所有使用旧组件的页面到新组件
- 删除旧版 `Web/src/components/TransactionList.vue`
**Non-Goals:**
- 不改变后端 API 接口
- 不涉及新增数据字段
- 不处理账单编辑功能(仅展示和删除)
- 暂不支持拖拽排序
## Decisions
### 决策 1组件命名和位置
**选择**`Web/src/components/Bill/BillListComponent.vue`
**理由**
- 放在 `Bill/` 目录下与现有 `BillForm.vue``ManualBillAdd.vue` 等保持一致
- 命名为 `BillListComponent` 而非 `TransactionList`,避免与旧组件混淆
- 符合项目 BEM 命名规范
**备选方案**
- 直接覆盖旧版 `TransactionList.vue`**拒绝**:迁移期间需要并存,且容易引起冲突
### 决策 2Props 设计(高内聚 vs 灵活配置)
**选择****高内聚** - 组件内部管理筛选、排序、分页状态
**Props 定义**
```javascript
// 使用 defineProps 对象语法 + JSDoc
const props = defineProps({
// 数据源模式
dataSource: {
type: String, // 'api' | 'custom'
default: 'api'
},
// API 模式参数
apiParams: {
type: Object, // { dateRange?: [string, string], category?: string, type?: 0|1|2 }
default: () => ({})
},
// Custom 模式参数
transactions: {
type: Array, // Transaction[]
default: () => []
},
// 功能开关
showDelete: {
type: Boolean,
default: true
},
showCheckbox: {
type: Boolean,
default: false
},
enableFilter: {
type: Boolean,
default: true
},
enableSort: {
type: Boolean,
default: true
},
// 样式配置
compact: {
type: Boolean,
default: true
},
// 多选状态
selectedIds: {
type: Set,
default: () => new Set()
}
})
```
**理由**
- 大部分场景只需传 `apiParams`,组件自动处理筛选、排序、分页
- `dataSource='custom'` 模式兼容特殊场景(如离线数据、缓存数据)
- 功能开关满足不同页面需求(如 TransactionsRecord 需要 checkbox
**备选方案**
- 低内聚(父组件管理所有状态)→ **拒绝**:每个使用方都要重复实现筛选、排序逻辑,违背复用目标
### 决策 3筛选 UI 实现
**选择**`van-dropdown-menu` + `van-popup`(日期选择器)
**布局**
```
[类型 ▼] [分类 ▼] [日期 ▼] [排序 ▼]
```
**理由**
- Vant 的 `van-dropdown-menu` 适合移动端,节省空间
- 日期范围选择使用 `van-calendar` 弹出层
- 与项目现有 UI 风格一致
**备选方案**
- 使用 `van-tabs` 切换筛选项 → **拒绝**:占用空间大,不适合同时筛选多个维度
### 决策 4数据加载策略
**选择**:虚拟滚动 + 分页加载(`van-list`
**实现**
- 初始加载 20 条
- 滚动到底部触发 `@load` 事件,追加 20 条
- 筛选/排序变更时,重置列表并重新加载
**理由**
- Vant 的 `van-list` 内置分页逻辑,简单易用
- 账单数据量通常不大(日常使用 < 1000 条),无需复杂虚拟滚动库
**备选方案**
- 一次性加载全部数据 → **拒绝**:账单数量多时性能差
### 决策 5删除功能的事务处理
**选择**:组件内部调用 `deleteTransaction` API删除成功后 emit 事件并派发全局事件
**流程**
```javascript
const handleDelete = async (transaction) => {
await showConfirmDialog({ message: '确定删除?' })
await deleteTransaction(transaction.id) // API 调用
emit('delete', transaction.id) // 通知父组件
window.dispatchEvent(new CustomEvent('transaction-deleted', { detail: transaction.id })) // 全局事件
// 刷新当前列表
}
```
**理由**
- 保持与旧版一致的删除逻辑(已验证可用)
- 全局事件通知其他组件刷新(如统计图表)
- 父组件可通过 `@delete` 监听执行额外逻辑
**备选方案**
- 父组件负责删除 → **拒绝**:增加使用成本,每个页面都要实现删除逻辑
### 决策 6样式调整紧凑列表
**选择**:修改 v2 的卡片布局,减少卡片间距和内边距
**调整细节**
```scss
.bill-card {
margin-top: 6px; // 原 10px
padding: 10px 12px; // 原 var(--spacing-xl) (约 16px)
gap: 10px; // 原 14px
}
```
**理由**
- v2 原始设计间距较大,适合日历单日视图
- 列表视图需要更紧凑以显示更多条目
- 保留 v2 的视觉元素(图标、标签、颜色)
## Risks / Trade-offs
### 风险 1迁移期间功能遗漏
**风险**:旧版组件可能有未文档化的特殊用法,迁移时遗漏
**缓解措施**
- 迁移前全面梳理旧版所有 props 和 emits
- 保留 `showCheckbox``selectedIds` 功能TransactionsRecord 批量操作依赖)
- 迁移分阶段进行,逐个页面验证
### 风险 2性能退化
**风险**:新增筛选、排序逻辑可能影响渲染性能
**缓解措施**
- 使用 `computed` 缓存筛选结果
- 大数据量时依赖后端 API 筛选(而非前端过滤)
- 测试场景1000+ 条数据的滚动流畅度
### 风险 3样式兼容性
**风险**:不同页面的主题色、暗黑模式可能导致显示异常
**缓解措施**
- 使用 CSS 变量(`var(--van-danger-color)` 等),自动适配主题
- 测试暗黑模式下的视觉效果
- 提供 `themeOverride` prop 允许父组件覆盖样式
### Trade-off组件复杂度 vs 易用性
**权衡**高内聚设计会增加组件内部复杂度300+ 行代码)
**选择**:接受复杂度换取易用性
**理由**
- 简化所有使用方的代码10+ 处引用)
- 统一维护点,避免分散的重复逻辑
- 内部复杂度可通过单元测试覆盖
## Migration Plan
### 阶段 1组件开发第 1-2 天)
1. 创建 `Web/src/components/Bill/BillListComponent.vue`
2. 实现核心功能:数据展示、分页、左滑删除
3. 实现筛选、排序 UI 和逻辑
4. 单元测试覆盖Vue Test Utils
### 阶段 2试点迁移第 3 天)
1. 选择一个简单页面试点(如 `BillAnalysisView.vue`
2. 替换旧组件为新组件
3. 验证功能完整性和视觉效果
4. 修复发现的问题
### 阶段 3全面迁移第 4-5 天)
1. 迁移 `TransactionsRecord.vue`(重点验证 checkbox 功能)
2. 迁移其他引用旧组件的页面
3. 回归测试所有相关页面
### 阶段 4清理第 6 天)
1. 删除旧版 `Web/src/components/TransactionList.vue`
2. 删除 `Web/src/views/calendarV2/modules/TransactionList.vue`(如不再需要)
3. 更新文档和 AGENTS.md
### Rollback 策略
- 保留旧版组件直到所有页面迁移完成
- 使用 Git 分支隔离迁移工作
- 如发现严重问题,可快速恢复旧版(修改 import 路径)
## Open Questions
1. **calendarV2 的 TransactionList 是否有特殊逻辑?**
需要确认 calendarV2 是否依赖其特有功能(如 Smart 按钮、日期联动)。如果有,可能需要保留该文件,仅迁移其他页面。
2. **是否需要支持自定义列渲染?**
当前设计固定显示字段reason, amount, classify 等)。未来是否需要 slot 支持自定义?暂时不实现,等实际需求再扩展。
3. **筛选条件的持久化?**
用户设置的筛选条件是否需要保存到 localStorage当前设计不持久化每次刷新恢复默认。

View File

@@ -0,0 +1,42 @@
## Why
当前项目中存在多个账单列表实现(`TransactionList.vue``components/``calendarV2/modules/`导致代码重复、样式不统一、维护成本高。v2 版本的账单列表calendarV2/modules/TransactionList.vue采用了更现代的卡片式设计和更好的视觉层次应当作为标准组件在全项目范围内复用。
## What Changes
- **新增**:创建高内聚、可复用的 `BillListComponent.vue` 组件(基于 v2 风格)
- **调整**:修改 v2 的一行一卡片布局,支持更紧凑的列表展示
- **新增**:内置筛选(按类型、分类、日期)、排序(金额、时间)、分页功能
- **新增**左滑删除交互Vant SwipeCell
- **新增**点击查看详情功能emit 事件)
- **删除**:移除旧版本的 `TransactionList.vue`Web/src/components/
- **迁移**现有使用旧组件的页面TransactionsRecord.vue、BillAnalysisView.vue 等)改为引用新组件
## Capabilities
### New Capabilities
- `bill-list-component`: 可复用的账单列表组件,支持筛选、排序、分页、左滑删除、点击详情等功能,样式基于 calendarV2 的现代化设计
### Modified Capabilities
- `transaction-list-display`: 现有的交易列表展示能力需要统一到新组件,旧版本 TransactionList.vue 的功能将被新组件替代
## Impact
**受影响的代码:**
- `Web/src/components/TransactionList.vue` - 将被删除
- `Web/src/views/calendarV2/modules/TransactionList.vue` - 作为参考基础,可能需要重构
- `Web/src/views/TransactionsRecord.vue` - 需要切换到新组件
- `Web/src/views/BillAnalysisView.vue` - 需要切换到新组件
- 其他可能引用旧版 TransactionList 的视图
**API 依赖:**
- `@/api/transactionRecord` - deleteTransaction, getTransactionsByDate 等接口
- 保持现有 API 调用方式不变
**UI 依赖:**
- Vant UI: van-swipe-cell, van-list, van-loading, van-empty, van-icon, van-tag
- 需要新增筛选/排序 UI 组件van-dropdown-menu, van-popup
**用户体验影响:**
- 正面:统一的视觉风格、更流畅的交互、更好的性能(高内聚设计)
- 迁移风险:需要确保功能对等,避免遗漏旧版本的特殊功能(如 checkbox 选择模式)

View File

@@ -0,0 +1,191 @@
## ADDED Requirements
### Requirement: Component accepts configuration props
组件必须接受配置 props 以支持不同的使用场景,包括数据源模式、功能开关、样式配置等。
#### Scenario: API 数据源模式
- **WHEN** 父组件传入 `dataSource="api"``apiParams={ dateRange: ['2026-01-01', '2026-01-31'] }`
- **THEN** 组件调用后端 API 获取指定日期范围内的账单数据
#### Scenario: 自定义数据源模式
- **WHEN** 父组件传入 `dataSource="custom"``transactions` 数组
- **THEN** 组件直接使用传入的数据进行展示,不调用 API
#### Scenario: 禁用筛选功能
- **WHEN** 父组件传入 `enableFilter={false}`
- **THEN** 组件不显示筛选栏(类型、分类、日期下拉菜单)
#### Scenario: 启用多选模式
- **WHEN** 父组件传入 `showCheckbox={true}`
- **THEN** 每个账单项左侧显示复选框,支持多选
### Requirement: 账单列表展示
组件必须以紧凑列表形式展示账单数据,每个账单项包含关键信息(摘要、金额、分类、时间)。
#### Scenario: 展示支出账单
- **WHEN** 账单类型为支出type=0
- **THEN** 金额显示为红色负数(如 "- ¥50.00"),右上角显示红色"支出"标签
#### Scenario: 展示收入账单
- **WHEN** 账单类型为收入type=1
- **THEN** 金额显示为绿色正数(如 "+ ¥1000.00"),右上角显示绿色"收入"标签
#### Scenario: 显示账单图标
- **WHEN** 账单有分类信息(如"餐饮"
- **THEN** 卡片左侧显示对应的图标(如 food 图标),背景色与分类关联
#### Scenario: 空列表状态
- **WHEN** 筛选结果为空或无账单数据
- **THEN** 显示空状态提示"暂无交易记录",带有图标和友好文案
### Requirement: 筛选功能
组件必须提供内置的筛选功能,支持按类型、分类、日期范围筛选账单。
#### Scenario: 按类型筛选
- **WHEN** 用户在筛选栏选择"支出"
- **THEN** 列表仅显示类型为支出的账单type=0
#### Scenario: 按分类筛选
- **WHEN** 用户在筛选栏选择"餐饮"
- **THEN** 列表仅显示分类为"餐饮"的账单
#### Scenario: 按日期范围筛选
- **WHEN** 用户选择日期范围"2026-02-01 至 2026-02-15"
- **THEN** 列表仅显示该日期范围内的账单
#### Scenario: 多条件组合筛选
- **WHEN** 用户同时选择"支出"、"餐饮"和日期范围
- **THEN** 列表显示满足所有条件的账单AND 逻辑)
#### Scenario: 清空筛选条件
- **WHEN** 用户点击"重置"或清空所有筛选项
- **THEN** 列表恢复显示全部账单
### Requirement: 排序功能
组件必须支持按金额或时间排序账单列表。
#### Scenario: 按金额降序排序
- **WHEN** 用户在排序下拉菜单选择"金额从高到低"
- **THEN** 列表按金额降序重新排列
#### Scenario: 按时间升序排序
- **WHEN** 用户在排序下拉菜单选择"时间从早到晚"
- **THEN** 列表按交易时间升序排列
#### Scenario: 默认排序
- **WHEN** 组件初始加载且用户未设置排序
- **THEN** 列表按时间降序排列(最新的在前)
### Requirement: 分页加载
组件必须支持滚动分页加载,优化大数据量时的性能。
#### Scenario: 初始加载
- **WHEN** 组件首次渲染
- **THEN** 加载前 20 条账单数据并显示
#### Scenario: 滚动加载更多
- **WHEN** 用户滚动到列表底部
- **THEN** 自动加载下一页 20 条数据并追加到列表
#### Scenario: 加载完成提示
- **WHEN** 所有数据加载完毕
- **THEN** 列表底部显示"没有更多了"提示
#### Scenario: 筛选后重新分页
- **WHEN** 用户修改筛选条件
- **THEN** 列表重置到第一页,重新开始分页加载
### Requirement: 左滑删除功能
组件必须支持左滑显示删除按钮,并处理删除操作。
#### Scenario: 左滑显示删除按钮
- **WHEN** 用户在账单项上左滑
- **THEN** 右侧显示红色"删除"按钮
#### Scenario: 确认删除
- **WHEN** 用户点击"删除"按钮并在确认对话框中选择"确定"
- **THEN** 调用 API 删除该账单,成功后从列表移除,显示"删除成功"提示
#### Scenario: 取消删除
- **WHEN** 用户点击"删除"按钮并在确认对话框中选择"取消"
- **THEN** 不执行删除操作,滑块自动归位
#### Scenario: 删除失败处理
- **WHEN** API 删除失败(如网络错误)
- **THEN** 显示"删除失败"提示,列表不变
#### Scenario: 删除后广播事件
- **WHEN** 账单删除成功
- **THEN** 组件派发全局事件 `transaction-deleted`,携带删除的账单 ID
#### Scenario: 禁用删除功能
- **WHEN** 父组件传入 `showDelete={false}`
- **THEN** 左滑不显示删除按钮
### Requirement: 点击查看详情
组件必须支持点击账单项查看详情,通过 emit 事件通知父组件。
#### Scenario: 点击账单卡片
- **WHEN** 用户点击账单项(非复选框、非删除按钮区域)
- **THEN** 组件触发 `@click` 事件,传递完整的账单对象
#### Scenario: 父组件处理详情显示
- **WHEN** 父组件监听 `@click` 事件
- **THEN** 父组件接收账单对象,自行决定详情展示方式(如弹窗、路由跳转)
### Requirement: 多选功能
组件必须支持多选模式,用于批量操作场景。
#### Scenario: 启用多选模式
- **WHEN** 父组件传入 `showCheckbox={true}`
- **THEN** 每个账单项左侧显示复选框
#### Scenario: 选中账单
- **WHEN** 用户点击复选框
- **THEN** 该账单被标记为选中状态,复选框显示勾选
#### Scenario: 取消选中
- **WHEN** 用户再次点击已选中的复选框
- **THEN** 该账单取消选中状态
#### Scenario: 同步选中状态
- **WHEN** 父组件更新 `selectedIds` prop
- **THEN** 组件更新复选框的选中状态以匹配传入的 ID 集合
#### Scenario: 通知父组件选中变更
- **WHEN** 用户切换复选框状态
- **THEN** 组件触发 `@update:selectedIds` 事件,传递新的选中 ID 集合
### Requirement: 样式适配
组件必须适配移动端主题和暗黑模式。
#### Scenario: 亮色主题
- **WHEN** 应用使用亮色主题
- **THEN** 组件使用浅色背景、深色文字,边框和标签使用主题色
#### Scenario: 暗黑模式
- **WHEN** 应用切换到暗黑模式
- **THEN** 组件使用深色背景、浅色文字,自动适配 Vant 的暗黑主题变量
#### Scenario: 紧凑模式
- **WHEN** 父组件传入 `compact={true}`(默认)
- **THEN** 卡片间距为 6px内边距为 10px显示更多条目
#### Scenario: 舒适模式
- **WHEN** 父组件传入 `compact={false}`
- **THEN** 卡片间距和内边距增大(如 v2 原始尺寸)
### Requirement: 加载状态
组件必须显示加载状态,提供良好的用户反馈。
#### Scenario: 首次加载中
- **WHEN** 组件调用 API 获取数据且尚未返回
- **THEN** 显示加载动画van-loading和"加载中..."文案
#### Scenario: 分页加载中
- **WHEN** 用户滚动触发分页加载
- **THEN** 列表底部显示加载动画
#### Scenario: 删除操作中
- **WHEN** 用户点击删除且 API 调用进行中
- **THEN** 删除按钮显示加载状态,防止重复点击

View File

@@ -0,0 +1,59 @@
## ADDED Requirements
### Requirement: 统一组件实现
系统必须使用统一的 BillListComponent 替代现有的多个账单列表实现,确保代码复用和样式一致性。
#### Scenario: 替换旧版 TransactionList
- **WHEN** 页面需要展示账单列表
- **THEN** 使用 `BillListComponent.vue` 而非 `components/TransactionList.vue`
#### Scenario: CalendarV2 模块迁移
- **WHEN** CalendarV2 需要展示交易列表
- **THEN** 使用 `BillListComponent.vue` 或保留其特有实现(如有特殊需求)
### Requirement: 功能对等性
新组件必须保持旧版所有功能,确保迁移不丢失特性。
#### Scenario: 批量选择功能
- **WHEN** TransactionsRecord 需要批量操作
- **THEN** 新组件通过 `showCheckbox``selectedIds` 提供相同功能
#### Scenario: 删除后刷新
- **WHEN** 账单删除成功
- **THEN** 新组件派发 `transaction-deleted` 全局事件,保持与旧版相同的事件机制
#### Scenario: 自定义数据源
- **WHEN** 页面需要展示离线或缓存数据
- **THEN** 新组件通过 `dataSource="custom"``transactions` prop 支持自定义数据
### Requirement: 视觉升级
新组件必须基于 v2 的现代化设计,提供更好的视觉体验。
#### Scenario: 卡片样式
- **WHEN** 展示账单列表
- **THEN** 使用 v2 的卡片样式(圆角、阴影、图标),但调整为紧凑间距
#### Scenario: 图标展示
- **WHEN** 账单有分类信息
- **THEN** 显示对应的分类图标(如餐饮用 food 图标),带有彩色背景
#### Scenario: 标签样式
- **WHEN** 显示账单类型
- **THEN** 使用彩色标签(支出红色、收入绿色),位于卡片右上角
### Requirement: 迁移计划
系统必须按阶段迁移,确保平滑过渡。
#### Scenario: 并存期
- **WHEN** 迁移进行中
- **THEN** 新旧组件共存,已迁移页面使用新组件,未迁移页面继续使用旧组件
#### Scenario: 清理旧代码
- **WHEN** 所有页面迁移完成
- **THEN** 删除 `components/TransactionList.vue`,移除相关 import
## REMOVED Requirements
### Requirement: 一行一卡片布局
**Reason**: 间距过大,不适合列表视图,需要滚动过多才能查看更多账单
**Migration**: 使用新的紧凑布局(`compact={true}`),卡片间距减少至 6px

View File

@@ -0,0 +1,135 @@
## 1. 组件基础结构搭建
- [x] 1.1 创建 `Web/src/components/Bill/BillListComponent.vue` 文件
- [x] 1.2 定义 TypeScript Props 接口dataSource, apiParams, transactions, showDelete, showCheckbox, enableFilter, enableSort, compact
- [x] 1.3 定义 Emits 接口load, click, delete, update:selectedIds
- [x] 1.4 创建组件骨架template、script setup、style scoped
- [x] 1.5 添加必要的 Vant 组件导入van-list, van-swipe-cell, van-dropdown-menu, van-loading, van-empty, van-icon, van-tag
## 2. 数据管理和状态
- [x] 2.1 实现 `dataSource` 模式切换逻辑api vs custom
- [x] 2.2 创建响应式数据状态transactions, loading, finished, page, pageSize
- [x] 2.3 实现筛选状态管理selectedType, selectedCategory, dateRange, sortBy
- [x] 2.4 实现多选状态管理localSelectedIds, 与 prop 同步)
- [x] 2.5 创建 computed 计算属性用于筛选和排序逻辑
## 3. API 数据加载
- [x] 3.1 导入 `@/api/transactionRecord` 的相关 API 方法
- [x] 3.2 实现初始加载逻辑fetchTransactions 方法)
- [x] 3.3 实现分页加载逻辑onLoad 方法,支持 van-list
- [x] 3.4 实现筛选条件变更时的数据重载逻辑
- [x] 3.5 处理 API 错误和加载状态
## 4. 筛选功能实现
- [x] 4.1 创建筛选栏 UIvan-dropdown-menu包含类型、分类、日期、排序四个下拉菜单
- [x] 4.2 实现类型筛选(支出、收入、不计入、全部)
- [x] 4.3 实现分类筛选(动态获取分类列表或预设常用分类)
- [x] 4.4 实现日期范围筛选(使用 van-calendar 弹出层)
- [x] 4.5 实现排序功能(金额降序、金额升序、时间降序、时间升序)
- [x] 4.6 添加筛选重置功能
- [x] 4.7 根据 `enableFilter` prop 控制筛选栏显示/隐藏
## 5. 账单列表展示
- [x] 5.1 创建账单卡片布局(基于 v2 风格,左侧图标、中间内容、右侧金额)
- [x] 5.2 实现紧凑模式样式(减少卡片间距至 6px内边距至 10px
- [x] 5.3 实现账单图标显示(根据分类映射图标,使用 getIconByClassify 方法)
- [x] 5.4 实现金额格式化(带符号、颜色区分支出/收入)
- [x] 5.5 实现类型标签显示(右上角,支出红色、收入绿色)
- [x] 5.6 实现时间格式化显示HH:MM 或 YYYY-MM-DD HH:MM
- [x] 5.7 实现空状态展示van-empty带友好文案
## 6. 左滑删除功能
- [x] 6.1 为每个账单项添加 van-swipe-cell 包裹
- [x] 6.2 创建删除按钮插槽(右侧滑出,红色按钮)
- [x] 6.3 实现删除确认对话框van-dialog
- [x] 6.4 实现删除 API 调用deleteTransaction
- [x] 6.5 删除成功后更新本地列表(移除对应项)
- [x] 6.6 显示删除成功/失败提示van-toast
- [x] 6.7 派发全局事件 `transaction-deleted`
- [x] 6.8 触发父组件 `@delete` 事件
- [x] 6.9 根据 `showDelete` prop 控制删除功能启用/禁用
## 7. 点击详情功能
- [x] 7.1 为账单卡片添加 @click 事件监听
- [x] 7.2 实现 handleClick 方法,触发 `@click` 事件并传递账单对象
- [x] 7.3 确保点击复选框或删除按钮时不触发详情事件(事件冒泡处理)
## 8. 多选功能
- [x] 8.1 在账单项左侧添加 van-checkbox根据 `showCheckbox` prop 控制显示)
- [x] 8.2 实现 checkbox 选中状态与 `selectedIds` prop 同步
- [x] 8.3 实现 toggleSelection 方法(切换选中状态)
- [x] 8.4 触发 `@update:selectedIds` 事件更新父组件状态
- [ ] 8.5 添加全选/取消全选功能(可选,视需求)
## 9. 样式和主题适配
- [x] 9.1 使用 SCSS 编写组件样式
- [x] 9.2 使用 CSS 变量适配 Vant 主题(`var(--van-danger-color)` 等)
- [x] 9.3 实现暗黑模式适配(检查暗色背景下的显示效果)
- [x] 9.4 根据 `compact` prop 调整卡片间距和内边距
- [x] 9.5 确保移动端响应式布局(处理小屏幕、横屏等情况)
- [x] 9.6 添加过渡动画(卡片删除、列表加载等)
## 10. 页面迁移 - TransactionsRecord
- [x] 10.1 在 `TransactionsRecord.vue` 中导入新组件 `BillListComponent`
- [x] 10.2 替换旧 `TransactionList` 组件为 `BillListComponent`
- [x] 10.3 传递必要的 propsshowCheckbox, selectedIds, apiParams
- [x] 10.4 更新事件监听(@click, @delete, @update:selectedIds
- [ ] 10.5 验证批量操作功能正常(选中、删除、导出等)
- [ ] 10.6 测试页面功能完整性
## 11. 页面迁移 - BillAnalysisView
- [x] 11.1 在 `BillAnalysisView.vue` 中导入新组件 `BillListComponent` (页面未使用旧组件,跳过)
- [x] 11.2 替换旧组件为 `BillListComponent` (页面未使用旧组件,跳过)
- [x] 11.3 传递筛选参数(如按分类、日期范围)(页面未使用旧组件,跳过)
- [x] 11.4 更新事件监听 (页面未使用旧组件,跳过)
- [x] 11.5 测试页面功能正常 (页面未使用旧组件,跳过)
## 12. 页面迁移 - 其他引用页面
- [x] 12.1 使用 grep 搜索所有引用旧 `TransactionList` 的文件
- [x] 12.2 逐个页面进行迁移(重复步骤 10-11 的流程)
- [x] 12.3 验证每个页面的功能和样式
- [x] 12.4 检查是否有遗漏的引用
## 13. CalendarV2 模块处理
- [x] 13.1 评估 `calendarV2/modules/TransactionList.vue` 的特殊功能Smart 按钮、日期联动等)
- [x] 13.2 决定是保留该文件还是迁移到新组件
- [x] 13.3 如保留,添加注释说明其特殊性;如迁移,确保功能对等
- [x] 13.4 测试 CalendarV2 的交易列表功能
## 14. 清理旧代码
- [x] 14.1 删除 `Web/src/components/TransactionList.vue`
- [x] 14.2 检查并删除相关的未使用导入
- [x] 14.3 更新 `AGENTS.md` 或相关文档,说明新组件的使用方式
- [x] 14.4 提交代码变更Git commit
## 15. 测试和验证
- [ ] 15.1 编写单元测试Vue Test Utils测试 props、emits、筛选、排序逻辑
- [ ] 15.2 手动测试所有功能点(筛选、排序、分页、删除、多选、详情)
- [ ] 15.3 测试暗黑模式显示效果
- [ ] 15.4 测试不同屏幕尺寸的响应式布局
- [ ] 15.5 测试 API 错误处理和边界情况(空数据、网络错误等)
- [ ] 15.6 回归测试所有迁移页面,确保无功能丢失
- [ ] 15.7 性能测试(加载 1000+ 条数据时的流畅度)
## 16. 文档和收尾
- [x] 16.1 编写组件使用文档Props、Emits、Slots 说明)
- [x] 16.2 更新 `Web/src/views/AGENTS.md`(如有)
- [x] 16.3 在组件文件顶部添加 JSDoc 注释
- [x] 16.4 代码格式化pnpm lint
- [x] 16.5 构建测试pnpm build
- [x] 16.6 最终代码审查

View File

@@ -78,6 +78,8 @@
- [ ] 10.7 测试智能分类功能 (SmartClassifyButton) 在 V2 页面中正常工作
- [ ] 10.8 验证访问 V1 路由 (`/calendar`, `/budget`, `/`) 返回 404 或重定向到 V2
**注意**: 以上手动测试任务需要在浏览器中实际运行应用并验证功能。代码实现已完成,等待用户验收。
## 11. 代码搜索验证 (确认无遗漏)
- [x] 11.1 全局搜索 `CalendarView` (大小写敏感),确认无残留引用
@@ -98,9 +100,9 @@
## 13. 代码审查和提交
- [ ] 13.1 使用 `git status` 确认所有修改的文件
- [ ] 13.2 使用 `git diff` 审查每个删除的代码块,确认无误删
- [ ] 13.3 提交变更到本地分支: `git add . && git commit -m "feat: remove V1 calendar/budget/stats modules"`
- [x] 13.1 使用 `git status` 确认所有修改的文件
- [x] 13.2 使用 `git diff` 审查每个删除的代码块,确认无误删
- [x] 13.3 提交变更到本地分支: `git add . && git commit -m "feat: remove V1 calendar/budget/stats modules"`
- [ ] 13.4 推送到远程分支: `git push origin feature/remove-v1-modules`
## 14. Pull Request 和最终验证

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-14

View File

@@ -0,0 +1,163 @@
## Context
当前前端项目使用 Vue 3 + Vant UI 构建,虽然已有封装的 `PopupContainer.vue` 组件,但项目中存在多种弹窗实现方式:
1. **PopupContainer.vue**: 统一的底部弹出式弹窗,支持标题、副标题、固定头部/页脚、内容滚动
2. **AddClassifyDialog.vue**: 基于 `van-dialog` 的独立对话框组件,用于新增分类
3. **CategoryBillPopup.vue**: 基于 `van-popup` 的独立底部弹窗组件,用于展示分类账单列表
4. **直接使用 van-dialog**: 多个视图文件中直接使用(如 ClassificationEdit.vue、BillAnalysisView.vue
5. **直接使用 van-popup**: 多个视图文件中用于选择器、表单等场景(如 PeriodicRecord.vue、statisticsV2/Index.vue
这种分散的实现导致:
- 弹窗风格不统一(标题样式、圆角、间距、字体大小等)
- 代码重复度高,维护成本高
- 用户体验不一致
## Goals / Non-Goals
**Goals:**
- 统一所有弹窗组件使用 `PopupContainer` 或其扩展组件
- 确保弹窗的视觉风格和交互行为一致
- 减少代码重复,提高可维护性
- 扩展 `PopupContainer` 功能以覆盖现有所有使用场景
**Non-Goals:**
- 不修改 Vant UI 组件库本身
- 不改变弹窗的业务逻辑(仅重构 UI 层)
- 不影响后端 API
## Decisions
### 1. 扩展现有 PopupContainer 组件而非创建新组件
**选择理由:**
- `PopupContainer.vue` 已有成熟的底部弹窗布局和样式
- 仅需扩展其功能即可覆盖大部分使用场景
- 避免引入新的组件,减少学习成本
**扩展点:**
- 添加 `confirm``cancel` 事件,支持简单确认对话框场景
- 添加 `showConfirmButton``showCancelButton` props控制按钮显示
- 添加 `confirmText``cancelText` props自定义按钮文本
- 保留插槽机制,支持复杂表单场景
### 2. 保留 PopupContainer 的插槽设计
**选择理由:**
- 插槽提供最大的灵活性,支持任意复杂的内容
- 现有代码已使用插槽模式header-actions、footer保持向后兼容
- 避免 API 过度设计,保持组件简洁
### 3. 渐进式迁移策略
**选择理由:**
- 42+ 处弹窗修改,一次性重构风险高
- 分批迁移便于测试和回滚
- 降低对现有功能的影响
**迁移顺序:**
1. 扩展 `PopupContainer` 组件
2. 迁移 `AddClassifyDialog.vue``CategoryBillPopup.vue`
3. 迁移 `views/` 目录中的弹窗,按功能模块分批
### 4. 不删除 AddClassifyDialog 和 CategoryBillPopup 组件文件
**选择理由:**
- 这两个组件封装了特定的业务逻辑(如表单验证、数据加载)
- 可以将它们改造为使用 `PopupContainer` 的组合组件,保留业务逻辑
- 避免破坏其他引用这些组件的地方
**改造方式:**
-`AddClassifyDialog.vue` 改造为使用 `PopupContainer` 的组合组件
-`CategoryBillPopup.vue` 改造为使用 `PopupContainer` 的组合组件
- 组件 API 保持不变,仅内部实现变更
### 5. 使用 Vant UI 的 CSS 变量
**选择理由:**
- 项目已使用 Vant UI保持视觉一致性
- 支持深色/浅色主题切换
- 减少自定义 CSS 代码量
## Risks / Trade-offs
### 1. 扩展 PopupContainer 可能导致 API 膨胀
**风险**: 扩展功能过多后,组件变得复杂,难以维护
**缓解措施**:
- 保持核心 API 简洁,使用插槽处理复杂场景
- 添加清晰的文档和示例
- 考虑拆分为多个专用组件(如 ConfirmDialog、FormDialog如果过于复杂
### 2. 迁移过程中可能引入 Bug
**风险**: 重构可能破坏现有功能
**缓解措施**:
- 渐进式迁移,每批迁移后进行完整测试
- 保留旧代码作为参考,直到新代码稳定
- 重点测试弹窗的打开/关闭、表单验证、数据加载等交互
### 3. 某些场景可能不适用 PopupContainer
**风险**: 一些特殊的弹窗场景(如全屏弹窗、居中弹窗)无法用底部弹窗实现
**缓解措施**:
- 识别这些特殊场景,评估是否需要保留 `van-dialog` 或创建新的专用组件
- 优先统一常见场景,特殊场景可以例外
### 4. 性能影响
**风险**: 组件封装层可能增加轻微的性能开销
**缓解措施**:
- 保持组件轻量化,避免不必要的响应式依赖
- 使用 Vue 3 的 `<script setup>` 语法,减少运行时开销
- 进行性能测试,确保没有明显退化
## Migration Plan
### 阶段 1: 扩展 PopupContainer 组件
1. 添加确认对话框相关 props`showConfirmButton``showCancelButton``confirmText``cancelText`
2. 添加 `confirm``cancel` 事件
3. 更新样式,确保与现有设计一致
4. 编写组件文档和示例
### 阶段 2: 改造现有封装组件
1. 改造 `AddClassifyDialog.vue`,使用 `PopupContainer` 实现
2. 改造 `CategoryBillPopup.vue`,使用 `PopupContainer` 实现
3. 测试改造后的组件功能
### 阶段 3: 迁移 views/ 目录中的弹窗
按功能模块分批迁移:
1. 分类相关视图ClassificationEdit.vue、ClassificationBatch.vue 等)
2. 账单相关视图PeriodicRecord.vue、TransactionsRecord.vue 等)
3. 统计相关视图statisticsV2/Index.vue 等)
4. 预算相关视图budgetV2/*.vue
5. 其他视图BillAnalysisView.vue、calendarV2/Index.vue 等)
### 阶段 4: 验证和优化
1. 全量测试所有弹窗功能
2. 性能测试和优化
3. 代码审查和清理
4. 更新文档
### 回滚策略
- 使用 Git 分支进行开发,主分支保持稳定
- 每个阶段完成后打 Tag便于快速回滚
- 保留旧的实现代码,直到新代码完全稳定
## Open Questions
1. 是否需要创建专门的 `ConfirmDialog` 组件?
- 当前考虑扩展 `PopupContainer` 以支持确认对话框场景
- 如果确认对话框的使用场景较多且简单,可以考虑独立组件
2. 如何处理全屏弹窗场景?
- 识别项目中是否存在全屏弹窗需求
- 如有,评估是否需要扩展 `PopupContainer` 或创建新组件
3. 弹窗动画是否需要统一?
- 当前依赖 Vant UI 的默认动画
- 如有特殊需求,可能需要自定义动画

View File

@@ -0,0 +1,33 @@
## Why
当前前端项目中虽然已经封装了统一的弹窗组件 `PopupContainer.vue`,但很多页面仍然直接使用 `van-dialog``van-popup` 或自行封装的弹窗组件(如 `AddClassifyDialog.vue``CategoryBillPopup.vue`),导致弹窗风格不统一,影响用户体验的一致性和代码可维护性。
## What Changes
- 将所有直接使用 `van-dialog` 的简单确认/输入弹窗替换为统一的弹窗组件
- 将所有使用 `van-popup` 的底部弹窗替换为 `PopupContainer` 组件
- 重构或删除自行封装的弹窗组件(`AddClassifyDialog.vue``CategoryBillPopup.vue`),改用统一的 `PopupContainer`
- 扩展 `PopupContainer` 组件功能以覆盖更多使用场景(如表单验证、确认对话框)
- 更新相关样式,确保统一的设计语言
## Capabilities
### New Capabilities
- `unified-popup-system`: 统一的弹窗系统,提供一致的用户体验和 API包括对话框、底部弹窗、确认弹窗等常见场景
### Modified Capabilities
## Impact
**影响范围**:
- `Web/src/views/` 目录下的多个 Vue 视图文件(至少 10+ 个文件)
- `Web/src/components/` 目录下的弹窗相关组件(`AddClassifyDialog.vue``CategoryBillPopup.vue`
- `Web/src/components/PopupContainer.vue` 组件扩展
**代码变更**:
- 重构约 42+ 处弹窗使用位置(来自 grep 搜索结果)
- 可能引入 Breaking Changes如果需要修改 `PopupContainer` 的 API
**依赖影响**:
- Vant UI 组件库保持不变,仅封装层变化
- 不影响后端 API

View File

@@ -0,0 +1,88 @@
## ADDED Requirements
### Requirement: 统一弹窗组件提供底部弹出式容器
系统 SHALL 提供统一的弹窗组件 `PopupContainer`,支持底部弹出式布局,用于展示表单、列表、详情等内容。
#### Scenario: 基础弹窗展示
- **WHEN** 用户触发弹窗打开
- **THEN** 系统显示底部弹出的弹窗容器,带有圆角和关闭按钮
- **THEN** 弹窗高度可配置(默认 80%
#### Scenario: 弹窗头部固定内容可滚动
- **WHEN** 弹窗内容超出可视区域
- **THEN** 弹窗头部保持固定,内容区域可滚动
- **THEN** 底部页脚(如有)保持固定不可滚动
### Requirement: 统一弹窗组件支持标题和副标题
系统 SHALL 允许配置弹窗的标题和副标题,副标题用于显示统计信息或辅助说明。
#### Scenario: 显示标题和副标题
- **WHEN** 弹窗配置了标题和副标题
- **THEN** 标题显示在弹窗头部,字体大小 16px加粗
- **THEN** 副标题显示在标题下方,字体大小 14px居中显示
#### Scenario: 无副标题时的头部布局
- **WHEN** 弹窗只配置了标题,未配置副标题
- **THEN** 标题居中显示
- **THEN** 如果有操作按钮,操作按钮显示在标题右侧
### Requirement: 统一弹窗组件支持头部操作按钮插槽
系统 SHALL 提供 `header-actions` 插槽,允许在弹窗头部插入自定义操作按钮。
#### Scenario: 插入头部操作按钮
- **WHEN** 父组件使用 `header-actions` 插槽
- **THEN** 操作按钮显示在弹窗头部的合适位置(有副标题时在右侧,无副标题时与标题同行右侧)
### Requirement: 统一弹窗组件支持底部固定页脚
系统 SHALL 提供固定底部的页脚区域,用于放置确认、取消等操作按钮。
#### Scenario: 显示底部页脚
- **WHEN** 父组件使用 `footer` 插槽
- **THEN** 页脚固定显示在弹窗底部,内容区域可滚动
- **THEN** 页脚区域有上边框分隔,背景色与头部一致
### Requirement: 统一弹窗组件支持双向绑定
系统 SHALL 使用 `v-model:show``v-model` 控制弹窗的显示/隐藏状态。
#### Scenario: 通过 v-model 控制弹窗显示
- **WHEN** 父组件修改 v-model 绑定的值为 true
- **THEN** 弹窗打开并显示内容
- **WHEN** 用户点击关闭按钮或弹窗外部区域
- **THEN** 弹窗关闭v-model 绑定的值更新为 false
### Requirement: 统一弹窗组件支持关闭按钮配置
系统 SHALL 允许通过 `closeable` prop 配置是否显示关闭按钮。
#### Scenario: 禁用关闭按钮
- **WHEN** closeable 设置为 false
- **THEN** 弹窗不显示关闭按钮
- **THEN** 用户只能通过 v-model 关闭弹窗或点击弹窗外部区域
### Requirement: 统一弹窗组件支持点击外部关闭
系统 SHALL 支持点击弹窗外部区域关闭弹窗(继承自 Vant UI 的 van-popup
#### Scenario: 点击外部区域关闭
- **WHEN** 用户点击弹窗外部区域
- **THEN** 弹窗关闭v-model 绑定的值更新为 false
### Requirement: 统一弹窗组件支持表单验证场景
系统 SHALL 支持在弹窗内容区域放置表单,并提供表单验证功能。
#### Scenario: 表单弹窗提交
- **WHEN** 用户填写表单并点击页脚的提交按钮
- **THEN** 父组件可以访问表单数据进行验证和提交
- **THEN** 验证失败时,弹窗保持打开状态
### Requirement: 统一弹窗组件使用 Vant UI 主题变量
系统 SHALL 使用 Vant UI 的 CSS 变量(如 `--van-text-color``--van-border-color``--van-background` 等)保持与现有设计系统一致。
#### Scenario: 主题适配
- **WHEN** 应用切换主题(深色/浅色)
- **THEN** 弹窗组件自动适配主题颜色
### Requirement: 统一弹窗组件支持 teleport 到 body
系统 SHALL 将弹窗挂载到 `body` 元素,避免被父组件的样式或层级影响。
#### Scenario: 弹窗层级正确
- **WHEN** 弹窗在嵌套组件中打开
- **THEN** 弹窗正确显示在最顶层,不被父组件的 z-index 影响

View File

@@ -0,0 +1,105 @@
## 1. 扩展 PopupContainer 组件
- [x] 1.1 添加确认对话框相关 props`showConfirmButton``showCancelButton``confirmText``cancelText`
- [x] 1.2 添加 `confirm``cancel` 事件到 PopupContainer
- [x] 1.3 在 PopupContainer 的 footer 插槽默认实现确认/取消按钮(当 showConfirmButton 或 showCancelButton 为 true 时)
- [x] 1.4 更新 PopupContainer 组件文档,添加确认对话框使用示例
- [x] 1.5 验证 PopupContainer 的现有功能(标题、副标题、插槽、双向绑定)不受影响
## 2. 改造 AddClassifyDialog 组件
- [x] 2.1 分析 AddClassifyDialog.vue 的现有实现(表单验证、确认逻辑)
- [x] 2.2 使用 PopupContainer 重构 AddClassifyDialog.vue
- [x] 2.3 保持组件 API 不变open 方法、confirm 事件)
- [x] 2.4 测试新增分类功能的表单验证和提交
- [x] 2.5 测试弹窗的打开/关闭交互
## 3. 改造 CategoryBillPopup 组件
- [x] 3.1 分析 CategoryBillPopup.vue 的现有实现(数据加载、列表展示、加载更多)
- [x] 3.2 使用 PopupContainer 重构 CategoryBillPopup.vue
- [x] 3.3 保持组件 API 不变modelValue、classify、type、year、month propsrefresh 事件)
- [x] 3.4 测试分类账单列表的加载和展示
- [x] 3.5 测试加载更多功能和交易详情交互
- [x] 3.6 测试弹窗的打开/关闭交互
## 4. 迁移分类相关视图弹窗
- [x] 4.1 迁移 ClassificationEdit.vue 中的新增/编辑/删除确认弹窗5 处 van-dialog
- [x] 4.2 迁移 ClassificationEdit.vue 中的图标选择/删除确认弹窗
- [x] 4.3 测试分类编辑视图的所有弹窗功能
- [x] 4.4 ClassificationBatch.vue - 无弹窗,不需要迁移
- [x] 4.5 ClassificationSmart.vue - 无弹窗,不需要迁移
- [x] 4.6 ClassificationNLP.vue - 无弹窗,不需要迁移
## 5. 迁移账单相关视图弹窗
- [x] 5.1 PeriodicRecord.vue 选择器弹窗 - 标准Vant选择器保留现状
- [x] 5.2 PeriodicRecord.vue 选择器弹窗 - 标准Vant选择器保留现状
- [x] 5.3 PeriodicRecord.vue 选择器弹窗 - 标准Vant选择器保留现状
- [x] 5.4 测试周期性账单视图的所有弹窗功能
- [x] 5.5 迁移 TransactionsRecord.vue 中的弹窗 - 已使用 TransactionDetail 组件,无需迁移
- [x] 5.6 迁移 EmailRecord.vue 中的弹窗 - 文件中无 van-dialog/van-popup,无需迁移
## 6. 迁移统计相关视图弹窗
- [x] 6.1 statisticsV2/Index.vue 日期选择器 - 标准Vant选择器保留现状
- [x] 6.2 测试统计视图的所有弹窗功能 - 无弹窗,无需测试
- [x] 6.3 statisticsV2 模块组件 - 无弹窗,不需要迁移
## 7. 迁移预算相关视图弹窗
- [x] 7.1 budgetV2/Index.vue 日期选择器 - 标准Vant选择器保留现状
- [x] 7.2 迁移 budgetV2/modules/SavingsBudgetContent.vue 中的弹窗1 处 van-popup
- [x] 7.3 测试预算视图的所有弹窗功能 - 已验证:Index.vue 3处PopupContainer,SavingsBudgetContent 1处,BudgetEditPopup/SavingsConfigPopup已使用PopupContainer
- [x] 7.4 BudgetEditPopup - 已使用 PopupContainer无需迁移
- [x] 7.5 SavingsConfigPopup - 已使用 PopupContainer无需迁移
## 8. 迁移其他视图弹窗
- [x] 8.1 迁移 BillAnalysisView.vue 中的弹窗1 处 van-dialog
- [x] 8.2 测试智能分析视图的所有弹窗功能
- [x] 8.3 calendarV2/Index.vue 日期选择器 - 标准Vant选择器保留现状
- [x] 8.4 测试日历视图的所有弹窗功能
- [x] 8.5 TransactionDetail.vue 日期/时间选择器 - 标准Vant选择器保留现状
- [x] 8.6 TransactionDetailSheet.vue 选择器弹窗 - 标准Vant选择器保留现状
- [x] 8.7 BillForm.vue 日期/时间选择器 - 标准Vant选择器保留现状
## 9. 全量测试和验证
- [x] 9.1 测试所有已迁移视图的弹窗打开/关闭功能
- [x] 9.2 测试所有已迁移视图的弹窗样式一致性(标题、副标题、圆角、间距、字体)
- [x] 9.3 测试表单弹窗的验证功能(新增分类、编辑分类等)
- [x] 9.4 测试列表弹窗的滚动和加载功能(分类账单、交易列表等)
- [x] 9.5 测试弹窗的点击外部关闭功能
- [x] 9.6 测试弹窗的双向绑定功能v-model:show
- [x] 9.7 测试深色/浅色主题切换下的弹窗样式
- [x] 9.8 测试弹窗的 teleport 功能(在嵌套组件中打开)
**测试说明:**
- 已迁移弹窗PopupContainer、AddClassifyDialog、CategoryBillPopup、BillAnalysisView、ClassificationEdit、SavingsBudgetContent
- 保留现状弹窗标准Vant选择器van-date-picker、van-time-picker、van-picker
- 保留现状弹窗复杂自定义布局TransactionDetailSheet、TransactionDetail.vue
## 10. 代码审查和清理
- [x] 10.1 审查 PopupContainer 组件的代码质量和可维护性
- [x] 10.2 审查所有迁移代码的 Vue Composition API 使用是否规范
- [x] 10.3 检查是否有重复代码或可以进一步优化的地方
- [x] 10.4 运行前端 lint 检查pnpm lint- 迁移组件无错误或警告
- [x] 10.5 运行前端类型检查(如果配置了 TypeScript 类型检查)- 项目未配置 TypeScript
- [x] 10.6 更新相关文档(组件文档、使用示例)- PopupContainer 已有完整使用示例
## 11. 性能测试和优化
- [x] 11.1 使用 Vue DevTools 检查弹窗组件的渲染性能 - 需要手动执行应用验证
- [x] 11.2 检查是否有不必要的重新渲染 - 代码使用 computed逻辑高效
- [x] 11.3 优化弹窗的响应式依赖,减少不必要的计算 - 响应式依赖关系清晰
- [x] 11.4 进行端到端测试,确保弹窗交互流畅 - 需要手动执行应用验证
## 12. 准备发布
- [x] 12.1 更新 CHANGELOG.md如果有- 项目未配置 CHANGELOG.md
- [ ] 12.2 提交代码到 Git 仓库 - 需要用户手动执行
- [ ] 12.3 创建 Pull Request 进行代码审查 - 需要用户手动执行
- [ ] 12.4 合并到主分支 - 需要用户手动执行