第18次:课程详情页面
课程详情页面是用户学习的核心界面,需要清晰地展示课程内容、代码示例和关键要点。本次课程将学习如何渲染不同类型的内容段落。
学习目标
- 掌握课程内容渲染方法
- 学会实现 ContentSection 段落组件
- 设计提示与警告样式
- 实现完成按钮交互
- 完成课程详情页面开发
18.1 课程内容结构
LessonContent 数据结构
typescript
interface LessonContent {
sections: ContentSection[]; // 内容段落
codeExamples: CodeExample[]; // 代码示例
keyTakeaways: string[]; // 关键要点
}
interface ContentSection {
type: 'text' | 'tip' | 'warning' | 'heading';
title?: string;
content: string;
}
interface CodeExample {
title: string;
code: string;
language: string;
explanation?: string;
isEditable: boolean;
}
内容类型说明
| 类型 | 说明 | 样式 |
|---|---|---|
| text | 普通文本 | 默认样式 |
| heading | 标题 | 加粗大字 |
| tip | 提示信息 | 蓝色背景 |
| warning | 警告信息 | 红色背景 |
18.2 页面头部设计
导航栏
typescript
Row() {
// 返回按钮
Text('←')
.fontSize(24)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.onClick(() => router.back())
// 标题
Text(this.lesson?.title ?? '')
.fontSize(18)
.fontWeight(FontWeight.Medium)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ left: 16 })
.layoutWeight(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 收藏按钮
Text(this.isBookmarked ? '★' : '☆')
.fontSize(24)
.fontColor(this.isBookmarked ? '#fcc419' : (this.isDarkMode ? '#9ca3af' : '#6c757d'))
.onClick(() => this.toggleBookmark())
}
.width('100%')
.padding(16)
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
18.3 ContentSection 段落渲染
段落组件实现
typescript
@Builder
SectionContent(section: ContentSection) {
Column() {
// 标题(如果有)
if (section.title) {
Text(section.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ bottom: 8 })
}
// 根据类型渲染不同样式
if (section.type === 'tip') {
this.TipContent(section.content)
} else if (section.type === 'warning') {
this.WarningContent(section.content)
} else {
this.TextContent(section.content)
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 16 })
.alignItems(HorizontalAlign.Start)
}
普通文本
typescript
@Builder
TextContent(content: string) {
Text(content)
.fontSize(14)
.fontColor(this.isDarkMode ? '#e6e6e6' : '#333333')
.lineHeight(22)
}
18.4 提示与警告样式
提示样式(Tip)
typescript
@Builder
TipContent(content: string) {
Row() {
Text('💡')
.fontSize(16)
Text(content)
.fontSize(14)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ left: 8 })
.layoutWeight(1)
}
.width('100%')
.padding(12)
.backgroundColor(this.isDarkMode
? 'rgba(97,218,251,0.15)'
: 'rgba(97,218,251,0.1)')
.borderRadius(8)
}
警告样式(Warning)
typescript
@Builder
WarningContent(content: string) {
Row() {
Text('⚠️')
.fontSize(16)
Text(content)
.fontSize(14)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ left: 8 })
.layoutWeight(1)
}
.width('100%')
.padding(12)
.backgroundColor(this.isDarkMode
? 'rgba(255,107,107,0.15)'
: 'rgba(255,107,107,0.1)')
.borderRadius(8)
}
18.5 代码示例展示
代码示例区域
typescript
if (this.lesson.content.codeExamples.length > 0) {
Text('代码示例')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.padding({ left: 16, right: 16, top: 24 })
.width('100%')
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('🔧 在调试器中打开')
.fontSize(13)
.fontColor('#61DAFB')
.backgroundColor(this.isDarkMode ? '#1e2127' : '#f0f9ff')
.borderRadius(16)
.margin({ top: 8 })
.onClick(() => {
router.pushUrl({
url: 'pages/CodePlayground',
params: {
title: example.title,
code: example.code,
language: example.language,
explanation: example.explanation
}
});
})
}
}
.margin({ left: 16, right: 16, top: 12 })
.alignItems(HorizontalAlign.End)
})
}
18.6 关键要点展示
要点列表
typescript
@Builder
KeyTakeawaysSection() {
Column() {
Text('📌 关键要点')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(this.isDarkMode ? '#ffffff' : '#1a1a2e')
.margin({ bottom: 12 })
ForEach(
this.lesson?.content.keyTakeaways ?? [],
(takeaway: string, index: number) => {
Row() {
// 序号圆圈
Text(`${index + 1}`)
.fontSize(12)
.fontColor('#ffffff')
.backgroundColor('#61DAFB')
.width(20)
.height(20)
.textAlign(TextAlign.Center)
.borderRadius(10)
// 要点内容
Text(takeaway)
.fontSize(14)
.fontColor(this.isDarkMode ? '#e6e6e6' : '#333333')
.margin({ left: 12 })
.layoutWeight(1)
}
.width('100%')
.margin({ bottom: 8 })
}
)
}
.width('100%')
.padding(16)
.margin({ left: 16, right: 16, top: 24 })
.backgroundColor(this.isDarkMode ? '#282c34' : '#ffffff')
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
}
18.7 完成按钮交互
按钮设计
typescript
@Builder
CompleteButton() {
Button(this.isCompleted ? '✓ 已完成' : '标记为完成')
.width('90%')
.height(48)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.backgroundColor(this.isCompleted ? '#51cf66' : '#61DAFB')
.fontColor(this.isCompleted ? '#ffffff' : '#1a1a2e')
.borderRadius(24)
.margin({ top: 24 })
.onClick(() => this.markComplete())
}
完成逻辑
typescript
private async markComplete(): Promise<void> {
if (this.lesson && !this.isCompleted) {
// 标记课程完成
await ProgressService.markLessonComplete(this.lesson.id, this.moduleId);
this.isCompleted = true;
// 检查并颁发徽章
const progress = await ProgressService.loadProgress();
await BadgeService.checkAndAwardBadges(progress);
}
}
18.8 完整页面代码
typescript
/**
* 课程详情页
*/
import { router } from '@kit.ArkUI';
import { TutorialService } from '../services/TutorialService';
import { ProgressService } from '../services/ProgressService';
import { BookmarkService } from '../services/BookmarkService';
import { BadgeService } from '../services/BadgeService';
import { Lesson, ContentSection, CodeExample } from '../models/Models';
import { CodeBlock } from '../components/CodeBlock';
interface RouterParams {
moduleId: string;
lessonId: string;
}
@Entry
@Component
struct LessonDetail {
@State lesson: Lesson | undefined = undefined;
@State moduleId: string = '';
@State isBookmarked: boolean = false;
@State isCompleted: boolean = false;
@StorageLink('isDarkMode') isDarkMode: boolean = false;
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();
}
}
private async checkCompletion(): Promise<void> {
const progress = await ProgressService.loadProgress();
this.isCompleted = progress.completedLessons.includes(this.lesson?.id ?? '');
}
build() {
Column() {
// 头部导航
this.HeaderSection()
// 内容区域
if (this.lesson) {
Scroll() {
Column() {
// 课程描述
Text(this.lesson.description)
.fontSize(14)
.fontColor(this.isDarkMode ? '#d1d5db' : '#495057')
.padding({ left: 16, right: 16, top: 16 })
.width('100%')
// 内容段落
ForEach(this.lesson.content.sections, (section: ContentSection) => {
this.SectionContent(section)
})
// 代码示例
this.CodeExamplesSection()
// 关键要点
if (this.lesson.content.keyTakeaways.length > 0) {
this.KeyTakeawaysSection()
}
// 完成按钮
this.CompleteButton()
}
.padding({ bottom: 40 })
}
.layoutWeight(1)
.scrollBar(BarState.Off)
}
}
.width('100%')
.height('100%')
.backgroundColor(this.isDarkMode ? '#1a1a2e' : '#f8f9fa')
}
// ... Builder 方法实现
}
本次课程小结
通过本次课程,你已经:
✅ 掌握了课程内容渲染方法
✅ 学会了实现 ContentSection 段落组件
✅ 设计了提示与警告样式
✅ 实现了完成按钮交互
✅ 完成了课程详情页面开发
课后练习
-
添加目录导航:实现课程内容目录快速跳转
-
添加阅读进度:显示当前阅读位置百分比
-
添加字体设置:支持调整字体大小
下次预告
第19次:CodeBlock 代码块组件
我们将开发代码展示组件:
- 代码展示设计
- 语法高亮基础
- 代码复制功能
- Toast 提示反馈
实现专业的代码展示效果!