news 2026/7/16 5:07:42

HarmonyOS7精选文章流页面实战:分类筛选与点赞收藏交互

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HarmonyOS7精选文章流页面实战:分类筛选与点赞收藏交互

文章目录

      • 前言
      • Article 模型里混合了内容和互动状态
      • 分类筛选不改 articles 本身
      • 点赞要同时改 isLiked 和 likes
      • 收藏只改布尔值,不影响计数
      • 精选卡片是固定取第三篇文章
      • 这个页面的价值在于状态口径清楚
      • 完整代码
      • 最后总结

前言

内容流页面看起来只是文章卡片堆起来,但只要加上分类、点赞、收藏,状态关系就开始变复杂。分类改变的是列表视图,点赞和收藏改变的是文章本身;这两类状态如果混在一起写,很容易出现“筛选后点赞丢失”或者“回到全部列表状态不一致”的问题。

这个示例的实现比较适合拿来练内容列表的基本功:articles是主数据源,selectedCategory只负责过滤视图,点赞和收藏都回写到对应文章对象。它没有做搜索、详情跳转和分页加载,所以重点可以放在列表状态怎么保持一致上。

Article 模型里混合了内容和互动状态

Article_nyyr既保存文章展示信息,也保存用户对文章的互动状态。

interfaceArticle_nyyr{id:numbertitle:stringauthor:stringavatar:stringreadTime:numbercategory:stringlikes:numbercomments:numberisLiked:booleanisBookmarked:booleansummary:stringpublishDate:string}

titleauthorsummarypublishDate决定卡片内容;category用来做筛选;likescommentsisLikedisBookmarked决定底部互动区怎么显示。

这个模型在示例里很直观,但真实项目里可能会把互动状态拆出去。原因是文章内容通常来自推荐流接口,而点赞、收藏状态可能来自用户关系接口,两者更新频率并不一样。教学案例放在一起,能更容易看清 UI 联动。

分类筛选不改 articles 本身

selectedCategory保存当前选中的分类,filteredgetter 根据它返回要展示的文章列表。

@StateselectedCategory:string='全部'@Statecategories:string[]=['全部','技术','设计','AI','生活']getfiltered():Article_nyyr[]{if(this.selectedCategory==='全部')returnthis.articlesreturnthis.articles.filter(a=>a.category===this.selectedCategory)}

这里最重要的是:筛选只改变视图,不改变articles。点“设计”时,列表只显示设计类文章;切回“全部”时,原来的文章数组还在,点赞和收藏状态也不会丢。

顶部分类标签的选中态也来自selectedCategory

Text(cat).fontColor(this.selectedCategory===cat?'#FFF':'#666').backgroundColor(this.selectedCategory===cat?'#007AFF':'#F0F0F0').onClick(()=>this.selectedCategory=cat)

这让一个状态同时控制筛选结果和标签样式。写内容流时,这种单一状态源会比到处传布尔值稳很多。

点赞要同时改 isLiked 和 likes

toggleLike(id)不是只切换红心图标,它还会同步增减点赞数。

toggleLike(id:number){this.articles=this.articles.map(a=>{if(a.id===id){a.likes=a.isLiked?a.likes-1:a.likes+1;a.isLiked=!a.isLiked}returna})}

这里的顺序也值得看:先根据旧的isLiked判断点赞数该加还是减,再翻转isLiked。如果先翻转再算数量,很容易把加减方向写反。

这段代码仍然是本地乐观更新,没有接口失败回滚。真实产品里,点赞通常会先让 UI 立即变,再请求服务端;如果请求失败,要么回滚,要么做重试提示。示例里没有这层网络状态,所以教程里不要把它讲成完整点赞系统。

收藏只改布尔值,不影响计数

收藏逻辑比点赞简单,只有isBookmarked一个字段。

toggleBookmark(id:number){this.articles=this.articles.map(a=>{if(a.id===id){a.isBookmarked=!a.isBookmarked}returna})}

列表底部根据它切换收藏图标。

Text(article.isBookmarked?'🔖':'📑').onClick(()=>this.toggleBookmark(article.id))

它不会影响文章排序,也不会把文章加入单独的收藏列表。当前实现只是“在当前文章流里标记收藏状态”。如果后续要做收藏页,需要额外从articles里筛isBookmarked,或者维护独立收藏数据。

精选卡片是固定取第三篇文章

顶部“今日精选”不是根据筛选结果取第一篇,也不是根据点赞数计算出来,而是直接展示this.articles[2]

Text(this.articles[2].title)Text(this.articles[2].summary)Text(this.articles[2].author)

这点很容易被旧稿讲偏。它现在只是一个固定精选位,分类切换不会影响它,点赞收藏也没有入口作用在这张精选卡片上。如果要做真正的精选模块,至少应该有isFeatured字段,或者用推荐接口返回专门的精选文章。

