fix
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

This commit is contained in:
SunCheng
2026-02-20 14:57:19 +08:00
parent 6e95568906
commit 32d5ed62d0
27 changed files with 1520 additions and 1114 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-20

View File

@@ -0,0 +1,216 @@
## 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 的视觉差异可能被用户感知
- **暂定答案**: 作为内部优化,不需要用户通知

View File

@@ -0,0 +1,45 @@
## Why
项目中存在两个版本的弹窗组件(`PopupContainer.vue``PopupContainerV2.vue`造成代码冗余和维护成本增加。V2 版本采用更现代化的视觉风格Inter 字体、16px 圆角、纯色背景),且 API 更简洁。为了统一 UI 风格、减少技术债务,需要将所有使用旧版本的组件迁移到 V2并删除 V1 版本。
## What Changes
- 删除 `Web/src/components/PopupContainer.vue`(旧版本)
- 将 18 个使用 `PopupContainer` 的文件迁移到 `PopupContainerV2`
- 适配 API 差异V1 支持 subtitle、confirm/cancel 按钮V2 更简洁只提供 footer 插槽)
- 确保迁移后的视觉效果和交互逻辑保持一致
## Capabilities
### New Capabilities
- `popup-v2-migration`: 定义从 PopupContainer V1 到 V2 的迁移规范,包括 API 映射、样式对齐、兼容性处理
### Modified Capabilities
<!-- 无现有功能的需求变更 -->
## Impact
**受影响的文件**18 个 Vue 组件):
- `BudgetChartAnalysis.vue`
- `IconSelector.vue`
- `ClassificationEdit.vue`
- `SavingsBudgetContent.vue`
- `budgetV2/Index.vue`
- `PeriodicRecord.vue`
- `EmailRecord.vue`
- `ClassificationNLP.vue`
- `BillAnalysisView.vue`
- `TransactionDetail.vue`
- `ReasonGroupList.vue`
- `CategoryBillPopup.vue`
- `BudgetEditPopup.vue`
- `BudgetCard.vue`
- `AddClassifyDialog.vue`
- `MessageView.vue`
- `SavingsConfigPopup.vue`
- `GlobalAddBill.vue`
**迁移风险**:
- V1 的 `subtitle``showConfirmButton``showCancelButton` 等 props 在 V2 中不存在,需要重构为插槽方式
- V1 默认高度为 `80%`V2 为 `auto`(最大 `85%`),需要调整布局
- V1 使用 Vant 主题变量V2 使用硬编码颜色,暗色模式处理不同

View File

