【喵汪星球HarmonyOS 6.0】技术实战 08:陪玩推荐、收藏和内容型功能的产品化
2026/7/5 14:26:55 网站建设 项目流程

前言

很多工具型 App 都会有“知识库”“建议库”“技巧列表”,但大多数最后都变成没人点的静态页面。原因很简单:用户打开应用时不想读百科,他想要一个当下能执行的答案。

喵汪星球的陪玩模块就是围绕这个目标设计的:不是给用户一堆文章,而是直接回答“今天玩什么”。用户可以随机换一个,可以收藏,也可以从更多建议里选择一个玩法。每条内容都包含时长、道具、目标和注意事项。

这篇重点讲内容型功能的产品化实现。

内容型功能的关键:先给答案

陪玩页的设计不是先展示分类筛选,而是先展示一个主推荐:

@Builder PlayPage() { Column({ space: Theme.spaceXL }) { this.PageHeader('今天玩什么', this.playHeaderText()) this.EntertainmentNotice( '该功能以娱乐为主,陪玩建议请结合宠物年龄、体力和现场状态选择' ) Column({ space: Theme.spaceL }) { Stack({ alignContent: Alignment.BottomStart }) { this.PlayHeroImage(this.currentPlayTitle()) Column({ space: Theme.spaceS }) { Text(this.currentPlayTitle()) Text(this.currentPlayMeta()) Row({ space: Theme.spaceS }) { this.Tag('放电', 'primary') this.Tag('低门槛', 'warm') this.Tag('可收藏', 'blue') } } } Row({ space: Theme.spaceM }) { Text('换一个') Text(this.isFavoritePlay(this.currentPlayTitle()) ? '取消收藏' : '收藏') } } } }

这个结构像一个“推荐卡”,而不是文章列表。用户打开页面后马上知道今天可以玩什么,再决定要不要换一个或收藏。

内容功能常见误区是把所有内容摊给用户。更好的方式是先给一个可执行答案,再提供更多选择。

内容模型:四个字段就够用

陪玩建议模型很克制:

interface PlaySuggestion { title: string meta: string goal: string caution: string }

示例数据:

private readonly playCards: PlaySuggestion[] = [ { title: '纸团追逐', meta: '10 分钟 · 纸团 / 逗猫棒', goal: '放电 · 缓解无聊', caution: '结束后收走小物件,避免误吞' }, { title: '嗅闻寻宝', meta: '12 分钟 · 零食 / 嗅闻垫', goal: '探索 · 建立亲密关系', caution: '零食量计入当天总摄入' }, { title: '慢速跟随', meta: '8 分钟 · 无需道具', goal: '低强度 · 老年宠物友好', caution: '避免强迫互动,观察呼吸频率' } ]

这四个字段刚好覆盖用户决策:

  • title:玩什么。
  • meta:多久、需要什么。
  • goal:为什么玩。
  • caution:注意什么。

移动端内容不适合一上来写长文。结构化字段能让用户扫一眼就行动。

当前推荐:playIndex 而不是复制对象

项目用playIndex保存当前推荐:

@State playIndex: number = 0 private currentPlay(): PlaySuggestion { if (this.playIndex < 0 || this.playIndex >= this.playCards.length) { return this.playCards[0] } return this.playCards[this.playIndex] } private currentPlayTitle(): string { return this.currentPlay().title } private currentPlayMeta(): string { return this.currentPlay().meta }

为什么不直接把当前玩法对象存到状态里?

因为玩法内容是静态数组,playIndex更轻,也更容易持久化。内容更新时,只要 index 合法,页面就能重新拿到最新内容。如果 index 越界,回退到第一条。

恢复状态时也做了保护:

if (this.playIndex < 0 || this.playIndex >= this.playCards.length) { this.playIndex = 0 }

这是内容库迭代时很有用的小细节。以后删掉某条玩法,旧用户本地保存的 index 也不会导致页面异常。

随机换一个:避免“没变化”的尴尬

换一个推荐:

private nextPlay(): void { let nextIndex = Math.floor(Math.random() * this.playCards.length) if (this.playCards.length > 1 && nextIndex === this.playIndex) { nextIndex = (nextIndex + 1) % this.playCards.length } this.playIndex = nextIndex this.saveState() }

这里特意避免连续随机到同一条。因为用户点击“换一个”后,如果内容没变,他会怀疑按钮失效。

这是内容型功能里非常细微但重要的体验:随机不是数学意义上的纯随机,而是用户感知上的“发生变化”。

收藏:从单字段升级到列表

早期可以只保存一个收藏标题:

@State favoritePlayTitle: string = ''

后来升级为收藏列表:

interface FavoritePlayItem { title: string } @State favoritePlayItems: FavoritePlayItem[] = []

判断是否收藏:

private isFavoritePlay(title: string): boolean { for (let index = 0; index < this.favoritePlayItems.length; index++) { if (this.favoritePlayItems[index].title === title) { return true } } return false }

切换收藏:

