前言
做電商項目呢,離不開多規格商品,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; }
然后封裝使用在 父組件使用,
這個就是效果圖,
嘻嘻 這樣就算封裝完畢