1
Some checks failed
Docker Build & Deploy / Build Docker Image (push) Waiting to run
Docker Build & Deploy / Deploy to Production (push) Has been cancelled
Docker Build & Deploy / Cleanup Dangling Images (push) Has been cancelled
Docker Build & Deploy / WeChat Notification (push) Has been cancelled

This commit is contained in:
SunCheng
2026-02-18 21:16:45 +08:00
parent 77c9b47246
commit c49f66757e
116 changed files with 6909 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
## MODIFIED Requirements
### Requirement: AI生成分类图标
**Reason**: 原AI生成SVG图标方案不够直观生成的图标与分类名称不匹配影响用户体验。改为使用Iconify API检索真实图标库。
系统SHALL能够根据分类名称生成搜索关键字并允许用户从Iconify图标库中选择图标。
#### Scenario: 生成搜索关键字
- **WHEN** 系统接收到分类名称
- **THEN** 系统SHALL使用AI生成3-5个相关英文搜索关键字
- **THEN** 系统SHALL将搜索关键字保存到TransactionCategory.IconKeywords字段
#### Scenario: 用户选择图标
- **WHEN** 用户从Iconify图标列表中选择一个图标
- **THEN** 系统SHALL将Iconify标识符如"mdi:home"保存到TransactionCategory.Icon字段
#### Scenario: 前端图标渲染
- **WHEN** 前端接收到图标标识符
- **THEN** 前端SHALL使用Iconify图标组件渲染`<span class="iconify" data-icon="mdi:home"></span>`
- **THEN** 前端不需要额外的npm包直接使用Iconify CDN

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,130 @@
## REMOVED Requirements
### Requirement: Get Uncovered Categories
**Reason**: 该接口仅被 V1 预算页面使用,用于展示"未设置预算的分类"。V2 预算页面不包含此功能。
**Migration**: 如需在 V2 中实现类似功能,应重新设计并创建新接口。当前无直接迁移路径。
**原有功能**:
- **接口**: `GET /api/Budget/GetUncoveredCategories`
- **Controller**: `BudgetController.GetUncoveredCategoriesAsync`
- **参数**: `category` (enum: Expense/Income/Saving), `date` (DateTime)
- **返回**: `List<string>` (未设置预算的分类名称列表)
- **业务逻辑**:
1. 查询指定月份的所有交易记录,提取所有出现的分类
2. 查询指定月份已设置预算的分类
3. 计算差集,返回"有交易但未设置预算"的分类列表
**被以下代码调用**:
- `Web/src/views/BudgetView.vue` 中的 `fetchUncoveredCategories` 方法
- `Web/src/api/budget.js` 中的 `getUncoveredCategories` 函数
---
### Requirement: Get Archive Summary
**Reason**: 该接口仅被 V1 预算页面使用,用于展示"历史预算归档总结"。V2 预算页面不包含此功能。
**Migration**: 如需在 V2 中实现类似功能,应重新设计并创建新接口。当前无直接迁移路径。
**原有功能**:
- **接口**: `GET /api/Budget/GetArchiveSummary`
- **Controller**: `BudgetController.GetArchiveSummaryAsync`
- **参数**: `date` (DateTime, 用于指定查询的年月)
- **返回**: `ArchiveSummaryDto` (包含归档总结数据)
- **业务逻辑**:
1. 查询 `BudgetArchive` 表中指定月份的归档记录
2. 汇总支出、收入、存款的预算执行情况
3. 返回总结数据(如预算达成率、超支分类等)
**被以下代码调用**:
- `Web/src/views/BudgetView.vue` 中的 `showArchiveSummary` 方法
- `Web/src/api/budget.js` 中的 `getArchiveSummary` 函数
---
## Context
本规范定义了 EmailBill 后端 `BudgetController` 中两个 V1 专用接口的移除操作。
### 接口背景
这两个接口是 V1 预算页面的特色功能:
1. **未覆盖分类提示**: 帮助用户发现"有交易但未设置预算"的分类,提醒用户完善预算设置
2. **归档总结**: 展示历史月份的预算执行总结,帮助用户回顾过去的财务状况
### V2 设计变更
V2 预算页面重新设计了用户体验,移除了上述两个功能:
- **未覆盖分类**: V2 采用"按需创建预算"模式,不主动提示未覆盖分类
- **归档总结**: V2 使用实时统计替代归档总结,用户可随时查看任意月份的预算执行情况
### 技术依赖
这两个接口依赖以下 Service 和 Repository
- `BudgetService.GetUncoveredCategoriesAsync`
- `BudgetService.GetArchiveSummaryAsync`
- `BudgetRepository`
- `BudgetArchiveRepository`
- `TransactionRecordRepository`
移除接口后,相关 Service 方法也将被移除(见 `budget-service` 规范)。
---
## Validation
### 验证标准
1. **代码搜索验证**:
- 全局搜索 `GetUncoveredCategories`,确认仅在以下位置出现:
- `BudgetController.GetUncoveredCategoriesAsync` (待删除)
- `BudgetApplication.GetUncoveredCategoriesAsync` (待删除)
- `BudgetService.GetUncoveredCategoriesAsync` (待删除)
- `BudgetView.vue` (已删除)
- `budget.js` (已清理)
- 全局搜索 `GetArchiveSummary`,确认仅在 V1 相关代码中出现
2. **编译验证**:
- 删除 `BudgetController` 中的两个方法后,后端项目编译通过
- 删除 `BudgetApplication``BudgetService` 中的对应方法后,编译通过
3. **API 文档验证**:
- Swagger/Scalar 文档中不再显示以下端点:
- `/api/Budget/GetUncoveredCategories`
- `/api/Budget/GetArchiveSummary`
4. **运行时验证**:
- 前端调用上述端点返回 404
- V2 预算页面 (`/budget-v2`) 正常加载和操作,不受影响
---
## Dependencies
移除这两个接口的前置条件:
1. `BudgetView.vue` (V1 预算页面) 已删除
2. `Web/src/api/budget.js` 中的 `getUncoveredCategories``getArchiveSummary` 方法已清理
3. V2 预算页面已验证不依赖这两个接口
移除后连带删除:
- `BudgetApplication.GetUncoveredCategoriesAsync`
- `BudgetApplication.GetArchiveSummaryAsync`
- `BudgetService.GetUncoveredCategoriesAsync`
- `BudgetService.GetArchiveSummaryAsync`
移除后不影响:
- 其他预算相关接口 (`GetList`, `GetCategoryStats`, `Create`, `Update`, `Delete` 等)
- `BudgetRepository``BudgetArchiveRepository` 的查询逻辑 (仍被其他接口使用)
- V2 预算页面的任何功能
---
## Notes
### 功能对比表
| 功能 | V1 实现 | V2 实现 |
|------|---------|---------|
| **未覆盖分类提示** | 专用接口 `GetUncoveredCategories` | 无(按需创建预算) |
| **归档总结** | 专用接口 `GetArchiveSummary` | 实时统计 `GetCategoryStats` |
| **预算列表** | `GetList` | `GetList` (共用) |
| **分类统计** | `GetCategoryStats` | `GetCategoryStats` (共用) |
### 潜在影响
如果未来需要在 V2 中恢复"未覆盖分类"或"归档总结"功能:
1. **不能直接恢复删除的代码**,因为业务逻辑可能已过时
2. **应重新设计接口**,考虑 V2 的数据模型和用户体验
3. **建议先调研用户需求**,确认是否真的需要这些功能

View File

