羽球联盟 HarmonyOS NEXT 实战系列 (09/20):ArticleDetail内容块渲染、收藏与分享

📅 2026/7/6 10:51:24
羽球联盟 HarmonyOS NEXT 实战系列 (09/20):ArticleDetail内容块渲染、收藏与分享
文章导读详情页把正文、阅读数、收藏、分享和相关推荐收在同一条阅读链路里。ContentBlock负责把 text、image、gallery 三种内容块分发成统一的 ArkUI 视图。收藏状态直接读StorageLink(favorites)底部按钮会随持久化状态即时刷新。分享没有接入重型 SDK而是优先复制标题和原文链接到系统剪贴板。页面效果资讯详情页不是简单展示一段正文而是要把“读内容”和“继续操作”连接起来。用户从首页或搜索页点进来以后先看到标题、来源、时间和阅读数随后连续浏览正文图片、图集和相关推荐页面底部再给出收藏、分享两个高频动作。这样做的直接收益是阅读、回看和二次传播都集中在一个页面内不需要再跳去额外的中间页。从界面上还能看出两个实现要求。第一正文必须兼容不同内容形态不能只支持一整段纯文本第二收藏和分享的反馈要足够即时点完以后不应该让用户猜测操作是否成功。这也是本篇重点拆的两条线内容块渲染和底部互动状态。实战拆解ArticleDetailPage的入口并不复杂但它把数据优先级处理得很明确。页面进入时先从路由参数里取文章 id再优先读取RemoteRepo里的联网结果如果当前文章还没被远程刷新覆盖就回退到本地findArticle(id)。对详情页来说这个判断非常关键因为它决定了首页、搜索页、历史记录进入详情时都能落到同一份业务模型而不是在不同来源间写两套渲染逻辑。正文区域采用ContentBlock数组驱动而不是把所有内容写死在一段字符串模板里。这样做有两个明显好处。其一ForEach renderBlock()让文本、单图、图集分别对应独立分支后续要加引用块、代码块或视频块时只需要扩展类型和渲染器其二列表和详情的数据契约更清晰详情页不需要了解抓取逻辑只关心收到的是哪一类块、该如何排版。底部互动栏的实现同样值得单独拎出来讲。收藏按钮没有把“是否已收藏”提前缓存成普通字段而是直接调用isFav()读取StorageLink(favorites)。这样PrefStore.toggleFavorite()完成持久化后Image、文案和颜色会立即重算避免 Builder 参数失去响应式追踪。分享则走更轻量的路线把标题和原文链接写进系统剪贴板让微信、QQ 或浏览器内置分享面板自己识别链接而不是为一篇资讯详情页引入整套第三方分享 SDK。关键代码ForEach(this.article!.content, (block: ContentBlock, idx: number) { this.renderBlock(block) }, (block: ContentBlock, idx: number) ${idx}) Builder renderBlock(block: ContentBlock) { if (block.type text block.text) { Text(block.text) .fontSize(AppSizes.f15) .fontColor(AppColors.textPrimary) .lineHeight(26) .width(100%) .margin({ bottom: AppSizes.s12 }) } else if (block.type image (block.imageUrl ! undefined block.imageUrl.length 0 || block.image)) { Column() { Image(block.imageUrl ! undefined block.imageUrl.length 0 ? block.imageUrl : block.image) .width(100%) .borderRadius(AppSizes.r8) .objectFit(ImageFit.Cover) if (block.caption) { Text(block.caption) .fontSize(AppSizes.f12) .fontColor(AppColors.textTertiary) .margin({ top: 6 }) } } .width(100%) .margin({ bottom: AppSizes.s16 }) } else if (block.type gallery block.images) { Scroll() { Row({ space: AppSizes.s8 }) { ForEach(block.images!, (img: Resource, i: number) { Image(img) .width(160).height(120) .borderRadius(AppSizes.r8) .objectFit(ImageFit.Cover) }, (img: Resource, i: number) g-${i}) } } .scrollable(ScrollDirection.Horizontal) .scrollBar(BarState.Off) } }这一段把详情页的“块级正文”思路说透了。ForEach负责顺序遍历renderBlock()负责类型分发文本块关注排版单图块补上圆角和说明图集块则横向滚动展示多张图片。UI 结构和内容类型是一一对应的所以页面扩展时不会因为加一种新块就重写整段正文。再看底部互动栏关键不是“能不能切换图标”而是状态来源必须和持久化层保持同一份真相。只要favorites来自StorageLink详情页、收藏页和个人中心统计看到的就是同一份数据而不是各自维护一份临时拷贝。StorageLink(favorites) favorites: string[] []; private isFav(): boolean { if (!this.article) { return false; } return this.favorites.indexOf(this.article!.id) 0; } private async toggleFav(): Promisevoid { if (!this.article) { return; } try { const added: boolean await PrefStore.getInstance().toggleFavorite(this.article!.id); Toast.show(this.getUIContext(), added ? 已加入收藏 : 已取消收藏); } catch (e) { Toast.show(this.getUIContext(), 操作失败); } } private onShare(): void { if (!this.article) { return; } const url: string this.article!.url ! undefined ? this.article!.url! : ; const title: string this.article!.title; const text: string url.length 0 ? ${title}\n${url} : title; const pb pasteboard.getSystemPasteboard(); const data pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, text); pb.setData(data).then(() { Toast.show(this.getUIContext(), 已复制分享内容到剪贴板); }); }分享这里采用的是很典型的移动端折中方案。对资讯详情页来说复制“标题 链接”已经覆盖了大多数转发场景还能复用系统剪贴板能力避免第三方分享 SDK 带来的权限、包体积和审核成本。如果后续真的要接分享面板也建议先保留这个纯系统能力分支作为兜底。取舍分析详情页正文按块渲染代价是模型和页面都要接受“结构化内容”这件事开发初期看上去比一整段字符串要麻烦一些。但这笔成本换来的收益很实在图片说明、图集滚动、引用块、代码块都能顺着同一套机制继续加不会把页面越写越像一团条件分支。对于资讯类页面这种结构比后期硬拆文本更省事。收藏状态直接绑定StorageLink也有代价。它要求开发侧接受“派生结果不要乱缓存”的约束否则一旦把isFav()的结果提前塞进普通字段界面刷新就会和持久化脱节。不过从用户体验看这个约束完全值得遵守因为详情页、个人中心和收藏页只要共享同一份存储状态就能保证数字、图标和文案同步变化。分享能力的取舍则更偏产品判断。接社交 SDK 确实能做出更花哨的分享卡片但对这个项目现阶段来说复制标题和链接已经能满足“把资讯发出去”的核心目标而且兼容性更稳。轻量实现先跑通闭环再看是否值得为更复杂的分享样式付出额外集成成本这是更实际的顺序。设计落点详情页要用同一份Article模型承接首页、搜索、历史和相关推荐的跳转入口。正文渲染按ContentBlock分发文本、单图、图集各自独立排版后续扩展不会牵一发而动全身。收藏状态直接读取StorageLink(favorites)避免详情页和收藏页各算各的。分享优先走系统剪贴板先满足稳定可用再考虑更重的第三方能力。易踩坑不要把favorites派生出来的图标或文案缓存成普通字段否则收藏后底部栏可能不刷新。不要把所有正文内容拼成单一长字符串图片说明、图集滚动和后续扩展都会变得别扭。远程文章只有摘要或原文链接时要明确保留“查看原文”入口避免用户误以为正文被截断。分享失败时要给出明确提示不能只在控制台打印错误让用户误判为按钮无效。验证方式从首页或搜索页进入资讯详情确认标题、来源、阅读数和正文块顺序正常。连续点击收藏按钮两次确认图标、文案和 Toast 提示都会跟着切换。返回个人中心或收藏页确认收藏数量与详情页操作保持一致。点击分享后到系统输入框粘贴确认得到的是“文章标题 原文链接”。打开带原文链接的资讯确认“查看原文”按钮可以继续跳到外部浏览器。参考资料ArkUI 状态管理总览官方文档ArkUI 自定义组件与 Builder官方文档剪贴板服务 pasteboard官方文档小结第 09 篇真正想说明的不是某个Text或Image组件怎么写而是详情页如何把阅读内容、状态同步和用户操作收进同一个页面闭环。只要正文块模型稳定、收藏状态来源统一、分享能力保持轻量可用这类资讯详情页就能在 ArkTS 项目里长期演进而不是随着功能增长越来越难维护。