@@ -0,0 +1,108 @@
## ADDED Requirements
### Requirement: PopupContainer 组件导入路径迁移
所有引用 `PopupContainer.vue` 的文件必须更新导入路径为 `PopupContainerV2.vue`,并将组件名更改为 `PopupContainerV2`
#### Scenario: 更新 import 语句
- **WHEN** 文件中存在 `import PopupContainer from '@/components/PopupContainer.vue'``import PopupContainer from './PopupContainer.vue'`
- **THEN** 系统必须将其替换为 `import PopupContainerV2 from '@/components/PopupContainerV2.vue'`
#### Scenario: 更新模板中的组件名
- **WHEN** 模板中使用 `<PopupContainer>` 标签
- **THEN** 系统必须将其替换为 `<PopupContainerV2>`
### Requirement: Props API 映射转换
V1 和 V2 的 props 差异必须通过重构适配,确保功能等价。
#### Scenario: 基础 props 映射
- **WHEN** V1 使用 `v-model:show``title` 等基础 props
- **THEN** V2 必须保留这些 props 不变(`modelValue``title` 在两个版本中一致)
#### Scenario: height prop 默认值处理
- **WHEN** V1 未显式指定 `height` prop默认 `80%`
- **THEN** V2 必须显式添加 `:height="'80%'"` 以保持一致的视觉效果
#### Scenario: 移除不支持的 props
- **WHEN** V1 使用 `closeable``subtitle``showConfirmButton``showCancelButton``confirmText``cancelText` 等 props
- **THEN** 系统必须移除这些 props并通过插槽方式重构见下一需求
### Requirement: subtitle 功能迁移
V1 的 `subtitle` prop 必须转换为 V2 的默认插槽内容或自定义标题结构。
#### Scenario: subtitle 作为内容区域展示
- **WHEN** V1 使用 `subtitle` prop 显示副标题
- **THEN** 必须将 subtitle 内容移至 `<PopupContainerV2>` 的默认插槽中,并使用适当的样式包裹(如 `<p class="subtitle-text">{{ subtitle }}</p>`
#### Scenario: subtitle 包含 HTML 内容
- **WHEN** V1 的 `subtitle` 使用 `v-html` 渲染(如统计信息)
- **THEN** 必须在默认插槽中创建等价的 HTML 结构,保持语义和样式一致
### Requirement: 确认/取消按钮迁移
V1 的 `showConfirmButton``showCancelButton` 等按钮相关 props 必须转换为 V2 的 `footer` 插槽。
#### Scenario: 标准确认/取消按钮
- **WHEN** V1 使用 `show-confirm-button``show-cancel-button` props
- **THEN** 必须在 V2 的 `<template #footer>` 中手动创建 `<van-button>` 组件,绑定相同的事件处理器(`@confirm``@cancel`
#### Scenario: 自定义按钮文本
- **WHEN** V1 使用 `confirm-text``cancel-text` 自定义按钮文字
- **THEN** 必须将文本内容应用到 footer 插槽中的按钮组件
#### Scenario: 按钮布局样式
- **WHEN** 创建 footer 插槽内的按钮
- **THEN** 必须使用 flexbox 布局确保按钮水平排列,间距为 12px与 V1 的视觉效果一致
### Requirement: header-actions 插槽迁移
V1 的 `header-actions` 插槽必须根据业务逻辑转换为 V2 的内容区域或自定义实现。
#### Scenario: 移除 header-actions 插槽
- **WHEN** V1 使用 `<template #header-actions>` 插槽放置操作按钮
- **THEN** V2 必须将这些按钮移至默认插槽顶部或 footer 插槽中,根据业务语义选择合适位置
#### Scenario: 保持操作按钮的视觉层级
- **WHEN** V1 的 header-actions 与标题同行显示grid 布局)
- **THEN** 必须在 V2 的默认插槽中创建自定义布局,使用绝对定位或 flexbox 实现相同效果
### Requirement: 样式和暗色模式兼容性
迁移后的组件必须保持视觉一致性,正确响应暗色模式。
#### Scenario: 暗色模式自动适配
- **WHEN** 用户切换到暗色模式
- **THEN** V2 的硬编码颜色(`#ffffff``#09090b` 等)必须通过 `@media (prefers-color-scheme: dark)` 自动切换
#### Scenario: 内容区域 padding 处理
- **WHEN** V1 的可滚动内容区域有默认样式
- **THEN** V2 的内容插槽无默认 padding必须由使用方手动添加`<div class="content" style="padding: 16px">`
### Requirement: 事件处理器兼容性
V1 的事件(`@confirm``@cancel`)必须正确映射到 V2 的按钮点击事件。
#### Scenario: 确认事件触发
- **WHEN** 用户点击 footer 插槽中的确认按钮
- **THEN** 必须手动触发原有的 `@confirm` 事件处理逻辑(可能需要通过 `emit` 或直接调用方法)
#### Scenario: 取消事件触发
- **WHEN** 用户点击取消按钮或关闭弹窗
- **THEN** 必须确保原有的 `@cancel` 逻辑被正确执行V2 已通过关闭按钮自动关闭弹窗,但可能需要额外的清理逻辑)
### Requirement: 代码质量和测试
迁移后的代码必须通过 ESLint 检查,并保持功能正确性。
#### Scenario: ESLint 验证通过
- **WHEN** 完成迁移后
- **THEN** 运行 `pnpm lint` 必须无错误和警告
#### Scenario: 功能回归测试
- **WHEN** 迁移后的页面加载
- **THEN** 弹窗的打开/关闭、内容展示、按钮交互必须与迁移前行为一致
### Requirement: 删除旧版本组件
所有迁移完成后,必须删除 `PopupContainer.vue` 文件以避免混淆。
#### Scenario: 文件删除
- **WHEN** 所有 18 个文件迁移完成并验证通过
- **THEN** 系统必须删除 `Web/src/components/PopupContainer.vue` 文件
#### Scenario: 无残留引用
- **WHEN** 删除旧组件后
- **THEN** 项目中不得存在任何对 `PopupContainer.vue` 的引用(通过全局搜索验证)

