HarmonyOS APP<玩转React>开源教程十八:课程详情页面
第18次:课程详情页面
本文讲解单节课程内容页的实现,对应文件是
entry/src/main/ets/pages/LessonDetail.ets。如果说模块详情页是目录,那么课程详情页就是正文,这里既要展示讲解内容,也要展示代码示例、关键要点、收藏入口和完成按钮。
这一页在整个流程里的位置
当用户从模块详情点击某一节课时,会进入 LessonDetail。这个页面需要解决的事情非常多:
- 根据
moduleId + lessonId查到具体课时 - 展示课程说明和正文段落
- 根据不同 section 类型渲染普通文本、提示和警告
- 通过
CodeBlock展示代码示例 - 支持跳转到代码调试器
- 支持收藏与完成状态切换
- 在完成后联动进度与徽章服务
一、页面初始化时拿到的不是整本模块,而是单节课
LessonDetail 在 aboutToAppear() 中做的事情很直接:
- 从路由参数里拿到
moduleId和lessonId - 调用
TutorialService.getLessonById(moduleId, lessonId) - 同时读取当前收藏状态
- 再检查当前课时是否已完成
这一步说明一个很重要的设计点:课时详情页不自己遍历全部数据,而是精准查找单条业务对象。这样页面状态会更轻、更清晰,也更适合做细粒度交互。
二、正文内容不是一整块字符串,而是结构化 section
这一页真正值得学的地方,是课程正文的组织方式。项目没有把正文写成一个超长富文本,而是把内容拆成 ContentSection[]。每个 section 至少包含:
typetitlecontent
在页面层,SectionContent(section) 根据 type 做不同渲染:
- 普通文本:直接
Text tip:显示高亮提示块warning:显示警告块
这样做有几个明显优势:
- 数据和渲染是解耦的。
- 同一套数据结构可以给不同页面或组件复用。
- 以后要增加新的内容类型,比如“引用”“小结”“练习题”,只需要扩展渲染逻辑。
这就是教程内容型应用非常典型的写法:正文内容结构化,页面负责解释结构。
三、代码示例区为什么单独做成一个板块
当前项目里,课程内容中的代码示例不是混在普通 section 里,而是放在 lesson.content.codeExamples 里单独渲染。它的好处非常明显:
- 可以统一使用
CodeBlock组件。 - 每段代码可以带标题、语言类型和解释说明。
- 某些代码示例还能带
isEditable,一键跳到CodePlayground。
这就把“阅读代码”和“动手练习”连接起来了。对于学习类应用来说,这一步非常关键,因为只有展示代码不够,最好还能让用户试着改、试着运行。
你会看到页面中为可编辑示例提供了“在调试器中打开”按钮,这个按钮点击后会把:
titlecodelanguageexplanation
一起通过路由参数传给 CodePlayground。这是一种很实用的鸿蒙页面协作方式。
四、关键要点区块为什么放在正文后面
KeyTakeawaysSection() 负责展示 keyTakeaways。它没有放在最上面,而是放在正文和代码示例之后,原因很合理:
- 先理解内容
- 再看代码
- 最后做归纳
这和真实学习习惯是一致的。关键要点不是替代正文,而是帮助复盘。UI 上使用编号圆点,也是为了让要点更容易扫读。
这一块设计很适合你以后写知识类应用时借鉴。很多初学者会把“重点”塞进正文里,结果越写越乱。更好的做法就是像当前项目这样:正文负责展开,关键要点负责收束。
五、完成按钮背后的联动非常值得记
CompleteButton() 的点击事件并不只是把按钮颜色改一下。真实逻辑是:
- 调用
ProgressService.markLessonComplete(this.lesson.id, this.moduleId) - 把本地
isCompleted更新成true - 再读取最新 progress
- 调用
BadgeService.checkAndAwardBadges(progress)
这说明“完成学习”这个动作,其实会牵动多个系统:
- 课程系统
- 进度系统
- 徽章系统
- 首页与个人中心的展示系统
所以你以后看到一个按钮,不要只盯着它的 UI 样式,而要追踪它背后的业务链路。
六、收藏按钮为什么放在标题栏
这个细节其实体现了页面优先级。课时详情页最核心的交互只有两个:
- 收藏
- 标记完成
收藏按钮被放在标题栏右侧,是因为它属于“轻量即时操作”,不需要占大面积空间;而“标记完成”放到底部,是因为它属于主要动作,需要更明确、更有存在感。
这是一种很合理的交互分层:
- 轻操作放头部
- 主动作放底部
- 内容放中间
你以后做文章页、笔记页、教程页时,都可以参考这种层次。
七、这一页和上一页的边界一定要分清
很多人写教程时容易把 ModuleDetail 和 LessonDetail 讲混。最简单的区分方式是:
ModuleDetail负责“列目录”LessonDetail负责“讲正文”
具体来说:
- 模块页关心的是课时列表、完成状态、收藏状态。
- 课程页关心的是正文结构、代码示例、关键要点、完成按钮。
如果你把两者边界理清,后面看 CodeBlock 和 CodePlayground 的时候就会发现,代码阅读与调试功能之所以能成立,前提就是 LessonDetail 已经把课时内容组织得足够清楚。
八、自己实操时建议怎么验证
- 从任意模块进入一节课程。
- 看顶部标题、课程描述、正文 section 是否都能正常显示。
- 找到代码示例,确认
CodeBlock渲染正常。 - 点击“在调试器中打开”,观察参数是否正确传到
CodePlayground。 - 点击收藏,再返回模块详情页,看收藏状态是否同步。
- 点击“标记为完成”,再返回首页和个人中心,看学习统计和徽章是否变化。
只要你自己把这条链路走通一次,这个页面的设计思路就会非常牢固。
九、本篇常见坑
1. 把正文内容写死在页面里
页面应该消费 lesson.content.sections,而不是自己拼一堆文字。
2. 代码示例和正文混着写
把代码示例单独抽成 codeExamples,更利于展示和后续调试跳转。
3. 完成按钮只改本地状态
如果不调用 ProgressService,那只是“假完成”,刷新页面数据就丢了。
4. 忽略徽章联动
完成课时之后还要继续检查徽章条件,这样奖励体系才是闭环。
本篇小结
课程详情页是这个项目真正承载教学内容的地方。你应该重点记住这几件事:
- 它按
moduleId + lessonId精准定位课时。 - 它通过
sections、codeExamples、keyTakeaways组织正文。 - 它把收藏、完成、代码调试入口都串到了同一页。
理解了这一页,接下来读 CodeBlock 和 CodePlayground 就会非常自然,因为它们本来就是为这页服务的。
跟着真实源码继续往下看
课时详情页加载课时和收藏状态的真实代码:
aboutToAppear(): void {
const params = router.getParams() as RouterParams;
if (params?.moduleId && params?.lessonId) {
this.moduleId = params.moduleId;
this.lesson = TutorialService.getLessonById(params.moduleId, params.lessonId);
this.isBookmarked = BookmarkService.isBookmarked(params.lessonId);
this.checkCompletion();
}
}
代码示例区域的真实写法如下:
ForEach(this.lesson.content.codeExamples, (example: CodeExample) => {
Column() {
CodeBlock({
code: example.code,
language: example.language,
title: example.title,
explanation: example.explanation
})
if (example.isEditable) {
Button('🔧 在调试器中打开')
.onClick(() => {
router.pushUrl({
url: 'pages/CodePlayground',
params: {
title: example.title,
code: example.code,
language: example.language,
explanation: example.explanation
}
});
})
}
}
})
按这个顺序动手
- 打开
entry/src/main/ets/pages/LessonDetail.ets。 - 搜索
codeExamples,看代码示例是如何交给CodeBlock的。 - 再搜索
markComplete(),确认完成动作如何联动进度和徽章。
课后练习
- 画出
LessonDetail -> CodePlayground的参数传递关系。 - 总结
SectionContent()针对普通文本、tip、warning 三类内容分别采用了什么视觉样式。 - 思考如果你要加入“课后练习”板块,应该放进
sections,还是单独做成一个新字段,为什么。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)