当前位置: 首页 > 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.jsqmd.com/news/1103976/

相关文章:

  • TikTok评论采集神器:3分钟零代码获取完整评论数据,轻松实现数据分析
  • 云尖信息参编《Token驱动智能经济研究报告》正式发布
  • 真机 RL 正在迎来拐点:机器人如何从「会模仿」走向「会进化」?
  • Kylix 项目追踪(三十):v3.3.0 正式发布!Body 绑定 + JWT + OpenAPI
  • 告别散热短板!TF双组份导热凝胶,高精度电子散热优选方案
  • 几句话做出个人简介网站:零基础实战全记录
  • CVE-2025-12108漏洞应急响应实战:从情报研判到深度防御的完整指南
  • AI写了60%的代码,你的研发周期却没变短?问题不在AI,在你对“写代码”的理解
  • 详情页从 2.8 秒到 380 毫秒:我只用了 2KB 代码,没动一行业务逻辑
  • 实战案例类: 从8%到35%:某电销团队提升机器人外呼接通率的实战案例复盘
  • 如何在Mac上实现优雅的桌面歌词显示:LyricsX完全指南
  • Trae界面闪烁?一招禁用GPU硬件加速轻松搞定!
  • 显存碎片怎么破,vLLM 在 ROCm 7.x 下的内存管理策略
  • 终极免费财经数据获取指南:用AKShare三步开启Python金融分析之旅
  • 微信聊天记录删了别乱找!官方全套恢复方法,无备份也能救
  • 3分钟免费解锁Microsoft 365完整功能:终极Office激活钩子工具指南
  • C++20:深入Concepts:剖析模板接口的类型与约束定义问题
  • 使用无障碍技术实现自动化脚本
  • 微信聊天记录删了怎么找回?5 套官方恢复教程,零基础一看就会
  • 生成式 AI 赋能钓鱼邮件多维特征检测与闭环防御技术研究
  • 从消费决策变化看信息透明化的商业价值
  • 第3章 为什么输入一个命令,电脑就会执行?
  • 解决Visual Studio在双击鼠标输入时总是多段选中的问题
  • 告别部署报错!OpenClaw 2.7.9 Win11超稳安装配置全流程
  • 多卡通信不卡顿,RCCL 在 AMD 集群中的调优技巧
  • 基于现成大模型搭建智能体 Agent
  • Nuke Survival Toolkit:150个专业插件打造高效合成工作流
  • HarmonyOS7 SKU 选择器为什么总写崩?规格组合和库存联动这次讲清
  • 开源版Coze:Agent三件套48小时狂揽9K星
  • 《深度学习及应用》期末考试计算题回忆版