当前位置: 首页 > news >正文

HarmonyOS6 半年磨一剑:RcTag 组件实战案例(一)内容展示与商品筛选

文章目录

    • 一、场景一:内容详情页标签展示
      • 1.1 场景描述
      • 1.2 设计思路
      • 1.3 完整代码
      • 1.4 代码详解
    • 二、场景二:商品分类筛选
      • 2.1 场景描述
      • 2.2 设计思路
      • 2.3 完整代码
      • 2.4 代码详解
    • 总结

Hello 各位开发者们大家好,我是若城,本篇是RcTag实战系列的第一篇文章,将通过两个真实业务场景,展示如何用RcTag构建内容详情页标签区域和电商商品分类筛选功能。


一、场景一:内容详情页标签展示

1.1 场景描述

文章详情页顶部展示文章分类标签、内容标签和难度标签,纯展示用途,无交互。这类需求在技术博客、内容社区、资讯类 App 中极为普遍,标签本身承载的是信息分类职责,设计上需要做到层次分明而不杂乱。

1.2 设计思路

这个场景的核心是在有限的屏幕宽度内,用视觉差异区分出三种信息层级:主分类(主语意最强)、难度(次要信息)、内容关键词(辅助信息)。

主分类标签采用实色填充,这是最高强调等级,抢先抓住用户视线。难度标签采用胶囊形状,缩小尺寸至 mini,既保持可读性又不抢占主分类的主导地位。内容关键词数量最多,若同样使用实色会造成视觉噪声,因此采用rcTagPlain: true+rcTagPlainFill: true的镂空填充风格——有背景色但背景透明度低,整体轻量,适合数量多的场景。

布局方面,第一行使用Row横向排列分类标签和难度标签,这两个标签数量固定,不会换行。第二行使用Flex+FlexWrap.Wrap展示内容关键词,标签数量不确定,自动换行是必要的。

1.3 完整代码

