鸿蒙原生应用实战(三):诗词详情与作者天地页面——复杂数据展示与交互设计

前言

前两章我们完成了项目架构和首页/诗词库页面的开发。本章将聚焦于应用中数据最密集的两个页面:

  1. 诗词详情页(PoemDetailPage.ets) —— 12 套完整诗词数据、注释、背景、赏析
  2. 作者天地页(AuthorPage.ets) —— 8 位诗人信息、代表作标签

这两个页面展示了 ArkTS 中复杂数据对象的组织、折叠面板交互、计时器等高级功能。

一、诗词详情页(PoemDetailPage.ets)

1.1 数据结构设计

详情页最核心的是数据结构设计。我们定义了一个包含完整信息的接口:

interface PoemAnnotation {
  term: string;     // 词语
  meaning: string;  // 释义
}

interface PoemFullData {
  id: number;
  title: string;
  author: string;
  dynasty: string;
  fullContent: string[];       // 全文诗句数组
  annotations: PoemAnnotation[];  // 逐词注释
  appreciation: string;        // 名家赏析
  background: string;          // 创作背景
  type: string;                // 诗词类型
}

1.2 12 套完整数据嵌入

在 App 内部直接嵌入 12 套诗词数据,避免了网络请求的依赖。数据以 Record<string, PoemFullData> 的形式组织,键为诗词 ID:

const poemsData: Record<string, PoemFullData> = {
  '1': {
    id: 1,
    title: '静夜思',
    author: '李白',
    dynasty: '唐',
    fullContent: ['床前明月光', '疑是地上霜', '举头望明月', '低头思故乡'],
    annotations: [
      { term: '静夜思', meaning: '安静的夜晚产生的思绪。' },
      { term: '疑', meaning: '好像,仿佛。' },
      { term: '举头', meaning: '抬头。' }
    ],
    appreciation: '这首诗描绘了秋日夜晚旅居在外的游子对故乡的深切思念...',
    background: '此诗作于唐玄宗开元十四年(726年),李白在扬州旅舍...',
    type: '五言绝句'
  },
  '2': { /* 春晓——孟浩然 */ },
  '3': { /* 水调歌头——苏轼 */ },
  // ... 共 12 套完整数据
};

1.3 路由参数接收

详情页通过 aboutToAppear 生命周期方法接收路由参数:

@State @Watch('onPoemIdChange') poemId: number = 1;
@State poemData: PoemFullData = poemsData['1'];

aboutToAppear(): void {
  const params = router.getParams() as Record<string, Object>;
  if (params && params['poemId'] !== undefined) {
    this.poemId = params['poemId'] as number;
  }
}

onPoemIdChange(): void {
  this.poemData = poemsData[this.poemId.toString()] || poemsData['1'];
}

要点@Watch('onPoemIdChange') 确保当 poemId 变化时,poemData 自动更新。

1.4 页面结构

详情页从上到下包含:

┌──────────────────────────┐
│  ← 返回    ⭐ 收藏  ···   │  ← 顶部操作栏
├──────────────────────────┤
│      静 夜 思             │  ← 标题(大字号+字间距)
│      唐 · 李白            │
│      五言绝句 [标签]       │
├──────────────────────────┤
│  床前明月光               │
│  疑是地上霜               │  ← 全文展示(居中+行间距)
│  举头望明月               │
│  低头思故乡               │
├──────────────────────────┤
│  ❤ 点赞  📖 朗读  📤 分享  │  ← 操作按钮组
├──────────────────────────┤
│  📝 注释                  │
│  静夜思 │ 安静夜晚的思绪    │  ← 逐词注释
│  疑     │ 好像,仿佛       │
│  举头   │ 抬头            │
├──────────────────────────┤
│  📜 创作背景  [展开 ▼]     │  ← 折叠面板
│  (点击展开后显示内容)       │
├──────────────────────────┤
│  🎨 赏析                  │  ← 底部
└──────────────────────────┘

1.5 全文展示

诗文正文居中展示,每句诗单独一行,设置合适的行高和字间距:

ForEach(this.poemData.fullContent, (line: string) => {
  Text(line)
    .fontSize(19)
    .fontColor($r('app.color.text_primary'))
    .lineHeight(36)        // 行高,增加呼吸感
    .letterSpacing(2)      // 字间距,增强古典韵味
    .textAlign(TextAlign.Center)
    .width('100%')
    .padding({ top: 6 })
}, (line: string) => line)

