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
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:
20
openspec/specs/ai-category-icon-generation/spec.md
Normal file
20
openspec/specs/ai-category-icon-generation/spec.md
Normal 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
|
||||
40
openspec/specs/bill-management/spec.md
Normal file
40
openspec/specs/bill-management/spec.md
Normal 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** 按钮可以正常响应点击事件
|
||||
130
openspec/specs/budget-api/spec.md
Normal file
130
openspec/specs/budget-api/spec.md
Normal 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. **建议先调研用户需求**,确认是否真的需要这些功能
|
||||
49
openspec/specs/budget-gauge-display/spec.md
Normal file
49
openspec/specs/budget-gauge-display/spec.md
Normal 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 保持非全圆形态
|
||||
48
openspec/specs/budget-stats/spec.md
Normal file
48
openspec/specs/budget-stats/spec.md
Normal 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`
|
||||
73
openspec/specs/budget-visualization/spec.md
Normal file
73
openspec/specs/budget-visualization/spec.md
Normal 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
|
||||
21
openspec/specs/chart-data-filtering/spec.md
Normal file
21
openspec/specs/chart-data-filtering/spec.md
Normal 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
|
||||
25
openspec/specs/chart-detail-fix/spec.md
Normal file
25
openspec/specs/chart-detail-fix/spec.md
Normal 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 保持非全圆形态
|
||||
21
openspec/specs/chart-label-overlay/spec.md
Normal file
21
openspec/specs/chart-label-overlay/spec.md
Normal 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
|
||||
14
openspec/specs/chart-layout-constraint/spec.md
Normal file
14
openspec/specs/chart-layout-constraint/spec.md
Normal 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
|
||||
48
openspec/specs/chart-text-encoding-fix/spec.md
Normal file
48
openspec/specs/chart-text-encoding-fix/spec.md
Normal 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 正确显示
|
||||
70
openspec/specs/chart-theme-system/spec.md
Normal file
70
openspec/specs/chart-theme-system/spec.md
Normal 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
|
||||
|
||||
无删除的需求。
|
||||
71
openspec/specs/chartjs-integration/spec.md
Normal file
71
openspec/specs/chartjs-integration/spec.md
Normal 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
|
||||
109
openspec/specs/consistent-color-scheme/spec.md
Normal file
109
openspec/specs/consistent-color-scheme/spec.md
Normal 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 的配色方案冲突
|
||||
72
openspec/specs/icon-search/spec.md
Normal file
72
openspec/specs/icon-search/spec.md
Normal 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返回更新后的分类信息
|
||||
28
openspec/specs/line-chart-dynamic-range/spec.md
Normal file
28
openspec/specs/line-chart-dynamic-range/spec.md
Normal 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 中对应的空数据点
|
||||
37
openspec/specs/navigation/spec.md
Normal file
37
openspec/specs/navigation/spec.md
Normal 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 组件相关的警告或错误
|
||||
27
openspec/specs/pie-chart-center-label/spec.md
Normal file
27
openspec/specs/pie-chart-center-label/spec.md
Normal 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 或暗色主题对应色)
|
||||
28
openspec/specs/pie-chart-label-positioning/spec.md
Normal file
28
openspec/specs/pie-chart-label-positioning/spec.md
Normal 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 不重叠遮挡
|
||||
63
openspec/specs/responsive-chart-layout/spec.md
Normal file
63
openspec/specs/responsive-chart-layout/spec.md
Normal 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
|
||||
|
||||
无删除的需求。
|
||||
77
openspec/specs/routing/spec.md
Normal file
77
openspec/specs/routing/spec.md
Normal 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`)
|
||||
141
openspec/specs/statistics-api/spec.md
Normal file
141
openspec/specs/statistics-api/spec.md
Normal 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 笔) 的月份,确认渲染流畅
|
||||
77
openspec/specs/statistics-charts/spec.md
Normal file
77
openspec/specs/statistics-charts/spec.md
Normal 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
|
||||
76
openspec/specs/transaction-api/spec.md
Normal file
76
openspec/specs/transaction-api/spec.md
Normal 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 接口使用)
|
||||
103
openspec/specs/unified-card-styles/spec.md
Normal file
103
openspec/specs/unified-card-styles/spec.md
Normal 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** 卡片布局不能出现溢出、内容截断或错位问题
|
||||
88
openspec/specs/unified-popup-system/spec.md
Normal file
88
openspec/specs/unified-popup-system/spec.md
Normal 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 影响
|
||||
Reference in New Issue
Block a user