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

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: truercTagPlainFill: true的镂空填充风格——有背景色但背景透明度低整体轻量适合数量多的场景。布局方面第一行使用Row横向排列分类标签和难度标签这两个标签数量固定不会换行。第二行使用FlexFlexWrap.Wrap展示内容关键词标签数量不确定自动换行是必要的。1.3 完整代码import{RcTag}fromrchouiEntryComponentV2struct ArticleDetailTagsDemo{// 文章元数据privatearticleCategory:string鸿蒙开发privatearticleTags:string[][ArkTS,ArkUI,组件库,UI设计,开源]privatearticleDifficulty:string进阶privatearticleDifficultyType:primary|warning|errorwarningbuild(){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这使得难度类型与RcTag的rcTagType直接兼容无需任何转换就可以透传给组件。这个设计细节值得学习让数据层的类型定义与 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)这确保内部的Row和Flex都左对齐。如果不设置默认居中对齐标签会出现在屏幕中央与正文排版惯例不符。二、场景二商品分类筛选2.1 场景描述电商 App 的商品列表页顶部提供分类筛选标签支持单选切换点击标签实时过滤商品列表。这是电商 App 中最常见的交互模式之一分类标签横向排列超出屏幕宽度时可以横向滑动点击某分类后列表立即刷新。2.2 设计思路这个场景的技术核心有两点一是用rcTagPlain的动态值来表达选中/未选中两种视觉状态二是用rcTagName携带分类 ID通过回调事件更新状态变量从而驱动列表过滤。选中状态的实现思路是当某个分类标签的id等于当前selectedCategory时rcTagPlain为false实色表示选中否则为true镂空表示未选中。这样一行表达式rcTagPlain: this.selectedCategory ! cat.id就完整描述了选中状态逻辑非常清晰。2.3 完整代码import{RcTag}fromrchouiinterfaceProductCategory{id:numberlabel:string}interfaceProduct{id:numbername:stringcategory:numberprice:string}EntryComponentV2struct ProductFilterDemo{LocalselectedCategory:number0// 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.selectedCategory0){returnthis.products}returnthis.products.filter(pp.categorythis.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.selectedCategorynameasnumber}})},(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(cc.idproduct.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 代码详解接口定义代码在组件外定义了ProductCategory和Product两个接口。ProductCategory用id: number作为分类的唯一标识id: 0被约定为全部这一特殊分类。Product的category字段存储的就是ProductCategory的id二者通过数字 ID 关联这是一种轻量的关联数据结构不引入复杂的嵌套。状态变量设计selectedCategory使用Local装饰初始值为0对应全部分类。Local是 ComponentV2 中的局部状态装饰器修改该变量会触发组件重新渲染这是驱动筛选栏视觉更新和列表内容刷新的根本机制。计算属性 filteredProductsfilteredProducts定义为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 时标签处于镂空状态未选中样式等于时rcTagPlain为false标签呈现实色填充选中样式。由于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: true、rcTagSize: mini视觉上轻量不与左侧的商品主信息竞争注意力。商品列表的布局权重商品信息的Column设置了.layoutWeight(1)这使其在Row中占据除右侧标签以外的所有剩余宽度。layoutWeight是 ArkUI 中类似 CSSflex: 1的属性配合Row使用时效果等同于弹性布局中的按比例分配空间保证标签始终靠右对齐商品名称可以充分利用剩余空间。总结本文介绍的两个案例分别代表了RcTag的两种典型用法纯展示通过类型和风格传达信息层级和交互筛选通过状态变量与 rcTagPlain 联动实现选中态。rcTagMargin解决了多标签排列的间距问题rcTagName 事件回调解决了 ForEach 场景下的标识传递问题。这两个模式在大多数 App 的标签需求中都能复用。如果这篇文章对你有帮助欢迎点赞、收藏、关注你的支持是我持续创作的动力