@@ -0,0 +1,49 @@
## Purpose
定义预算页面仪表图的布局展示规范,确保图表正确居中显示,无错位问题。
## Requirements
### Requirement: 仪表图容器布局
预算仪表图 SHALL 在容器内正确居中显示,无错位。
#### Scenario: 垂直居中展示
- **WHEN** 用户查看预算页面的仪表图
- **THEN** 仪表图 SHALL 在容器内垂直居中
- **AND** SHALL 在容器内水平居中
- **AND** 与上下其他元素 SHALL 保持适当间距16px
#### Scenario: 响应式布局
- **WHEN** 用户在不同屏幕尺寸下查看仪表图
- **THEN** 仪表图 SHALL 保持居中不偏移
- **AND** 容器高度 SHALL 自适应确保图表完整显示
### Requirement: 页面错误处理
预算页面 SHALL 正确加载并显示仪表图,无运行时错误。
#### Scenario: 正常加载
- **WHEN** 用户访问预算页面
- **THEN** 页面 SHALL 无 JavaScript 错误
- **AND** 仪表图 SHALL 正常渲染
- **AND** 所有交互功能 SHALL 正常工作
#### Scenario: 错误边界处理
- **WHEN** 仪表图组件发生异常
- **THEN** 系统 SHALL 捕获错误并显示友好提示
- **AND** SHALL 不阻塞页面其他功能
### Requirement: 仪表盘信息展示约束
预算仪表盘 SHALL 仅展示已用、预算、余额三项核心信息,且不出现颠倒文字或额外数字。
#### Scenario: 仅展示核心信息
- **WHEN** 系统渲染预算仪表盘
- **THEN** 仪表盘中心区域 SHALL 仅显示已用、预算、余额
- **AND** SHALL 不出现颠倒文字或额外数字
### Requirement: 仪表盘弧度范围
预算仪表盘弧形 SHALL 略大于半圆但不形成全圆。
#### Scenario: 弧度扩展显示
- **WHEN** 系统渲染预算仪表盘
- **THEN** 仪表盘弧形 SHALL 超过 180°
- **AND** SHALL 保持非全圆形态

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,73 @@
## MODIFIED Requirements
### Requirement: Budget gauge charts must display health status
The system SHALL render monthly and yearly budget health using gauge (semi-circle) charts showing current usage vs limit.
#### Scenario: Monthly gauge shows expense usage
- **WHEN** user views expense budget analysis
- **THEN** monthly gauge displays current expense / monthly limit as a percentage with remaining balance in center
#### Scenario: Monthly gauge shows income progress
- **WHEN** user views income budget analysis
- **THEN** monthly gauge displays current income / monthly target as a percentage with shortfall/excess in center
#### Scenario: Yearly gauge shows expense usage
- **WHEN** user views expense budget analysis
- **THEN** yearly gauge displays current expense / yearly limit as a percentage with remaining balance in center
#### Scenario: Yearly gauge shows income progress
- **WHEN** user views income budget analysis
- **THEN** yearly gauge displays current income / yearly target as a percentage with shortfall/excess in center
#### Scenario: Gauge changes color when exceeding limit
- **WHEN** expense usage exceeds 100% of budget
- **THEN** gauge arc color changes to red (var(--van-danger-color))
#### Scenario: Gauge changes color when exceeding income target
- **WHEN** income exceeds 100% of target
- **THEN** gauge arc color changes to green (var(--van-success-color))
### Requirement: Budget variance chart must show category-level differences
The system SHALL render a horizontal bar chart comparing actual vs budgeted amounts for each category.
#### Scenario: Variance chart displays all categories
- **WHEN** user has multiple budget categories
- **THEN** chart shows horizontal bars for each category with actual (solid) and budget (dashed) values
#### Scenario: Variance chart highlights overbudget categories
- **WHEN** a category's actual exceeds budget
- **THEN** the bar is colored red and labeled with overage amount
#### Scenario: Variance chart shows underbudget categories
- **WHEN** a category's actual is below budget
- **THEN** the bar is colored green and labeled with remaining amount
### Requirement: Budget burndown chart must track daily spending trend
The system SHALL render line charts showing cumulative spending vs ideal pace for monthly and yearly periods.
#### Scenario: Monthly burndown chart shows ideal vs actual
- **WHEN** user views monthly burndown
- **THEN** chart displays two lines: ideal linear spending and actual cumulative spending
#### Scenario: Monthly burndown projects month-end total
- **WHEN** current date is mid-month
- **THEN** chart shows projected month-end total based on current pace (dotted line extension)
#### Scenario: Yearly burndown chart shows ideal vs actual
- **WHEN** user views yearly burndown
- **THEN** chart displays two lines: ideal linear spending and actual cumulative spending by month
#### Scenario: Yearly burndown highlights current month
- **WHEN** user views yearly burndown
- **THEN** chart highlights the current month's data point with a larger marker
### Requirement: Charts must maintain existing interaction behavior
The system SHALL preserve all existing click, tooltip, and zoom interactions from the ECharts implementation.
#### Scenario: Chart tooltip shows on hover/tap
- **WHEN** user hovers over (desktop) or taps (mobile) a data point
- **THEN** tooltip displays formatted value with label
#### Scenario: Chart updates when switching budget type
- **WHEN** user switches between expense/income/savings tabs
- **THEN** all charts update their data and labels within 300ms

View File

@@ -0,0 +1,21 @@
## ADDED Requirements
### Requirement: Line chart date range truncation
The line chart SHALL display data only up to the current date, not the full natural period.
#### Scenario: Monthly view shows data to current day
- **GIVEN** today is the 15th of the month
- **WHEN** the user views the monthly statistics chart
- **THEN** the chart SHALL display data from the 1st to the 15th only
- **AND** the X-axis SHALL NOT show dates beyond the current day
#### Scenario: Weekly view shows data to current day
- **GIVEN** today is Wednesday
- **WHEN** the user views the weekly statistics chart
- **THEN** the chart SHALL display data from Monday to Wednesday only
- **AND** no flat line segments SHALL appear for future dates
#### Scenario: Yearly view shows data to current month
- **GIVEN** today is in June
- **WHEN** the user views the yearly statistics chart
- **THEN** the chart SHALL display data from January to June only

View File

@@ -0,0 +1,25 @@
## ADDED Requirements
### Requirement: 饼图不直接显示扇区文字
系统 SHALL 禁止在饼图扇区内直接绘制分类名称文字。
#### Scenario: 默认不显示扇区文字
- **WHEN** 系统渲染支出分类饼图
- **THEN** 扇区内 SHALL 不绘制分类名称文字
- **AND** 分类信息 SHALL 通过列表或图例展示
### Requirement: 仪表盘仅展示三项核心信息
预算仪表盘 SHALL 仅展示已用、预算、余额三项核心信息,且文字方向正确。
#### Scenario: 移除多余与颠倒文本
- **WHEN** 系统渲染预算仪表盘
- **THEN** 仪表盘中心区域 SHALL 仅显示已用、预算、余额
- **AND** SHALL 不出现颠倒文字或额外数字
### Requirement: 仪表盘弧度略大于半圆
预算仪表盘的弧形范围 SHALL 略大于半圆但不形成全圆。
#### Scenario: 弧度扩展显示
- **WHEN** 系统渲染预算仪表盘
- **THEN** 仪表盘弧形 SHALL 超过 180°
- **AND** SHALL 保持非全圆形态

View File

