001project_wildgrowth/ios/WildGrowth/COMPLETION_NAVIGATION_审查报告.md

134 lines
7.6 KiB
Markdown
Raw Normal View History

2026-02-11 15:26:03 +08:00
# 完成页导航方案 — 审查报告
**审查对象**:基于「零副作用 / SSOT / 响应式导航 / 原生手感」的完成页与播放器导航改造方案
**审查结论**:只做审查,不修改代码;本报告供决策与后续实现参考。
---
## 一、方案目标与标准(引用)
| 标准 | 含义 |
|-----|------|
| **零副作用** | 不引入「假页面」污染数据源,无 Hack |
| **单一事实来源 (SSOT)** | 数据源 `allCourseNodes` 仅含真实课程章节 |
| **响应式导航** | 用 `NavigationPath` 堆栈精确控制流向,消除白屏回退 |
| **原生手感** | 用手势识别(非页面索引)触发跳转 |
---
## 二、方案要点摘要
1. **删除 CompletionPlaceholderPage 的导航职责**(或整页删除,视实现而定)
- 若保留:仅作纯视觉占位,无 `onAppear` 等导航逻辑。
2. **VerticalScreenPlayerView**
- 在**最后一节**挂载「边缘左滑手势」修饰符(如 `LastPageSwipeModifier`)。
- 左滑触发:`navigationPath.append(CourseNavigation.completion(...))`,或先设 `currentNodeId = "wg://completion"` 再由现有 `onChange` 统一 push。
- TabView 仅渲染真实 `allCourseNodes`,不把「完成」当作一节数据。
3. **CompletionView**
- 新增 `@Binding var navigationPath: NavigationPath`
- 「回到我的内容」/「继续学习」调用 `handleReturnToMap()``navigationPath.removeLast(2)`,一次 pop 完成页 + 播放器,直接回地图。
4. **调用方**
- GrowthView / ProfileView / DiscoveryView 的 `.completion` 分支向 `CompletionView` 传入对应 path 的 `Binding`(如 `$navStore.growthPath` 等)。
---
## 三、与当前实现的对照
| 维度 | 当前实现 | 方案 |
|------|----------|------|
| **数据源** | `allCourseNodes` 纯净TabView 多一页 `CompletionPlaceholderPage` 用 tag `"wg://completion"`**未**写入 `allCourseNodes` | 与当前一致或更进一步:完全移除占位页,仅手势触发 push |
| **进入完成页** | 左滑到占位页 → `onChange(of: currentNodeId)` → 0.1s 后 append `.completion` | 最后一节左滑手势 → 直接 append 或先切 tag 再 append不依赖「多一页」 |
| **完成页返回** | `navStore.switchToGrowthTab()` + `dismiss()`,先回播放器再靠系统/逻辑 | `removeLast(2)` 穿透回地图,无中间层 |
| **占位页** | 存在,纯视觉 + 父视图 `onChange` 驱动 push无占位内 `onAppear` | 方案 A保留为纯视觉方案 B删除仅手势 |
**结论**:方案在「单一事实来源」和「响应式导航」上比当前更彻底;当前已避免在数据源里掺假节点,但仍依赖 TabView 多一页占位。
---
## 四、按标准的符合度
### 4.1 零副作用
- **方案**:若不保留占位页,则无「假页」;若保留占位页且仅视觉、无逻辑,则也算零逻辑副作用。
- **当前**:占位页无 `onAppear` 导航,副作用已收敛,但 TabView 仍多一个「虚拟页」。
- **符合度**:方案完全符合;当前基本符合,方案更干净。
### 4.2 单一事实来源 (SSOT)
- **方案**`allCourseNodes` 仅课程章节;完成页由导航栈 + 手势驱动,不进入数据模型。
- **当前**`allCourseNodes` 已是纯净的 `flatMap` 章节节点,未追加 placeholder node。
- **符合度**:方案与当前都符合;方案在视图层也不再依赖「多一页」的 tagSSOT 更纯粹。
### 4.3 响应式导航
- **方案**`CompletionView` 通过 `Binding<NavigationPath>` 执行 `removeLast(2)`,栈由 path 唯一决定,无 `switchToGrowthTab()` + `dismiss()` 的二次操作。
- **当前**:依赖 `dismiss()` 回播放器,再从播放器回地图,存在中间层与潜在白屏/闪烁。
- **符合度**:方案明显更符合;当前有改进空间。
### 4.4 原生手感
- **方案**:最后一节用 `DragGesture`(或类似)识别左滑,不依赖「滑到下一 tab 索引」才触发。
- **当前**:依赖用户滑到「占位页」才触发,仍与 TabView 索引/选页绑定。
- **符合度**:方案更贴近「手势驱动」;当前是「页面索引 + 手势」混合。
---
## 五、与既有文档的一致性
### 5.1 COMPLETION_VIEW_UI_LAYER_SPEC.md
- 说明要求:返回用 `dismiss()`**不**接收或操作 `NavigationPath`
- **方案**:改为接收 `Binding<NavigationPath>``removeLast(2)`,与当前 spec 冲突。
- **建议**:若采用方案,需**同步更新** COMPLETION_VIEW_UI_LAYER_SPEC
- 写明「穿透式返回」为可选/推荐实现;
- 接口增加 `@Binding var navigationPath: NavigationPath`
- 底部按钮行为改为「可调用 `removeLast(2)` 直接回地图」,并注明调用方需传入对应 path 的 Binding。
### 5.2 COMPLETION_PAGE_MEANING.md
- 当前描述为:最后一节后再左滑一页进入占位页,占位页触发 push。
- **方案**:最后一节左滑即触发(或先切 tag 再触发),可完全去掉「多一页」概念。
- **建议**:若采用方案,更新 COMPLETION_PAGE_MEANING
- 「最后一页」仍指课程的最后一节;
- 进入完成页的方式改为「在最后一节左滑(手势)触发」,并注明是否保留占位页。
---
## 六、风险与注意点
1. **多入口 path**
CompletionView 在 Growth / Profile / Discovery 三处被 present需分别传入 `growthPath` / `profilePath` / `homePath` 的 Binding漏传或传错会导致 `removeLast(2)` 作用在错误栈上。建议:
- 在审查/实现时逐处确认传参;
- 或为 CompletionView 封装「当前栈」来源(例如 Environment 注入当前 path避免调用方误绑。
2. **NavigationPath.count**
`removeLast(2)` 前需保证 `path.count >= 2`(完成页 + 播放器)。若从深链或异常入口进入完成页,栈深度可能不足,需保留兜底(如 `dismiss()`)。
3. **手势与 TabView 滑动冲突**
最后一节左滑既会触发自定义手势,也是 TabView 的翻页手势。需通过 `minimumDistance`、`coordinateSpace` 或「边缘优先」等策略区分,避免误触或重复触发。建议在真机多测:最后一节左滑、快速连续左滑、斜滑。
4. **从完成页返回后的 Tab 状态**
当前实现:从完成页 dismiss 回播放器后,`onAppear` 里若 `currentNodeId == "wg://completion"` 会切回 `allCourseNodes.last?.id`。采用方案后,若使用 `removeLast(2)`,不会回到播放器,故无需再依赖该逻辑;若仍保留「先 dismiss 再回地图」的入口,需保留或等价处理该状态恢复。
---
## 七、审查结论与建议
| 项目 | 结论 |
|------|------|
| **架构与标准** | 方案满足「零副作用、SSOT、响应式导航、原生手感」四项标准且比当前实现更彻底。 |
| **与现有 spec** | 与 COMPLETION_VIEW_UI_LAYER_SPEC 的「不操作 NavigationPath」冲突需更新 spec。 |
| **实现成本** | 中等CompletionView 接口与三处调用方改动VerticalScreenPlayerView 增加手势与可选移除占位页;需回归测试返回与多入口。 |
| **建议** | 若采纳方案:
1. 先更新 COMPLETION_VIEW_UI_LAYER_SPEC 与 COMPLETION_PAGE_MEANING再改代码
2. CompletionView 保留 `path.count >= 2` 判断与 `dismiss()` 兜底;
3. 手势与 TabView 的冲突在真机验证,必要时加防抖或边缘区域限制;
4. 三处 navigationDestination 的 `Binding` 传参做清单检查,避免漏传/错传。 |
---
**报告日期**:基于当前代码与所提供方案整理,未对仓库做任何代码修改。