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

HarmonyOS7 购物车看着简单最容易翻车:增删改、全选、价格计算一篇讲透

文章目录

    • 前言
    • 购物车数据模型
    • 全选与反选逻辑
    • 数量步进器
    • 滑动删除
    • 价格计算
    • 完整页面拼装
    • 一些实用建议

前言

购物车这个页面,看着简单,做起来坑真不少。增删改查、全选反选、滑动删除、实时价格计算——每个功能单独拎出来都不难,凑一块儿状态管理就容易乱。今天把鲜选商城的购物车完整跑通,顺便聊聊我踩过的那些坑。

购物车数据模型

先把数据结构定好,后面所有逻辑都围绕它转。购物车里的每一条商品需要记录商品基本信息、选中状态和购买数量。

// CartItem 数据模型@ObservedclassCartItem{id:stringgoodsId:stringname:stringcoverUrl:stringspecText:string// 规格描述,如"红色/XL"price:number// 单价(分)originalPrice:number// 原价quantity:numberchecked:booleanstock:number// 库存上限shopId:stringconstructor(partial:Partial<CartItem>){this.id=partial.id??''this.goodsId=partial.goodsId??''this.name=partial.name??''this.coverUrl=partial.coverUrl??''this.specText=partial.specText??''this.price=partial.price??0this.originalPrice=partial.originalPrice??0this.quantity=partial.quantity??1this.checked=partial.checked??falsethis.stock=partial.stock??99this.shopId=partial.shopId??''}}

有个经验:价格用"分"而不是"元"存储。浮点数加减乘除会出精度问题,比如0.1 + 0.2 = 0.30000000000000004,用整数算完再除以 100 显示,稳得多。

全选与反选逻辑

全选按钮的状态分三种:全不选、部分选、全选。我用一个计算属性来搞定:

// 全选状态:三种态getcheckedState():CheckboxState{constcheckedItems=this.cartList.filter(item=>item.checked)if(checkedItems.length===0)returnCheckboxState.Uncheckedif(checkedItems.length===this.cartList.length)returnCheckboxState.CheckedreturnCheckboxState.Indeterminate// 半选态}// 全选/取消全选toggleAll(){constnewChecked=this.checkedState!==CheckboxState.Checkedthis.cartList.forEach(item=>item.checked=newChecked)}

这里容易犯的错误是直接拿布尔值做判断,忽略了"半选"状态。鸿蒙的Checkbox支持CheckboxState.Indeterminate,用上它全选按钮才有那味儿。

数量步进器

鸿蒙自带Stepper组件,但它默认样式比较朴素,购物车里通常需要自定义。我包了一层:

@Componentstruct QuantityStepper{@Prop@Watch('onValueChange')value:number=1min:number=1max:number=99onValueChange?:(val:number)=>voidbuild(){Row(){Button('-').fontSize(16).width(28).height(28).backgroundColor('#F5F5F5').enabled(this.value>this.min).opacity(this.value>this.min?1:0.4).onClick(()=>{if(this.value>this.min)this.value--})Text(`${this.value}`).fontSize(14).width(40).textAlign(TextAlign.Center)Button('+').fontSize(16).width(28).height(28).backgroundColor('#F5F5F5').enabled(this.value<this.max).opacity(this.value<this.max?1:0.4).onClick(()=>{if(this.value<this.max)this.value++})}}}

@Watch装饰器是关键——外部传入onValueChange回调,数量变了自动通知父组件更新购物车数据和价格。

滑动删除

鸿蒙的ListItem配合swipeAction属性,滑动删除几行代码就搞定:

ForEach(this.cartList,(item:CartItem)=>{ListItem(){CartItemCard({item:item})}.swipeAction({end:this.buildDeleteAction(item)})},(item:CartItem)=>item.id)// 滑出来的删除按钮@BuilderbuildDeleteAction(item:CartItem){Row(){Button('删除').backgroundColor('#FF4D4F').fontColor('#FFFFFF').height('100%').width(80).onClick(()=>{this.removeItem(item.id)})}}

批量删除也不复杂,底部栏加个"删除"按钮,点击时把checked === true的全干掉。记得加个确认弹窗,不然用户误操作就炸了。

价格计算

价格计算我抽成独立方法,所有需要总价的地方都调它:

// 计算选中商品总价calcSelectedTotal():PriceBreakdown{constselected=this.cartList.filter(item=>item.checked)consttotalOriginal=selected.reduce((sum,item)=>sum+item.originalPrice*item.quantity,0)consttotalSale=selected.reduce((sum,item)=>sum+item.price*item.quantity,0)constdiscount=totalOriginal-totalSaleconstcount=selected.reduce((sum,item)=>sum+item.quantity,0)return{totalAmount:totalSale,// 实付金额totalOriginal:totalOriginal,discount:discount,// 优惠金额selectedCount:count}}