1.6 注释区域

注释区域使用 ForEach 遍历 annotations 数组,每行显示词语和释义:

ForEach(this.poemData.annotations, (ann: PoemAnnotation) => {
  Row() {
    Text(ann.term)
      .fontSize(14)
      .fontColor($r('app.color.accent_purple'))
      .fontWeight(FontWeight.Medium)

    Text(ann.meaning)
      .fontSize(13)
      .fontColor($r('app.color.text_secondary'))
      .margin({ left: 8 })
      .layoutWeight(1)
  }
  .width('100%')
  .alignItems(VerticalAlign.Top)
  .padding({ top: 4, bottom: 4 })
}, (ann: PoemAnnotation) => ann.term)

1.7 折叠面板(创作背景)

创作背景默认折叠,点击"展开"按钮后显示。状态由 isShowFull 控制:

@State isShowFull: boolean = false;

Column() {
  Row() {
    Text('📜 创作背景')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
    Blank()
    Text(this.isShowFull ? '收起 ▲' : '展开 ▼')
      .fontSize(12)
      .fontColor($r('app.color.accent_purple'))
  }
  .width('100%')
  .onClick(() => { this.isShowFull = !this.isShowFull; })

  // 折叠内容——只有展开时渲染
  if (this.isShowFull) {
    Text(this.poemData.background)
      .fontSize(14)
      .fontColor($r('app.color.text_primary'))
      .lineHeight(22)
      .padding({ top: 10 })
  }
}

技巧:在 ArkTS 中,if 条件包裹的组件只有条件为 true 时才会被创建,天然实现了懒加载。

1.8 交互操作

页面提供了多种交互:

// 收藏切换
Text(this.isFav ? '⭐' : '☆')
  .fontSize(22)
  .onClick(() => { this.isFav = !this.isFav; })

// 点赞切换
Row() {
  Text(this.isLiked ? '❤' : '🤍').fontSize(20)
  Text('点赞').fontSize(13).margin({ left: 4 })
}.onClick(() => { this.isLiked = !this.isLiked; })

二、作者天地页(AuthorPage.ets)

2.1 诗人数据

我们整理了 8 位最具代表性的诗人,每位包含简介、生卒年、风格标签和代表作:

const authors: AuthorInfo[] = [
  {
    id: 1,
    name: '李白',
    dynasty: '唐',
    avatar: '🪶',
    bio: '字太白,号青莲居士,唐代伟大的浪漫主义诗人,被后人誉为"诗仙"。',
    birthDeath: '701年—762年',
    poemCount: 1010,
    style: '飘逸奔放',
    masterpieces: ['静夜思', '将进酒', '望庐山瀑布', '蜀道难', '行路难']
  },
  // 杜甫、苏轼、李清照、白居易、李商隐、王维、李煜
];

2.2 诗人卡片设计

每个诗人卡片展示丰富的信息层次:

@Builder
createAuthorCard(author: AuthorInfo) {
  Column() {
    Row() {
      // Emoji 头像
      Text(author.avatar).fontSize(40)

      Column() {
        Row() {
          Text(author.name).fontSize(20).fontWeight(FontWeight.Bold)
          Text(author.dynasty + '代').fontSize(12)
            .fontColor($r('app.color.text_secondary'))
        }
        Text(author.birthDeath).fontSize(12)
          .fontColor($r('app.color.text_secondary'))

        Row() {
          Text('风格: ' + author.style)
            .fontSize(11).fontColor($r('app.color.accent_purple'))
            .padding({ left: 6, right: 6, top: 2, bottom: 2 })
            .backgroundColor($r('app.color.accent_purple') + '15')
            .borderRadius(4)
          Text('📝 ' + author.poemCount.toString() + '首')
            .fontSize(11).margin({ left: 8 })
        }
      }
      .layoutWeight(1).padding({ left: 14 })

      Text('>').fontSize(18).fontColor($r('app.color.text_secondary'))
    }

    // 人物简介(最多两行)
    Text(author.bio)
      .fontSize(13).lineHeight(20)
      .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })

    // 代表作标签
    Row() {
      Text('代表作: ').fontSize(12)
      ForEach(author.masterpieces.slice(0, 3), (title: string) => {
        Text(title)
          .fontSize(12).fontColor($r('app.color.accent_purple'))
          .padding({ left: 8, right: 8, top: 2, bottom: 2 })
          .backgroundColor($r('app.color.accent_purple') + '10')
          .borderRadius(4).margin({ right: 6 })
      }, (title: string) => title)
    }
  }
  .width('100%').padding(16)
  .backgroundColor($r('app.color.bg_card'))
  .borderRadius(16)
  .onClick(() => {
    // 点击跳转到诗词库并自动搜索该作者
    router.pushUrl({
      url: 'pages/PoemListPage',
      params: { searchAuthor: author.name }
    });
  })
}