private toggleFavoritePlay(title: string): void { if (this.isFavoritePlay(title)) { this.favoritePlayItems = this.favoritePlayItems.filter( (item: FavoritePlayItem) => item.title !== title ) } else { this.favoritePlayItems = this.favoritePlayItems.concat([{ title: title }]) } this.favoritePlayTitle = this.favoritePlayItems.length > 0 ? this.favoritePlayItems[0].title : '' this.saveState() }

这里保留favoritePlayTitle是为了兼容旧字段。恢复状态时,会把旧收藏迁移到新列表:

if (this.favoritePlayTitle !== '' && !this.favoritePlayListContains(this.favoritePlayItems, this.favoritePlayTitle)) { this.favoritePlayItems = this.favoritePlayItems.concat([ { title: this.favoritePlayTitle } ]) }

本地 App 的字段演进一定要考虑旧数据。用户升级后,不应该因为你改了收藏结构就丢收藏。

图片映射:用标题选择不同玩法图

玩法图不是动态下载,而是用本地资源:

@Builder PlayHeroImage(title: string) { if (title === '嗅闻寻宝' || title === '零食路线' || title === '毛毯藏物') { Image($r('app.media.play_sniff')) .width('100%') .height('100%') .objectFit(ImageFit.Cover) } else if (title === '慢速跟随' || title === '窗边观察' || title === '梳毛奖励') { Image($r('app.media.play_slow')) .width('100%') .height('100%') .objectFit(ImageFit.Cover) } else { Image($r('app.media.play_paper')) .width('100%') .height('100%') .objectFit(ImageFit.Cover) } }

这是一个 MVP 取舍:不为每一条玩法都准备独立图片,而是把玩法归到几类视觉资源里。这样既有视觉丰富度,又不会增加太多资源成本。

如果后续内容库变大,可以把图片字段加入模型:

interface PlaySuggestion { title: string meta: string goal: string caution: string image: Resource }

首版为了快速交付,用标题映射就够了。

更多建议列表:不是重复,而是切换入口

主推荐下面是更多玩法:

Column({ space: Theme.spaceM }) { ForEach(this.playCards, (item: PlaySuggestion, index: number) => { this.PlayRow(item, index) }, (item: PlaySuggestion) => item.title) }

每个玩法行都能点击设置为当前推荐:

@Builder PlayRow(item: PlaySuggestion, index: number) { Row({ space: Theme.spaceM }) { this.PlayThumb(item.title) Column({ space: Theme.spaceS }) { Row() { Text(item.title) Blank() Text(this.isFavoritePlay(item.title) ? '已收藏' : '收藏') } Text(item.meta) Row({ space: Theme.spaceS }) { this.Tag(item.goal.split(' · ')[0], 'primary') this.Tag(item.meta.split(' · ')[0], 'warm') } Text(item.caution) } } .backgroundColor(index === this.playIndex ? Theme.primaryPale : Theme.surface) .onClick(() => { this.playIndex = index this.saveState() }) }

列表不是单纯“更多内容”,而是主推荐的切换器。选中项用primaryPale高亮,用户能理解当前推荐和列表之间的关系。

风险提示是内容质量的一部分

陪玩内容每条都有caution

结束后收走小物件,避免误吞 零食量计入当天总摄入 避免强迫互动,观察呼吸频率 确认窗户锁好,避免高处跌落 遇到抗拒立即暂停,少量多次

这比单纯写“怎么玩”更重要。养宠建议的价值不只是让用户陪宠物玩,还要告诉用户什么时候该停、什么东西不能用、哪些情况要观察。

内容型功能不是“塞文案”,而是把专业边界产品化。

从 ArkTS 数组到内容库的演进

当前playCards写在代码里,适合首版。后续可以迁移为:

resources/rawfile/play_suggestions.json -> KnowledgeRepository -> PlayService -> PlayPage

进一步可以支持:

  • 按猫/狗过滤。
  • 按幼年、成年、老年过滤。
  • 按目标过滤:放电、安抚、嗅闻、亲密关系。
  • 收藏优先推荐。
  • 最近玩过的内容短期内不重复。
  • 每日固定推荐,避免刷新后频繁变化。

但不要过早复杂化。首版最重要的是让用户打开页面就能马上行动。

这部分最容易踩的坑

第一,内容不要只做长列表。主推荐能显著提升使用率。

第二,随机推荐要避免连续重复。用户感知比数学随机更重要。

第三,收藏结构升级时要兼容旧字段。

第四,长标题、长注意事项要加maxLinestextOverflow

第五,图片资源要有兜底,不要让某条内容因为没图就空白。

第六,内容建议要有风险提示,尤其是涉及运动、零食、小物件的玩法。

本篇小结

陪玩模块的技术难度不在 API,而在产品化:

  • 用结构化模型代替长文本。
  • 用主推荐回答“今天玩什么”。
  • playIndex管理当前推荐。
  • 用随机切换制造轻量探索感。
  • 用收藏沉淀用户偏好。
  • 用本地图片资源提升视觉完整度。
  • 用风险提示守住内容边界。

下一篇进入健康模块。健康症状库更敏感:它要帮用户整理异常信息,但不能替代兽医诊断。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询