All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 26s
Docker Build & Deploy / Deploy to Production (push) Successful in 8s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
7.9 KiB
7.9 KiB
Context
当前预算页面(BudgetView.vue)是一个 1000+ 行的单文件组件,存在以下问题:
- 与 calendarV2、statisticsV2 风格不一致(页头和时间段切换设计不同)
- 数据加载逻辑复杂且存在 bug(未覆盖分类、存款计划切换、日期同步问题)
- 组件职责不清晰,难以维护
参考其他 v2 页面的经验,我们需要重构预算页面以实现:
- 统一的页头设计(DateSelectHeader)
- 模块化的组件结构(主页面 + 子模块)
- 清晰的数据流和状态管理
- 简洁高效的用户体验(月度视图为主)
技术栈约束:
- Vue 3 Composition API +
<script setup> - Vant UI 组件库
- 现有的
@/api/budget接口(不修改后端) - 复用现有的 DateSelectHeader 组件
Goals / Non-Goals
Goals:
- 创建简洁高效的预算 v2 页面
- 使用统一的 DateSelectHeader 组件
- 修复现有 bug(数据加载、日期同步、存款计划切换)
- 提升代码可维护性(拆分子模块,清晰职责)
- 保持所有现有业务功能(支出/收入/计划预算管理)
Non-Goals:
- 不修改后端 API(完全前端重构)
- 不改变预算业务逻辑(仅重构实现)
- 不添加新的预算功能(如预算模板、智能推荐等)
- 不添加周/年视图(保持月度视图为主)
- 不删除旧的 BudgetView.vue(保留作为回退方案,后续独立 PR 删除)
Decisions
1. 页面布局结构:简洁的两层布局
决策:采用简洁的两层布局结构
- DateSelectHeader (年月选择器 + 左右箭头 + 通知图标)
- van-tabs (支出/收入/计划切换)
- 可滚动内容区域
- 下拉刷新
- 预算内容(统计卡片、图表、列表)
理由:
- ✅ 简洁明了,减少复杂度
- ✅ 预算管理以月度为主,不需要周/年视图
- ✅ 与现有预算页面的逻辑一致,降低用户学习成本
替代方案:
- ❌ 引入 TimePeriodTabs:增加复杂度,预算管理不需要多时间段
- ❌ 保留当前布局:无法实现风格统一和 bug 修复
2. 组件拆分策略:主页面 + 子模块
决策:创建 budgetV2/Index.vue 作为主页面,按业务功能拆分子模块
budgetV2/
├── Index.vue # 主页面(布局、时间段切换、tabs)
└── modules/
├── ExpenseBudgetContent.vue # 支出预算内容(统计 + 列表)
├── IncomeBudgetContent.vue # 收入预算内容(统计 + 列表)
└── SavingsBudgetContent.vue # 存款计划内容(列表 + 日期切换)
理由:
- ✅ 主页面职责清晰(只负责布局和状态协调)
- ✅ 子模块独立开发和测试
- ✅ 参考 calendarV2 的 modules 模式
替代方案:
- ❌ 单文件组件:难以维护(当前问题)
- ❌ 更细粒度拆分(如 ExpenseStats.vue, ExpenseList.vue):过度设计,增加文件数量
3. 数据加载策略:月度数据为主
决策:专注于月度数据加载,在主页面集中管理数据加载逻辑
// Index.vue
const currentDate = ref(new Date()) // 当前年月
const activeTab = ref(BudgetCategory.Expense) // 当前业务 tab
// 统一数据加载函数(月度)
const loadBudgetData = async () => {
const year = currentDate.value.getFullYear()
const month = currentDate.value.getMonth() + 1
// 调用 getBudgetList, getCategoryStats 等月度 API
}
// 子模块通过 props 接收数据
<ExpenseBudgetContent :budgets="expenseBudgets" :stats="expenseStats" />
理由:
- ✅ 简化逻辑,避免周/年数据的复杂处理
- ✅ 预算管理以月度为核心
- ✅ 统一的错误处理和加载状态
替代方案:
- ❌ 支持多时间段:增加复杂度,预算场景不需要
- ❌ 子模块独立加载数据:重复逻辑,难以同步
4. 存款计划特殊处理:独立的日期切换逻辑
决策:存款计划保留现有的独立日期切换功能(卡片底部的前后箭头)
- 不受页头的
currentDate影响 - 每个存款计划独立维护自己的
periodStart状态 - 通过
getSavingsBudget(year, month, type)切换日期
理由:
- ✅ 符合业务需求(存款计划可能需要查看历史数据)
- ✅ 保持现有功能不变
风险:
- ⚠️ 两套日期状态可能造成用户困惑
- 缓解措施:在 UI 上明确区分(页头日期用于支出/收入,卡片底部日期用于存款计划)
5. 路由策略:创建新路由,保留旧路由
决策:
- 创建新路由
/budget-v2(指向budgetV2/Index.vue) - 保留旧路由
/budget(指向BudgetView.vue) - 在导航中将"预算"菜单指向
/budget-v2
理由:
- ✅ 支持灰度发布(可以快速回退)
- ✅ 便于对比测试
迁移计划:
- 第一阶段:创建新路由,灰度测试
- 第二阶段:确认稳定后,删除旧路由和旧页面
6. 组件复用策略
决策:最大化复用现有组件,只在必要时调整
- ✅ 直接复用:DateSelectHeader(月度模式)、BudgetEditPopup、SavingsConfigPopup、BudgetCard、BudgetChartAnalysis
- ⚠️ 不使用 TimePeriodTabs(简化设计)
理由:
- 减少重复代码
- 保持 UI 一致性
- 简化维护
Risks / Trade-offs
Risk 1: 月度数据加载的稳定性
风险:数据加载和状态管理需要正确处理各种边界情况 → 缓解措施:
- 参考现有 BudgetView 的数据加载逻辑
- 编写单元测试覆盖边界情况
- 详细的代码注释
Risk 2: 存款计划的双日期状态可能造成用户困惑
风险:页头日期和存款计划的独立日期切换可能让用户困惑 → 缓解措施:
- 在 UI 上明确区分(页头日期影响支出/收入,存款计划有独立切换)
- 添加帮助提示或引导
Risk 3: 旧预算页面的兼容性
风险:用户习惯了旧页面的交互方式,可能不适应新页面 → 缓解措施:
- 保留旧路由作为回退方案
- 收集用户反馈,快速迭代
Risk 4: API 兼容性
风险:现有 API 可能存在未预见的边界情况 → 缓解措施:
- 充分测试所有 API 调用
- 添加完善的错误处理
Trade-off 1: 新旧页面并存增加维护成本
权衡:保留旧页面作为回退方案,但会增加短期维护成本 → 接受理由:
- 灰度发布的安全性更重要
- 确认稳定后会删除旧页面
Trade-off 2: 功能简化
权衡:不支持周/年视图,只支持月度 → 接受理由:
- 预算管理以月度为核心,周/年视图使用频率低
- 简化设计,降低复杂度和维护成本
- 如果后续有需求,可以独立迭代
Migration Plan
阶段 1:开发和本地测试(1 天)
- 创建
budgetV2/Index.vue和子模块 - 实现基础布局(DateSelectHeader + 业务 tabs)
- 实现支出/收入/存款三个 tabs 的内容(月度数据)
- 本地测试所有功能(重点测试数据加载和日期切换)
阶段 2:灰度发布(1 周)
- 添加新路由
/budget-v2 - 在导航中将"预算"菜单指向新路由
- 保留旧路由
/budget作为回退 - 收集用户反馈,修复 bug
阶段 3:稳定后清理(独立 PR)
- 确认新页面稳定后,删除旧路由
- 删除
BudgetView.vue - 清理相关的测试代码
Rollback 策略
如果新页面出现严重 bug:
- 在导航中将"预算"菜单改回
/budget - 修复 bug 后再次切换到
/budget-v2
Open Questions
-
BudgetChartAnalysis 组件是否需要调整?
- 需要评估当前组件是否完全满足月度数据展示需求
- 是否需要调整图表样式或数据格式
-
未覆盖分类的展示位置?
- 当前在页头右侧有警告图标
- 新布局中是否保留此功能?位置在哪里?
-
是否需要支持左右滑动切换月份?
- 是否需要与其他 v2 页面保持一致?