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