@@ -0,0 +1,21 @@
## ADDED Requirements
### Requirement: Pie chart direct category labeling
The pie chart SHALL display category names directly on or adjacent to their corresponding sectors.
#### Scenario: Category labels rendered on pie sectors
- **WHEN** the expense category pie chart is displayed
- **THEN** each sector SHALL display its category name as a label
- **AND** the label SHALL be positioned to not obscure the sector
#### Scenario: Labels adapt to sector size
- **GIVEN** a category represents less than 5% of total expenses
- **WHEN** the pie chart renders
- **THEN** the label for that small sector MAY be hidden to avoid clutter
- **AND** the category SHALL still be identifiable via tooltip on hover
#### Scenario: Label visibility in dark mode
- **GIVEN** the application is in dark mode
- **WHEN** the pie chart displays labels on sectors
- **THEN** the label text color SHALL provide sufficient contrast against the sector color
- **AND** labels SHALL remain readable against both light and dark sector colors

View File

@@ -0,0 +1,14 @@
## ADDED Requirements
### Requirement: Chart container boundary enforcement
The chart SHALL be fully contained within its parent card container without overflow.
#### Scenario: Chart renders within card boundaries
- **WHEN** the statistics page displays a line chart in a card component
- **THEN** the chart canvas SHALL NOT extend beyond the card's padding boundaries
- **AND** the chart SHALL adapt to container resize events
#### Scenario: Chart adapts to mobile viewport
- **WHEN** the viewport width is less than 375px
- **THEN** the chart SHALL scale down proportionally
- **AND** no horizontal scrolling SHALL be required to view the full chart

View File

