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

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}title、author、summary、publishDate决定卡片内容category用来做筛选likes、comments、isLiked、isBookmarked决定底部互动区怎么显示。这个模型在示例里很直观但真实项目里可能会把互动状态拆出去。原因是文章内容通常来自推荐流接口而点赞、收藏状态可能来自用户关系接口两者更新频率并不一样。教学案例放在一起能更容易看清 UI 联动。分类筛选不改 articles 本身selectedCategory保存当前选中的分类filteredgetter 根据它返回要展示的文章列表。StateselectedCategory:string全部Statecategories:string[][全部,技术,设计,AI,生活]getfiltered():Article_nyyr[]{if(this.selectedCategory全部)returnthis.articlesreturnthis.articles.filter(aa.categorythis.selectedCategory)}这里最重要的是筛选只改变视图不改变articles。点“设计”时列表只显示设计类文章切回“全部”时原来的文章数组还在点赞和收藏状态也不会丢。顶部分类标签的选中态也来自selectedCategory。Text(cat).fontColor(this.selectedCategorycat?#FFF:#666).backgroundColor(this.selectedCategorycat?#007AFF:#F0F0F0).onClick(()this.selectedCategorycat)这让一个状态同时控制筛选结果和标签样式。写内容流时这种单一状态源会比到处传布尔值稳很多。点赞要同时改 isLiked 和 likestoggleLike(id)不是只切换红心图标它还会同步增减点赞数。toggleLike(id:number){this.articlesthis.articles.map(a{if(a.idid){a.likesa.isLiked?a.likes-1:a.likes1;a.isLiked!a.isLiked}returna})}这里的顺序也值得看先根据旧的isLiked判断点赞数该加还是减再翻转isLiked。如果先翻转再算数量很容易把加减方向写反。这段代码仍然是本地乐观更新没有接口失败回滚。真实产品里点赞通常会先让 UI 立即变再请求服务端如果请求失败要么回滚要么做重试提示。示例里没有这层网络状态所以教程里不要把它讲成完整点赞系统。收藏只改布尔值不影响计数收藏逻辑比点赞简单只有isBookmarked一个字段。toggleBookmark(id:number){this.articlesthis.articles.map(a{if(a.idid){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}fromkit.ArkUIinterfaceArticle_nyyr{id:numbertitle:stringauthor:stringavatar:stringreadTime:numbercategory:stringlikes:numbercomments:numberisLiked:booleanisBookmarked:booleansummary:stringpublishDate:string}EntryComponentstruct 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(aa.categorythis.selectedCategory)}toggleLike(id:number){this.articlesthis.articles.map(a{if(a.idid){a.likesa.isLiked?a.likes-1:a.likes1;a.isLiked!a.isLiked}returna})}toggleBookmark(id:number){this.articlesthis.articles.map(a{if(a.idid){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.selectedCategorycat?#FFF:#666).backgroundColor(this.selectedCategorycat?#007AFF:#F0F0F0).borderRadius(16).padding({left:16,right:16,top:6,bottom:6}).margin({right:8}).onClick(()this.selectedCategorycat)})}.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)}}最后总结精选文章流页面 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。