HarmonyOS应用<节气通>开发第6篇:节气详情页(下)——诗词与养生

引言

在上一篇文章中,我们完成了节气详情页的上半部分,包括顶部大图、基本信息、气候特点、物候现象和传统习俗。这篇文章将继续完成详情页的下半部分:

  • 节气诗词展示
  • 节气食谱推荐
  • 养生建议
  • 相关文章推荐
  • 收藏和分享功能

通过本文,你将掌握更多高级UI组件的使用技巧,以及如何实现交互功能。


学习目标

完成本文后,你将能够:

  • ✅ 实现诗词卡片的优雅展示
  • ✅ 创建食谱列表展示
  • ✅ 实现收藏功能
  • ✅ 添加分享功能
  • ✅ 处理页面交互状态

需求分析

功能模块设计

模块 功能描述 技术要点
诗词展示 展示与节气相关的古诗词 卡片布局、竖排文字
节气食谱 推荐应季美食 图片+文字、列表布局
养生建议 健康养生知识 图标+文字、分段展示
相关文章 推荐阅读 横向滚动、卡片布局
收藏分享 收藏和分享功能 状态切换、弹窗提示

核心实现

步骤1: 诗词展示区域

完整代码
typescript 复制代码
/**
 * 构建诗词展示区域
 */
@Builder
buildPoetrySection(): void {
  Card() {
    Column({ space: 16 }) {
      // 标题
      Row({ space: 8 }) {
        Image($r('app.media.ic_poetry'))
          .width(20)
          .height(20)
          .fillColor('#4A9B6D')
        
        Text('节气诗词')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
      }
      
      // 诗词列表
      Column({ space: 16 }) {
        ForEach(this.holiday!.poetry || [], (poem: PoetryItem) => {
          this.buildPoetryCard(poem)
        }, (poem: PoetryItem) => poem.title)
      }
    }
    .padding(16)
  }
  .width('92%')
  .margin({ left: '4%', right: '4%' })
}

/**
 * 构建诗词卡片
 */
@Builder
buildPoetryCard(poem: PoetryItem): void {
  Column({ space: 12 }) {
    // 标题和作者
    Row({ space: 8 }) {
      Text(poem.title)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
      
      Text('·')
        .fontSize(14)
        .fontColor('#999999')
      
      Text(poem.author)
        .fontSize(14)
        .fontColor('#999999')
    }
    .width('100%')
    .justifyContent(FlexAlign.Center)
    
    // 诗句
    Column({ space: 8 }) {
      ForEach(poem.content, (line: string) => {
        Text(line)
          .fontSize(15)
          .fontColor('#555555')
          .fontStyle(FontStyle.Italic)
          .textAlign(TextAlign.Center)
          .letterSpacing(4)
      })
    }
    
    // 分隔线
    Divider()
      .width('80%')
      .color('#EEEEEE')
      .height(1)
  }
  .padding(16)
  .backgroundColor('#FAFAFA')
  .borderRadius(12)
}
设计要点

1. 诗词排版

  • 使用居中对齐
  • 增加字间距提升可读性
  • 使用斜体风格营造诗意氛围

2. 卡片布局

  • 浅灰色背景区分诗词区域
  • 圆角边框增加美感
  • 分隔线区分不同诗词

步骤2: 节气食谱区域

typescript 复制代码
/**
 * 构建节气食谱区域
 */
@Builder
buildRecipesSection(): void {
  Card() {
    Column({ space: 16 }) {
      // 标题
      Row({ space: 8 }) {
        Image($r('app.media.ic_food'))
          .width(20)
          .height(20)
          .fillColor('#4A9B6D')
        
        Text('节气食谱')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
      }
      
      // 食谱列表
      Column({ space: 12 }) {
        ForEach(this.holiday!.recipes || [], (recipe: RecipeItem) => {
          this.buildRecipeCard(recipe)
        }, (recipe: RecipeItem) => recipe.name)
      }
    }
    .padding(16)
  }
  .width('92%')
  .margin({ left: '4%', right: '4%' })
}

/**
 * 构建食谱卡片
 */
