SKU商品规格属性-笛卡尔乘积算法
前言
只要是做电商类相关的产品,比如购物 APP、购物网站等等,都会遇到这么一个场景,每个商品对应着多个规格,用户可以根据不同的规格组合,选择出自己想要的产品。
我们自己在生活中也会经常用到这个功能,然而就是这样一个看似简单的商品多规格属性组合算法,在电商类业务中却是比较复杂的一块内容了。
通过上图我们就能发现。商品多属性的集合通常是通过将各个规格的取值组合在一起,生成一系列 SKU。这些 SKU 可以唯一地标识一个商品,包括其多属性规格信息,例如颜色、尺码、款式等。
使用笛卡尔乘积算法
商品多属性SKU集合计算公式通常是:
SKU = 属性1选项值 + 属性2选项值 + …+ 属性n选项值
例如,假设一个T恤有三个属性:颜色、尺码和材质。颜色有红色、蓝色和绿色可选,尺码有S、M、L可选,材质有棉质和涤纶可选。
const combination = [
{ name: '颜色', id:'color', list: ['红色', '蓝色', '绿色'] },
{ name: '尺码', id:'size', list: ['S', 'M', 'L'] },
{ name: '材质', id:'texture', list: ['棉质', '涤纶'] },
]
通过属性匹配就会存在以下几种SKU:
const skuList = [
{'红色', 'S', '棉质'}, {'红色', 'S', '涤纶'}, {'红色', 'M', '棉质'},
{'红色', 'M', '涤纶'}, {'红色', 'L', '棉质'}, {'红色', 'L', '涤纶'},
{'蓝色', 'S', '棉质'}, {'蓝色', 'S', '涤纶'}, {'蓝色', 'M', '棉质'},
{'蓝色', 'M', '涤纶'}, {'蓝色', 'L', '棉质'}, {'蓝色', 'L', '涤纶'},
{'绿色', 'S', '棉质'}, {'绿色', 'S', '涤纶'}, {'绿色', 'M', '棉质'},
{'绿色', 'M', '涤纶'}, {'绿色', 'L', '棉质'}, {'绿色', 'L', '涤纶'}
]
那么,我们应该怎么把 combination 通过计算成 skuList 呢?
其实很简单,我们可以使用 笛卡尔乘积算法
在数学中,笛卡尔积可以从多个集合中分别选取一个元素,进行组合的操作,生成一个新的集合。
设A,B为一个集合,将A中的元素作为第一个元素,B中的元素作为第二个元素,形成有序对。所有这些有序对都由一个称为a和B的笛卡尔积的集合组成,并被记录为AxB。
// 假设已经有的集中属性
const combination = reactive([
{ name: '颜色', id:'color', list: ['红色', '蓝色', '绿色'] },
{ name: '尺码', id:'size', list: ['S', 'M', 'L'] },
{ name: '材质', id:'texture', list: ['棉质', '涤纶'] },
// { name:'品牌', id:'pinpai',list:['三星','华为','苹果','小米','XF'] }
])
let skuList = reactive([])
// 需要根据笛卡尔算法计算出各种排列组合
function cartesianProduct(){
const array = combination.map(item => item.list.map(v => { return ({ name:item.name,val:v,id:item.id }) }))
console.log('array',array);
const data = []
// 上述已经把当前的数据组合为[{name:"颜色",val:"红色"},{name:"颜色",val:"蓝色"}....]格式
let func = function(...arrays){
console.log('arrays',arrays);
return arrays.reduce((acc, curr) => {
const result = [];
acc.forEach(x => {
curr.forEach(y => {
result.push([...x, y]);
});
});
return result;
}, [[]]);
}
console.log('....',...array);
let newList = func(...array)
return newList
}
skuList = cartesianProduct()
console.log('skuList',skuList);
判断规格是否有存货
在电商系统中,一般会有一个商品的 SKU 列表,每个 SKU 都代表着一种具体的产品规格和库存量。
当用户选择某一个规格时,需要遍历所有可能的 SKU,找到与该规格组合匹配的 SKU,并检查该 SKU 的库存情况。如果有库存,则该规格可以被勾选;否则,该规格应该被禁用或者显示为无法选择的状态。
如上 combination 表可知,当前产品有三个规格:颜色、尺码、材质。
其中,颜色和尺寸和材质之间存在依赖关系,即不同的颜色对应不同的尺寸和材质。
此外,每个 SKU 都会对应着特定的库存量。
当用户选择颜色为红色时,需要遍历所有SKU,找到颜色为红色的SKU,并获取该SKU对应的尺寸。
接下来,需要检查所有库存量大于0的SKU,看看它们中是否存在尺寸与当前选择的尺寸相同的SKU。
如果存在,则该尺寸可以被勾选;否则,该尺寸应该被禁用或者显示为无法选择的状态。
我们把 cartesianProduct 函数稍微改一下,加入库存
判断是否有存货,加入库存
import { reactive,ref } from 'vue'
// 假设已经有的集中属性
const combination = reactive([
{ name: '颜色', id:'color', list: ['红色', '蓝色', '绿色'] },
{ name: '尺码', id:'size', list: ['S', 'M', 'L'] },
{ name: '材质', id:'texture', list: ['棉质', '涤纶'] },
// { name:'品牌', id:'pinpai',list:['三星','华为','苹果','小米','XF'] }
])
let skuList = reactive([])
// 需要根据笛卡尔算法计算出各种排列组合
function cartesianProduct(){
const array = combination.map(item => item.list.map(v => { return ({ name:item.name,val:v,id:item.id }) }))
console.log('array',array);
const data = []
// 上述已经把当前的数据组合为[{name:"颜色",val:"红色"},{name:"颜色",val:"蓝色"}....]格式
let func = function(...arrays){
console.log('arrays',arrays);
return arrays.reduce((acc, curr) => {
const result = [];
acc.forEach(x => {
curr.forEach(y => {
result.push([...x, y]);
});
});
return result;
}, [[]]);
}
console.log('....',...array);
let newList = func(...array)
return newList.map(item => {
const attributesValue = {}
item.forEach(({id,val}) => {
attributesValue[id] = val
})
return {
attributes:item, // { id:"color",name:"颜色",val:"红色" }
attributesValue,
SKU:item.map(i => i.val).join(),
stock: Math.floor(Math.random() * 6),
price:1001
}
})
}
skuList = cartesianProduct()
console.log('skuList',skuList);
页面布局
<template>
<h2>sku</h2>
<div>
<div v-for="item in combination">
<div class="left">
{{ item.name }}:
<button :disabled="!checkIsNoStock(item.id,btnValue)" :class="[attributesValue[item.id] == btnValue ? 'is-checked' : '']" @click="checkedHandler(item.id,btnValue)" style="margin: 5px;" v-for="(btnValue,index) in item.list" >{{ btnValue }}</button>
</div>
</div>
<button style="padding: 10px 20px;border-radius: 0px;background-color: rgba(255, 38, 0, 0.644);color: #fff;width: 200px;margin-top: 50px;margin-left: calc(100% - 200px);cursor: pointer;" @click="submitSku">提交</button>
</div>
</template>
<style lang="css" scoped>
button {
background-color: transparent;
border: 1px solid #ececec;
border-radius: 20px;
padding: 3px 15px;
}
.is-checked {
color: blue;
border-color: blue;
}
</style>
布局效果
加入函数校验当前按钮是否可以点击
如果需要实现每选中一个规格,其他依赖此规格的组合是否有存货,我们还需要添加一个函数计算是否可选
在js中添加函数 checkInventory
// 判断当前按钮是否可以点击 是否有库存
let checkIsNoStock = (id,value) => {
let newAttributesValue = { ...attributesValue,[id]:value }
for(const sku of skuList){
let match = true
for(const [key,v] of Object.entries(newAttributesValue)) {
if(v && sku.attributesValue[key] !==v) {
match = false
break
}
}
if(match&&sku.stock >0){
return true
}
}
return false
}
完整代码
<script setup>
import { reactive,ref } from 'vue'
// 假设已经有的集中属性
const combination = reactive([
{ name: '颜色', id:'color', list: ['红色', '蓝色', '绿色'] },
{ name: '尺码', id:'size', list: ['S', 'M', 'L'] },
{ name: '材质', id:'texture', list: ['棉质', '涤纶'] },
// { name:'品牌', id:'pinpai',list:['三星','华为','苹果','小米','XF'] }
])
let skuList = reactive([])
// 需要根据笛卡尔算法计算出各种排列组合
function cartesianProduct(){
const array = combination.map(item => item.list.map(v => { return ({ name:item.name,val:v,id:item.id }) }))
console.log('array',array);
const data = []
// 上述已经把当前的数据组合为[{name:"颜色",val:"红色"},{name:"颜色",val:"蓝色"}....]格式
let func = function(...arrays){
console.log('arrays',arrays);
return arrays.reduce((acc, curr) => {
const result = [];
acc.forEach(x => {
curr.forEach(y => {
result.push([...x, y]);
});
});
return result;
}, [[]]);
}
console.log('....',...array);
let newList = func(...array)
return newList.map(item => {
const attributesValue = {}
item.forEach(({id,val}) => {
attributesValue[id] = val
})
return {
attributes:item, // { id:"color",name:"颜色",val:"红色" }
attributesValue,
SKU:item.map(i => i.val).join(),
stock: Math.floor(Math.random() * 6),
price:1001
}
})
}
skuList = cartesianProduct()
console.log('skuList',skuList);
const attributesValue = reactive({
color:'',
size:'',
texture:'',
})
let checkedHandler = (id,value) => {
attributesValue[id] = value
console.log('id',id,value);
}
// 判断当前按钮是否可以点击 是否有库存
let checkIsNoStock = (id,value) => {
let newAttributesValue = { ...attributesValue,[id]:value }
for(const sku of skuList){
let match = true
for(const [key,v] of Object.entries(newAttributesValue)) {
if(v && sku.attributesValue[key] !==v) {
match = false
break
}
}
if(match&&sku.stock >0){
return true
}
}
return false
}
let submitSku = () => {
console.log('提交的商品sku',attributesValue);
}
</script>
<template>
<h2>sku</h2>
<div>
<div v-for="item in combination">
<div class="left">
{{ item.name }}:
<button :disabled="!checkIsNoStock(item.id,btnValue)" :class="[attributesValue[item.id] == btnValue ? 'is-checked' : '']" @click="checkedHandler(item.id,btnValue)" style="margin: 5px;" v-for="(btnValue,index) in item.list" >{{ btnValue }}</button>
</div>
</div>
<button style="padding: 10px 20px;border-radius: 0px;background-color: rgba(255, 38, 0, 0.644);color: #fff;width: 200px;margin-top: 50px;margin-left: calc(100% - 200px);cursor: pointer;" @click="submitSku">提交</button>
</div>
</template>
<style lang="css" scoped>
button {
background-color: transparent;
border: 1px solid #ececec;
border-radius: 20px;
padding: 3px 15px;
}
.is-checked {
color: blue;
border-color: blue;
}
</style>
项目演示地址
版权声明
本文系作者 @黄粱一梦 转载请注明出处,文中若有转载的以及参考文章地址也需注明。\(^o^)/~