文章目录
- 一套代码,渲染多种状态
- 数据模型设计
- 完整 Demo
- 数据驱动多态样式的三个关键点
- 1. 枚举定义状态,避免魔法字符串
- 2. 映射表集中管理样式配置
- 3. 金额格式化在方法层处理
- 小结
一套代码,渲染多种状态
做过电商或者企业应用的都知道,订单卡片是个麻烦事:
同样一张卡片,"待付款"是橙色、"处理中"是蓝色、"已完成"是绿色、"已取消"是灰色。商品名可能很短也可能很长。价格格式要固定。操作按钮根据状态不一样……
如果你的思路是"每种状态写一个卡片样式",代码量会爆炸,后续改一个颜色要改四处。
正确的做法是:设计好数据模型,用数据驱动样式。颜色是数据的映射,按钮是状态的映射,文字是字段的格式化。
HarmonyOS PC 端的订单管理场景比手机端更复杂——PC 端通常在一个页面里显示更多的订单信息,支持多选、批量操作,行高和信息密度也更高。本文就从数据模型出发,把多态样式绑定讲清楚。
数据模型设计
好的数据模型让后面的渲染代码简单很多。先定义订单的枚举和接口:
// 订单状态枚举enumOrderStatus{PENDING_PAYMENT='pending_payment',// 待付款PROCESSING='processing',// 处理中SHIPPING='shipping',// 配送中COMPLETED='completed',// 已完成CANCELLED='cancelled',// 已取消REFUNDING='refunding',// 退款中}// 订单数据接口interfaceOrderItem{id:stringtitle:string// 商品名subtitle:string// 副标题/规格price:number// 金额(分)quantity:number// 数量status:OrderStatus// 订单状态createdAt:string// 创建时间imageUrl?:string// 商品图(可选)}// 订单卡片展示用的样式配置interfaceStatusConfig{label:string// 状态文字color:string// 主色bgColor:string// 背景色actions:string[]// 可用操作按钮}把"状态→样式配置"的映射关系集中维护,是多态卡片设计的关键:
constSTATUS_CONFIG:Record<string,StatusConfig>={pending_payment:{label:'待付款',color:'#FF7A00',bgColor:'#FFF3E8',actions:['立即付款','取消订单']},processing:{label:'处理中',color:'#0A59F7',bgColor:'#EBF2FF',actions:['查看详情']},shipping:{label:'配送中',color:'#00B578',bgColor:'#E8FFF4',actions:['查看物流','确认收货']},completed:{label:'已完成',color:'#00B578',bgColor:'#E8FFF4',actions:['再次购买','申请退款']},cancelled:{label:'已取消',color:'#999999',bgColor:'#F5F5F5',actions:['删除记录']},refunding:{label:'退款中',color:'#FF4040',bgColor:'#FFF0F0',actions:['查看进度']},}完整 Demo
新建文件PcOrderCardPage.ets:
enumOrderStatus{PENDING_PAYMENT='pending_payment',PROCESSING='processing',SHIPPING='shipping',COMPLETED='completed',CANCELLED='cancelled',REFUNDING='refunding',}interfaceOrderItem{id:stringtitle:stringsubtitle:stringprice:numberquantity:numberstatus:OrderStatus createdAt:string}classStatusConfig{label:stringcolor:stringbgColor:stringactions:string[]constructor(label:string,color:string,bgColor:string,actions:string[]){this.label=labelthis.color=colorthis.bgColor=bgColorthis.actions=actions}}interfaceFilterItem{id:stringlabel:string}@Entry@Componentstruct PcOrderCardPage{@StateactiveFilter:string='all'@StateselectedIds:string[]=[]privateorders:OrderItem[]=[{id:'ORD-20250601-001',title:'HarmonyOS 开发者套件(专业版)',subtitle:'颜色: 星空黑 / 版本: 专业版',price:129900,quantity:1,status:OrderStatus.SHIPPING,createdAt:'2025-06-01 10:30'},{id:'ORD-20250530-002',title:'ArkUI 组件开发实战课程',subtitle:'在线课程 / 永久有效',price:29900,quantity:1,status:OrderStatus.COMPLETED,createdAt:'2025-05-30 14:22'},{id:'ORD-20250528-003',title:'华为 MatePad Pro 11 智能键盘保护套',subtitle:'适配 MatePad Pro 11 2024款',price:49900,quantity:2,status:OrderStatus.PENDING_PAYMENT,createdAt:'2025-05-28 09:15'},{id:'ORD-20250525-004',title:'开发者技术交流年会入场券',subtitle:'2025 HarmonyOS 开发者峰会',price:0,quantity:1,status:OrderStatus.CANCELLED,createdAt:'2025-05-25 16:40'},{id:'ORD-20250520-005',title:'鸿蒙应用商店推广套餐',subtitle:'企业版 / 3个月',price:299900,quantity:1,status:OrderStatus.PROCESSING,createdAt:'2025-05-20 11:08'},{id:'ORD-20250515-006',title:'华为云开发者存储套餐',subtitle:'500GB / 一年',price:59900,quantity:1,status:OrderStatus.REFUNDING,createdAt:'2025-05-15 08:30'},]privatefilters:FilterItem[]=[{id:'all',label:'全部'},{id:'pending_payment',label:'待付款'},{id:'processing',label:'处理中'},{id:'shipping',label:'配送中'},{id:'completed',label:'已完成'},]privategetFilteredOrders():OrderItem[]{if(this.activeFilter==='all'){returnthis.orders}returnthis.orders.filter((order:OrderItem)=>order.status===this.activeFilter)}// 格式化金额:分 → 元privateformatPrice(cents:number):string{if(cents===0){return'免费'}return`¥${(cents/100).toFixed(2)}`}privategetStatusConfig(status:OrderStatus):StatusConfig{switch(status){caseOrderStatus.PENDING_PAYMENT:returnnewStatusConfig('待付款','#FF7A00','#FFF3E8',['立即付款','取消订单'])caseOrderStatus.PROCESSING:returnnewStatusConfig('处理中','#0A59F7','#EBF2FF',['查看详情'])caseOrderStatus.SHIPPING:returnnewStatusConfig('配送中','#00B578','#E8FFF4',['查看物流','确认收货'])caseOrderStatus.COMPLETED:returnnewStatusConfig('已完成','#00B578','#E8FFF4',['再次购买','申请退款'])caseOrderStatus.CANCELLED:returnnewStatusConfig('已取消','#999999','#F5F5F5',['删除记录'])caseOrderStatus.REFUNDING:returnnewStatusConfig('退款中','#FF4040','#FFF0F0',['查看进度'])default:returnnewStatusConfig('未知','#999999','#F5F5F5',[])}}build(){Column(){// ── 顶部标题栏 ──Row(){Text('订单管理').fontSize(18).fontWeight(FontWeight.Bold).fontColor('#1A1A1A')Blank()Text(`共${this.getFilteredOrders().length}笔`).fontSize(13).fontColor('#AAAAAA')}.width('100%').padding({left:24,right:24,top:20,bottom:12})// ── 状态筛选 Tab ──Row({space:4}){ForEach(this.filters,(f:FilterItem)=>{Text(f.label).fontSize(14).fontColor(this.activeFilter===f.id?'#0A59F7':'#666666').fontWeight(this.activeFilter===f.id?FontWeight.Bold:FontWeight.Normal).padding({left:16,right:16,top:6,bottom:6}).backgroundColor(this.activeFilter===f.id?'#EBF2FF':'transparent').borderRadius(20).onClick(()=>{this.activeFilter=f.id})})}.width('100%').padding({left:16,right:16,bottom:12})Divider().color('#EEEEEE')// ── 订单列表 ──Scroll(){Column({space:12}){ForEach(this.getFilteredOrders(),(order:OrderItem)=>{this.buildOrderCard(order)})}.padding({left:16,right:16,top:12,bottom:24})}.layoutWeight(1).scrollBar(BarState.Auto)}.width('100%').height('100%').backgroundColor('#F5F6F8')}// ── 订单卡片 ──@BuilderbuildOrderCard(order:OrderItem){// ── 卡片头部:订单号 + 状态标签 ──Row(){Text(order.id).fontSize(12).fontColor('#AAAAAA').layoutWeight(1)Text(this.getStatusConfig(order.status).label).fontSize(12).fontColor(this.getStatusConfig(order.status).color).backgroundColor(this.getStatusConfig(order.status).bgColor).padding({left:8,right:8,top:3,bottom:3}).borderRadius(4)}.width('100%').padding({left:16,right:16,top:12,bottom:10})Divider().color('#F0F0F0')// ── 卡片主体:商品信息 ──Row({space:12}){// 商品图占位Column().width(56).height(56).backgroundColor('#F0F0F0').borderRadius(6)// 商品名 + 规格Column({space:4}){Text(order.title).fontSize(14).fontColor('#1A1A1A').maxLines(2).textOverflow({overflow:TextOverflow.Ellipsis})Text(order.subtitle).fontSize(12).fontColor('#AAAAAA').maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Text(order.createdAt).fontSize(11).fontColor('#CCCCCC')}.layoutWeight(1).alignItems(HorizontalAlign.Start)// 金额 + 数量Column({space:4}){Text(this.formatPrice(order.price)).fontSize(16).fontColor('#1A1A1A').fontWeight(FontWeight.Bold)Text(`x${order.quantity}`).fontSize(12).fontColor('#AAAAAA')}.alignItems(HorizontalAlign.End)}.width('100%').padding({left:16,right:16,top:12,bottom:12})Divider().color('#F0F0F0')// ── 操作按钮区:按钮由状态的 actions 数据驱动 ──Row({space:8}){Blank()ForEach(this.getStatusConfig(order.status).actions,(action:string,index:number)=>{Button(action).height(32).padding({left:16,right:16}).fontSize(13).borderRadius(6).backgroundColor(index===0?'#0A59F7':'transparent').fontColor(index===0?'#FFFFFF':'#0A59F7').onClick(()=>{console.log(`订单${order.id}操作:${action}`)})})}.width('100%').padding({left:16,right:16,top:10,bottom:12}).backgroundColor('#FFFFFF').borderRadius(12).shadow({radius:4,color:'rgba(0,0,0,0.06)',offsetX:0,offsetY:2})}}数据驱动多态样式的三个关键点
1. 枚举定义状态,避免魔法字符串
// ❌ 不好的做法if(order.status==='shipping'){...}// ✅ 推荐的做法enumOrderStatus{SHIPPING='shipping'}if(order.status===OrderStatus.SHIPPING){...}枚举让代码可读性更高,也有 IDE 的类型提示。
2. 映射表集中管理样式配置
所有"状态 → 颜色/文字/按钮"的映射放在一个Record里统一维护。新增一个状态,只需要在映射表里加一条,渲染代码不用改。
3. 金额格式化在方法层处理
不要在模板里写(price / 100).toFixed(2),抽成一个formatPrice()方法,逻辑集中,好测试好维护。
小结
多态卡片的本质是"状态 → 样式的映射"。把这个映射关系从渲染代码里抽出来,用Record集中管理,渲染层就变得非常干净:卡片不关心"待付款是什么颜色",只管从 config 里取颜色用。
这个思路在 PC 端更有价值——PC 端的状态种类通常更多,信息密度更高,如果不做好数据和渲染的分离,代码会很快失控。