2.3 数据过滤

作者页同样使用 @State + @Watch 模式处理搜索和朝代筛选:

@State @Watch('onAuthorFilterChange') searchText: string = '';
@State @Watch('onAuthorFilterChange') activeDynasty: string = 'all';
@State filteredAuthorsList: AuthorInfo[] = authors;

onAuthorFilterChange(): void {
  let result: AuthorInfo[] = authors;
  if (this.searchText.length > 0) {
    const kw: string = this.searchText.toLowerCase();
    result = result.filter((a: AuthorInfo) => a.name.includes(kw));
  }
  if (this.activeDynasty !== 'all') {
    result = result.filter((a: AuthorInfo) => a.dynasty === this.activeDynasty);
  }
  this.filteredAuthorsList = result;
}

2.4 @Builder 参数化筛选标签

因为 @Builder 内不能声明变量,我们通过参数传递来避免此限制:

// 在 build() 中直接调用 @Builder 方法
Row() {
  this.dynastyFilter('all', '全部', 0)
  this.dynastyFilter('唐', '唐代', 1)
  this.dynastyFilter('宋', '宋代', 2)
  this.dynastyFilter('五代', '五代', 3)
}

@Builder
dynastyFilter(opt: string, label: string, idx: number) {
  Text(label)
    .fontSize(13)
    .fontColor(opt === this.activeDynasty ?
      Color.White : $r('app.color.text_secondary'))
    .padding({ left: 16, right: 16, top: 6, bottom: 6 })
    .backgroundColor(opt === this.activeDynasty ?
      $r('app.color.accent_purple') : $r('app.color.bg_card'))
    .borderRadius(16)
    .margin({ right: 8 })
    .onClick(() => { this.activeDynasty = opt; })
}

三、组件复用最佳实践

3.1 底部导航栏的复用

所有页面共享同一套底部导航栏。为了避免重复代码,每个页面都定义了自己的 createBottomNav @Builder,通过参数 activePage 标识当前高亮项:

@Builder
createBottomNav(activePage: string) {
  this.renderNavBar(activePage)
}

@Builder
renderNavBar(activePage: string): void {
  Row() {
    this.navItem('🏠', '首页', 'home', activePage, 'pages/Index')
    this.navItem('📚', '诗词库', 'list', activePage, 'pages/PoemListPage')
    this.navItem('👤', '作者', 'author', activePage, 'pages/AuthorPage')
    this.navItem('⭐', '收藏', 'collection', activePage, 'pages/CollectionPage')
  }
  .width('100%').height(60)
  .backgroundColor($r('app.color.bg_card'))
  .shadow({ radius: 8, color: '#15000000', offsetX: 0, offsetY: -2 })
}

@Builder
navItem(icon: string, label: string, page: string,
        activePage: string, route: string) {
  Column() {
    Text(icon).fontSize(22)
    Text(label).fontSize(10)
      .fontColor(page === activePage ?
        $r('app.color.accent_purple') : $r('app.color.text_secondary'))
      .margin({ top: 2 })
  }
  .layoutWeight(1)
  .alignItems(HorizontalAlign.Center)
  .onClick(() => {
    if (page !== activePage) { router.pushUrl({ url: route }); }
  })
}

小结

本章完成了诗词详情页和作者天地页的开发,核心内容包括:

  • 12 套完整诗词数据的组织和访问
  • 折叠面板交互实现
  • 诗人卡片的多层次信息展示
  • @Builder 参数化设计避免变量声明限制
  • 跨页面跳转时自动搜索作者

下一章我们将完成收藏页面和全局底部导航的整合,并确保所有页面之间的无缝跳转。
在这里插入图片描述


【系列目录】

  • (一)项目初始化与架构设计
  • (二)首页与诗词库页面开发
  • (三)诗词详情与作者天地页面开发 ← 本文
  • (四)收藏页面与底部导航实现
  • (五)编译调试与问题修复经验
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