这里有个细节:@Observed修饰的CartItem,内部属性变化能触发 UI 刷新。但如果你的数组很深(比如嵌套了店铺分组),得注意@Observed只监听第一层属性,深层嵌套需要用@ObjectLink传递。

完整页面拼装

把上面的模块拼到一起,页面结构大概是这样的:

@Componentstruct CartPage{@StatecartList:CartItem[]=[]@StatepriceBreakdown:PriceBreakdown=newPriceBreakdown()build(){Column(){// 顶部导航NavBar({title:'购物车'})// 商品列表(按店铺分组)List(){ForEach(this.groupByShop(),(group:ShopGroup)=>{ListItemGroup({header:this.buildShopHeader(group)})ForEach(group.items,(item:CartItem)=>{ListItem(){CartItemCard({item:item})}.swipeAction({end:this.buildDeleteAction(item)})})})}.width('100%').layoutWeight(1).scrollBar(BarState.Off)// 底部结算栏BottomBar({checkedState:this.checkedState,onToggleAll:()=>this.toggleAll(),breakdown:this.priceBreakdown,onCheckout:()=>this.goCheckout()})}.width('100%').height('100%').backgroundColor('#F5F5F5')}}

购物车页面状态比较多,我一开始想着全用@State管理,结果发现状态之间互相影响——改了数量要更新总价,改了选中要更新底部栏。后来改成用@Observed+@ObjectLink的方式,每个CartItem自己管理自己的状态,总价通过计算方法实时算,清爽很多。

一些实用建议

空状态别忘了。购物车为空的时候显示个占位图 + "去逛逛"按钮,别让用户看到一个空白页面。

本地缓存很重要。用户加购了商品、改过数量,突然杀进程再进来发现购物车空了,体验极差。用Preferences或者relationalStore做个本地持久化,数据回来体验好很多。

步进器的库存校验。用户点"+"的时候不仅要判断不超过max,还要跟库存stock对比。库存不足的时候弹个 Toast 提示,比让用户到结算页才发现买不了强。

购物车这个页面就是细节多,每个小功能都不难,但状态流转一多就容易出 bug。我的建议是先把数据模型和状态流转图画清楚,再动手写代码,效率反而更高。下一篇我们搞 SKU 选择器,那个规格矩阵算法才是真的烧脑,不过搞懂了会觉得挺有意思。

http://www.gsyq.cn/news/1615949.html

相关文章:

  • 云尖信息参编《Token驱动智能经济研究报告》正式发布
  • 恶意软件窃取 Chrome 会话 Cookie 的攻击机制与防御研究
  • CVE-2025-12108漏洞应急响应实战:从情报研判到深度防御的完整指南
  • AI写了60%的代码,你的研发周期却没变短?问题不在AI,在你对“写代码”的理解
  • 如何在Mac上实现优雅的桌面歌词显示:LyricsX完全指南
  • Trae界面闪烁?一招禁用GPU硬件加速轻松搞定!
  • 微信聊天记录删了别乱找!官方全套恢复方法,无备份也能救
  • 使用无障碍技术实现自动化脚本
  • 告别部署报错!OpenClaw 2.7.9 Win11超稳安装配置全流程
  • Nuke Survival Toolkit:150个专业插件打造高效合成工作流
  • 从消费决策变化看信息透明化的商业价值
  • 分层实验智能体(HExA):基于上下文自演化物理推理智能体框架
  • 如何选择靠谱的装修公司?从泰美空间设计合作案例看筛选标准
  • SQL优化_监管指标计算性能全维度优化方案
  • GEO 是什么?从 “关键词匹配” 到 “AI 信任” 的营销革命
  • 三明 开店扫码点餐系统到底要花多少钱?别被坑了才知道!
  • 总部-门店素材协同:从统一上传到一键调用的落地指南
  • Wu.CommTool工业通信调试工具技术实现深度解析:基于C WPF的模块化架构设计
  • 基于ArcGIS Pro、R、INVEST等多技术融合下生态系统服务权衡与协同动态分析实践应用
  • 强烈推荐一个基于 .NET 8 开发的企业级 OAuth 2.0 / OpenID Connect 认证框架
  • 2026数字化转型新锚点:4SAPI企业级大模型API中转网关赋能商业级AI规模化落地
  • 3PEAK思瑞浦 TPA135A2-S5TR-S SOT23-5 电流信号检测放大器
  • 论文 deadline 只剩一周?笔墨 AI 流程化辅助,快速搭好完整论文框架
  • 数字IC功耗来源
  • 精准避坑|OpenClaw 安装路径、解压、启动全套技巧
  • 16 亿美元去哪了?我们追踪了一个 TRON 资金盘的完整链上资金网络
  • Windows 11系统镜像深度精简技术:tiny11builder架构解析与性能优化指南
  • 买二手电脑怕被坑?用鲁大师做这5项检查,卖家都不敢糊弄你
  • DevSecOps 视角下语音钓鱼(Vishing)通信安全全链路防护研究
  • CCF-GESP计算机学会等级考试2026年6月三级C++T2 字符转换