@Builder
buildRecipeCard(recipe: RecipeItem): void {
  Row({ space: 12 }) {
    // 图片
    Image('rawfile://recipes/' + recipe.image)
      .width(100)
      .height(75)
      .borderRadius(8)
      .objectFit(ImageFit.Cover)
    
    // 内容
    Column({ space: 6 }) {
      // 名称和标签
      Row({ space: 8 }) {
        Text(recipe.name)
          .fontSize(15)
          .fontWeight(FontWeight.Medium)
          .fontColor('#333333')
        
        Text(recipe.type)
          .fontSize(11)
          .fontColor('#4A9B6D')
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .backgroundColor('#E8F5E9')
          .borderRadius(4)
      }
      
      // 简介
      Text(recipe.desc)
        .fontSize(13)
        .fontColor('#666666')
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
      
      // 食材标签
      Wrap({ spacing: 6 }) {
        ForEach(recipe.ingredients.slice(0, 3), (ingredient: string) => {
          Text(ingredient)
            .fontSize(11)
            .fontColor('#999999')
            .padding({ left: 4, right: 4, top: 2, bottom: 2 })
            .backgroundColor('#F0F0F0')
            .borderRadius(4)
        })
      }
    }
    .flexGrow(1)
  }
  .width('100%')
  .padding(12)
  .backgroundColor('#FAFAFA')
  .borderRadius(12)
  .onClick(() => {
    // 点击查看详情
    try {
      router.pushUrl({
        url: 'pages/RecipeDetail',
        params: { recipeId: recipe.id }
      });
    } catch (error) {
      console.error('路由跳转失败: ' + JSON.stringify(error));
    }
  })
}

设计要点:

  • 左图右文布局
  • 食材标签使用Wrap自动换行
  • 点击卡片跳转详情页

步骤3: 养生建议区域

typescript 复制代码
/**
 * 构建养生建议区域
 */
@Builder
buildHealthSection(): void {
  Card() {
    Column({ space: 16 }) {
      // 标题
      Row({ space: 8 }) {
        Image($r('app.media.ic_health'))
          .width(20)
          .height(20)
          .fillColor('#4A9B6D')
        
        Text('养生建议')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
      }
      
      // 养生要点
      Column({ space: 12 }) {
        ForEach(this.healthTips, (tip: HealthTip) => {
          Row({ space: 12 }) {
            // 图标
            Stack({ alignContent: Alignment.Center }) {
              Circle()
                .width(40)
                .height(40)
                .fillColor(tip.color)
              
              Image(tip.icon)
                .width(20)
                .height(20)
                .fillColor('#FFFFFF')
            }
            
            // 内容
            Column({ space: 4 }) {
              Text(tip.title)
                .fontSize(14)
                .fontWeight(FontWeight.Medium)
                .fontColor('#333333')
              
              Text(tip.desc)
                .fontSize(13)
                .fontColor('#666666')
                .maxLines(2)
                .textOverflow({ overflow: TextOverflow.Ellipsis })
            }
            .flexGrow(1)
          }
          .width('100%')
        })
      }
    }
    .padding(16)
  }
  .width('92%')
  .margin({ left: '4%', right: '4%' })
}

// 养生建议数据
private healthTips: HealthTip[] = [
  { 
    icon: $r('app.media.ic_diet'), 
    title: '饮食调理', 
    desc: '建议多食用清淡易消化的食物,如粥、汤羹等',
    color: '#4A9B6D'
  },
  { 
    icon: $r('app.media.ic_exercise'), 
    title: '运动养生', 
    desc: '适当进行户外活动,如散步、太极拳等',
    color: '#F29F4A'
  },
  { 
    icon: $r('app.media.ic_sleep'), 
    title: '起居作息', 
    desc: '保证充足睡眠,早睡早起,顺应自然规律',
    color: '#7AA7D9'
  },
  { 
    icon: $r('app.media.ic_mind'), 
    title: '情志调节', 
    desc: '保持心情舒畅,避免情绪大起大落',
    color: '#F28FAE'
  }
];

interface HealthTip {
  icon: Resource;
  title: string;
  desc: string;
  color: string;
}

设计要点:

  • 使用彩色圆形图标
  • 图标颜色与内容主题呼应
  • 简洁的标题+描述结构

步骤4: 相关文章推荐

typescript 复制代码
/**
 * 构建相关文章区域
 */