View File

@@ -0,0 +1,72 @@
## 1. 准备和分析阶段
- [x] 1.1 审查 18 个待迁移文件,确认每个文件使用的 V1 功能subtitle、buttons、header-actions
- [x] 1.2 为每个文件创建迁移清单,标记需要特殊处理的部分(如 v-html、复杂布局
- [x] 1.3 备份当前代码(确保 Git 工作区干净,可以随时回滚)
## 2. 核心组件迁移 - 第一批(基础用法)
- [x] 2.1 迁移 `MessageView.vue` - 基础弹窗用法,无 subtitle 和按钮
- [x] 2.2 迁移 `EmailRecord.vue` - 基础弹窗用法
- [x] 2.3 迁移 `PeriodicRecord.vue` - 基础弹窗用法
- [x] 2.4 迁移 `ClassificationNLP.vue` - 基础弹窗用法
- [x] 2.5 迁移 `BillAnalysisView.vue` - 基础弹窗用法
- [x] 2.6 验证第一批迁移:运行 `pnpm lint`,手动测试每个页面的弹窗功能
## 3. 带 subtitle 的组件迁移 - 第二批
- [x] 3.1 迁移 `CategoryBillPopup.vue` - 处理 subtitle统计信息
- [x] 3.2 迁移 `BudgetChartAnalysis.vue` - 处理 subtitle
- [x] 3.3 迁移 `TransactionDetail.vue` - 处理 subtitle 和自定义内容
- [x] 3.4 迁移 `ReasonGroupList.vue` - 处理 subtitle
- [x] 3.5 验证第二批迁移:检查 subtitle 在默认插槽中的样式和位置
## 4. 带确认/取消按钮的组件迁移 - 第三批
- [x] 4.1 迁移 `AddClassifyDialog.vue` - 转换 show-confirm-button 和 show-cancel-button 为 footer 插槽
- [x] 4.2 迁移 `IconSelector.vue` - 处理确认/取消按钮和事件绑定
- [x] 4.3 迁移 `ClassificationEdit.vue` - 处理确认/取消按钮
- [x] 4.4 验证第三批迁移:测试按钮点击事件是否正确触发
## 5. 复杂布局组件迁移 - 第四批header-actions 和自定义布局)
- [x] 5.1 迁移 `BudgetCard.vue` - 处理 header-actions 插槽,移至内容区域顶部
- [x] 5.2 迁移 `BudgetEditPopup.vue` - 处理 header-actions 和 footer
- [x] 5.3 迁移 `SavingsConfigPopup.vue` - 处理自定义布局
- [x] 5.4 迁移 `SavingsBudgetContent.vue` - 处理 header-actions
- [x] 5.5 迁移 `budgetV2/Index.vue` - 处理复杂布局和多个弹窗实例
- [x] 5.6 验证第四批迁移:检查操作按钮的位置和交互是否符合预期
## 6. 全局组件迁移 - 第五批
- [x] 6.1 迁移 `GlobalAddBill.vue` - 处理全局弹窗的特殊逻辑
- [x] 6.2 验证全局组件:测试从不同页面触发弹窗的功能
## 7. 高度和样式调整
- [x] 7.1 检查所有迁移文件,为未显式设置 height 的组件添加 `:height="'80%'"`
- [x] 7.2 调整内容区域的 paddingV2 无默认 padding需要手动添加
- [x] 7.3 统一 footer 按钮的样式(创建全局 `.footer-buttons` 样式或在每个文件中复用)
- [ ] 7.4 验证暗色模式:切换到暗色模式,检查每个弹窗的颜色和对比度
## 8. 代码质量和测试
- [x] 8.1 运行 `pnpm lint` 修复所有 ESLint 错误和警告
- [ ] 8.2 运行 `pnpm build` 确保构建成功
- [ ] 8.3 手动测试所有 18 个迁移的页面,验证弹窗的打开/关闭、内容展示、按钮交互
- [ ] 8.4 测试边界情况:长文本、空内容、多次打开/关闭弹窗
- [ ] 8.5 检查控制台是否有警告或错误信息
## 9. 清理和文档更新
- [x] 9.1 确认所有迁移完成且测试通过
- [x] 9.2 删除 `Web/src/components/PopupContainer.vue` 文件
- [x] 9.3 全局搜索 `PopupContainer`(排除 `PopupContainerV2`),确认无残留引用
- [ ] 9.4 更新项目文档(如有组件使用说明,更新为 V2 的使用方式)
- [ ] 9.5 提交代码,编写清晰的 commit message
## 10. 后续优化(可选)
- [ ] 10.1 提取 footer 按钮样式为全局 CSS 类或 V2 组件的默认样式
- [ ] 10.2 考虑为 V2 添加常用的预设(如 `preset="confirm-dialog"`)简化未来的使用
- [ ] 10.3 在团队中分享迁移经验,更新最佳实践文档

View File

@@ -0,0 +1,108 @@
## ADDED Requirements
### Requirement: PopupContainer 组件导入路径迁移
所有引用 `PopupContainer.vue` 的文件必须更新导入路径为 `PopupContainerV2.vue`,并将组件名更改为 `PopupContainerV2`
#### Scenario: 更新 import 语句
- **WHEN** 文件中存在 `import PopupContainer from '@/components/PopupContainer.vue'``import PopupContainer from './PopupContainer.vue'`
- **THEN** 系统必须将其替换为 `import PopupContainerV2 from '@/components/PopupContainerV2.vue'`
#### Scenario: 更新模板中的组件名
- **WHEN** 模板中使用 `<PopupContainer>` 标签
- **THEN** 系统必须将其替换为 `<PopupContainerV2>`
### Requirement: Props API 映射转换
V1 和 V2 的 props 差异必须通过重构适配,确保功能等价。
#### Scenario: 基础 props 映射
- **WHEN** V1 使用 `v-model:show``title` 等基础 props
- **THEN** V2 必须保留这些 props 不变(`modelValue``title` 在两个版本中一致)
#### Scenario: height prop 默认值处理
- **WHEN** V1 未显式指定 `height` prop默认 `80%`
- **THEN** V2 必须显式添加 `:height="'80%'"` 以保持一致的视觉效果
#### Scenario: 移除不支持的 props
- **WHEN** V1 使用 `closeable``subtitle``showConfirmButton``showCancelButton``confirmText``cancelText` 等 props
- **THEN** 系统必须移除这些 props并通过插槽方式重构见下一需求
### Requirement: subtitle 功能迁移
V1 的 `subtitle` prop 必须转换为 V2 的默认插槽内容或自定义标题结构。
#### Scenario: subtitle 作为内容区域展示
- **WHEN** V1 使用 `subtitle` prop 显示副标题
- **THEN** 必须将 subtitle 内容移至 `<PopupContainerV2>` 的默认插槽中,并使用适当的样式包裹(如 `<p class="subtitle-text">{{ subtitle }}</p>`
#### Scenario: subtitle 包含 HTML 内容
- **WHEN** V1 的 `subtitle` 使用 `v-html` 渲染(如统计信息)
- **THEN** 必须在默认插槽中创建等价的 HTML 结构,保持语义和样式一致
### Requirement: 确认/取消按钮迁移
V1 的 `showConfirmButton``showCancelButton` 等按钮相关 props 必须转换为 V2 的 `footer` 插槽。
#### Scenario: 标准确认/取消按钮
- **WHEN** V1 使用 `show-confirm-button``show-cancel-button` props
- **THEN** 必须在 V2 的 `<template #footer>` 中手动创建 `<van-button>` 组件,绑定相同的事件处理器(`@confirm``@cancel`
#### Scenario: 自定义按钮文本
- **WHEN** V1 使用 `confirm-text``cancel-text` 自定义按钮文字
- **THEN** 必须将文本内容应用到 footer 插槽中的按钮组件
#### Scenario: 按钮布局样式
- **WHEN** 创建 footer 插槽内的按钮
- **THEN** 必须使用 flexbox 布局确保按钮水平排列,间距为 12px与 V1 的视觉效果一致
### Requirement: header-actions 插槽迁移
V1 的 `header-actions` 插槽必须根据业务逻辑转换为 V2 的内容区域或自定义实现。
#### Scenario: 移除 header-actions 插槽
- **WHEN** V1 使用 `<template #header-actions>` 插槽放置操作按钮
- **THEN** V2 必须将这些按钮移至默认插槽顶部或 footer 插槽中,根据业务语义选择合适位置
#### Scenario: 保持操作按钮的视觉层级
- **WHEN** V1 的 header-actions 与标题同行显示grid 布局)
- **THEN** 必须在 V2 的默认插槽中创建自定义布局,使用绝对定位或 flexbox 实现相同效果
### Requirement: 样式和暗色模式兼容性
迁移后的组件必须保持视觉一致性,正确响应暗色模式。
#### Scenario: 暗色模式自动适配
- **WHEN** 用户切换到暗色模式
- **THEN** V2 的硬编码颜色(`#ffffff``#09090b` 等)必须通过 `@media (prefers-color-scheme: dark)` 自动切换
#### Scenario: 内容区域 padding 处理
- **WHEN** V1 的可滚动内容区域有默认样式
- **THEN** V2 的内容插槽无默认 padding必须由使用方手动添加`<div class="content" style="padding: 16px">`
### Requirement: 事件处理器兼容性
V1 的事件(`@confirm``@cancel`)必须正确映射到 V2 的按钮点击事件。
#### Scenario: 确认事件触发
- **WHEN** 用户点击 footer 插槽中的确认按钮
- **THEN** 必须手动触发原有的 `@confirm` 事件处理逻辑(可能需要通过 `emit` 或直接调用方法)
#### Scenario: 取消事件触发
- **WHEN** 用户点击取消按钮或关闭弹窗
- **THEN** 必须确保原有的 `@cancel` 逻辑被正确执行V2 已通过关闭按钮自动关闭弹窗,但可能需要额外的清理逻辑)
### Requirement: 代码质量和测试
迁移后的代码必须通过 ESLint 检查,并保持功能正确性。
#### Scenario: ESLint 验证通过
- **WHEN** 完成迁移后
- **THEN** 运行 `pnpm lint` 必须无错误和警告
#### Scenario: 功能回归测试
- **WHEN** 迁移后的页面加载
- **THEN** 弹窗的打开/关闭、内容展示、按钮交互必须与迁移前行为一致
### Requirement: 删除旧版本组件
所有迁移完成后,必须删除 `PopupContainer.vue` 文件以避免混淆。
#### Scenario: 文件删除
- **WHEN** 所有 18 个文件迁移完成并验证通过
- **THEN** 系统必须删除 `Web/src/components/PopupContainer.vue` 文件
#### Scenario: 无残留引用
- **WHEN** 删除旧组件后
- **THEN** 项目中不得存在任何对 `PopupContainer.vue` 的引用(通过全局搜索验证)