130 lines
8.5 KiB
Markdown
130 lines
8.5 KiB
Markdown
|
|
# 完成页导航方案 — 实现后行为与影响说明
|
|||
|
|
|
|||
|
|
说明:实现方案后**会变成什么样**、**会不会影响原有逻辑和展示**、**会不会导致其他地方出问题**。不修改代码,仅作说明。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、实现后会变成什么样
|
|||
|
|
|
|||
|
|
### 1. 进入完成页(两种方式,二选一或并存)
|
|||
|
|
|
|||
|
|
| 方式 | 当前 | 实现后(若保留占位页 + 加手势) |
|
|||
|
|
|------|------|----------------------------------|
|
|||
|
|
| **左滑到「下一页」** | 最后一节再左滑 → 进入占位页(空白)→ 0.1s 后 push 完成页 | 不变:仍可滑到占位页,再由 `onChange` push |
|
|||
|
|
| **在最后一节左滑** | 无 | 新增:在最后一节直接左滑(手势识别)→ 可设为「切到占位页再 push」或「直接 push 完成页」 |
|
|||
|
|
|
|||
|
|
若方案采用「删除占位页、仅手势」:
|
|||
|
|
- 最后一节左滑 → 直接 push 完成页,**不再出现空白占位页那一屏**。
|
|||
|
|
- TabView 只有真实课程页,不再有「多一页」的占位。
|
|||
|
|
|
|||
|
|
**展示上**:完成页本身 UI 不变(仍是当前 CompletionView 的 3D 卡片、打字机等);变的只是「怎么进」和「怎么回」。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2. 从完成页返回(核心变化)
|
|||
|
|
|
|||
|
|
| 操作 | 当前 | 实现后 |
|
|||
|
|
|------|------|--------|
|
|||
|
|
| **顶部返回 (chevron)** | `dismiss()` → 回到播放器(最后一节或占位页) | 可保持不变:仍 `dismiss()`,回到播放器 |
|
|||
|
|
| **底部「继续学习」** | `navStore.switchToGrowthTab()` + `dismiss()` | `handleReturnToMap()`:`navigationPath.removeLast(2)` |
|
|||
|
|
|
|||
|
|
**「继续学习」行为对比**:
|
|||
|
|
|
|||
|
|
- **当前**:
|
|||
|
|
1. `dismiss()` → 栈 pop 一层,回到 **VerticalScreenPlayerView**(当前 Tab 可能是占位页,`onAppear` 里会切回最后一节)。
|
|||
|
|
2. `switchToGrowthTab()` → 切到「技能」Tab,且 **清空 `growthPath`**(`growthPath = NavigationPath()`)。
|
|||
|
|
3. 用户最终看到的是 **技能 Tab 的根界面(课程列表)**,不是地图。
|
|||
|
|
|
|||
|
|
- **实现后**:
|
|||
|
|
1. `removeLast(2)` → 一次 pop 掉「完成页」和「播放器」。
|
|||
|
|
2. 栈变成:**[MapView]**(或更短,视当前 path 而定)。
|
|||
|
|
3. **不**调用 `switchToGrowthTab()`,所以**不会清空 path**,也**不一定会切 Tab**。
|
|||
|
|
4. 用户最终看到的是 **当前 Tab 下的地图页**(从哪个 Tab 进的,就还在哪个 Tab)。
|
|||
|
|
|
|||
|
|
**总结**:
|
|||
|
|
- 实现后,点「继续学习」会**直接回到地图**,且**保留在当前 Tab**(发现 / 技能 / 我的)。
|
|||
|
|
- 当前是**回到课程列表**(且强制在技能 Tab)。这是**产品行为上的明确变化**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. 三条入口分别会怎样
|
|||
|
|
|
|||
|
|
| 入口 | 当前栈(到完成页时) | 当前「继续学习」后 | 实现后「继续学习」后 |
|
|||
|
|
|------|----------------------|--------------------|------------------------|
|
|||
|
|
| **技能 Tab** | growthPath: [Map, Player, Completion] | 切到技能 Tab + 清空栈 → **课程列表** | 仍技能 Tab,栈 [Map] → **地图** |
|
|||
|
|
| **发现 Tab** | homePath: [Map, Player, Completion] | dismiss 回播放器;switchToGrowthTab 切到技能并清空 → **课程列表** | 仍发现 Tab,栈 [Map] → **地图** |
|
|||
|
|
| **我的 Tab** | profilePath: [Map, Player, Completion] | 同上 → **课程列表** | 仍我的 Tab,栈 [Map] → **地图** |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、会不会影响原有逻辑和展示
|
|||
|
|
|
|||
|
|
### 1. 会改变的部分(有意为之)
|
|||
|
|
|
|||
|
|
- **底部按钮语义**:「继续学习」从「回课程列表(并切到技能)」变为「回地图(且留在当前 Tab)」。
|
|||
|
|
- **是否清空栈**:不再在完成页里清空 `growthPath`,所以不会出现「从完成页一点就回到空白课程列表」。
|
|||
|
|
- **是否切 Tab**:不再强制切到技能 Tab;用户会留在发现 / 技能 / 我的中的当前 Tab。
|
|||
|
|
|
|||
|
|
若产品期望就是「完成课后回到地图、留在当前 Tab」,则与方案一致;若期望是「回到课程列表、且一定在技能 Tab」,则与**当前**一致,与**实现后**不一致,需要产品确认。
|
|||
|
|
|
|||
|
|
### 2. 可保持不变的部分
|
|||
|
|
|
|||
|
|
- **完成页 UI**:3D 翻转卡片、打字机、顶部返回、按钮样式等都可以不改。
|
|||
|
|
- **顶部返回**:继续用 `dismiss()`,仍回到播放器,逻辑和展示都不变。
|
|||
|
|
- **进入完成页的方式**:若保留占位页,现有「滑到占位页再 push」仍可用;只是多了一种「最后一节左滑」的触发方式(若加手势)。
|
|||
|
|
|
|||
|
|
### 3. 依赖「当前行为」的地方
|
|||
|
|
|
|||
|
|
- **NavigationStore.switchToGrowthTab()**:当前只在 CompletionView 的「继续学习」里被调用。实现后 CompletionView 不再调用它,**不会影响其他使用处**(因为别处没有用这个方法)。
|
|||
|
|
- **growthPath 被清空**:当前只有「完成页点继续学习」会清空 growthPath。若没有「从完成页返回后依赖 growthPath 为空」的逻辑,则实现后**不会破坏其它功能**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、会不会导致其他地方出问题
|
|||
|
|
|
|||
|
|
### 1. 三个 Tab 的 navigationDestination(必须改)
|
|||
|
|
|
|||
|
|
- **GrowthView / ProfileView / DiscoveryView** 里 `.completion` 分支都要给 CompletionView 传 **Binding**:
|
|||
|
|
`navigationPath: $navStore.growthPath` / `$navStore.profilePath` / `$navStore.homePath`。
|
|||
|
|
- **漏传或错传**:
|
|||
|
|
- 漏传 → 编译不通过(CompletionView 多了必选参数)。
|
|||
|
|
- 错传(例如发现进的完成页却传了 `growthPath`)→ 点「继续学习」会 pop 错栈,可能白屏或回到错误 Tab。
|
|||
|
|
- **结论**:三处都必须改,且必须传**当前栈对应的 path**,否则会出问题。
|
|||
|
|
|
|||
|
|
### 2. 笔记流(NoteTreeView / NoteListView)— 不受影响
|
|||
|
|
|
|||
|
|
- 笔记流用的是 **NoteNavigationDestination.player**,打开的是 **VerticalScreenPlayerView**,且**没有传 navigationPath**(或传的是笔记自己的 path)。
|
|||
|
|
- 在这些地方打开的播放器里,`navigationPath` 为 nil,现有逻辑里 `onChange` 里会 `guard let path = navigationPath else { return }`,**不会 push CourseNavigation.completion**。
|
|||
|
|
- 因此:从笔记进的播放器,**不会**出现「滑到完成页」的 push(除非你后续在笔记流里也接 Course 的 completion)。
|
|||
|
|
- **结论**:实现完成页方案**不会影响笔记流**,也不会从笔记流误进完成页。
|
|||
|
|
|
|||
|
|
### 3. 从完成页返回时栈深度不足
|
|||
|
|
|
|||
|
|
- 正常流程栈至少为 [Map, Player, Completion],`removeLast(2)` 安全。
|
|||
|
|
- 若将来有「深链、通知、分享」等直接打开 CompletionView,栈可能只有 [Completion],此时 `path.count >= 2` 为假,应走兜底 `dismiss()`。
|
|||
|
|
- 方案里已建议保留 `if navigationPath.count >= 2 { removeLast(2) } else { dismiss() }`,**不会因为栈浅而崩溃**,最多退回单层 pop。
|
|||
|
|
|
|||
|
|
### 4. 手势与 TabView 滑动
|
|||
|
|
|
|||
|
|
- 在最后一节加「左滑进完成页」时,与 TabView 自带的左滑翻页**共用同一方向**,可能冲突(例如:想翻页却触发了完成页,或想进完成页却只翻到占位页)。
|
|||
|
|
- 若用「边缘左滑」或阈值(如 -60pt)、防抖,可减轻误触;需在真机多测。
|
|||
|
|
- **结论**:可能带来**体验上的小问题**(误触/难触),不是逻辑错误,可通过手势参数和测试收敛。
|
|||
|
|
|
|||
|
|
### 5. 其他使用 VerticalScreenPlayerView / CompletionView 的地方
|
|||
|
|
|
|||
|
|
- **VerticalScreenPlayerView**:除 GrowthView / ProfileView / DiscoveryView 外,仅在 **NoteTreeView / NoteListView** 出现,且走的是 NoteNavigationDestination,不涉及 CourseNavigation.completion,**不受影响**。
|
|||
|
|
- **CompletionView**:只在这三个 Tab 的 `navigationDestination(for: CourseNavigation.self)` 的 `.completion` 分支出现,**没有其它调用点**。
|
|||
|
|
- **结论**:只要三处传参正确,**不会导致其它页面逻辑错误**。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、简要结论表
|
|||
|
|
|
|||
|
|
| 问题 | 结论 |
|
|||
|
|
|------|------|
|
|||
|
|
| 实现后会变成什么样? | 进入完成页可增加「最后一节左滑」触发;「继续学习」从「回课程列表 + 切技能 Tab」变为「直接回地图 + 保留当前 Tab」;可选去掉占位页。 |
|
|||
|
|
| 会不会影响原有逻辑和展示? | 会:底部按钮语义和最终停留页(地图 vs 课程列表)、是否清栈/切 Tab 会变;顶部返回和完成页 UI 可保持不变。 |
|
|||
|
|
| 会不会导致其他地方出问题? | 三处传 Binding 必须正确,否则会 pop 错栈;笔记流、其它使用点不受影响;栈浅时用 dismiss 兜底;手势可能与 TabView 滑动冲突,需真机调参。 |
|
|||
|
|
|
|||
|
|
**建议**:实现前与产品确认「继续学习」的预期是「回地图」还是「回课程列表」;若确认为回地图且保留当前 Tab,再按方案改并保证三处 path 传参一致。
|