前言
做电商项目呢,离不开多规格商品,SKU 也是弄了许久才搞出来,主要是多层级的联动关系,用ID和库存来判断是否是按钮禁止状态
下面就放下代码:
以封装的小程序为例:
WXML:

<view class="sku-box" wx:if="{{cpSkuTree.length}}"> <view class="sku-row" wx:for="{{cpSkuTree}}" wx:key="{{index}}"> <view class="sku-title">{{item.k}}</view> <view class="sku-wrap flex-row"> <view class="sku-item {{iitem.disabled ? 'disabled': ''}} {{iitem.selected ? 'selected': ''}}" wx:for="{{item.v}}" wx:for-item="iitem" wx:for-index="iindex" wx:key="{{iindex}}" data-index="{{index}}" data-iindex="{{iindex}}" data-k="{{item}}" data-value="{{iitem}}" catchtap="selectSku">{{iitem.name}}</view> </view> </view> </view>
JS:

const computedBehavior = require('miniprogram-computed') Component({ behaviors: [computedBehavior], properties: { skuTree: { type: Array, value: [], observer: function (newVal) { let cpNewVal = JSON.parse(JSON.stringify(newVal)) cpNewVal.forEach( row => { row.v.forEach(item => { Object.assign(item, { selected: false, disabled: false }) }) }) this.setData({ cpSkuTree: cpNewVal }) this.judgeAllItem() } }, skuList: { type: Array, value: [] } }, data: { cpSkuTree: [], // 选择的 sku 组合 selectedSku: { } }, computed: { }, methods: { // 点击sku按钮 selectSku (e) { const k = e.currentTarget.dataset.k const index = e.currentTarget.dataset.index const value = e.currentTarget.dataset.value const iindex = e.currentTarget.dataset.iindex value.disabled = true if (this.data.cpSkuTree[index].v[iindex].disabled) { return } // 勾选或者反选 const key = `selectedSku.${k.ks}` if (!this.data.cpSkuTree[index].v[iindex].selected) { // 勾选把值记住 this.setData({ [key]: value.id }) } else { // 反选把值删掉 this.setData({ [key]: '' }) } this.setData({ [`cpSkuTree[${index}].v[${iindex}].selected`]: !this.data.cpSkuTree[index].v[iindex].selected }) this.cancelOption(index, value) this.judgeAllItem() this.changePic(index, iindex) if (this.isAllSelected()) { const skuData = this.getSkuComb() this.triggerEvent('selectChange', skuData) } else { this.triggerEvent('selectChange', null) } }, /** * 取消同一组所有选项 */ cancelOption (index, value) { let rowList = this.data.cpSkuTree[index].v for (let i = 0; i < rowList.length; i++) { if (rowList[i].id != value.id) { this.setData({ [`cpSkuTree[${index}].v[${i}].selected`]: false }) } } }, /** * 循环判断是否可选 */ judgeAllItem () { let tree = this.data.cpSkuTree for (let i = 0; i < tree.length; i++) { let v = tree[i].v for (let j = 0; j < v.length; j++) { if (this.isSkuChoosable(tree[i].ks, v[j].id)) { this.setData({ [`cpSkuTree[${i}].v[${j}].disabled`]: false }) } else { this.setData({ [`cpSkuTree[${i}].v[${j}].disabled`]: true }) } } } this.getSelectedText() }, /** * 判断可选项的库存 */ isSkuChoosable (ks, id) { const list = this.data.skuList const selectedSku = this.data.selectedSku // 先假设已经选中剩余按钮 let matchedSku = Object.assign({}, selectedSku, { [ks]: id }) // 将matchedSku中有效的key提取 let skusToCheck = Object.keys(matchedSku).filter( skuKey => matchedSku[skuKey] != '' ) // 有效key值匹配有多少sku let filterSku = list.filter(sku => skusToCheck.every( skuKey => matchedSku[skuKey] == sku[skuKey] ) ) // 假设按钮包含所有sku的库存数 let stock = filterSku.reduce((total, sku) => { total += sku.stock_num return total }, 0) return stock > 0 }, /** * 判断是否选完所有规格 */ isAllSelected () { let selectedSku = this.data.selectedSku let selected = Object.keys(selectedSku).filter( skuKey => selectedSku[skuKey] !== '' ) return selected.length === this.data.cpSkuTree.length }, /** * 获得已经确定的组合 */ getSkuComb () { let selectedSku = this.data.selectedSku let list = this.data.skuList let skusToCheck = [] this.data.cpSkuTree.forEach(item => { skusToCheck.push(item.ks) }) let filteredSku = list.filter(sku => ( skusToCheck.every(skuKey => selectedSku[skuKey] == sku[skuKey]) ) ) return filteredSku[0] }, /** * 修改图片 */ changePic (index, iindex) { if (index == 0) { this.triggerEvent('changePic', this.data.cpSkuTree[index].v[iindex].picUrl) } }, // 选择属性文字 getSelectedText () { let selectedSku = this.data.selectedSku let text = '' Object.keys(selectedSku).forEach(skuKey => { const id = selectedSku[skuKey] const tree = this.data.cpSkuTree for (let i = 0; i < tree.length; i++) { const v = tree[i].v for (let j = 0; j < v.length; j++) { if (v[j].id == id) { text = `${text} ${v[j].name}` } } } }) this.triggerEvent('textChange', text) } } })
上面都有注释:
CSS

.flex-row { display: flex; flex-direction: row; } .flex-col { display: flex; flex-direction: column; } .sku-box { width: 100%; background: #fff; padding: 40rpx; } .sku-row { margin-bottom: 36rpx; } .sku-title { color: #333; font-size: 24rpx; line-height: 1; } .sku-wrap { margin-top: 36rpx; flex-wrap: wrap; } .sku-item { min-width: 160rpx; padding: 18rpx 14rpx; color: #555555; font-size: 24rpx; line-height: 1; border-radius: 4rpx; background: #EAEAEA; border: 1rpx solid #EAEAEA; text-align: center; margin-bottom: 20rpx; } .sku-item+.sku-item { margin-left: 24rpx; } .sku-item.disabled { color: #ababab; } .sku-item.selected { color: #38ADFF; background:rgba(56,173,255,0.1); border: 1rpx solid #38ADFF; }
然后封装使用在 父组件使用,
这个就是效果图,
嘻嘻 这样就算封装完毕