右上角搜索图标也是纯展示,没有绑定搜索输入或点击事件。当前页面可交互的部分只有分类、点赞和收藏。

这个页面的价值在于状态口径清楚

这页最值得借的不是卡片阴影或渐变,而是状态口径:主数据只有articles,分类是视图条件,点赞和收藏都更新主数据。这样即使在筛选后的列表里操作某篇文章,回到全部列表时状态仍然一致。

继续扩展时,我会先加文章详情跳转,再补搜索和分页。分页时要特别注意点赞后的状态合并:新数据回来以后,不能把用户刚刚点过的本地状态冲掉。

完整代码

下面保留案例完整代码,方便你直接对照学习、复制和重构。

import{router}from'@kit.ArkUI'interfaceArticle_nyyr{id:numbertitle:stringauthor:stringavatar:stringreadTime:numbercategory:stringlikes:numbercomments:numberisLiked:booleanisBookmarked:booleansummary:stringpublishDate:string}@Entry@Componentstruct FeaturedArticleFeed{@Statearticles:Article_nyyr[]=[{id:1,title:'HarmonyOS 5.0 新特性深度解析',author:'技术达人',avatar:'👨‍💻',readTime:8,category:'技术',likes:234,comments:18,isLiked:false,isBookmarked:false,summary:'深入探讨鸿蒙5.0在应用开发、分布式能力和性能优化方面的重大更新...',publishDate:'2小时前'},{id:2,title:'从零开始学产品设计:UI/UX 入门指南',author:'设计师小王',avatar:'🎨',readTime:12,category:'设计',likes:567,comments:42,isLiked:true,isBookmarked:true,summary:'产品设计不仅仅是视觉美观,更需要深入理解用户心理和业务逻辑...',publishDate:'5小时前'},{id:3,title:'2026年AI工具推荐:提升工作效率50%',author:'AI探索者',avatar:'🤖',readTime:6,category:'AI',likes:1203,comments:89,isLiked:false,isBookmarked:false,summary:'精选10款AI工具,覆盖写作、编程、设计、数据分析等多个场景...',publishDate:'昨天'},{id:4,title:'极简主义生活方式:断舍离的实践哲学',author:'生活美学',avatar:'🌿',readTime:5,category:'生活',likes:788,comments:56,isLiked:true,isBookmarked:false,summary:'在信息爆炸的时代,学会取舍反而是一种生产力...',publishDate:'2天前'},]@StateselectedCategory:string='全部'@Statecategories:string[]=['全部','技术','设计','AI','生活']getfiltered():Article_nyyr[]{if(this.selectedCategory==='全部')returnthis.articlesreturnthis.articles.filter(a=>a.category===this.selectedCategory)}toggleLike(id:number){this.articles=this.articles.map(a=>{if(a.id===id){a.likes=a.isLiked?a.likes-1:a.likes+1;a.isLiked=!a.isLiked}returna})}toggleBookmark(id:number){this.articles=this.articles.map(a=>{if(a.id===id){a.isBookmarked=!a.isBookmarked}returna})}build(){Column(){Row(){Image($r('app.media.ic_back')).width(24).height(24).onClick(()=>router.back())Text('发现精选').fontSize(18).fontWeight(FontWeight.Bold).layoutWeight(1).textAlign(TextAlign.Center)Text('🔍').fontSize(20)}.width('100%').padding({left:16,right:16,top:12,bottom:12})// CategoriesScroll(){Row(){ForEach(this.categories,(cat:string)=>{Text(cat).fontSize(13).fontColor(this.selectedCategory===cat?'#FFF':'#666').backgroundColor(this.selectedCategory===cat?'#007AFF':'#F0F0F0').borderRadius(16).padding({left:16,right:16,top:6,bottom:6}).margin({right:8}).onClick(()=>this.selectedCategory=cat)})}.padding({left:16,right:16})}.scrollable(ScrollDirection.Horizontal).width('100%').margin({bottom:12})// FeaturedColumn(){Text('🔥 今日精选').fontSize(13).fontWeight(FontWeight.Bold).fontColor('#FF4757').margin({bottom:8})Text(this.articles[2].title).fontSize(17).fontWeight(FontWeight.Bold).fontColor('#FFF').margin({bottom:4})Text(this.articles[2].summary).fontSize(12).fontColor('#FFFFFFCC').lineHeight(18).margin({bottom:8})Row(){Text(this.articles[2].author).fontSize(12).fontColor('#FFFFFFAA')Text(\` · \${this.articles[2].readTime}分钟阅读\`).fontSize(12).fontColor('#FFFFFFAA')}}.width('calc(100% - 32vp)').backgroundColor('#007AFF').borderRadius(16).padding(20).linearGradient({colors:[['#667EEA',0.0],['#764BA2',1.0]],angle:135}).alignItems(HorizontalAlign.Start).margin({bottom:16})// Article_nyyr listList({space:12}){ForEach(this.filtered,(article:Article_nyyr)=>{ListItem(){Column(){Row(){Text(article.avatar).fontSize(32).margin({right:12})Column(){Text(article.author).fontSize(13).fontColor('#555').fontWeight(FontWeight.Medium)Text(article.publishDate).fontSize(11).fontColor('#AAA').margin({top:2})}.layoutWeight(1).alignItems(HorizontalAlign.Start)Text(article.category).fontSize(11).fontColor('#007AFF').backgroundColor('#E8F0FE').borderRadius(8).padding({left:8,right:8,top:3,bottom:3})}.width('100%').margin({bottom:8})Text(article.title).fontSize(16).fontWeight(FontWeight.Bold).fontColor('#333').margin({bottom:6})Text(article.summary).fontSize(13).fontColor('#777').lineHeight(20).margin({bottom:10}).maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})Row(){Text(\`⏱ \${article.readTime}min\`).fontSize(12).fontColor('#AAA')Blank()Text(article.isLiked?'❤️':'🤍').fontSize(16).margin({right:4}).onClick(()=>this.toggleLike(article.id))Text(\`\${article.likes}\`).fontSize(12).fontColor('#888').margin({right:12})Text('💬').fontSize(16).margin({right:4})Text(\`\${article.comments}\`).fontSize(12).fontColor('#888').margin({right:12})Text(article.isBookmarked?'🔖':'📑').fontSize(16).onClick(()=>this.toggleBookmark(article.id))}.width('100%')}.width('100%').backgroundColor('#FFF').borderRadius(14).padding(16).shadow({radius:4,color:'#0000001A',offsetY:2})}})}.layoutWeight(1).padding({left:16,right:16})}.width('100%').height('100%').backgroundColor('#F5F5F5')}}

最后总结

精选文章流页面 这个案例并不靠复杂技巧取胜,它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后,你再去写同类型功能,速度会明显快很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/16 5:07:13

HLW8110电能计量芯片的硬件选型与软件校准实战指南

1. HLW8110芯片基础认知与选型策略第一次接触HLW8110时,我被它SOP-8封装里蕴含的强大功能震撼到了——这个比指甲盖还小的芯片,居然能同时测量电压、电流、功率等十余种电参数。在实际项目中踩过几次坑后,我总结出选型时必须关注的三个核心指…

作者头像 李华
网站建设 2026/7/16 5:04:31

基于Duilib的C++音乐播放器开发实战:从UI定制到音频处理

1. 项目概述与核心价值最近在整理硬盘,翻到了一个几年前自己捣鼓的C项目——一个基于duilib界面库、模仿酷狗音乐播放器UI和部分功能的桌面应用。这个项目代号“Redrain”,算是我从纯后端逻辑转向客户端开发的一个里程碑。当时市面上成熟的C UI框架选择不…

作者头像 李华
网站建设 2026/7/16 5:03:55

pg_basebackup(物理备份)

pg_basebackup(物理备份) 常用参数速查 参数 简写 说明 示例 --pgdataDIRECTORY -D 备份目标目录(必需) -D /backup/base --formatp|t -F 输出格式:plain|tar -Fp 或 -Ft --wal-methodMETHOD -X WAL 处…

作者头像 李华
网站建设 2026/7/16 5:03:11

逆向工程实战:深度解析Wallpaper Engine的RePKG文件格式与解包工具开发

1. 项目概述:从用户到探索者如果你和我一样,是个喜欢折腾桌面美化的人,那么对 Wallpaper Engine 这款软件一定不陌生。它强大的动态壁纸引擎和创意工坊生态,让我们的桌面从静态图片变成了一个可以互动、可以呼吸的活体空间。但不知…

作者头像 李华
网站建设 2026/7/16 5:01:56

Mac新手上路:DBeaver社区版安装与核心配置避坑指南

1. 为什么选择DBeaver社区版 作为Mac用户,当你需要一款数据库管理工具时,DBeaver社区版绝对是个不错的选择。它完全免费开源,支持几乎所有主流数据库,从MySQL、PostgreSQL到Oracle、SQL Server,甚至连Hive这种大数据组…

作者头像 李华
网站建设 2026/7/16 5:01:27

C# RSA非对称加密实战:从原理到跨平台对接与性能优化

1. 项目概述:为什么C#开发者绕不开RSA?如果你用C#做过任何涉及数据安全传输或存储的功能,比如用户密码令牌、支付接口签名、配置文件加密,那你大概率听说过或者用过RSA。这玩意儿就像程序世界的“数字信封”:发信人用一…

作者头像 李华