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