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

HarmonyOS7 SKU 选择器为什么总写崩?规格组合和库存联动这次讲清

文章目录

    • 前言
    • 什么是 SKU
    • 规格矩阵的核心算法
    • 选择联动
    • 半模态 SKU 选择器
    • 使用方式
    • 踩过的坑

前言

SKU 选择器应该是电商 App 里算法含量最高的组件之一了。规格排列组合、库存联动、无库存自动置灰——这几个需求放一块儿,第一次写的时候我折腾了两天。今天把思路彻底捋清楚,保证你看完能自己写出来。

什么是 SKU

先对齐概念。SPU 是标准化产品单元,比如"iPhone 16"。SKU 是最小库存单元,比如"iPhone 16 / 黑色 / 256GB"。一个 SPU 下面可能有几十种 SKU 组合,每种 SKU 的价格和库存都不一样。

数据结构我这样设计:

// 规格值interfaceSpecValue{id:stringname:string// "黑色"、"白色"、"XL"}// 规格组interfaceSpecGroup{id:stringname:string// "颜色"、"尺码"values:SpecValue[]}// SKU 条目interfaceSkuItem{id:stringspecIds:string[]// 对应每个规格组的选中值 id,如 ["color_black", "size_xl"]price:numberstock:numberimageUrl:string}// 完整的 SKU 数据interfaceSkuData{specGroups:SpecGroup[]skuList:SkuItem[]}

关键是specIds这个数组,它定义了每个 SKU 对应的规格组合。后面判断某个规格组合有没有库存,就靠它。

规格矩阵的核心算法

这是整个 SKU 选择器最难的部分。用户在"颜色"里选了"黑色",我需要判断"尺码"里哪些选项还有货。比如"黑色+XL"有货,但"黑色+XXL"没货,那 XXL 就得置灰。

核心思路:对于每个未选的规格组,遍历它的每个规格值,检查"当前已选规格 + 这个规格值"能否组成一个有效 SKU(库存>0)

// 判断某个规格组合是否可选isSpecValueAvailable(currentSelected:Map<string,string>,// groupId -> valueIdgroupId:string,valueId:string):boolean{// 构建一个临时的选择状态,把当前要判断的规格值也加进去consttestSelected=newMap(currentSelected)testSelected.set(groupId,valueId)// 遍历所有 SKU,看有没有匹配的且有库存的returnthis.skuData.skuList.some(sku=>{if(sku.stock<=0)returnfalse// 检查这个 SKU 是否包含所有已选的规格值for(const[gid,vid]oftestSelected.entries()){constgroup=this.skuData.specGroups.find(g=>g.id===gid)if(!group)continueconstidx=this.skuData.specGroups.indexOf(group)if(sku.specIds[idx]!==vid)returnfalse}returntrue})}

这段代码跑通了你会发现一个问题:规格组很多的时候性能有点慢。实际上三到四个规格组、每组十来个规格值的情况完全够用,因为总计算量也就几百次遍历。但如果你的商品规格特别多(比如定制类商品),可以考虑提前构建一个"规格组合->SKU"的映射表来加速。

选择联动

用户选完所有规格后,要联动展示价格、库存和商品图。逻辑很简单——找到完全匹配的那个 SKU:

// 找到匹配的 SKUfindMatchedSku():SkuItem|null{constallSelected=this.selectedMap.size===this.skuData.specGroups.lengthif(!allSelected)returnnullreturnthis.skuData.skuList.find(sku=>{returnthis.skuData.specGroups.every((group,idx)=>{returnsku.specIds[idx]===this.selectedMap.get(group.id)})})??null}// 选中后联动onSpecSelect(groupId:string,valueId:string){// 切换选中if(this.selectedMap.get(groupId)===valueId){this.selectedMap.delete(groupId)// 取消选择}else{this.selectedMap.set(groupId,valueId)}// 联动更新constmatched=this.findMatchedSku()if(matched){this.currentPrice=matched.pricethis.currentStock=matched.stockthis.currentImage=matched.imageUrl}}

半模态 SKU 选择器

UI 层面用bindSheet做半模态弹出,体验跟淘宝、京东一样:

