fix
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
@@ -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` 确认影响范围
|
||||
@@ -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` 的数据一致性
|
||||
@@ -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, 期间内外)
|
||||
@@ -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 返回到存款计划卡片视图
|
||||
@@ -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 如果用户要求,推送到远程仓库
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
@@ -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
|
||||
|
||||
无待解决的开放问题。设计已明确技术方案和实现路径。
|
||||
@@ -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`: 使用统一的提示词服务
|
||||
|
||||
**数据库**:
|
||||
- 无表结构变更(仅更新分类记录的图标字段为空)
|
||||
@@ -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** 提示词建议使用通用的图标隐喻(如钱包、硬币等)
|
||||
@@ -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 准备部署文档和回滚计划
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
@@ -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 文档或进行技术验证。
|
||||
- 现有分类中是否有语义特别抽象的分类,需要特殊处理?(需要统计分类名称分析)
|
||||
- 用户对图标风格的偏好是否有特定趋势?(可以通过历史用户行为数据或调研获取)
|
||||
@@ -0,0 +1,25 @@
|
||||
## Why
|
||||
|
||||
当前 AI 生成的分类图标过于复杂,用户无法直观看出图标与分类的对应关系,使用体验不佳。需要优化提示词,使生成的图标更简约、更清晰。
|
||||
|
||||
## What Changes
|
||||
|
||||
- **优化分类图标生成的 AI 提示词**
|
||||
- 调整图标生成风格,确保简约易懂
|
||||
- 建立图标与分类名称的明确视觉关联
|
||||
- 提升图标生成的一致性和可识别性
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `ai-category-icon-generation`: AI 驱动的分类图标生成能力,支持简约风格的图标生成,确保图标与分类名称的视觉关联性
|
||||
|
||||
### Modified Capabilities
|
||||
- (无)
|
||||
|
||||
## Impact
|
||||
|
||||
- **受影响的服务**: AI 图标生成服务(可能涉及 Service 层)
|
||||
- **配置变更**: AI 提示词配置(可能涉及 Application 层的配置管理)
|
||||
- **前端交互**: 分类图标展示(可能需要调整图标展示样式)
|
||||
- **用户体验**: 提升分类图标可识别度,改善整体使用体验
|
||||
@@ -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 记录每次提示词迭代的版本号和时间戳
|
||||
@@ -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 和类型检查,确保代码质量
|
||||
2
openspec/changes/fix-budget-and-ui-bugs/.openspec.yaml
Normal file
2
openspec/changes/fix-budget-and-ui-bugs/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
143
openspec/changes/fix-budget-and-ui-bugs/design.md
Normal file
143
openspec/changes/fix-budget-and-ui-bugs/design.md
Normal 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 测试**: 当前只计划单元测试,是否需要添加端到端测试覆盖完整流程?
|
||||
42
openspec/changes/fix-budget-and-ui-bugs/proposal.md
Normal file
42
openspec/changes/fix-budget-and-ui-bugs/proposal.md
Normal 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}` 响应结构变更(新增字段,向后兼容)
|
||||
|
||||
**依赖**:
|
||||
- 无外部依赖变更
|
||||
@@ -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** 按钮可以正常响应点击事件
|
||||
@@ -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`
|
||||
@@ -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 组件相关的警告或错误
|
||||
65
openspec/changes/fix-budget-and-ui-bugs/tasks.md
Normal file
65
openspec/changes/fix-budget-and-ui-bugs/tasks.md
Normal 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 验证控制台无错误或警告
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
24
openspec/changes/fix-january-2026-budget-usage/proposal.md
Normal file
24
openspec/changes/fix-january-2026-budget-usage/proposal.md
Normal 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` 值
|
||||
- **用户体验**:修复后,用户查看月度预算统计时,将看到包含所有实际支出的准确数据
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-15
|
||||
260
openspec/changes/refactor-bill-list-component/design.md
Normal file
260
openspec/changes/refactor-bill-list-component/design.md
Normal 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` → **拒绝**:迁移期间需要并存,且容易引起冲突
|
||||
|
||||
### 决策 2:Props 设计(高内聚 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?当前设计不持久化,每次刷新恢复默认。
|
||||
42
openspec/changes/refactor-bill-list-component/proposal.md
Normal file
42
openspec/changes/refactor-bill-list-component/proposal.md
Normal 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 选择模式)
|
||||
@@ -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** 删除按钮显示加载状态,防止重复点击
|
||||
@@ -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
|
||||
135
openspec/changes/refactor-bill-list-component/tasks.md
Normal file
135
openspec/changes/refactor-bill-list-component/tasks.md
Normal 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 创建筛选栏 UI(van-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 传递必要的 props(showCheckbox, 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 最终代码审查
|
||||
@@ -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 和最终验证
|
||||
|
||||
2
openspec/changes/unified-popup-component/.openspec.yaml
Normal file
2
openspec/changes/unified-popup-component/.openspec.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-14
|
||||
163
openspec/changes/unified-popup-component/design.md
Normal file
163
openspec/changes/unified-popup-component/design.md
Normal 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 的默认动画
|
||||
- 如有特殊需求,可能需要自定义动画
|
||||
33
openspec/changes/unified-popup-component/proposal.md
Normal file
33
openspec/changes/unified-popup-component/proposal.md
Normal 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
|
||||
@@ -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 影响
|
||||
105
openspec/changes/unified-popup-component/tasks.md
Normal file
105
openspec/changes/unified-popup-component/tasks.md
Normal 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 props,refresh 事件)
|
||||
- [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 合并到主分支 - 需要用户手动执行
|
||||
Reference in New Issue
Block a user