多规格商品SKU 组件封装
前言
做电商项目呢,离不开多规格商品,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; }
然后封装使用在 父组件使用,


这是数据结构
sku: { list: [{ "skuId": "123", "guidePrice": null, "sellPrice": "11", "photoUrl": "", "stockUnit": null, "propIds": ["1234"], //规格组 "stockCount": "15", "status": null, "propNames": null, "propValues": null, "originalPrice": null, "id": "1111", //必须字段 "price": "11", //必须字段 "stock_num": "15", //必须字段 "s1": "1234" //规格id, 必须字段 }], tree: [{ //tree中都是必须字段 "k": "净含量", //规格name "v": [{ "id": "", //规格id "name": "8.5 OZ.(250ML)", //规格对应的key "attrId": "ea5fdbdf40814b1b9e0beb866873f28b" //规格id }], "k_s": "s1" }] }
这个就是效果图,
嘻嘻 这样就算封装完毕

浙公网安备 33010602011771号