HarmonyOS APP<玩转React>开源教程十八:课程详情页面

第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 段落组件

✅ 设计了提示与警告样式

✅ 实现了完成按钮交互

✅ 完成了课程详情页面开发


课后练习

  1. 添加目录导航:实现课程内容目录快速跳转

  2. 添加阅读进度:显示当前阅读位置百分比

  3. 添加字体设置:支持调整字体大小


下次预告

第19次:CodeBlock 代码块组件

我们将开发代码展示组件:

  • 代码展示设计
  • 语法高亮基础
  • 代码复制功能
  • Toast 提示反馈

实现专业的代码展示效果!

相关推荐
弓.长.2 小时前
ReactNative for OpenHarmony项目鸿蒙化三方库:react-native-waterfall-flow — 瀑布流布局组件
react native·react.js·harmonyos
听风者一号2 小时前
cssMoudle生成器
前端·javascript·json
霍理迪2 小时前
Vue—其他指令及自定义指令
前端·javascript·vue.js
爱丽_2 小时前
Vue Router 权限路由:动态路由、导航守卫与白名单的工程落地
前端·javascript·vue.js
小江的记录本2 小时前
【Filter / Interceptor】过滤器(Filter)与拦截器(Interceptor)全方位对比解析(附底层原理 + 核心对比表)
java·前端·后端·spring·java-ee·前端框架·web
亿坊电商2 小时前
免费开源商用CMS-亿坊企业建站系统|稳定+安全+易用!
安全·开源·cms
弓.长.2 小时前
ReactNative for OpenHarmony项目鸿蒙化三方库:@react-native-ohos/image-editor
react native·react.js·harmonyos
独泪了无痕2 小时前
Vue3动态组件Component的深度解析与应用
前端·vue.js·web components
今夕资源网4 小时前
Utils.fun 开源在线工具站 一个简洁、可自部署、支持多语言与 SEO 路由的在线工具站,覆盖开发、文本、时间、图片、编码与常用生成场景工具。
开源·开源软件·在线工具·站长工具·seo在线工具·在线工具网站源码·在线工具网站