@@ -0,0 +1,48 @@
## Purpose
定义 Chart.js 图表中文文本编码与字体配置规范,确保跨平台显示清晰无乱码。
## Requirements
### Requirement: 图表文本编码修复
Chart.js 图表 SHALL 正确显示中文文本,不出现乱码或异常字符。
#### Scenario: 饼图标签中文显示
- **WHEN** 系统渲染支出分类饼图,分类名称为中文
- **THEN** 分类标签 SHALL 正确显示中文字符
- **AND** 标签文字 SHALL 清晰可读,无乱码
#### Scenario: Tooltip 中文显示
- **WHEN** 用户 hover 到图表数据点
- **THEN** Tooltip SHALL 正确显示中文内容
- **AND** 金额和分类名称 SHALL 无乱码
#### Scenario: 中心文本中文显示
- **WHEN** 仪表盘图表渲染中心文本
- **THEN** 中心显示的余额/超支文本 SHALL 正确显示中文
- **AND** 文字 SHALL 清晰无乱码
### Requirement: 图表字体配置
Chart.js 配置 SHALL 使用兼容的字体设置,确保跨平台文本正确渲染。
#### Scenario: 字体族配置
- **WHEN** 图表初始化时
- **THEN** 系统 SHALL 配置 `font.family` 为兼容中文字体的字体栈(如 `'PingFang SC', 'Microsoft YaHei', sans-serif`
#### Scenario: 响应式字体大小
- **WHEN** 图表在不同尺寸屏幕上渲染
- **THEN** 字体大小 SHALL 根据屏幕尺寸自动调整
- **AND** 文字 SHALL 始终保持清晰可读
### Requirement: Tooltip 格式化修复
Tooltip 回调函数 SHALL 正确处理文本编码和格式化。
#### Scenario: Tooltip Label 格式化
- **WHEN** Tooltip 显示数据标签
- **THEN** 回调函数 SHALL 返回正确编码的字符串
- **AND** 特殊字符 SHALL 正确转义
#### Scenario: 金额格式化显示
- **WHEN** Tooltip 显示金额
- **THEN** 金额格式 SHALL 为 "¥XXX.XX"
- **AND** 货币符号 SHALL 正确显示

View File

@@ -0,0 +1,70 @@
---
title: 图表主题系统规格
author: AI Assistant
date: 2026-02-16
---
## ADDED Requirements
### Requirement: 图表类型感知配置
`useChartTheme` 组合式函数 SHALL 根据图表类型自动提供合适的默认配置。
#### Scenario: 饼图/环形图自动隐藏坐标轴
- **WHEN** 调用 `getChartOptionsByType('doughnut', customOptions)`
- **THEN** 返回的配置中 `scales.x.display``scales.y.display` 均为 `false`
- **AND** 返回的配置 SHALL 与 customOptions 深度合并
#### Scenario: 折线图/柱状图保留简化坐标轴
- **WHEN** 调用 `getChartOptionsByType('line', customOptions)`
- **THEN** 返回的配置包含简化的坐标轴样式
- **AND** 网格线使用 `--van-border-color` 30% 透明度
- **AND** 刻度标签字体大小为 10px
### Requirement: 现代化配色方案
图表主题系统 SHALL 提供符合现代审美的配色方案。
#### Scenario: 主色板包含 8 个颜色
- **WHEN** 访问 `chartPalette`
- **THEN** 返回包含 8 个颜色的数组
- **AND** 颜色 SHALL 从 Vant 主题色派生并降低 20% 饱和度
#### Scenario: 支出/收入颜色区分
- **WHEN** 配置支出相关图表
- **THEN** 默认使用暖色调(橙红系)
- **WHEN** 配置收入相关图表
- **THEN** 默认使用冷色调(青绿系)
### Requirement: 暗色模式适配
图表 SHALL 自动适配 Vant UI 的暗色模式。
#### Scenario: 暗色模式颜色切换
- **WHEN** Vant 主题切换为暗色模式
- **THEN** 图表文本颜色 SHALL 自动变为浅色
- **AND** 图表背景色 SHALL 与卡片背景一致
- **AND** 网格线颜色 SHALL 变为深色系的边框色
#### Scenario: 手动颜色获取
- **WHEN** 调用 `colors.text`
- **THEN** 返回当前主题的文本颜色 CSS 变量值
- **AND** SHALL 实时响应主题切换
### Requirement: 动画配置
图表 SHALL 支持可配置的动画效果。
#### Scenario: 默认动画配置
- **WHEN** 获取图表配置
- **THEN** 默认动画持续时间为 600ms
- **AND** 缓动函数为 `easeOutQuart`
#### Scenario: 减少动画偏好
- **WHEN** 用户系统偏好 `prefers-reduced-motion: reduce`
- **THEN** 动画持续时间 SHALL 自动设为 0
- **AND** 图表 SHALL 立即渲染完成
## MODIFIED Requirements
无修改的现有需求。
## REMOVED Requirements
无删除的需求。

View File

@@ -0,0 +1,71 @@
## ADDED Requirements
### Requirement: Chart.js must be integrated with Vue 3 Composition API
The system SHALL use vue-chartjs 5.x to integrate Chart.js with Vue 3 components using the Composition API pattern.
#### Scenario: Chart component renders successfully
- **WHEN** a component imports and uses vue-chartjs chart components
- **THEN** the chart renders correctly in the DOM without console errors
#### Scenario: Chart updates reactively
- **WHEN** the chart's data prop changes
- **THEN** the chart re-renders with the new data using Chart.js update mechanism
### Requirement: Theme system must support Vant UI color scheme
The system SHALL provide a centralized theme configuration that adapts to Vant UI's theme variables, including dark mode support.
#### Scenario: Chart uses Vant primary color
- **WHEN** a chart is rendered
- **THEN** the chart uses `var(--van-primary-color)` for primary elements (lines, bars, etc.)
#### Scenario: Chart adapts to dark mode
- **WHEN** user switches to dark mode via Vant ConfigProvider
- **THEN** chart text color changes to `var(--van-text-color)` and background adapts accordingly
### Requirement: Base chart component must encapsulate common configuration
The system SHALL provide a `BaseChart.vue` component that encapsulates responsive behavior, theme integration, and error handling.
#### Scenario: Chart responds to container resize
- **WHEN** the parent container resizes (e.g., orientation change)
- **THEN** the chart automatically adjusts its dimensions using ResizeObserver
#### Scenario: Chart shows loading state
- **WHEN** chart data is being fetched
- **THEN** the component displays a loading indicator (Vant Loading component)
#### Scenario: Chart handles empty data gracefully
- **WHEN** chart receives empty or null data
- **THEN** the component displays an empty state message without errors
### Requirement: Gauge chart plugin must be available
The system SHALL provide a custom Chart.js plugin that renders a gauge chart using Doughnut chart with center text overlay.
#### Scenario: Gauge chart displays percentage
- **WHEN** gauge chart is rendered with value and limit props
- **THEN** the chart shows a semi-circle gauge with percentage text in the center
#### Scenario: Gauge chart supports color thresholds
- **WHEN** gauge value exceeds 100%
- **THEN** the gauge color changes to danger color (red for expense, green for income)
### Requirement: Charts must support mobile touch interactions
The system SHALL enable touch-friendly interactions including tap-to-highlight and pan gestures.
#### Scenario: User taps chart segment
- **WHEN** user taps a bar/pie segment on mobile
- **THEN** the segment highlights and shows tooltip with details
#### Scenario: User pans line chart
- **WHEN** user swipes horizontally on a line chart with many data points
- **THEN** the chart scrolls to show hidden data points
### Requirement: Chart animations must be configurable
The system SHALL allow disabling or customizing chart animations via configuration.
#### Scenario: Animation duration is consistent
- **WHEN** a chart first loads
- **THEN** the animation completes in 750ms (matching Vant UI transition timing)
#### Scenario: Animation respects prefers-reduced-motion
- **WHEN** user has `prefers-reduced-motion: reduce` enabled
- **THEN** charts render instantly without animation

View File

@@ -0,0 +1,109 @@
## ADDED Requirements
### Requirement: 日历 V2 页面使用统一的配色方案
日历 V2 页面(`calendarV2/Index.vue`)及其所有子模块 SHALL 使用与统计 V2 页面一致的配色方案:页面背景使用 `var(--bg-secondary)`(灰底),卡片背景使用 `var(--bg-primary)`(白卡片/黑卡片)。
#### Scenario: 亮色模式下日历页面背景为灰色
- **WHEN** 用户在亮色主题下访问日历 V2 页面
- **THEN** 页面主容器(`.calendar-scroll-content`)的背景色 MUST 为 `#F6F7F8`(对应 `var(--bg-secondary)`
#### Scenario: 暗色模式下日历页面背景为深灰色
- **WHEN** 用户在暗色主题下访问日历 V2 页面
- **THEN** 页面主容器(`.calendar-scroll-content`)的背景色 MUST 为 `#18181b`(对应 `var(--bg-secondary)`
#### Scenario: 亮色模式下卡片背景为白色
- **WHEN** 用户在亮色主题下查看日历页面的统计卡片
- **THEN** 卡片容器(`.stats-card`)的背景色 MUST 为 `#FFFFFF`(对应 `var(--bg-primary)`
#### Scenario: 暗色模式下卡片背景为黑色
- **WHEN** 用户在暗色主题下查看日历页面的统计卡片
- **THEN** 卡片容器(`.stats-card`)的背景色 MUST 为 `#09090B`(对应 `var(--bg-primary)`
### Requirement: 统计 V2 页面配色方案保持不变
统计 V2 页面(`statisticsV2/Index.vue`)的配色方案 SHALL 保持现有设计不变,作为标准参考。
#### Scenario: 统计 V2 页面背景色不受影响
- **WHEN** 修改完成后用户访问统计 V2 页面
- **THEN** 页面主容器(`.statistics-scroll-content`)的背景色 MUST 仍为 `var(--bg-secondary)`
- **AND** 统计卡片(`.common-card`)的背景色 MUST 仍为 `var(--bg-primary)`
### Requirement: 页面切换时配色保持一致
用户在统计 V2 页面和日历 V2 页面之间切换时SHALL 保持视觉一致性,不出现配色反转。
#### Scenario: 从统计页面切换到日历页面配色一致
- **WHEN** 用户从统计 V2 页面导航到日历 V2 页面
- **THEN** 两个页面的背景色(灰底)和卡片背景色(白卡片/黑卡片MUST 保持一致
- **AND** 不出现白底↔灰底或黑底↔灰底的闪烁或突变
#### Scenario: 主题切换后两个页面配色同步
- **WHEN** 用户在任一页面切换亮色/暗色主题
- **THEN** 统计 V2 和日历 V2 页面 MUST 同时应用新主题的配色方案
- **AND** 两个页面的配色方案 MUST 保持一致(都是灰底 + 白卡片/黑卡片)
### Requirement: 子模块遵循统一配色规范
日历 V2 页面的所有子模块(`Calendar.vue`, `Stats.vue`, `TransactionList.vue`SHALL 遵循统一的配色规范。
#### Scenario: Stats 模块卡片使用白色/黑色背景
- **WHEN** 用户查看日历页面的统计模块(`Stats.vue`
- **THEN** 统计卡片(`.stats-card`)的背景色 MUST 使用 `var(--bg-primary)`
- **AND** 在亮色模式下显示为白色(`#FFFFFF`),暗色模式下显示为黑色(`#09090B`
#### Scenario: Calendar 模块不引入额外背景
- **WHEN** 用户查看日历模块(`Calendar.vue`
- **THEN** 日历网格本身 MUST NOT 添加独立的背景色层
- **AND** MUST 透明或继承父容器的 `var(--bg-secondary)` 背景
#### Scenario: TransactionList 模块卡片使用白色/黑色背景
- **WHEN** 用户查看交易列表模块(`TransactionList.vue`)中的卡片元素
- **THEN** 如果存在卡片容器,其背景色 MUST 使用 `var(--bg-primary)`
- **AND** 如果无卡片容器,交易项 MUST 透明或继承父容器背景
### Requirement: CSS 变量使用规范
所有配色修改 SHALL 通过替换 CSS 变量引用实现MUST NOT 使用硬编码的十六进制颜色值。
#### Scenario: 使用 CSS 变量而非硬编码颜色
- **WHEN** 开发者检查修改后的样式代码
- **THEN** 所有背景色声明 MUST 使用 `var(--bg-primary)``var(--bg-secondary)`
- **AND** MUST NOT 出现硬编码的 `#FFFFFF`, `#F6F7F8`, `#09090B`, `#18181b` 等值
#### Scenario: 主题系统变量定义不变
- **WHEN** 修改完成后检查主题系统文件(`theme.css`
- **THEN** CSS 变量 `--bg-primary``--bg-secondary` 的定义 MUST 保持不变
- **AND** 不引入新的主题变量
### Requirement: 不影响其他页面配色
修改 SHALL 仅影响日历 V2 页面MUST NOT 影响其他页面(如 BudgetView, SettingView, statisticsV1的配色。
#### Scenario: 预算页面配色不受影响
- **WHEN** 修改完成后用户访问预算页面(`BudgetView.vue`
- **THEN** 预算页面的背景色和卡片配色 MUST 与修改前完全一致
#### Scenario: 设置页面配色不受影响
- **WHEN** 修改完成后用户访问设置页面(`SettingView.vue`
- **THEN** 设置页面的背景色和卡片配色 MUST 与修改前完全一致
#### Scenario: 统计 V1 页面配色不受影响
- **WHEN** 修改完成后用户访问统计 V1 页面(`statisticsV1/Index.vue`
- **THEN** 统计 V1 页面的配色 MUST 与修改前完全一致
- **AND** 不与统计 V2 或日历 V2 的配色方案冲突

View File

@@ -0,0 +1,72 @@
## ADDED Requirements
### Requirement: 图标搜索能力
系统SHALL能够根据分类名称搜索Iconify图标库中的图标。
#### Scenario: AI生成搜索关键字
- **WHEN** 系统接收到分类名称(如"餐饮"、"交通"
- **THEN** 系统SHALL使用AI生成多个英文搜索关键字如"food", "restaurant", "dining"
- **THEN** 系统SHALL将搜索关键字保存到TransactionCategory.IconKeywords字段JSON数组格式
#### Scenario: 检索图标
- **WHEN** 系统使用搜索关键字调用Iconify API
- **THEN** 系统SHALL获取最多N个图标N可配置默认为20
- **THEN** 每个图标包含图标集名称和图标名称
#### Scenario: 更新分类图标
- **WHEN** 用户为分类选择一个图标
- **THEN** 系统SHALL将Iconify图标标识符如"mdi:home"保存到TransactionCategory.Icon字段
- **THEN** 系统SHALL更新TransactionCategory记录
#### Scenario: 获取多个图标供选择
- **WHEN** 前端请求某分类的图标候选列表
- **THEN** 系统SHALL返回Iconify API检索到的图标列表
- **THEN** 返回数据SHALL包含图标集名称、图标名称和Iconify渲染标识符
### Requirement: Iconify API集成
系统SHALL通过Iconify搜索API检索图标库。
#### Scenario: API调用格式
- **WHEN** 系统调用Iconify搜索API
- **THEN** 请求URL格式MUST为`https://api.iconify.design/search?query=<keyword>&limit=<limit>`
- **THEN** 响应数据MUST包含图标集名称和图标名称
#### Scenario: 响应数据解析
- **WHEN** 系统接收到Iconify API响应
- **THEN** 系统SHALL解析响应JSON提取每个图标的`name`(图标名称)和`collection.name`(图标集名称)
- **THEN** 系统SHALL构建Iconify渲染标识符`{collection.name}:{name}`
#### Scenario: API错误处理
- **WHEN** Iconify API返回错误
- **THEN** 系统SHALL记录错误日志
- **THEN** 系统SHALL返回错误信息给调用方
### Requirement: AI搜索关键字生成
系统SHALL使用AI根据分类名称生成英文搜索关键字。
#### Scenario: 生成搜索关键字
- **WHEN** 系统接收到中文分类名称
- **THEN** 系统SHALL生成3-5个相关英文搜索关键字
- **THEN** 关键字SHALL涵盖同义词、相关概念和常见英文表达
#### Scenario: 输入验证
- **WHEN** 系统接收到空或无效的分类名称
- **THEN** 系统SHALL返回错误
- **THEN** 系统SHALL不调用AI服务
### Requirement: API接口
系统SHALL提供RESTful API接口用于图标管理。
#### Scenario: 生成搜索关键字
- **WHEN** 客户端调用 `POST /api/icons/search-keywords` 请求体包含分类名称
- **THEN** 系统SHALL返回AI生成的搜索关键字数组
#### Scenario: 搜索图标(供用户选择)
- **WHEN** 客户端调用 `POST /api/icons/search` 请求体包含搜索关键字
- **THEN** 系统SHALL调用Iconify API搜索图标
- **THEN** 系统SHALL返回Iconify API检索到的图标列表
#### Scenario: 更新分类图标
- **WHEN** 客户端调用 `PUT /api/categories/{categoryId}/icon` 请求体包含图标标识符
- **THEN** 系统SHALL更新TransactionCategory.Icon字段
- **THEN** 系统SHALL返回更新后的分类信息

View File

@@ -0,0 +1,28 @@
## Purpose
定义折线图的日期范围展示规则,确保只展示有效数据日期,避免显示未来无效日期。
## Requirements
### Requirement: 动态日期范围
收支折线图 SHALL 仅展示有实际数据的日期范围,不包含未来无效日期。
#### Scenario: 当前日期之前的趋势展示
- **WHEN** 用户查看收支折线图例如当前为17号
- **THEN** 图表 SHALL 只展示从月初到当前日期的数据点
- **AND** SHALL 不包含17号之后到月底的空白日期
- **AND** X轴标签 SHALL 对应实际有数据的日期
#### Scenario: 整月数据展示
- **WHEN** 用户查看历史月份的收支折线图
- **THEN** 图表 SHALL 展示该月的完整日期范围1号到月末
- **AND** 所有日期点 SHALL 有对应的数据值
### Requirement: 数据点过滤逻辑
系统 SHALL 根据当前日期自动过滤未来日期的数据点。
#### Scenario: 实时数据过滤
- **WHEN** 组件加载当月收支数据
- **THEN** 系统 SHALL 获取当前日期
- **AND** SHALL 过滤掉 labels 数组中大于当前日期的日期
- **AND** SHALL 同步过滤 datasets 中对应的空数据点

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,27 @@
## Purpose
定义饼图Doughnut在中心区域展示总金额的规范提升数据可读性。
## Requirements
### Requirement: 饼图中心展示总金额
支出分类饼图 SHALL 在镂空区域中心位置展示当前选中数据的总金额。
#### Scenario: 显示总支出金额
- **WHEN** 用户查看统计页面的支出分类饼图
- **THEN** 系统 SHALL 在饼图中心显示当前展示分类的总支出金额
- **AND** 金额格式 SHALL 使用人民币格式¥xx,xxx.xx
#### Scenario: 响应式适配
- **WHEN** 用户在不同屏幕尺寸下查看饼图
- **THEN** 中心文字 SHALL 自动调整大小以适应饼图尺寸
- **AND** 文字 SHALL 始终保持水平和垂直居中
### Requirement: 中心文字样式
饼图中心文字 SHALL 使用统一的视觉样式。
#### Scenario: 样式一致性
- **WHEN** 系统渲染中心金额文字
- **THEN** 字体大小 SHALL 为图表高度的 20%
- **AND** 字体粗细 SHALL 为 bold
- **AND** 字体颜色 SHALL 使用主题主色(#333333 或暗色主题对应色)

View File

@@ -0,0 +1,28 @@
## Purpose
定义饼图分类标签的位置和展示方式,避免标签与图标重叠,确保信息清晰可读。
## Requirements
### Requirement: 分类标签智能定位
饼图的分类标签 SHALL 避免与图标重叠,并清晰展示分类名称。
#### Scenario: 标签位置优化
- **WHEN** 系统渲染支出分类饼图
- **THEN** 分类标签 SHALL 显示在饼图扇区外侧
- **AND** 标签 SHALL 通过引导线与对应扇区连接
- **AND** 标签文字 SHALL 显示分类名称而非仅在图标上叠加
#### Scenario: 避免标签重叠
- **WHEN** 多个分类扇区相邻且较小时
- **THEN** 系统 SHALL 自动调整标签位置避免相互重叠
- **AND** 当空间不足时 SHALL 使用图例(legend)代替直接标签
### Requirement: 图标与标签分离
分类图标和分类名称 SHALL 分开展示,不互相遮挡。
#### Scenario: 清晰的视觉层次
- **WHEN** 用户查看饼图
- **THEN** 分类图标 SHALL 显示在饼图扇区内部或作为图例图标
- **AND** 分类名称 SHALL 显示在标签位置而非图标上
- **AND** 两者 SHALL 不重叠遮挡

View File

@@ -0,0 +1,63 @@
---
title: 响应式图表布局规格
author: AI Assistant
date: 2026-02-16
---
## ADDED Requirements
### Requirement: 容器自适应
BaseChart 组件 SHALL 自动适应父容器大小。
#### Scenario: 容器大小变化
- **WHEN** 父容器大小发生变化
- **THEN** 图表 SHALL 自动调整尺寸
- **AND** 使用 ResizeObserver 进行监听
- **AND** 图表 SHALL 保持比例不失真
#### Scenario: 横竖屏切换
- **WHEN** 移动设备从竖屏切换到横屏
- **THEN** 图表 SHALL 在 300ms 内完成重绘
- **AND** 所有元素 SHALL 保持可读性
### Requirement: 触控交互优化
图表 SHALL 针对移动设备触控操作进行优化。
#### Scenario: 悬停效果增强
- **WHEN** 用户悬停/触摸饼图扇区
- **THEN** `hoverOffset` SHALL 为 8px比默认值大
- **AND** 过渡动画 SHALL 流畅自然
#### Scenario: 折线图点触控
- **WHEN** 用户触摸折线图数据点
- **THEN** 点的 `pointHoverRadius` SHALL 为 6px
- **AND** 触控目标 SHALL 足够大(最小 44px
### Requirement: 空状态处理
图表组件 SHALL 优雅处理空数据情况。
#### Scenario: 无数据时显示空状态
- **WHEN` 传入的数据为空数组或 datasets 为空
- **THEN** 显示 VanEmpty 组件
- **AND** 显示文案 "暂无数据"
#### Scenario: 加载状态
- **WHEN** `loading` prop 为 true
- **THEN** 显示 VanLoading 组件
- **AND** 显示文案 "加载中..."
### Requirement: 最小高度限制
图表容器 SHALL 有最小高度限制以确保可读性。
#### Scenario: 小容器适配
- **WHEN** 父容器高度小于 200px
- **THEN** 图表 SHALL 使用 200px 作为最小高度
- **AND** SHALL 显示滚动条或缩放提示
## MODIFIED Requirements
无修改的现有需求。
## REMOVED Requirements
无删除的需求。

View File

@@ -0,0 +1,77 @@
## REMOVED Requirements
### Requirement: Calendar View Route
**Reason**: V1 日历页面已由 V2 版本完全替代V1 路由不再需要
**Migration**: 用户应访问 `/calendar-v2` 路由,使用新版日历功能
---
### Requirement: Budget View Route
**Reason**: V1 预算页面已由 V2 版本完全替代V1 路由不再需要
**Migration**: 用户应访问 `/budget-v2` 路由,使用新版预算管理功能
---
### Requirement: Statistics V1 Default Route
**Reason**: V1 统计页面已由 V2 版本完全替代V1 默认路由不再需要
**Migration**: 系统默认路由应指向 `/statistics-v2`,用户将自动使用新版统计功能
---
### Requirement: V1/V2 Version Toggle Logic
**Reason**: V1 版本完全下线后,版本切换逻辑不再需要
**Migration**: 移除 `useVersionStore` 中的版本切换代码,简化路由守卫逻辑。所有用户默认使用 V2 版本。
---
## Context
本规范定义了 EmailBill 前端路由系统中 V1 相关路由的移除操作。随着 V2 版本的稳定上线V1 的日历、预算、统计三个核心模块的路由定义已成为遗留代码。
### 受影响的路由
- `/calendar` → CalendarView.vue (V1 日历视图)
- `/budget` → BudgetView.vue (V1 预算管理)
- `/` → statisticsV1/Index.vue (V1 统计页面,默认首页)
### 现有版本控制机制
- `useVersionStore` 提供 `isV2()` 方法判断用户偏好
- 路由守卫根据版本偏好自动跳转到对应的 V1 或 V2 路由
- V2 路由命名规则: 原路由名 + `-v2` 后缀
### 移除后的预期行为
- 访问 `/calendar``/budget``/` 将返回 404 或重定向到 V2 版本
- `useVersionStore` 中的版本切换逻辑被简化或移除
- 路由守卫不再需要判断 V1/V2 版本
---
## Validation
### 验证标准
1. **路由配置文件** (`Web/src/router/index.js`):
- 不包含 `/calendar`, `/budget`, `/` 的 V1 路由定义
- V2 路由 (`/calendar-v2`, `/budget-v2`, `/statistics-v2`) 正常工作
2. **版本控制逻辑** (`Web/src/stores/version.js` 或类似文件):
- 移除或简化 `isV2()` 相关的版本切换代码
- 确保所有路由默认使用 V2 版本
3. **手动测试验证**:
- 直接访问 `/calendar` 不会加载 V1 页面
- 直接访问 `/budget` 不会加载 V1 页面
- 访问根路径 `/` 自动跳转到 V2 统计页面或返回 404
- V2 路由 (`/calendar-v2`, `/budget-v2`, `/statistics-v2`) 正常访问
---
## Dependencies
移除 V1 路由的前置条件:
1. V1 页面文件 (`CalendarView.vue`, `BudgetView.vue`, `statisticsV1/Index.vue`) 已删除
2. V2 页面功能已验证完整,可以完全替代 V1
3. 用户已迁移到 V2 版本,或系统强制使用 V2
移除后不影响:
- V2 路由配置和页面功能
- 路由守卫的认证检查逻辑 (`requiresAuth`)
- 其他非核心模块的路由 (如 `/login`, `/settings`)

View File

@@ -0,0 +1,141 @@
## REMOVED Requirements
### Requirement: Get Balance Statistics
**Reason**: 该接口仅被 V1 统计页面使用,用于绘制"余额变化折线图"。V2 统计页面使用不同的图表渲染逻辑,不依赖此接口。
**Migration**: V2 统计页面使用 `GetDailyStatistics` 接口获取数据,并在前端计算累积余额。无需后端专用接口。
**原有功能**:
- **接口**: `GET /api/TransactionStatistics/GetBalanceStatistics`
- **Controller**: `TransactionStatisticsController.GetBalanceStatisticsAsync`
- **参数**: `year` (int), `month` (int)
- **返回**: `BalanceStatisticsDto` (包含每日的累积余额数据)
- **业务逻辑**:
1. 查询指定月份的所有交易记录,按日期排序
2. 计算每日的累积余额 (前一日余额 + 当日收入 - 当日支出)
3. 返回包含日期和余额的时间序列数据
**被以下代码调用**:
- `Web/src/views/statisticsV1/Index.vue` 中的 `fetchBalanceData` 方法
- `Web/src/api/statistics.js` 中的 `getBalanceStatistics` 函数
**图表渲染逻辑**:
- V1 使用 ECharts 折线图渲染余额曲线
- 数据源: 后端计算好的累积余额
- 渲染方法: `renderBalanceChart()`
---
## Context
本规范定义了 EmailBill 后端 `TransactionStatisticsController` 中 V1 专用接口的移除操作。
### 接口背景
该接口是 V1 统计页面的核心功能之一,用于支持"余额变化趋势图"
- **设计理念**: 后端负责累积余额计算,前端只负责渲染
- **性能考虑**: 避免前端处理大量交易记录数据
- **适用场景**: V1 统计页面需要独立的余额统计视图
### V2 设计变更
V2 统计页面重新设计了数据获取和渲染逻辑:
- **数据获取**: 使用 `GetDailyStatistics` 一次性获取日度支出/收入数据
- **余额计算**: 在前端 Vue 组件中计算累积余额 (computed property)
- **性能优化**: 前端缓存计算结果,减少重复请求
- **代码简化**: 后端不需要维护专用的余额统计接口
### 技术对比
| 维度 | V1 实现 | V2 实现 |
|------|---------|---------|
| **数据获取** | 专用接口 `GetBalanceStatistics` | 通用接口 `GetDailyStatistics` |
| **余额计算** | 后端计算 | 前端计算 |
| **网络请求** | 2 次 (日度统计 + 余额统计) | 1 次 (日度统计) |
| **前端逻辑** | 简单渲染 | 计算 + 渲染 |
| **后端复杂度** | 高 (多个接口) | 低 (统一接口) |
### 依赖关系
该接口依赖以下 Service 和 Repository
- `TransactionStatisticsService.GetBalanceStatisticsAsync`
- `TransactionStatisticsApplication.GetBalanceStatisticsAsync`
- `TransactionRecordRepository` (查询交易记录)
移除接口后,相关 Service 方法也将被移除。
---
## Validation
### 验证标准
1. **代码搜索验证**:
- 全局搜索 `GetBalanceStatistics`,确认仅在以下位置出现:
- `TransactionStatisticsController.GetBalanceStatisticsAsync` (待删除)
- `TransactionStatisticsApplication.GetBalanceStatisticsAsync` (待删除)
- `TransactionStatisticsService.GetBalanceStatisticsAsync` (待删除)
- `statisticsV1/Index.vue` (已删除)
- `statistics.js` (已清理)
2. **编译验证**:
- 删除 `TransactionStatisticsController` 中的方法后,后端项目编译通过
- 删除 `TransactionStatisticsApplication``TransactionStatisticsService` 中的对应方法后,编译通过
3. **API 文档验证**:
- Swagger/Scalar 文档中不再显示 `/api/TransactionStatistics/GetBalanceStatistics` 端点
4. **运行时验证**:
- 前端调用 `/api/TransactionStatistics/GetBalanceStatistics` 返回 404
- V2 统计页面 (`/statistics-v2`) 正常加载,余额曲线正常渲染
5. **功能验证 (V2 统计页面)**:
- 打开 `/statistics-v2`
- 切换到"月度视图"
- 确认余额折线图正常显示
- 确认数据与 V1 保持一致 (基于相同的 `GetDailyStatistics` 数据)
---
## Dependencies
移除此接口的前置条件:
1. `statisticsV1/Index.vue` (V1 统计页面) 已删除
2. `Web/src/api/statistics.js` 中的 `getBalanceStatistics` 方法已清理
3. V2 统计页面已验证可以通过前端计算实现相同功能
移除后连带删除:
- `TransactionStatisticsApplication.GetBalanceStatisticsAsync`
- `TransactionStatisticsService.GetBalanceStatisticsAsync`
移除后不影响:
- 其他统计接口 (`GetMonthlyStatistics`, `GetCategoryStatistics`, `GetDailyStatistics`)
- `TransactionRecordRepository` 的查询逻辑 (仍被其他接口使用)
- V2 统计页面的任何功能 (余额计算逻辑已在前端实现)
---
## Implementation Notes
### V2 前端余额计算示例 (参考)
```javascript
// Web/src/views/statisticsV2/Index.vue
computed: {
balanceData() {
let cumulativeBalance = 0
return this.dailyData.map(day => {
cumulativeBalance += day.income - day.expense
return {
date: day.date,
balance: cumulativeBalance
}
})
}
}
```
### 性能分析
- **V1 方案**: 后端查询 + 计算 + 序列化 → 前端接收 + 渲染 (总时间: ~200ms)
- **V2 方案**: 后端查询 + 序列化 → 前端接收 + 计算 + 渲染 (总时间: ~180ms)
- **结论**: V2 方案性能略优,且减少了后端复杂度
### 回归测试建议
删除接口后,应手动验证 V2 统计页面的以下场景:
1. **正常月份**: 选择有交易的月份,确认余额曲线正常
2. **无交易月份**: 选择无交易的月份,确认显示"暂无数据"
3. **跨年场景**: 验证 1 月份的余额计算是否正确 (初始余额为 0)
4. **性能测试**: 加载包含大量交易 (>1000 笔) 的月份,确认渲染流畅

View File

@@ -0,0 +1,77 @@
## MODIFIED Requirements
### Requirement: Daily trend chart must display expense/income over time
The system SHALL render a line chart showing daily transaction totals for the selected time period (week/month/year).
#### Scenario: Week view shows 7 days
- **WHEN** user selects "Week" time period
- **THEN** chart displays 7 data points (Mon-Sun) with expense and income lines
#### Scenario: Month view shows daily trend
- **WHEN** user selects "Month" time period
- **THEN** chart displays 28-31 data points (one per day) with expense and income lines
#### Scenario: Year view shows monthly trend
- **WHEN** user selects "Year" time period
- **THEN** chart displays 12 data points (one per month) with expense and income lines
#### Scenario: Chart highlights max expense day
- **WHEN** user views daily trend
- **THEN** the day with highest expense has a highlighted marker
#### Scenario: Chart supports zooming
- **WHEN** user pinches on mobile or scrolls on desktop
- **THEN** chart zooms in/out to show more/less detail
### Requirement: Expense category pie chart must show spending breakdown
The system SHALL render a pie chart displaying expense amounts grouped by category for the selected time period.
#### Scenario: Pie chart shows all expense categories
- **WHEN** user has expenses in multiple categories
- **THEN** chart displays one slice per category with percentage labels
#### Scenario: Pie chart uses category colors
- **WHEN** categories have custom colors defined
- **THEN** chart slices use the corresponding category colors
#### Scenario: Pie chart shows "Others" for small categories
- **WHEN** more than 8 categories exist
- **THEN** categories below 3% are grouped into "Others" slice
#### Scenario: Tapping slice shows category detail
- **WHEN** user taps a pie slice
- **THEN** app navigates to category detail view with transaction list
### Requirement: Monthly expense bar chart must compare months
The system SHALL render a vertical bar chart comparing expense totals across recent months.
#### Scenario: Bar chart shows 6 recent months
- **WHEN** user views monthly expense card
- **THEN** chart displays 6 bars representing the last 6 months
#### Scenario: Current month bar is highlighted
- **WHEN** user views monthly expense card
- **THEN** current month's bar uses primary color, previous months use gray
#### Scenario: Bar height reflects expense amount
- **WHEN** a month has higher expenses
- **THEN** its bar is proportionally taller
#### Scenario: Bar shows tooltip with formatted amount
- **WHEN** user hovers/taps a bar
- **THEN** tooltip displays month name and expense amount formatted as "¥X,XXX.XX"
### Requirement: Charts must maintain existing responsive behavior
The system SHALL ensure all statistics charts adapt to different screen sizes and orientations.
#### Scenario: Chart scales on narrow screens
- **WHEN** screen width is less than 375px
- **THEN** chart font sizes scale down proportionally while maintaining readability
#### Scenario: Chart reflows on orientation change
- **WHEN** device orientation changes from portrait to landscape
- **THEN** chart re-renders to fill available width within 300ms
#### Scenario: Chart labels truncate on small screens
- **WHEN** category names are longer than 12 characters
- **THEN** labels show ellipsis (e.g., "Entertainment..." ) and full text in tooltip

View File

@@ -0,0 +1,76 @@
## REMOVED Requirements
### Requirement: Get Daily Statistics (Obsolete)
**Reason**: 该接口已标记 `[Obsolete]`,仅被 V1 日历页面使用。V2 使用 `TransactionStatisticsController.GetDailyStatisticsAsync` 替代。
**Migration**: 前端应调用 `GET /api/TransactionStatistics/GetDailyStatistics` 接口,后端应使用 `TransactionStatisticsService.GetDailyStatisticsAsync` 方法。
**原有功能**:
- **接口**: `GET /api/TransactionRecord/GetDailyStatistics`
- **Controller**: `TransactionRecordController.GetDailyStatisticsAsync`
- **参数**: `year` (int), `month` (int)
- **返回**: `List<DailyStatisticDto>` (包含每日的支出、收入、余额统计)
**被以下代码调用**:
- `Web/src/views/CalendarView.vue` 中的 `fetchDailyStatistics` 方法
---
## Context
本规范定义了 EmailBill 后端 `TransactionRecordController` 中废弃的 V1 专用接口的移除操作。
### 接口历史
- **创建时间**: V1 版本早期,用于支持日历视图的日度统计
- **废弃原因**: 职责不清晰,日度统计应由 `TransactionStatisticsController` 统一管理
- **废弃标记**: 已标记 `[Obsolete]` 属性,建议开发者使用新接口
- **当前使用**: 仅被 `CalendarView.vue` (V1) 调用V2 日历页面不使用此接口
### 新接口对比
| 维度 | V1 接口 (待删除) | V2 接口 (推荐) |
|------|----------------|---------------|
| **路径** | `/api/TransactionRecord/GetDailyStatistics` | `/api/TransactionStatistics/GetDailyStatistics` |
| **Controller** | `TransactionRecordController` | `TransactionStatisticsController` |
| **职责** | 混杂在交易记录 CRUD 中 | 专注于统计查询 |
| **返回格式** | `List<DailyStatisticDto>` | 相同 |
| **性能** | 相同 | 相同 |
### 移除影响
- **前端**: `CalendarView.vue` 删除后,无其他前端代码调用此接口
- **后端**: `TransactionRecordController` 移除此方法后,不影响其他交易记录相关接口
- **数据库**: 无影响,底层查询逻辑由 `TransactionStatisticsService` 处理
---
## Validation
### 验证标准
1. **代码搜索验证**:
- 全局搜索 `GetDailyStatistics`,确认仅在以下位置出现:
- `TransactionRecordController.GetDailyStatisticsAsync` (待删除)
- `CalendarView.vue` (已删除)
- `TransactionStatisticsController.GetDailyStatisticsAsync` (保留)
2. **编译验证**:
- 删除 `TransactionRecordController.GetDailyStatisticsAsync` 方法后,后端项目编译通过
- 无其他 Controller 或 Application 层代码引用此方法
3. **API 文档验证**:
- Swagger/Scalar 文档中不再显示 `/api/TransactionRecord/GetDailyStatistics` 端点
- `/api/TransactionStatistics/GetDailyStatistics` 端点正常显示
4. **运行时验证**:
- 前端调用 `/api/TransactionRecord/GetDailyStatistics` 返回 404
- V2 日历页面调用 `/api/TransactionStatistics/GetDailyStatistics` 正常返回数据
---
## Dependencies
移除此接口的前置条件:
1. `CalendarView.vue` (V1 日历页面) 已删除
2. V2 日历页面已完全使用 `TransactionStatisticsController.GetDailyStatisticsAsync` 接口
移除后不影响:
- `TransactionStatisticsController` 中的同名方法 (不同 Controller)
- 其他交易记录相关接口 (`GetById`, `GetByDate`, `Update`, `Delete` 等)
- `TransactionRecordRepository` 的查询逻辑 (仍被 V2 接口使用)

View File

@@ -0,0 +1,103 @@
## ADDED Requirements
### Requirement: Card containers SHALL use unified spacing values
所有卡片容器必须使用统一的 CSS 变量来定义内边距padding确保视觉一致性。
#### Scenario: Standard card padding
- **WHEN** 渲染任何卡片组件
- **THEN** 卡片的 padding 必须使用 `var(--spacing-xl)`16px
#### Scenario: Gauge card compact padding
- **WHEN** 渲染仪表盘类型的卡片gauge card
- **THEN** 卡片的 padding 可以使用 `var(--spacing-lg)`12px以适应紧凑布局
### Requirement: Card containers SHALL use unified border radius
所有卡片容器必须使用统一的 CSS 变量来定义圆角border-radius保持设计语言一致。
#### Scenario: Standard card border radius
- **WHEN** 渲染任何卡片组件
- **THEN** 卡片的 border-radius 必须使用 `var(--radius-lg)`12px
### Requirement: Card containers SHALL use unified background color
所有卡片容器必须使用统一的背景色变量,确保在明暗模式下自动适配。
#### Scenario: Card background in light mode
- **WHEN** 应用处于明亮模式
- **THEN** 卡片的 background 必须使用 `var(--bg-secondary)`
#### Scenario: Card background in dark mode
- **WHEN** 应用处于暗色模式
- **THEN** 卡片的 background 必须使用 `var(--bg-secondary)` 并自动适配为暗色系背景
### Requirement: Card containers SHALL use unified shadow
所有卡片容器必须使用统一的阴影变量,提供微妙的层次感。
#### Scenario: Standard card shadow
- **WHEN** 渲染任何卡片组件
- **THEN** 卡片的 box-shadow 必须使用 `var(--shadow-sm)`
#### Scenario: Card shadow in dark mode
- **WHEN** 应用处于暗色模式
- **THEN** 卡片的阴影必须根据暗色模式自动调整透明度和颜色
### Requirement: Card spacing SHALL be consistent across pages
页面级别的卡片布局必须使用统一的外边距margin和间距gap确保三个页面的视觉韵律一致。
#### Scenario: Card bottom margin
- **WHEN** 在页面中垂直排列多个卡片
- **THEN** 卡片之间的间距必须使用 `var(--spacing-xl)`16px或通过父容器的 gap 属性设置
#### Scenario: Page container padding
- **WHEN** 渲染包含卡片的页面容器
- **THEN** 容器的左右 padding 必须使用 `var(--spacing-xl)`16px
- **AND** 容器的顶部 padding 可以使用 `var(--spacing-md)`12px以紧跟 header
- **AND** 容器的底部 padding 应使用 `var(--spacing-3xl)`24px以预留安全区域
### Requirement: Hard-coded style values MUST be replaced
现有代码中的硬编码样式值必须被对应的 CSS 变量替换,以便集中管理和未来维护。
#### Scenario: Replace hard-coded padding
- **WHEN** 发现卡片使用硬编码的 `padding: 16px``padding: 12px`
- **THEN** 必须替换为 `padding: var(--spacing-xl)``padding: var(--spacing-lg)`
#### Scenario: Replace hard-coded border-radius
- **WHEN** 发现卡片使用硬编码的 `border-radius: 12px`
- **THEN** 必须替换为 `border-radius: var(--radius-lg)`
#### Scenario: Replace hard-coded margins and gaps
- **WHEN** 发现卡片或容器使用硬编码的 `margin-bottom: 12px``gap: 12px`
- **THEN** 必须替换为 `margin-bottom: var(--spacing-xl)``gap: var(--spacing-xl)`
### Requirement: Common card class SHALL be consistently applied
所有符合标准卡片样式的元素必须应用 `.common-card` 类或内联使用相同的 CSS 变量组合。
#### Scenario: Apply common-card class
- **WHEN** 创建或修改卡片组件
- **THEN** 卡片的根元素应添加 `class="common-card"` 或在 scoped style 中定义 `.common-card` 样式
#### Scenario: Inline CSS variable usage
- **WHEN** 不使用 `.common-card` 类时
- **THEN** 必须确保使用与 `.common-card` 相同的 CSS 变量组合background, border-radius, padding, box-shadow
### Requirement: Visual regression testing MUST verify style consistency
样式统一后必须通过视觉测试验证三个页面的卡片表现一致。
#### Scenario: Test in light mode
- **WHEN** 应用处于明亮模式
- **THEN** 日历、统计、预算页面的卡片样式必须在视觉上保持一致(相同的边距、圆角、阴影)
#### Scenario: Test in dark mode
- **WHEN** 应用处于暗色模式
- **THEN** 日历、统计、预算页面的卡片样式必须在视觉上保持一致(相同的边距、圆角、阴影,且背景色和阴影自动适配)
#### Scenario: Test on different screen sizes
- **WHEN** 在不同移动设备屏幕尺寸上查看(如 iPhone SE, iPhone 14 Pro, iPad mini
- **THEN** 卡片布局不能出现溢出、内容截断或错位问题

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 影响