尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
HarmonyOS7餐厅点餐页面实战:分类切换与购物车汇总
文章目录前言菜品数据同时服务列表和购物车分类切换只是换视图不改菜单数据购物车汇总全靠派生计算数量加减要守住下限底部购物车栏由 totalCount 控制显示真实点餐页还差哪些东西完整代码最后总结前言点餐页的状态流很典型用户切分类列表要变用户加减菜品底部购物车要变购物车为空时底部栏还要消失。这个案例没有做复杂动画也没有接接口反而方便看清楚 ArkUI 页面里“主数据 派生数据 交互更新”的关系。当前实现是一个简化的餐厅点餐页。它支持分类切换、菜品数量加减、总价和总件数汇总。需要注意的是showCart虽然会在点击购物车图标时切换但源码里没有根据它渲染购物车明细弹层去结算按钮也没有提交订单逻辑。菜品数据同时服务列表和购物车每个菜品用MenuItem_n29l表示。这个模型里最关键的字段不是名字或价格而是count。interfaceMenuItem_n29l{id:numbername:stringprice:numbercategory:stringemoji:stringdesc:stringcount:number}category决定菜品出现在哪个分类下count决定它是否进入购物车。也就是说页面没有单独维护一个购物车数组而是把“购物车状态”压回到每个菜品自己的count字段里。对这种小型点餐页来说这样写很直观。分类切换只是换视图不改菜单数据页面顶部的分类保存在categories当前选中的分类保存在selectedCat。Statecategories:string[][推荐,招牌,主食,小食,饮品,甜品]StateselectedCat:string推荐点击某个分类时只是修改selectedCat.onClick(()this.selectedCatcat)真正展示在列表里的数据来自filteredgettergetfiltered():MenuItem_n29l[]{returnthis.menuItems.filter(mm.categorythis.selectedCat)}这个设计挺干净。menuItems仍然保存完整菜单分类切换只是改变观察角度不会把数组删来删去。用户在“推荐”里加了红烧肉再切到“饮品”红烧肉的count仍然留在原数据里底部总价也不会丢。购物车汇总全靠派生计算源码里有三个和购物车有关的 gettergetcartItems():MenuItem_n29l[]{returnthis.menuItems.filter(mm.count0)}gettotalPrice():number{returnthis.menuItems.reduce((s,m)sm.price*m.count,0)}gettotalCount():number{returnthis.menuItems.reduce((s,m)sm.count,0)}cartItems当前没有被 UI 使用但它已经表达了购物车明细该怎么取只要count 0就算入购物车。底部栏用的是totalPrice和totalCount所以任何菜品数量变化都会立刻反映到总价和角标数量上。这里的好处是没有重复状态。页面不需要同时维护menuItems、cartItems、totalPrice三份数据只要维护菜品数量其他结果实时算出来就行。小数据量下这种写法够用如果菜单有几百上千项再考虑缓存或拆分计算也不迟。数量加减要守住下限changeCount()是整页唯一真正修改菜品数量的函数。它接收菜品id和变化量delta然后用map()更新目标菜品。changeCount(id:number,delta:number){this.menuItemsthis.menuItems.map(m{if(m.idid){m.countMath.max(0,m.countdelta)}returnm})}Math.max(0, m.count delta)防止数量减成负数。列表里也做了一个 UI 约束只有item.count 0时才显示减号和数量默认只露出加号。if(item.count0){Text(−).onClick(()this.changeCount(item.id,-1))Text(${item.count})}Text().onClick(()this.changeCount(item.id,1))这段逻辑适合教学但从工程习惯上看还可以更稳一点。当前map()里直接修改了m.count再返回原对象。更彻底的不可变写法是命中目标时返回{ ...m, count: Math.max(0, m.count delta) }。页面现在能正常表达状态刷新但复杂项目里我会避免原地改对象。底部购物车栏由totalCount控制显示底部栏外面包了一层条件渲染if(this.totalCount0){Row(){Text(${this.totalCount})Text(¥${this.totalPrice})Button(去结算)}}这个细节很符合真实点餐体验没选菜时不显示购物车栏选了菜以后才出现。购物车角标显示总件数总价用所有菜品的price * count累加得到。不过源码里点击购物车图标只是执行this.showCart !this.showCart没有任何地方根据showCart显示明细。也就是说当前页面底部栏只是一个入口雏形。如果要补完整可以用Stack再叠一个购物车面板数据直接使用已经写好的cartItemsgetter。真实点餐页还差哪些东西如果把这个案例继续做成可上线的点餐功能我会先处理三件事购物车明细、规格选择、结算校验。很多菜品不只是数量还有规格、口味、加料这时count放在菜品上就不够了需要引入真正的购物车项模型。结算前也要重新校验价格、库存和配送状态不能完全相信前端当前显示的总价。这个示例的价值在于把最小闭环写清楚了分类只控制列表视图数量才是购物车状态底部汇总全部从菜单数据推导出来。理解这条线再去加规格弹窗或订单页代码不容易散。完整代码下面保留案例完整代码方便你直接对照学习、复制和重构。import{router}fromkit.ArkUIinterfaceMenuItem_n29l{id:numbername:stringprice:numbercategory:stringemoji:stringdesc:stringcount:number}EntryComponentstruct RestaurantOrderingPage{Statecategories:string[][推荐,招牌,主食,小食,饮品,甜品]StateselectedCat:string推荐StatemenuItems:MenuItem_n29l[][{id:1,name:红烧肉套餐,price:38,category:推荐,emoji:,desc:秘制红烧肉配米饭肥而不腻,count:0},{id:2,name:夫妻肺片,price:26,category:推荐,emoji:,desc:传统川味麻辣鲜香,count:0},{id:3,name:招牌炸鸡,price:32,category:招牌,emoji:,desc:外酥内嫩秘制腌料,count:0},{id:4,name:番茄鸡蛋面,price:18,category:主食,emoji:,desc:手工拉面汤头浓郁,count:0},{id:5,name:炸薯条,price:12,category:小食,emoji:,desc:现炸现卖外脆里软,count:0},{id:6,name:柠檬水,price:10,category:饮品,emoji:,desc:新鲜柠檬微甜爽口,count:0},{id:7,name:芒果冰淇淋,price:16,category:甜品,emoji:,desc:进口芒果浓郁香甜,count:0},{id:8,name:特色宫保鸡丁,price:34,category:招牌,emoji:,desc:经典川菜花生鸡丁,count:0},]StateshowCart:booleanfalsegetfiltered():MenuItem_n29l[]{returnthis.menuItems.filter(mm.categorythis.selectedCat)}getcartItems():MenuItem_n29l[]{returnthis.menuItems.filter(mm.count0)}gettotalPrice():number{returnthis.menuItems.reduce((s,m)sm.price*m.count,0)}gettotalCount():number{returnthis.menuItems.reduce((s,m)sm.count,0)}changeCount(id:number,delta:number){this.menuItemsthis.menuItems.map(m{if(m.idid){m.countMath.max(0,m.countdelta)}returnm})}build(){Stack({alignContent:Alignment.Bottom}){Column(){Row(){Image($r(app.media.ic_back)).width(24).height(24).onClick(()router.back())Column(){Text(饭点小厨).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#333)Text( 预计配送 30 分钟).fontSize(11).fontColor(#888)}.layoutWeight(1).alignItems(HorizontalAlign.Center)Text().fontSize(20)}.width(100%).padding({left:16,right:16,top:12,bottom:12}).backgroundColor(#FFF)// Category tabsScroll(){Row(){ForEach(this.categories,(cat:string){Text(cat).fontSize(13).fontColor(this.selectedCatcat?#FF6000:#666).borderWidth({bottom:this.selectedCatcat?2:0}).borderColor(#FF6000).padding({left:16,right:16,bottom:8}).onClick(()this.selectedCatcat)})}.padding({left:8})}.scrollable(ScrollDirection.Horizontal).backgroundColor(#FFF).margin({bottom:8})// Menu listList({space:1}){ForEach(this.filtered,(item:MenuItem_n29l){ListItem(){Row(){Text(item.emoji).fontSize(50).width(70).textAlign(TextAlign.Center)Column(){Text(item.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(#333)Text(item.desc).fontSize(12).fontColor(#888).margin({top:4}).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis})Row(){Text(\¥\).fontSize(12).fontColor(#FF6000)Text(\\${item.price}\).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#FF6000)}.margin({top:6})}.layoutWeight(1).alignItems(HorizontalAlign.Start).padding({left:8})// Qty controlRow(){if(item.count0){Text(−).fontSize(18).width(28).height(28).borderRadius(14).backgroundColor(#FF6000).fontColor(#FFF).textAlign(TextAlign.Center).lineHeight(28).onClick(()this.changeCount(item.id,-1))Text(\\${item.count}\).fontSize(14).fontWeight(FontWeight.Bold).width(28).textAlign(TextAlign.Center)}Text().fontSize(18).width(28).height(28).borderRadius(14).backgroundColor(#FF6000).fontColor(#FFF).textAlign(TextAlign.Center).lineHeight(28).onClick(()this.changeCount(item.id,1))}.alignItems(VerticalAlign.Center).padding({right:4})}.width(100%).backgroundColor(#FFF).padding({top:12,bottom:12}).border({width:{bottom:1},color:#F5F5F5})}})}.layoutWeight(1)}.width(100%).height(100%).backgroundColor(#F8F8F8)// Cart barif(this.totalCount0){Row(){Row(){Text(\\${this.totalCount}\).fontSize(13).fontColor(#FFF).width(22).height(22).borderRadius(11).backgroundColor(#FF4757).textAlign(TextAlign.Center).lineHeight(22)Text().fontSize(24).margin({left:8})}.onClick(()this.showCart!this.showCart)Text(\¥\${this.totalPrice}\).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#FFF).layoutWeight(1).margin({left:12})Button(去结算).backgroundColor(#FFF).fontColor(#FF6000).borderRadius(24).padding({left:20,right:20,top:8,bottom:8})}.width(calc(100% - 32vp)).backgroundColor(#FF6000).borderRadius(32).padding({left:16,right:8,top:8,bottom:8}).margin({bottom:20}).shadow({radius:12,color:#FF600040,offsetY:4})}}}}最后总结餐厅点餐页面 这个案例并不靠复杂技巧取胜它更像一个结构扎实、逻辑完整的业务页面样板。把这篇文章里的状态设计、函数组织和页面分层吃透之后你再去写同类型功能速度会明显快很多。
RELATED

相关推荐

STM32F103C8T6 Zephyr RTOS环境配置完整指南

STM32F103C8T6 Zephyr RTOS环境配置完整指南

如果你正在使用STM32F103C8T6这种"蓝莓派"最小系统板进行嵌入式开发,却对Zephyr RTOS的环境配置感到头疼,那么这篇文章正是为你准备的。很多开发者误以为Zephyr的环境配置只是简单的PATH设置,实际上它涉及工具链管理、Python虚拟环…

📅 2026/11/9 21:55:54
红米K40定制内核编译指南:修复1%电量bug与集成KernelSU

红米K40定制内核编译指南:修复1%电量bug与集成KernelSU

如果你正在使用红米K40(代号alioth)并且遇到了电量显示卡在1%的问题,或者想要一个支持KernelSU的定制内核,那么今天的内容绝对值得你花时间了解。这个困扰很多用户的"1%电量bug"并非硬件故障,而是内核层面的…

📅 2026/11/9 21:55:54
鸿蒙端YOLOv8量化部署实战指南

鸿蒙端YOLOv8量化部署实战指南

在鸿蒙端实现YOLOv8模型的量化部署,核心在于模型量化与端侧推理框架集成。主要路径包括使用NCNN框架进行原生部署,或通过PyTorch Mobile进行适配部署。以下是具体步骤与方案对比。1. 核心部署路径对比部署路径核心步骤优点缺点/注意事项NCNN框架 (推荐)1…

📅 2026/9/12 18:34:16
MORE NEWS

更多资讯

📰

在北京如何报考电工证全流程办理指南

之前考的证在外省能不能转过来?北京电工证报考真实经历全解析 之前考的证在外省能不能转过来?这是我接到最多的问题之一,尤其是在北京从事电工相关工作的朋友,经常因为证书归属地不同而遇到各种限制。今天我就结合自己的真实经历,聊聊在北京如何报考电工…

📰

余杭 电工证合格标准与通过率

余杭电工证报名全攻略:官方报名入口在哪?怎么避坑? 你是不是正在为找不到工作发愁?特种作业证就是你的“通行证”,特别是 余杭电工证 ,没它连门都进不去。现在你最关心的问题就是: 官方报名入口…

📰

赤峰安监局办理电工证含金量有多高?别再等了,上岗前必须搞懂!

赤峰安监局办理电工证含金量有多高?别再等了,上岗前必须搞懂! 你有没有想过,为什么现在连找个电工的工作都要先有证?不是说没证干不了,而是 没证根本进不了门 。特别是像赤峰这种地方, 急需特种作业证才能上岗找工作…

📰

电工证没基础难吗 官方回应实操考试不难但要方法

电工证没基础难吗 官方回应实操考试不难但要方法 实操考试心里没底怕挂科?这是不少零基础考生最真实的担忧。尤其是电工证这种涉及电气安全、操作规范的证书,很多人一听到“实操”二字就紧张。但根据 河南省安全技术防范行业协会…

📰

合肥电工证复审地址在哪?含金量够不够找工作用?

合肥电工证复审地址在哪?含金量够不够找工作用? 你是不是正被“急需特种作业证才能上岗找工作”这个问题逼得焦头烂额?电工证作为特种作业证的一种,含金量高不高,能不能在合肥找到满意的工作,全靠这张证说话。现在合肥的电工证复审地址你还没搞清楚,那…

📰

没有电工证参与接线怎么搞?郑州报考避坑指南教你应对实操考试心慌

没有电工证参与接线怎么搞?郑州报考避坑指南教你应对实操考试心慌 实操考试心里没底怕挂科,这几乎是每个准备考电工证的学员心里的疙瘩。尤其是像你这样, 没有电工证参与接线 的新人,更担心一上手就出错。别急, 郑州报考避坑指南…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