@Componentstruct SkuSelector{@PropskuData:SkuData@StateselectedMap:Map<string,string>=newMap()@StatecurrentImage:string=''@StatecurrentPrice:number=0@Statequantity:number=1onConfirm?:(skuId:string,qty:number)=>voidbuild(){Column(){// 顶部商品信息区Row(){Image(this.currentImage||this.skuData.skuList[0]?.imageUrl).width(100).height(100).borderRadius(8)Column(){Text(`¥${(this.currentPrice/100).toFixed(2)}`).fontSize(20).fontColor('#FF4D4F').fontWeight(FontWeight.Bold)Text(`库存:${this.currentStock}`).fontSize(12).fontColor('#999').margin({top:4})Text(`已选:${this.getSelectedText()}`).fontSize(12).fontColor('#666').margin({top:4})}.alignItems(HorizontalAlign.Start).margin({left:12})}.width('100%').padding(16)Divider()// 规格选择区Scroll(){Column(){ForEach(this.skuData.specGroups,(group:SpecGroup)=>{Column(){Text(group.name).fontSize(14).fontWeight(FontWeight.Medium).margin({bottom:8})Flex({wrap:FlexWrap.Wrap}){ForEach(group.values,(val:SpecValue)=>{Text(val.name).fontSize(13).padding({left:14,right:14,top:6,bottom:6}).borderRadius(16).backgroundColor(this.getSpecBg(group.id,val.id)).fontColor(this.getSpecColor(group.id,val.id)).margin({right:8,bottom:8}).onClick(()=>this.onSpecSelect(group.id,val.id))})}}.width('100%').padding({left:16,right:16,top:12})})// 数量选择Row(){Text('数量').fontSize(14)Blank()QuantityStepper({value:this.quantity,max:this.currentStock})}.width('100%').padding(16)}}.layoutWeight(1)// 确认按钮Button('确定').width('92%').height(44).borderRadius(22).backgroundColor('#FF4D4F').fontColor('#FFFFFF').margin({bottom:20}).onClick(()=>this.handleConfirm())}.width('100%')}}

规格按钮的样式根据选中状态和无库存状态变化,这部分抽成方法读起来更清晰:

getSpecBg(groupId:string,valueId:string):ResourceColor{if(this.selectedMap.get(groupId)===valueId)return'#FFE8E8'if(!this.isSpecValueAvailable(this.selectedMap,groupId,valueId))return'#F5F5F5'return'#FFFFFF'}getSpecColor(groupId:string,valueId:string):ResourceColor{if(this.selectedMap.get(groupId)===valueId)return'#FF4D4F'if(!this.isSpecValueAvailable(this.selectedMap,groupId,valueId))return'#CCCCCC'return'#333333'}

使用方式

商品详情页弹出 SKU 选择器,用bindSheet一行搞定:

Image($r('app.media.btn_buy')).onClick(()=>{this.showSkuSheet=true}).bindSheet($$this.showSkuSheet,SkuSelector({skuData:this.skuData,onConfirm:(skuId,qty)=>{this.addToCart(skuId,qty)}}),{height:SheetSize.FIT_CONTENT,dragBar:true})

踩过的坑

取消选中导致已选规格无效的问题。用户在"颜色"里从"黑色"切换到"白色",这时候之前选的"XL"可能没库存了。切换规格后要重新校验其他规格组里的选中值,如果失效就自动清空。

只有一组规格的时候。别写死两层嵌套的逻辑,用ForEach动态渲染规格组,代码通用性好很多。

SKU 选择器写好了是个很通用的组件,稍微改改能用在各种需要"多维规格选择"的场景。下一篇我们进入订单确认页,那个页面的难点在优惠券计算逻辑,比 SKU 好搞多了。

http://www.jsqmd.com/news/1103948/

相关文章:

  • 开源版Coze:Agent三件套48小时狂揽9K星
  • 《深度学习及应用》期末考试计算题回忆版
  • LLM API架构瘦身:客户端策略固化实现请求链路‘归零’
  • 如何选择靠谱的装修公司?从泰美空间设计合作案例看筛选标准
  • 6DoF运动追踪:IIM-42652 IMU与STM32F302R8实战指南
  • 分层实验智能体(HExA):基于上下文自演化物理推理智能体框架
  • 【2026最新】Audacity免费版中文版下载安装使用全纪录:从打开到导出,一篇搞定
  • 2026年,靠谱的定量质控菌株供应商究竟是谁?
  • SQL优化_监管指标计算性能全维度优化方案
  • GEO 是什么?从 “关键词匹配” 到 “AI 信任” 的营销革命
  • 总部-门店素材协同:从统一上传到一键调用的落地指南
  • 三明 开店扫码点餐系统到底要花多少钱?别被坑了才知道!
  • 最小二乘法
  • 2026 网络安全零基础学习路线,保姆级实操教程可直接照搬
  • UE4SS:解锁虚幻引擎游戏的终极扩展工具 - 从零开始掌握游戏修改与Mod开发
  • 五、Prometheus安装nginx
  • 成都GEO优化公司选哪家?2026本地优质服务商推荐
  • 快速分子对接工具QuickVina 2:20倍加速的终极安装指南
  • 机器人技术:从自动化到具身智能,机器正走进现实
  • CCE Cash SOL混币实操:SOL跨链交换混币为BNB只需2-5分钟
  • 强烈推荐一个基于 .NET 8 开发的企业级 OAuth 2.0 / OpenID Connect 认证框架
  • 包装纸箱字符缺失、模糊、不清晰、颜色差异大智能检测方案 —— 昂德高 KEYTU 包装纸箱首件对版机落地应用价值分析
  • 企微SILK语音解析的工程痛点:流式解码管道、内存穿透与ASR异步转写架构
  • Wu.CommTool工业通信调试工具技术实现深度解析:基于C WPF的模块化架构设计
  • 2026数字化转型新锚点:4SAPI企业级大模型API中转网关赋能商业级AI规模化落地
  • 美团Longcat团队推VitaBench 2.0:揭示AI成“高情商助理”的短板与挑战
  • 基于ArcGIS Pro、R、INVEST等多技术融合下生态系统服务权衡与协同动态分析实践应用
  • LV3296与STM32F215ZG高精度信号采集系统设计
  • 个人网站每年盈利多少算是好网站?
  • 同一个App,报价5万到50万,到底差在哪?