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

217 lines
8.6 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
当前项目中存在两个弹窗组件:
- **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
<!-- 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 的极简标题设计
**实现模式**:
```vue
<!-- 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 的视觉差异可能被用户感知
- **暂定答案**: 作为内部优化,不需要用户通知