尧图网络 高端网站定制 · 原创设计
免费咨询热线
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/9/13 1:05:10
红米K40定制内核编译指南:修复1%电量bug与集成KernelSU

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

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

📅 2026/9/7 22:46:07
鸿蒙端YOLOv8量化部署实战指南

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

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

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

更多资讯

📰

克制的美学:为什么我的界面永远只有一个主行动点

克制的美学:为什么我的界面永远只有一个主行动点在很多商业软件或后台管理系统中,我们经常看到一个页面上塞满了花花绿绿的按钮: 【立即保存】、【一键分享】、【导出PDF】、【切换排版】、【历史对比】、【升级会员】、【进入广场】……每一…

📰

秋季新出三款小型创意模型速评:性价比与手账画风实测

秋季新出三款小型创意模型速评:性价比与手账画风实测在做轻量 AI 产品开发时,我们最关注的往往不是各大厂商动辄几千亿参数的“全能旗舰大模型”,而是那些参数量在 1B 到 8B 之间、响应延迟在毫秒级、单次调用成本几乎可以忽略不计的小型端侧…

📰

手账字体子集化流水线:字蛛与 pyftsubset 实战

手账字体子集化流水线:字蛛与 pyftsubset 实战在做中文字体排版时,前端工程师面临的最大矛盾是:既想要极具手作感的高级手写字体,又无法承受动辄 10MB~20MB 的完整中文字库对网页首屏加载速度的毁灭性打击。 很多开发者为了图省事…

📰

Python数据可视化:Plotly交互式图表实战指南

1. 为什么选择Plotly进行数据可视化在数据分析和可视化的世界里,Matplotlib曾经是Python生态中的绝对主流,但近年来交互式图表的需求日益增长。Plotly作为一个开源的数据可视化库,正在迅速崛起并改变这一格局。我第一次接触Plotly是在一个需要…

📰

多级蒙特卡洛方法在电力系统连锁故障风险评估中的应用

1. 项目概述在电力系统运行中,连锁故障风险评估一直是行业痛点。传统方法往往只考虑一次系统元件故障,而忽略了二次系统(如保护装置、自动控制设备)的影响。多级蒙特卡洛方法通过分层抽样技术,能够有效评估计及二次系统…

📰

Semantic Kernel Python Agent 快速上手:从 Chat Completion 到多 Agent 编排的完整实战指南

Semantic Kernel Python Agent 快速上手:从 Chat Completion 到多 Agent 编排的完整实战指南 【免费下载链接】semantic-kernel Integrate cutting-edge LLM technology quickly and easily into your apps 项目地址: https://gitcode.com/GitHub_Trending/se/sem…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

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

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

📞 💬