@Builder
buildRelatedArticles(): void {
  Card() {
    Column({ space: 12 }) {
      // 标题
      Row({ space: 8 }) {
        Image($r('app.media.ic_article'))
          .width(20)
          .height(20)
          .fillColor('#4A9B6D')
        
        Text('相关文章')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
        
        Blank()
        
        Text('查看更多')
          .fontSize(13)
          .fontColor('#4A9B6D')
          .onClick(() => {
            try {
              router.pushUrl({ url: 'pages/Encyclopedia' });
            } catch (error) {
              console.error('路由跳转失败: ' + JSON.stringify(error));
            }
          })
      }
      
      // 横向滚动列表
      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.relatedArticles, (article: ArticleItem) => {
            this.buildRelatedArticleCard(article)
          }, (article: ArticleItem) => article.id)
        }
        .padding({ left: 4, right: 4 })
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
    }
    .padding(16)
  }
  .width('92%')
  .margin({ left: '4%', right: '4%' })
}

/**
 * 构建相关文章卡片
 */
@Builder
buildRelatedArticleCard(article: ArticleItem): void {
  Column({ space: 8 }) {
    Image('rawfile://articles/' + article.coverImage)
      .width(140)
      .height(80)
      .borderRadius(8)
      .objectFit(ImageFit.Cover)
    
    Text(article.title)
      .fontSize(13)
      .fontColor('#333333')
      .maxLines(2)
      .textOverflow({ overflow: TextOverflow.Ellipsis })
      .width(140)
  }
  .width(140)
  .onClick(() => {
    try {
      router.pushUrl({
        url: 'pages/ArticleDetail',
        params: { articleId: article.id }
      });
    } catch (error) {
      console.error('路由跳转失败: ' + JSON.stringify(error));
    }
  })
}

设计要点:

  • 横向滚动展示
  • 固定宽度保证布局整齐
  • 点击查看详情

步骤5: 收藏和分享功能

typescript 复制代码
/**
 * 构建底部操作栏
 */
@Builder
buildBottomBar(): void {
  Stack({ alignContent: Alignment.Bottom }) {
    // 底部背景
    Row()
      .width('100%')
      .height(80)
      .backgroundColor('#FFFFFF')
      .shadow({ 
        radius: 8, 
        color: '#0D000000', 
        offsetX: 0, 
        offsetY: -4 
      })
      .padding({ left: 16, right: 16 })
    
    // 操作按钮
    Row({ space: 16 }) {
      // 收藏按钮
      Column({ space: 4 }) {
        Image(this.isCollected ? $r('app.media.ic_collect_active') : $r('app.media.ic_collect'))
          .width(24)
          .height(24)
          .fillColor(this.isCollected ? '#FF5252' : '#999999')
        
        Text(this.isCollected ? '已收藏' : '收藏')
          .fontSize(11)
          .fontColor(this.isCollected ? '#FF5252' : '#999999')
      }
      .width('25%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        this.toggleCollection();
      })
      
      // 分享按钮
      Column({ space: 4 }) {
        Image($r('app.media.ic_share'))
          .width(24)
          .height(24)
          .fillColor('#999999')
        
        Text('分享')
          .fontSize(11)
          .fontColor('#999999')
      }
      .width('25%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        this.showShareMenu();
      })
      
      // 评论按钮
      Column({ space: 4 }) {
        Image($r('app.media.ic_comment'))
          .width(24)
          .height(24)
          .fillColor('#999999')
        
        Text('评论')
          .fontSize(11)
          .fontColor('#999999')
      }
      .width('25%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        try {
          router.pushUrl({ url: 'pages/Comment' });
        } catch (error) {
          console.error('路由跳转失败: ' + JSON.stringify(error));
        }
      })
      
      // 更多按钮
      Column({ space: 4 }) {
        Image($r('app.media.ic_more'))
          .width(24)
          .height(24)
          .fillColor('#999999')
        
        Text('更多')
          .fontSize(11)
          .fontColor('#999999')
      }
      .width('25%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        this.showMoreMenu();
      })
    }
    .width('100%')
    .height('100%')
  }
  .width('100%')
  .height(80)
  .position({ bottom: 0, left: 0 })
}

/**
 * 切换收藏状态
 */
async toggleCollection(): Promise<void> {
  try {
    if (this.isCollected) {
      await this.storageService.removeCollection(this.holidayId);
      this.isCollected = false;
      prompt.showToast({ message: '已取消收藏' });
    } else {
      await this.storageService.addCollection(this.holidayId);
      this.isCollected = true;
      prompt.showToast({ message: '收藏成功' });
    }
  } catch (error) {
    console.error('收藏操作失败: ' + JSON.stringify(error));
    prompt.showToast({ message: '操作失败' });
  }
}

