# 完成页迭代方案 — 审查报告(禁止应用) **审查对象**:左滑进完成页、右滑回最后一节;无顶部返回;无打字机金句;底部「回到我的内容」回到技能页 Tab。 **结论**:只做审查与完整代码输出,不修改仓库文件。 --- ## 一、需求与方案对照 | 需求 | 方案 | 审查结论 | |------|------|----------| | 不想要点开页面导航栏的返回按钮 | 移除顶部导航栏和返回按钮,仅顶部留白 | ✅ 一致 | | 右滑回到最后一个小节 | `.enableSwipeBack()` + `SwipeBackEnabler` 强制开启侧滑返回 | ⚠️ 见下文「侧滑返回」 | | 最后小节左滑进完成页,右滑回最后小节 | 播放器最后一节挂 `LastPageSwipeModifier` 左滑 push;完成页右滑 = 系统 pop | ✅ 一致 | | 不要打字机金句 | 移除打字机区域 | ✅ 一致 | | 底部「回到我的内容」回到技能页 Tab | 方案写的是 `navigationPath = NavigationPath()` | ❌ **逻辑错误**,见下 | --- ## 二、关键问题:底部按钮语义 **需求**:底部「回到我的内容」要回到**技能页 Tab**(即技能 Tab 根:课程列表)。 **方案中的实现**:`handleReturnToRoot()` 里写的是 `navigationPath = NavigationPath()`,即清空**当前传入的 path**。 - 若从**技能 Tab**进入:传的是 `growthPath`,清空后 = 技能 Tab 根 ✅ - 若从**发现 Tab**进入:传的是 `homePath`,清空后 = **发现 Tab 根**,不会切到技能 Tab ❌ - 若从**我的 Tab**进入:传的是 `profilePath`,清空后 = **我的 Tab 根**,不会切到技能 Tab ❌ 因此:**仅清空当前 path 无法满足「无论从哪个 Tab 进,都回到技能页 Tab」**。 **正确做法**:底部按钮应调用 **`navStore.switchToGrowthTab()`**(切到技能 Tab + 清空 `growthPath`),与当前线上 CompletionView 的「继续学习」一致。 - CompletionView 需保留 **`@EnvironmentObject var navStore`** - 不需要为「回到我的内容」传入 **`@Binding var navigationPath`**(调用方无需改传参) **完整代码中已按此修正**:底部按钮调用 `navStore.switchToGrowthTab()`,不传 `navigationPath`。 --- ## 三、侧滑返回(SwipeBackEnabler) - 方案用 **`SwipeBackEnabler`**(`UIViewControllerRepresentable`)在 `.background` 里查找 `navigationController` 并打开 `interactivePopGestureRecognizer`,以在隐藏导航栏时恢复右滑返回。 - **风险**:`makeUIViewController` 返回的是一颗裸 `UIViewController()`,其 `navigationController` 在部分时机可能仍为 nil(例如尚未挂到 NavigationStack 上),`DispatchQueue.main.async` 能缓解但无法完全保证。若遇真机偶发无效,可考虑: - 在 `updateUIViewController` 中轮询/延迟再取一次 `navigationController`,或 - 使用 `UINavigationController` 子类 / 注入方式保证拿到的必为当前栈。 - **结论**:实现可保留,建议在真机多场景(从三个 Tab 进入完成页后右滑)验证;若失效再加强时机或注入方式。 --- ## 四、VerticalScreenPlayerView 与占位页 - 方案采用「**不使用占位页**,仅最后一节左滑手势 push 完成页」:TabView 只渲染 `ForEach(allCourseNodes)`,不再多一页 `CompletionPlaceholderPage`。 - **影响**: - 进入完成页**仅剩**「在最后一节左滑」这一种方式;「滑到下一空白页再进完成页」的路径被移除。 - 从完成页右滑或 dismiss 后,播放器不再存在「当前是占位页、需在 onAppear 里切回最后一节」的状态,可删除 `onAppear` 里对 `currentNodeId == "wg://completion"` 的处理。 - **与现有调用方**:NoteTreeView / NoteListView 不传 `navigationPath`,`triggerCompletionNavigation` 里需 `guard let path = navigationPath else { return }`,从笔记进的播放器仍不会 push 完成页,行为不变。 --- ## 五、CompletionView 视觉与接口 - **视觉**:从当前 3D 翻转卡片 + 打字机,改为「赛博印章」:圆环 + 未盖章「完」/ 盖章后「已完成的第 N 节」+ 底部「回到我的内容」。 - **接口**: - 保留:`courseId`, `courseTitle`, `completedLessonCount`。 - 不增加 `@Binding var navigationPath`(底部用 `navStore.switchToGrowthTab()`)。 - 保留 `@EnvironmentObject var navStore`。 - **调用方**:GrowthView / ProfileView / DiscoveryView 的 `.completion` 分支**无需**新增参数,仍为三参构造。 --- ## 六、审查结论汇总 | 项 | 结论 | |----|------| | 顶部去掉返回按钮 | ✅ 方案正确 | | 右滑回最后一节 | ✅ 思路正确;SwipeBackEnabler 需真机验证,偶发需加强时机 | | 最后小节左滑进完成页 | ✅ 保留 LastPageSwipeModifier 即可 | | 去掉打字机金句 | ✅ 方案正确 | | 底部回到技能页 Tab | ❌ 方案用「清空当前 path」会错;应用 `navStore.switchToGrowthTab()`,完整代码已改 | | 调用方改动 | ✅ CompletionView 不需新参数;VerticalScreenPlayerView 保持可选 `navigationPath`,笔记流不受影响 | --- **完整代码见下(仅作交付,不写入仓库)。**