SKU选择的实现
· 阅读需 6 分钟
最近在做电商相关的项目,电商中很常见的一个功能就是关于SKU选择,一开始没当回事,做了之后发现涉及到的逻辑还挺多的,特别是对按钮的禁选状态上的计算。
渲染选项
先了解一下数据结构,一个商品SKU是包含多个属性的,这里以一台手机为例,由运营商、容量、颜色组成一个完整的SKU。
实时编辑器
// 属性列表 function SkuChoose () { const [currentChoose, setCurrentChoose] = useState({}) const attrList = [ { attr_id: 'xxx', attr_name: '运营商', attr_values: [ { value_id: '11', value_name: '移动' }, { value_id: '22', value_name: '电信' }, { value_id: '33', value_name: '联通' }, ] }, { attr_id: 'yyy', attr_name: '容量', attr_values: [ { value_id: '64', value_name: '64GB' }, { value_id: '128', value_name: '128GB' }, { value_id: '256', value_name: '256GB' }, ] }, { attr_id: 'zzz', attr_name: '颜色', attr_values: [ { value_id: 'black', value_name: '黑色' }, { value_id: 'white', value_name: '白色' }, ] } ] const handleChoose = (attr_id, value_id) => { if (currentChoose[attr_id] === value_id) { const temp = { ...currentChoose } delete temp[attr_id] setCurrentChoose(temp) console.log(`取消了属性 ${attr_index}:${attr_id}@${value_id}`) return } setCurrentChoose({ ...currentChoose, [attr_id]: value_id }) console.log(`选中了属性 ${attr_index}:${attr_id}@${value_id}`) } return ( <div> { attrList.map(attr => ( <div key={attr.attr_id}> <label>{attr.attr_name}:</label> { attr.attr_values.map(v => ( <button key={v.value_id} onClick={() => handleChoose(attr.attr_id, v.value_id)} style={{ marginRight: 10, fontWeight: currentChoose[attr.attr_id] === v.value_id ? '900' : '300' }} >{v.value_name}</button> )) } </div> )) } 当前选中:{JSON.stringify(currentChoose)} </div> ) }
结果
Loading...
上面列出了属性列表,我们可以选择其中的属性组合成SKU,选中的属性会加粗显示,上面共形成了 3*3*2=18
款SKU,但实际情况是并不是每种组合都是有效的,只有有库存的SKU才是有效的属性组合。那么下面我们来看一个例子看一下真实的情况。
从SKU视角看问题
// 有效的SKU列表
const skuList = [
{
id: 1,
stock: 5,
sku_attr: [
{ attr_id: 'xxx', value_id: '11' },
{ attr_id: 'yyy', value_id: '128' },
{ attr_id: 'zzz', value_id: 'black' }
]
},
{
id: 2,
stock: 10,
sku_attr: [
{ attr_id: 'xxx', value_id: '22' },
{ attr_id: 'yyy', value_id: '128' },
{ attr_id: 'zzz', value_id: 'white' }
]
},
{
id: 3,
stock: 10,
sku_attr: [
{ attr_id: 'xxx', value_id: '22' },
{ attr_id: 'yyy', value_id: '64' },
{ attr_id: 'zzz', value_id: 'white' }
]
}
]
这里共列出了3款SKU,从SKU视角来考虑,在选择属性的时候我们需要验证当前属性是否可以被选择,比如在运营商选择了 电信 的时候,只有第一个SKU符合要求,则容量属性只有 64GB
、128GB
是可选的,其他按钮应该设置为禁选,同理颜色也只有 白色
是允许选择的;如果一开始选择的是 黑色 ,那其他两个属性就分别只有 移动
和 128GB
是可选的,所以我们可以得出对每个按钮来说,和已选择的属性一起合并起来,是否可能在列表中找到符合要求的SKU即可。
生成组合选项
比如对于第一个SKU,属性构成为:
const sku_attr = [
{ attr_id: 'xxx', value_id: '11' },
{ attr_id: 'yyy', value_id: '128' },
{ attr_id: 'zzz', value_id: 'black' }
]
允许产生的选项集合有:
const option1 = { xxx: '11' }
const option2 = { yyy: '128' }
const option3 = { zzz: 'black' }
const option4 = { xxx: '11', yyy: '128' }
const option5 = { xxx: '11', zzz: 'black' }
const option6 = { yyy: '128', zzz: 'black' }
const option7 = { xxx: '11', yyy: '128', zzz: 'black' }
考虑使用以下统计方式:
const psArr = []
sku_attr.forEach(attr => {
// 对已有的选项做遍历,在队尾添加当前选项叠加当前属性形成的新选项
for (let i = 0, len = psArr.length; i < len; i++) {
const option = { ...psArr[i], [attr.attr_id]: attr.value_id }
psArr.push(option)
}
// 添加一个当前属性独立组成的选项
psArr.push({ [attr.attr_id]: attr.value_id })
})
这是一个SKU形成的选项,我们要对所有的SKU都走一遍这个流程,最后加起来就是所有的选项了,当然这里面可能会有重复选项,可以进一步作去重处理(不去重对后面的计算也没有影响),去重的方法有很多,我这里采用 Set
:
function uniqueArray(array) {
const unique = Array.from(new Set(array.map(item => JSON.stringify(item))));
return unique.map(item => JSON.parse(item));
}
let allPsArr = []
skuList.forEach(sku => {
const sku_attr = sku.sku_attr
const psArr = []
// ...
allPsArr = allPsArr.concat(psArr)
})
uniqueArray(allPsArr)
设置按钮状态
到这里,我们拿到了所有的选项集合,那么很容易结合按钮本身的数据来设置禁选状态:
function isDisabled (currentChoose, attr_id, value_id) {
const option = { ...currentChoose, [attr_id]: value_id }
const keys = Object.keys(option)
const isExist = allPsArr.some(item => {
if (Object.keys(item).length !== keys.length) return false
for (let i = 0; i < keys.length; i++) {
if (item[keys[i]] !== option[keys[i]]) return false
}
return true
})
return !isExist
}