/**
 * 显示分享菜单
 */
showShareMenu(): void {
  // 模拟分享功能
  prompt.showToast({ message: '分享功能开发中' });
}

/**
 * 显示更多菜单
 */
showMoreMenu(): void {
  // 模拟更多功能
  prompt.showToast({ message: '更多功能开发中' });
}

设计要点:

  • 固定定位在页面底部
  • 四个功能按钮对称分布
  • 收藏状态切换动画
  • Toast提示操作结果

常见问题与解决方案

问题1: 收藏状态不同步

现象 :

收藏后返回首页,收藏状态没有更新。

原因:

  • 收藏状态仅保存在本地
  • 首页没有重新加载数据

解决方案:

typescript 复制代码
// 使用AppStorage同步状态
@StorageLink('collections') collections: string[] = [];

// 检查是否收藏
isCollected(): boolean {
  return this.collections.includes(this.holidayId);
}

问题2: 分享功能无法实现

现象 :

HarmonyOS没有内置分享API。

解决方案:

typescript 复制代码
// 使用系统能力
import share from '@ohos.share';

async shareArticle(): Promise<void> {
  try {
    await share.share({
      title: this.holiday!.name,
      content: this.holiday!.description,
      type: share.ShareType.TEXT
    });
  } catch (error) {
    console.error('分享失败: ' + JSON.stringify(error));
  }
}

问题3: 页面底部操作栏遮挡内容

现象 :

操作栏覆盖了部分内容。

解决方案:

typescript 复制代码
Scroll() {
  Column({ space: 16 }) {
    // 内容
  }
  .padding({ bottom: 120 })  // 预留操作栏空间
}

本章小结

核心知识点

本文完成了详情页下半部分的实现:

1. 诗词展示

  • 优雅的卡片布局
  • 居中对齐的诗句排版
  • 斜体风格营造氛围

2. 节气食谱

  • 左图右文的列表布局
  • 食材标签自动换行
  • 点击跳转详情

3. 养生建议

  • 图标+标题+描述结构
  • 彩色圆形图标
  • 分类展示不同养生要点

4. 相关文章

  • 横向滚动展示
  • 固定宽度卡片
  • 点击跳转

5. 底部操作栏

  • 固定定位在底部
  • 收藏、分享、评论、更多功能
  • Toast提示操作结果

最佳实践总结

诗词卡片

typescript 复制代码
Column({ space: 12 }) {
  Text(title).fontSize(16).fontWeight(FontWeight.Bold)
  Text(author).fontSize(14).fontColor('#999')
  
  Column({ space: 8 }) {
    ForEach(content, (line) => {
      Text(line).fontStyle(FontStyle.Italic).letterSpacing(4)
    })
  }
}

底部操作栏

typescript 复制代码
Stack({ alignContent: Alignment.Bottom }) {
  Row().height(80).backgroundColor('#FFFFFF')
  
  Row({ space: 16 }) {
    // 四个按钮
  }
}
.position({ bottom: 0, left: 0 })

下一步预告

详情页已经完成!在下一篇文章中,我们将学习:

  • 文章详情页的实现
  • 富文本展示
  • 点赞和评论功能
  • 相关文章推荐

相关链接

相关推荐
慧海灵舟1 小时前
鸿蒙南向开发教程Day 2:创建自己的 Hello World 工程
华为·harmonyos·写文章,赢小鸿ai
颜淡慕潇2 小时前
鸿蒙 PC的 vcpkg 交叉编译库在x86_64宿主环境下的AI自动化验证方案
人工智能·自动化·harmonyos
再见6582 小时前
HarmonyOS NEXT 实战:从零开发一款密码生成器应用
华为·harmonyos
李二。2 小时前
鸿蒙原生ArkTS布局方式之ColumnBaseline垂直排列
华为·harmonyos
yuegu7773 小时前
HarmonyOS应用<节气通>开发第8篇:知识百科页开发
华为·harmonyos
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“航界智脑“——PC端AI智能体沉浸式无人机集群任务规划与空域协同管理工作台
人工智能·ar·无人机·harmonyos·智能体
李二。4 小时前
鸿蒙原生ArkTS布局方式之ColumnCenter垂直排列
华为·harmonyos
不爱吃糖的程序媛4 小时前
@capacitor/camera 插件在鸿蒙PC平台的适配实践
华为·harmonyos