1、前言
只要是做电商类相关的产品,比如购物 APP、购物网站等等,都会遇到这么一个场景,每个商品对应着多个规格,用户可以根据不同的规格组合,选择出自己想要的产品。
我们自己在生活中也会经常用到这个功能,然而就是这样一个看似简单的商品多规格属性组合算法,在电商类业务中却是比较复杂的一块内容了。
我们先看一下使用场景:
通过上图我们就能发现。商品多属性的集合通常是通过将各个规格的取值组合在一起,生成一系列 SKU。这些 SKU 可以唯一地标识一个商品,包括其多属性规格信息,例如颜色、尺码、款式等。2、使用笛卡尔乘积算法
商品多属性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。
具体的实现步骤如下:
<script setup>
import { reactive,onMounted } from "vue";
const combination = reactive([
{ name: '颜色', id:'color', list: ['红色', '蓝色', '绿色'] },
{ name: '尺码', id:'size', list: ['S', 'M', 'L'] },
{ name: '材质', id:'texture', list: ['棉质', '涤纶'] },
]);
let skuList = reactive([])
// let attributesValue = reactive([])
// const checkInventory = ()=> {}
const cartesianProduct = () => {
const array = combination.map(item => item.list.map(itemVal => ({ name: item.name, val: itemVal })));
const data = [];
// 使用 笛卡尔乘积+递归 生成变体组合
/**
* skuarr:用于存储每次递归生成的组合结果的数组,初始为空数组。
* i:当前处理的数组 array 的索引。
* func 函数会递归地将 array 中的每个元素进行组合,生成一个包含所有可能组合的二维数组 data。
* 在递归过程中,每次都会创建一个新的数组 skuarr,用于存储当前递归层级的组合结果,从而确保不会对上一层级的组合结果造成干扰。
*/
const func = (skuarr = [], i) => {
for (let j = 0; j < array[i].length; j++) {
if (i < array.length - 1) {
skuarr[i] = array[i][j] // 将当前的变体选项加入 skuarr 数组中
func(skuarr, i + 1) // 递归调用下一层
} else {
const array = [...skuarr, array[i][j]]
const valueList = array.map(item => item.val)
data.push(valueList)
}
}
}
func([], 0);
return data;
};
onMounted(()=> {
const data = cartesianProduct ()
skuList.push(...data)
console.log('skuList: ', skuList);
})
</script>
如此,对于每个属性,只需要列出其所有的选项值,然后基于这些选项值,按照上面的公式来生成所有可能的SKU组合。
3、判断规格是否有存货
在电商系统中,一般会有一个商品的 SKU 列表,每个 SKU 都代表着一种具体的产品规格和库存量。
当用户选择某一个规格时,需要遍历所有可能的 SKU,找到与该规格组合匹配的 SKU,并检查该 SKU 的库存情况。如果有库存,则该规格可以被勾选;否则,该规格应该被禁用或者显示为无法选择的状态。
如上 combination 表可知,当前产品有三个规格:颜色、尺码、材质。
其中,颜色和尺寸和材质之间存在依赖关系,即不同的颜色对应不同的尺寸和材质。
此外,每个 SKU 都会对应着特定的库存量。
当用户选择颜色为红色时,需要遍历所有SKU,找到颜色为红色的SKU,并获取该SKU对应的尺寸。
接下来,需要检查所有库存量大于0的SKU,看看它们中是否存在尺寸与当前选择的尺寸相同的SKU。
如果存在,则该尺寸可以被勾选;否则,该尺寸应该被禁用或者显示为无法选择的状态。
我们把 cartesianProduct 函数稍微改一下,加入库存:
const cartesianProduct = () => {
const array = combination.map(item => item.list.map(itemVal => ({ name: item.name, val: itemVal })));
const data = [];
const func = (skuarr = [], i) => {
for (let j = 0; j < array[i].length; j++) {
if (i < array.length - 1) {
skuarr[i] = array[i][j]
func(skuarr, i + 1)
} else {
data.push([...skuarr, array[i][j]])
}
}
return data
}
let newList = func([], 0);
// 开始组装每一项变体属性
return newList.map(item => {
const attributesValue = {};
item.forEach(({ id, val }) => {
attributesValue[id] = val;
});
return {
attributes: item,
attributesValue,
SKU: item.map(i => i.val).join(),
stock: Math.floor(Math.random() * 6), // 生成0-5的随机整数库存
price: 100,
};
});
};
排列好规格组合:
<template>
<div class="attributes" v-for="item in combination" :key="item.id">
<div>{{ item.name }}</div>
<el-radio-group v-model="attributesValue[item.id]" size="large">
<el-radio-button :label="e" v-for="e in item.list" :disabled="!checkInventory(item.id, e)"/>
</el-radio-group>
</div>
</template>
<script setup>
import { reactive } from "vue";
const attributesValue = reactive({
color:'',
size:'',
texture:'',
})
...
</script>
如果需要实现每选中一个规格,其他依赖此规格的组合是否有存货,我们还需要添加一个函数计算是否可选
在js中添加函数 checkInventory :
<script setup>
...
const checkInventory = (id, val) => {
// 构造新的 attributesValue 对象
const newAttributesValue = { ...attributesValue, [id]: val };
for (const sku of skuList) {
let match = true;
for (const [key, value] of Object.entries(newAttributesValue)) {
if (value && sku.attributesValue[key] !== value) {
match = false;
break;
}
}
if (match && sku.stock > 0) {
return true;
}
}
return false;
}
</script>
好了,写到这里基本上完成了。再微调一下style,然后看看效果吧!
附上上述代码:
vue2
vue3
小伙伴们可以自己去试一试。
4、多属性规格的增删改
接下来我们把完整的商品多规格属性增删改流程补一补 查看详情