## Context 当前项目中存在两个弹窗组件: - **PopupContainer.vue (V1)**: 使用 Vant 主题变量,支持 subtitle、header-actions 插槽、确认/取消按钮等丰富功能,默认高度 80%,被 18 个组件使用 - **PopupContainerV2.vue (V2)**: 采用 Inter 字体和现代化设计风格(纯色背景、16px 圆角),API 更简洁(只提供 title、footer 插槽),默认高度 auto(最大 85%),已被 TransactionDetailSheet 使用 V1 和 V2 的 API 差异较大,V1 提供了更多开箱即用的功能(如内置按钮、subtitle),而 V2 追求灵活性和视觉一致性。此次迁移需要在保持功能不变的前提下,统一使用 V2 的现代化设计。 **约束条件**: - 不能改变现有页面的交互逻辑和用户体验 - 需要保持暗色模式的正确支持 - 必须通过 ESLint 和现有的代码规范 ## Goals / Non-Goals **Goals:** - 统一弹窗组件为 PopupContainerV2,删除 V1 版本 - 迁移 18 个使用 V1 的组件,保持功能等价性 - 适配 API 差异(props → 插槽、样式调整) - 确保迁移后视觉效果和交互逻辑一致 **Non-Goals:** - 不重新设计弹窗的交互流程或视觉风格(完全按照 V2 现有设计) - 不优化或重构业务逻辑(仅做组件替换和 API 适配) - 不处理与弹窗无关的代码问题 ## Decisions ### Decision 1: 迁移策略 - 逐文件手动迁移 vs 自动化脚本 **选择**: 手动逐文件迁移 **理由**: - V1 和 V2 的 API 差异大(props → 插槽),无法通过简单的查找替换完成 - 每个组件对 subtitle、header-actions、确认/取消按钮的使用方式不同,需要根据业务语义定制迁移方案 - 自动化脚本的开发成本高于手动迁移 18 个文件的时间成本 - 手动迁移可以确保每个文件的视觉和逻辑正确性 **备选方案**: - AST 转换工具(如 jscodeshift):复杂度高,难以处理插槽和样式的语义转换 ### Decision 2: subtitle 功能的迁移方式 **选择**: 根据业务语义分类处理 - **统计信息类 subtitle**(如 "共 10 笔交易")→ 移至默认插槽顶部,使用自定义样式组件 - **纯文本副标题** → 移至默认插槽,或合并到 title 中(如 "分类详情 - 餐饮") **理由**: - V2 没有 subtitle prop,必须通过插槽实现 - 统计信息通常有特定的业务含义,应作为内容区域的一部分而非标题的附属 - 纯文本副标题可以简化为一级标题的扩展 **备选方案**: - 扩展 V2 组件增加 subtitle prop:违背 V2 简化 API 的设计原则,不采纳 ### Decision 3: 确认/取消按钮的迁移方式 **选择**: 转换为 footer 插槽 + 手动事件绑定 **实现模式**: ```vue ``` **理由**: - V2 的 footer 插槽提供了足够的灵活性 - Vant Button 的样式与 V1 内置按钮一致,迁移成本低 - 手动绑定事件可以保持原有的业务逻辑不变 ### Decision 4: header-actions 插槽的处理 **选择**: 移至默认插槽顶部或改用自定义布局 **理由**: - V2 没有 header-actions 插槽,标题区域只有标题文本和关闭按钮 - 根据现有代码(如 BudgetCard.vue、SavingsBudgetContent.vue),header-actions 通常是"编辑"、"删除"等操作按钮 - 这些按钮更适合放在内容区域顶部或 footer 中,符合 V2 的极简标题设计 **实现模式**: ```vue
编辑
``` ### Decision 5: 高度属性的处理 **选择**: 显式指定 `:height="'80%'"` 保持视觉一致性 **理由**: - V1 默认 `height="80%"`,V2 默认 `height="auto"`(最大 85%) - 直接使用 V2 的 auto 可能导致内容过少时弹窗过小,影响用户体验 - 显式设置 80% 可以确保迁移前后视觉效果一致 - 如果某些组件的内容确实很少,可以在迁移时根据实际情况调整为 auto ### Decision 6: 样式和暗色模式的适配 **选择**: 信任 V2 的内置暗色模式支持,不额外修改 **理由**: - V2 已在组件内部通过 `@media (prefers-color-scheme: dark)` 实现暗色模式 - V1 使用 Vant 的 CSS 变量,V2 使用硬编码颜色,但两者在暗色模式下都能正确切换 - 业务组件只需确保内容区域的样式兼容暗色模式即可 **风险**: 如果业务组件的内容区域使用了与 V2 不兼容的颜色,需要单独调整(通过人工检查) ## Risks / Trade-offs ### Risk 1: 迁移后视觉效果差异 **风险**: V1 和 V2 的字体、颜色、圆角不同,可能导致用户感知到不一致 **缓解措施**: - 在开发环境逐个测试迁移后的页面 - 重点检查弹窗的标题、内容、按钮的对齐和间距 - 如果某个页面的差异过大,考虑调整 V2 的样式或在该页面单独处理 ### Risk 2: subtitle 和 header-actions 迁移语义变化 **风险**: 将 subtitle 移至内容区域可能改变信息层级,header-actions 移至内容顶部可能影响交互流畅性 **缓解措施**: - 迁移时保持原有的语义和视觉层级(如 subtitle 仍然显示在顶部且样式相似) - 通过 CSS 模拟 V1 的 Grid 布局,确保按钮位置不变 ### Risk 3: 高度变化导致滚动问题 **风险**: V1 的 80% 固定高度和 V2 的 auto 可能导致滚动行为不同(如内容过多时 V2 可能超出最大高度) **缓解措施**: - 统一使用 `:height="'80%'"` 作为默认值 - 对于内容特别少的弹窗(如确认对话框),可以单独设置为 auto ### Risk 4: 事件处理器遗漏 **风险**: 手动迁移确认/取消按钮时,可能遗漏原有的 `@confirm`、`@cancel` 事件逻辑 **缓解措施**: - 迁移前通过搜索确认每个组件是否使用了这些事件 - 迁移后通过功能测试验证按钮点击是否正确触发 ### Risk 5: ESLint 和代码规范问题 **风险**: 手动创建的 footer 插槽可能不符合项目的 ESLint 规则(如缩进、引号) **缓解措施**: - 迁移完成后运行 `pnpm lint` 并修复所有错误 - 参考现有 V2 的使用示例(TransactionDetailSheet.vue)保持风格一致 ## Migration Plan ### Phase 1: 准备阶段 1. 审查 18 个待迁移文件,分析每个文件使用的 V1 功能(subtitle、buttons、header-actions) 2. 为每个文件制定迁移清单(需要修改的部分) ### Phase 2: 迁移阶段 逐文件迁移,按以下步骤: 1. 更新 import 路径和组件名 2. 替换基础 props(保留 v-model:show、title,显式设置 height) 3. 迁移 subtitle(根据语义选择方案) 4. 迁移 header-actions(移至内容区域或 footer) 5. 迁移确认/取消按钮(创建 footer 插槽) 6. 调整内容区域的 padding(V2 无默认 padding) 7. 测试功能和视觉效果 ### Phase 3: 验证阶段 1. 运行 `pnpm lint` 确保代码规范 2. 手动测试每个迁移的页面,验证弹窗的打开/关闭、内容展示、按钮交互 3. 检查暗色模式下的显示效果 ### Phase 4: 清理阶段 1. 确认所有迁移完成且测试通过 2. 删除 `Web/src/components/PopupContainer.vue` 3. 全局搜索 `PopupContainer` 确认无残留引用 ### Rollback 策略 - 如果迁移后发现重大问题(如性能下降、功能缺失),可以通过 Git 回滚到迁移前的版本 - V1 和 V2 是独立文件,迁移失败不会影响现有功能(除非删除了 V1) - 建议在完成所有迁移并验证通过后再删除 V1 文件 ## Open Questions 1. **是否需要对 V2 组件进行增强?** - 例如增加 subtitle prop 简化迁移 - **暂定答案**: 不修改 V2,保持其简洁性,通过插槽实现所有功能 2. **是否需要统一 footer 按钮的样式?** - 目前每个文件需要手动创建 `.footer-buttons` 样式 - **暂定答案**: 可以提取为全局样式或在 V2 中提供默认样式(后续优化项) 3. **是否需要通知用户 UI 风格变化?** - V1 到 V2 的视觉差异可能被用户感知 - **暂定答案**: 作为内部优化,不需要用户通知