import{RcTag}from'rchoui'@Entry@ComponentV2struct ArticleDetailTagsDemo{// 文章元数据privatearticleCategory:string='鸿蒙开发'privatearticleTags:string[]=['ArkTS','ArkUI','组件库','UI设计','开源']privatearticleDifficulty:string='进阶'privatearticleDifficultyType:'primary'|'warning'|'error'='warning'build(){Column({space:16}){// 文章标题Text('HarmonyOS6 组件库实战:RcTag 深度解析').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#1a1a1a')// 标签区域Column({space:8}){// 第一行:主分类(实色)+ 难度(胶囊)Row({space:8}){RcTag({rcTagText:this.articleCategory,rcTagType:'primary',rcTagSize:'medium'})RcTag({rcTagText:this.articleDifficulty,rcTagType:this.articleDifficultyType,rcTagShape:'circle',rcTagSize:'mini'})}// 第二行:内容标签(镂空填充)Flex({wrap:FlexWrap.Wrap}){ForEach(this.articleTags,(tag:string)=>{RcTag({rcTagText:tag,rcTagType:'info',rcTagPlain:true,rcTagPlainFill:true,rcTagSize:'mini',rcTagMargin:{right:6,bottom:6}})},(tag:string)=>tag)}}.alignItems(HorizontalAlign.Start).width('100%')// 文章摘要Text('本文深度解析 RcTag 组件的架构设计、色彩系统...').fontSize(14).fontColor('#606266').lineHeight(22)}.padding(16).width('100%').backgroundColor(Color.White)}}

1.4 代码详解

数据结构设计

组件定义了三个私有变量来存储文章元数据:articleCategory是文章主分类,固定为字符串;articleTags是内容关键词数组,数量动态;articleDifficultyType的类型是联合类型'primary' | 'warning' | 'error',这使得难度类型与RcTagrcTagType直接兼容,无需任何转换就可以透传给组件。这个设计细节值得学习:让数据层的类型定义与 UI 层的 Props 保持一致,能大幅减少映射代码。

第一行:主分类与难度标签

主分类标签只配置了rcTagType: 'primary'rcTagSize: 'medium',未设置rcTagShape,因此使用默认的方形(square),整体感觉更稳重。难度标签则配置了rcTagShape: 'circle',呈现胶囊形状,视觉上比方形更活泼。rcTagSize: 'mini'使难度标签明显小于主分类标签,这种尺寸差异主动建立了视觉层级。

两个标签放在Row({ space: 8 })中,space: 8在两标签之间产生 8vp 的间距,无需借助rcTagMargin来处理间距,代码更简洁。

第二行:内容关键词标签

内容关键词数组用ForEach遍历渲染,key函数直接返回标签文本本身(tag: string) => tag。这在标签文本唯一时是没问题的——文章的内容标签通常不会重复,这个假设是合理的。若标签可能重复,则应改用索引或其他唯一标识。

每个标签设置了rcTagMargin: { right: 6, bottom: 6 },右边距和下边距共同作用于Flex换行场景:右边距控制同行标签间的水平间距,下边距则在换行后产生行间距,最终标签无论在哪一行哪一位置,与邻近元素的距离都是一致的 6vp。

rcTagPlain: true开启镂空描边模式,rcTagPlainFill: true在镂空基础上增加浅色背景填充。这两个属性必须同时设置才能产生镂空填充效果——单独设置rcTagPlainFill而不设置rcTagPlain,浅色背景不会生效。rcTagType: 'info'决定了浅色背景和描边的颜色基调,这里选用info是因为其灰蓝色调不强调、不抢眼,恰好适合次要信息的展示。

布局容器的 alignItems

标签区域的Column设置了alignItems(HorizontalAlign.Start),这确保内部的RowFlex都左对齐。如果不设置,默认居中对齐,标签会出现在屏幕中央,与正文排版惯例不符。


二、场景二:商品分类筛选

2.1 场景描述

电商 App 的商品列表页,顶部提供分类筛选标签,支持单选切换,点击标签实时过滤商品列表。这是电商 App 中最常见的交互模式之一,分类标签横向排列,超出屏幕宽度时可以横向滑动,点击某分类后列表立即刷新。

2.2 设计思路

这个场景的技术核心有两点:一是用rcTagPlain的动态值来表达"选中/未选中"两种视觉状态,二是用rcTagName携带分类 ID,通过回调事件更新状态变量从而驱动列表过滤。

选中状态的实现思路是:当某个分类标签的id等于当前selectedCategory时,rcTagPlainfalse(实色,表示选中);否则为true(镂空,表示未选中)。这样一行表达式rcTagPlain: this.selectedCategory !== cat.id就完整描述了选中状态,逻辑非常清晰。

2.3 完整代码

import{RcTag}from'rchoui'interfaceProductCategory{id:numberlabel:string}interfaceProduct{id:numbername:stringcategory:numberprice:string}@Entry@ComponentV2struct ProductFilterDemo{@LocalselectedCategory:number=0// 0 = 全部privatecategories:ProductCategory[]=[{id:0,label:'全部'},{id:1,label:'手机数码'},{id:2,label:'家用电器'},{id:3,label:'服装鞋包'},{id:4,label:'美妆护肤'},{id:5,label:'图书'}]privateproducts:Product[]=[{id:1,name:'HarmonyOS 手机',category:1,price:'3999'},{id:2,name:'智能冰箱',category:2,price:'4299'},{id:3,name:'运动夹克',category:3,price:'299'},{id:4,name:'精华液套装',category:4,price:'599'},{id:5,name:'ArkTS 实战',category:5,price:'89'},{id:6,name:'鸿蒙平板',category:1,price:'2599'}]privategetfilteredProducts():Product[]{if(this.selectedCategory===0){returnthis.products}returnthis.products.filter(p=>p.category===this.selectedCategory)}build(){Column(){// 分类筛选栏Scroll(){Row({space:8}){ForEach(this.categories,(cat:ProductCategory)=>{RcTag({rcTagText:cat.label,rcTagType:'primary',rcTagShape:'circle',rcTagPlain:this.selectedCategory!==cat.id,rcTagName:cat.id,onRcTagClick:(name)=>{this.selectedCategory=nameasnumber}})},(cat:ProductCategory)=>String(cat.id))}.padding({left:16,right:16,top:12,bottom:12})}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off).width('100%').backgroundColor(Color.White)// 商品列表List({space:0}){ForEach(this.filteredProducts,(product:Product)=>{ListItem(){Row({space:12}){Column({space:4}){Text(product.name).fontSize(15).fontColor('#1a1a1a')Text(`¥${product.price}`).fontSize(14).fontColor('#f56c6c').fontWeight(FontWeight.Medium)}.alignItems(HorizontalAlign.Start).layoutWeight(1)// 状态标签RcTag({rcTagText:this.categories.find(c=>c.id===product.category)?.label??'',rcTagType:'info',rcTagSize:'mini',rcTagPlain:true})}.padding({left:16,right:16,top:14,bottom:14}).width('100%').backgroundColor(Color.White)}},(product:Product)=>String(product.id))}.divider({strokeWidth:0.5,color:'#f0f0f0',startMargin:16,endMargin:16}).backgroundColor(Color.White).margin({top:8})}.width('100%').height('100%').backgroundColor('#f5f5f5')}}

2.4 代码详解

接口定义

代码在组件外定义了ProductCategoryProduct两个接口。ProductCategoryid: number作为分类的唯一标识,id: 0被约定为"全部"这一特殊分类。Productcategory字段存储的就是ProductCategoryid,二者通过数字 ID 关联,这是一种轻量的关联数据结构,不引入复杂的嵌套。

状态变量设计

selectedCategory使用@Local装饰,初始值为0,对应"全部"分类。@Local是 ComponentV2 中的局部状态装饰器,修改该变量会触发组件重新渲染,这是驱动筛选栏视觉更新和列表内容刷新的根本机制。

计算属性 filteredProducts

filteredProducts定义为private get,即 getter 计算属性。每次build()执行时都会调用它,根据当前selectedCategory过滤商品数组并返回结果。当selectedCategory为 0 时直接返回全量数据,否则用Array.filter过滤出匹配分类的商品。这个设计的好处是将过滤逻辑与 UI 布局代码分离,build()里只需读取filteredProducts即可,无需关心过滤细节。

筛选栏的横向滚动

分类标签放在Scroll容器中,scrollable(ScrollDirection.Horizontal)指定为横向滚动,scrollBar(BarState.Off)隐藏滚动条(滚动条在手机端通常视觉上不美观)。内部使用Row({ space: 8 })横向排列所有分类标签,设置了padding为标签四周预留呼吸空间。

rcTagPlain 驱动选中状态

这是本案例最核心的技术点。rcTagPlain: this.selectedCategory !== cat.id这行代码的含义:当前分类的 ID 不等于已选分类 ID 时,标签处于镂空状态(未选中样式);等于时rcTagPlainfalse,标签呈现实色填充(选中样式)。由于selectedCategory@Local状态变量,每次点击都会更新它,进而触发ForEach所有标签重新计算rcTagPlain的值,选中/未选中状态即时切换。

rcTagName 携带业务标识

rcTagName: cat.id将分类 ID 作为标签的"名字"附加到组件上。onRcTagClick回调的参数就是rcTagName的值,因此在回调中可以直接拿到被点击分类的 ID,执行this.selectedCategory = name as number完成状态更新。这避免了在回调闭包中捕获整个cat对象,是更干净的传参方式。

ForEach 的 key 函数

筛选栏的ForEach使用(cat: ProductCategory) => String(cat.id)作为 key 函数,商品列表使用(product: Product) => String(product.id)作为 key。数字 ID 需要通过String()转换为字符串,因为 ForEach 的 key 函数返回值类型是string。用 ID 作为 key 的好处是:即使数组顺序发生变化,ArkTS 框架也能精确识别哪些节点需要更新,而不会触发全量重渲染,性能更优。

商品列表中的分类标签

商品列表每一行右侧也有一个小标签,显示该商品所属的分类名称。this.categories.find(c => c.id === product.category)?.label ?? ''通过分类 ID 在分类数组中查找对应名称,?.可选链保证了 find 返回undefined时不报错,?? ''则提供了空字符串兜底。这个标签使用rcTagType: 'info'rcTagPlain: truercTagSize: 'mini',视觉上轻量,不与左侧的商品主信息竞争注意力。

商品列表的布局权重

商品信息的Column设置了.layoutWeight(1),这使其在Row中占据除右侧标签以外的所有剩余宽度。layoutWeight是 ArkUI 中类似 CSSflex: 1的属性,配合Row使用时效果等同于弹性布局中的按比例分配空间,保证标签始终靠右对齐,商品名称可以充分利用剩余空间。


总结

本文介绍的两个案例分别代表了RcTag的两种典型用法:纯展示(通过类型和风格传达信息层级)和交互筛选(通过状态变量与 rcTagPlain 联动实现选中态)。rcTagMargin解决了多标签排列的间距问题,rcTagName+ 事件回调解决了 ForEach 场景下的标识传递问题。这两个模式在大多数 App 的标签需求中都能复用。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!

http://www.jsqmd.com/news/497893/

相关文章:

  • LangChain大模型应用开发指南:小白也能轻松掌握,收藏必备!
  • 当LSTM戴上“概率眼镜“:用贝叶斯视角玩转时间序列预测
  • 热销榜单:2026年北京本凡科技推荐的最值得的小程序开发平台TOP3,助力企业数字化转型
  • 【Python × AI】Memory 机制深度解析:为大模型植入“长期记忆”的艺术
  • 中文乱码,解决
  • 2026普通人转行,推荐一个好就业的方向——人工智能大模型,非常详细!
  • 低空经济+电力:输电线路无人机巡检及要求
  • 72 编辑距离
  • Vue.js如何通过WebUploader控件解决汽车制造CAD图纸的超大附件分片校验上传?
  • GitNexus:零服务器代码知识图谱引擎,让代码理解更智能
  • 重庆包装袋制作供应厂家排行
  • 飞腾平台 UEFI 与 U-Boot 启动方案对比及选型建议
  • 2-3层网络测试仪全面解析北京网测科技--Supernova 系列产品介绍与选型指南
  • [Win11 Vmware17 CentOS7.6]安装Linux操作系统详细步骤(附VMware17+CentOS7下载链接)
  • 干货!跨境电商出海短视频矩阵工具怎么选?
  • 如何解决帝国CMS 7.5编辑器粘贴Word文档时格式和图片丢失的问题?
  • python+Ai技术框架的健身房课程预约管理系统的设计与实现django flask
  • 深入理解 async/await:现代异步编程的终极解决方案
  • 医疗行业票据合规要求高?智能接口严守风控关
  • 吉林省GEO营销哪个服务商技术强
  • 【CANoe】使用IG发报文触发busOff后不能恢复教程
  • 探索六自由度并联 Stewart Platform 平台的奇妙之旅
  • 基于秃鹰搜索算法优化BP神经网络的多变量时间序列预测
  • 东华复试OJ二刷复盘11
  • 三相调速永磁同步电动机maxwell模型 1、案例采用180-8极一字型冲片 2、转速为150...
  • 别再浪费硬盘了!用MediaMTX打造自动录制+HLS点播系统,还能钩子转码!
  • EasyDSS视频流媒体WebRTC技术解析:智慧校园直播、点播与会议一体化融合实践
  • Agent 4大协议:MCP/ACP/A2A/ANP
  • 文字宽度 文字包围盒
  • 帝国CMS 7.5编辑器导入Word内容为何会丢失样式?如何修复?