Files
EmailBill/openspec/changes/refactor-bill-list-component/design.md
SunCheng a88556c784 fix
2026-02-15 10:10:28 +08:00

261 lines
8.7 KiB
Markdown
Raw Blame History

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