001project_wildgrowth/ios/WildGrowth/COMPLETION_最终交付_isFirstNode...

104 lines
7.2 KiB
Markdown
Raw Normal View History

2026-02-11 15:26:03 +08:00
# 最终交付代码审查反馈isFirstNodeInChapter 完全回滚版)
**审查对象**您提供的「CompletionView 全量替换 + VerticalScreenPlayerView 仅替换 Struct」的最终交付代码并已恢复 `isFirstNodeInChapter` 为含 `.sorted { $0.order < $1.order }` 的写法。
**结论**:仅反馈,**不应用、不修改**仓库内任何文件。
---
## 参数说明(白话)
| 说法 | 指什么 | 作用 |
|------|--------|------|
| **CompletionView 仍为 3 个参数** | 构造时仍是 `(courseId, courseTitle, completedLessonCount)` | GrowthView / ProfileView / DiscoveryView 里已有 `CompletionView(courseId: ..., courseTitle: ..., completedLessonCount: ...)` 的调用不用改;替换成新 UI 后接口不变,不会报错。 |
| **VerticalScreenPlayerView init 6 参** | 构造时仍是 `(courseId, nodeId, initialScrollIndex?, navigationPath?, isLastNode?, courseTitle?)` | MapView / GrowthView / DiscoveryView / ProfileView 等传入的 6 个参数(或只传前几项、后几项用默认值)都不用改;只改播放器内部实现,调用方零改动。 |
---
## 应用后仅实现以下三点、且无多余修改
若您**只**做这两步:① 全量替换 `CompletionView.swift`;② 仅替换 `VerticalScreenPlayerView.swift` 里的 **struct VerticalScreenPlayerView**(保留同文件内 HeaderConfig、LessonPageView 等其余代码),则:
| # | 需求 | 是否由本交付代码实现 | 说明 |
|---|------|----------------------|------|
| 1 | 播放器最后一个小节左滑进入完结页,从完结页右滑回到最后一个小节页 | ✅ 是 | 完结页作为 TabView 的最后一页内嵌在播放器内,左滑最后一节→完结页,右滑完结页→最后一节,无 push/pop。 |
| 2 | 完结页的 UI以及从接口/数据获取「共完成多少个小节」 | ✅ 是 | 新 UI粉紫勋章、点击点亮数量来自 `UserManager.shared.studyStats.lessons`(应用内统计,通常由学习进度接口或本地完成逻辑更新)。 |
| 3 | 底部一个按钮,点击回到技能 Tab我的课程列表 | ✅ 是 | 按钮「回到我的内容」仅调用 `navStore.switchToGrowthTab()`,切到技能 Tab。 |
**其他没有任何多余修改**
- **只动 2 个文件**`CompletionView.swift`(全量)、`VerticalScreenPlayerView.swift`(仅主 struct
- **不改动**CourseNavigation、MainTabView、MapView、ProfileView、DiscoveryView、GrowthView、NoteTreeView、NoteListView 等所有其他页面与类型;不增删导航枚举、不改 Tab 结构、不改地图/发现/个人/技能页逻辑。
- 完结页不再通过「占位页 + onChange push .completion」出现而是作为播放器 TabView 最后一页展示,因此无需也不会去改各 Tab 的 `navigationDestination(for: .completion)` 的写法(它们保留不动,只是从播放器内不再 push .completion
**结论**:应用本交付代码后,行为严格限于上述 1、2、3 三点,无其他多余修改。
---
## 一、isFirstNodeInChapter与当前仓库 100% 一致 ✅
| 对比项 | 当前仓库实现 | 您提供的交付代码 | 结论 |
|--------|--------------|------------------|------|
| 章节内节点 | 先找到包含 `nodeId` 的 chapter再在该章内取 `validNodes` | 遍历每个 chapter`validNodes` | 语义等价 |
| 排序 | `chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }` | `chapter.nodes.filter { $0.status != .locked }.sorted { $0.order < $1.order }` | ✅ 完全一致 |
| 首节判定 | `validNodes.first?.id == nodeId` | `validNodes.first?.id == nodeId`(在含该 node 的章内) | ✅ 完全一致 |
**结论**:章节判定逻辑已完全回滚到与当前仓库一致的写法(含 `.sorted { $0.order < $1.order }`),不会改变现有「按 order 的章节首节」展示行为。
---
## 二、loadMapData无全局排序与当前一致 ✅
- **当前仓库**`allCourseNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,无 `.sorted`
- **交付代码**`realNodes = data.chapters.flatMap { $0.nodes }.filter { $0.status != .locked }`,仅 UI 层 `items = realNodes.map { .lesson($0) }``append(.completion)`,无全局排序。
**结论**:数据顺序与当前一致,未引入按 order 的整课排序。
---
## 三、CompletionView仅 UI 更新,接口兼容 ✅
| 项目 | 说明 |
|------|------|
| 构造参数 | 仍为 `(courseId, courseTitle, completedLessonCount)`GrowthView/ProfileView/DiscoveryView 等处已有调用无需改 |
| 依赖 | 仍使用 `@EnvironmentObject private var navStore: NavigationStore` |
| 按钮 | 「回到我的内容」仅调用 `navStore.switchToGrowthTab()`,未调用 `dismiss()` |
| 说明 | 在统一分页方案下,完结页作为 TabView 最后一页内嵌展示,无 push 栈,不调用 `dismiss()` 是正确行为 |
**结论**:可全量替换 `CompletionView.swift`,仅 UI 从「翻牌 + 打字机」改为「粉紫勋章 + 点击点亮」,对外接口与行为符合预期。
---
## 四、VerticalScreenPlayerView仅替换 Struct其余保留 ✅
| 项目 | 结论 |
|------|------|
| **init** | 6 个构造参数完整保留courseId, nodeId, initialScrollIndex?, navigationPath?, isLastNode?, courseTitle?MapView/GrowthView 等调用方无需改 ✅ |
| **PlayerItem** | 枚举 `.lesson(MapNode)` / `.completion` 仅用于 UI 数据源,不参与完成数等业务逻辑 ✅ |
| **allItems** | 由 `realNodes` + 末尾 `.completion` 构成,无全局排序 ✅ |
| **currentPositionProgress** | 仅用 `lesson` 项计算,排除完结页,进度条正确 ✅ |
| **完结页展示** | 顶部进度条在 `currentNodeId == "COMPLETION_PAGE"` 时隐藏,逻辑正确 ✅ |
| **LessonPageView** | `courseTitle: self.courseTitle ?? mapData?.courseTitle` 可减少标题闪烁 ✅ |
| **错误态** | 保留加载失败 + 重试;交付代码中错误文案为 `.foregroundColor(.gray)`,当前为 `.inkSecondary`,属风格差异,可酌情统一 |
| **合并范围** | 仅替换 `struct VerticalScreenPlayerView { ... }` 及其内部的 `enum PlayerItem`**必须保留**同文件内 `HeaderConfig`、`DuolingoProgressBar`、`CourseProgressNavBar`、`LessonPageView`、`LessonSkeletonView` 等所有其他类型,不得整文件覆盖 ✅ |
---
## 五、对其他页面的影响
- **CourseNavigation**、**MainTabView**、**MapView**、**ProfileView**、**DiscoveryView**、**GrowthView** 等无需因本交付代码而改动。
- 调用方仍按现有方式传入 6 参(含 `navigationPath?`、`isLastNode`、`courseTitle`);完结页由 TabView 内嵌展示,不再依赖 push `.completion` 或占位页 `onChange`
---
## 六、审查结论汇总
| 项目 | 结论 |
|------|------|
| **isFirstNodeInChapter** | 已完全回滚为含 `.sorted { $0.order < $1.order }` 的写法,与当前仓库 100% 一致 ✅ |
| **loadMapData** | 无全局排序,与当前一致 ✅ |
| **CompletionView** | 仅 UI 更新,可全量替换 ✅ |
| **VerticalScreenPlayerView** | 仅替换主视图 Struct保留同文件其余代码逻辑与展示符合「逻辑回滚 + 统一分页」目标 ✅ |
| **其他页面** | 无需改动,零影响 ✅ |
**未对仓库内任何文件进行修改。**