Files
EmailBill/openspec/changes/archive/2026-02-20-remove-popup-container-v1/design.md
SunCheng 32d5ed62d0
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 16s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
fix
2026-02-20 14:57:19 +08:00

8.6 KiB
Raw Blame History

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 插槽 + 手动事件绑定

实现模式:

<!-- V1 -->
<PopupContainer
  show-confirm-button
  show-cancel-button
  confirm-text="确定"
  @confirm="handleConfirm"
  @cancel="handleCancel"
/>

<!-- V2 -->
<PopupContainerV2>
  <template #footer>
    <div class="footer-buttons">
      <van-button plain @click="handleCancel">取消</van-button>
      <van-button type="primary" @click="handleConfirm">确定</van-button>
    </div>
  </template>
</PopupContainerV2>

<style scoped>
.footer-buttons {
  display: flex;
  gap: 12px;
}
.footer-buttons .van-button {
  flex: 1;
}
</style>

理由:

  • V2 的 footer 插槽提供了足够的灵活性
  • Vant Button 的样式与 V1 内置按钮一致,迁移成本低
  • 手动绑定事件可以保持原有的业务逻辑不变

Decision 4: header-actions 插槽的处理

选择: 移至默认插槽顶部或改用自定义布局

理由:

  • V2 没有 header-actions 插槽,标题区域只有标题文本和关闭按钮
  • 根据现有代码(如 BudgetCard.vue、SavingsBudgetContent.vueheader-actions 通常是"编辑"、"删除"等操作按钮
  • 这些按钮更适合放在内容区域顶部或 footer 中,符合 V2 的极简标题设计

实现模式:

<!-- V2 -->
<PopupContainerV2>
  <div class="content-with-actions">
    <div class="action-bar">
      <van-button size="small" @click="handleEdit">编辑</van-button>
    </div>
    <!-- 原内容区域 -->
  </div>
</PopupContainerV2>

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. 调整内容区域的 paddingV2 无默认 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 的视觉差异可能被用户感知
    • 暂定答案: 作为内部优化,不需要用户通知