加购商品sku选择算法方案 (vue)(淘宝)
是
目录
演示地址:https://codepen.io/louyanping723/pen/gOLeqWb
演示地址:https://codepen.io/louyanping723/pen/wvojPKd
演示地址:https://codepen.io/louyanping723/pen/gOLzXoR
附录:参考思路文档
1.什么是SKU?
在连锁零售门店中有时称单品为一个SKU(中文译为最小存货单位,英文全称为Stock Keeping Unit,简称SKU,定义为保存库存控制的最小可用单位,例如纺织品中一个SKU通常表示规格、颜色、款式)。
通俗一点就是如下图所示:男款+41+黑灰,就构成了一个SKU。
2.问题描述
拿手机举例,假设一台手机有颜色和内存和运营商 3 种可选属性,颜色有黑色和白色,内存只有 16G 和 32G 。我们把属性组合一下,列举出所有的 SKU ,同时也显示出库存数量和价格:
颜色 | 内存 | 库存 | 价格 |
黑色 | 16G | 0 | 5999 |
黑色 | 32G | 11 | 5999 |
白色 | 16G | 12 | 6299 |
白色 | 32G | 13 | 6299 |
3.解决办法
3.1 场景还原
要解决这个问题,我们先模拟一个商品购买选择 SKU 的场景。一般情况下,后台会通过接口提供给我们两组数据,分别是 属性集 和 数据集 ,这里用两组固定数据模拟一下:
// 属性集
var key = [
{name: '颜色', item: ['黑', '金', '白']},
{name: '内存', item: ['16G', '32G']},
{name: '运营商', item: ['电信', '移动', '联通']}
];
// 数据集
var sku = {
'黑;16G;电信': {price: 100, count: 10},
'黑;16G;移动': {price: 101, count: 11},
'黑;16G;联通': {price: 102, count: 0},
'黑;32G;电信': {price: 103, count: 13},
'黑;32G;移动': {price: 104, count: 14},
'黑;32G;联通': {price: 105, count: 0},
'金;16G;电信': {price: 106, count: 16},
'金;16G;移动': {price: 107, count: 17},
'金;16G;联通': {price: 108, count: 18},
'金;32G;电信': {price: 109, count: 0},
'金;32G;移动': {price: 110, count: 20},
'金;32G;联通': {price: 111, count: 21},
'白;16G;电信': {price: 112, count: 0},
'白;16G;移动': {price: 113, count: 23},
'白;16G;联通': {price: 114, count: 24},
'白;32G;电信': {price: 115, count: 0},
'白;32G;移动': {price: 116, count: 26},
'白;32G;联通': {price: 117, count: 27}
};
有了这两组数据,就可以实现最基本的 SKU 选择功能了。用 属性集 去渲染 DOM,当用户选择好 SKU 后,程序将用户选择的属性拼接成一个 sku 字符串,比如 金;16G;电信 ,再根据这个字符串去 数据集 里获取库存和价格,演示如下:
演示地址:https://codepen.io/louyanping723/pen/gOLeqWb
这里的属性集我加了一个itemIndex的属性,标识当前选中的属性索引值,用于选中高亮。
上面这个演示有个最大的问题就是,必须把每个属性都选择后,才能获取到对应的库存和价格,如果没有选择完整,就无法获取对应的数据。
原因也很简单,因为 数据集
里没有提供。比如只选择了 白
,那么当前拼接出来的 sku 则是 白;;
,自然找不到这条 sku 的相关数据。那要怎么解决呢?那就把 数据集
加工一下。
3.2 数据加工
我拿 数据集
里某一条 sku 举例,比如 黑;16G;电信
,将这个 sku 进行更小的拆分组合,希望得到以下的结果:
;;
黑;;
;16G;
;;电信
黑;16G;
黑;;电信
;16G;电信
黑;16G;电信
这里会涉及到本文中最核心的一个算法,让我们再仔细看下举例的这个 sku ,如果将它转为数组,就是:
['黑', '16G', '电信']
如果把最终希望得到的结果也转为数组,那就是:
['', '', '']
['黑', '', '']
['', '16G', '']
['', '', '电信']
['黑', '16G', '']
['黑', '', '电信']
['', '16G', '电信']
['黑', '16G', '电信']
然后仔细观察一下这组数据,看出些端倪了么?
没看出来的话,我们把这个 sku 再增加一个属性,如果数组是这样子的:
['黑', '16G', '电信', '2800mAh']
那最终希望得到的结果也会有变化:
// 4选0
['', '', '', '']
// 4选1
['黑', '', '', '']
['', '16G', '', '']
['', '', '电信', '']
['', '', '', '2800mAh']
// 4选2
['黑', '16G', '', '']
['黑', '', '电信', '']
['黑', '', '', '2800mAh']
['', '16G', '电信', '']
['', '16G', '', '2800mAh']
['', '', '电信', '2800mAh']
// 4选3
['黑', '16G', '电信', '']
['黑', '16G', '', '2800mAh']
['黑', '', '电信', '2800mAh']
['', '16G', '电信', '2800mAh']
// 4选4
['黑', '16G', '电信', '2800mA']
相信有人已经看出来了,这里需要实现的一个算法就是:
从 m 个不同元素中取出 n 个元素的组合数,而且是有序的。
但是其实从上面这样的列举法列出来,算法不太好写,因为是有序的选择,可能需要位移的偏移量来处理,比较麻烦和复杂。
那么换一种列举的方法,大家就可以看得比较明显了,如下所示:
['', '', '']
['', '', '电信']
----
['', '16G', '']
['', '16G', '电信']
-------------
['黑', '', '']
['黑', '', '电信']
----
['黑', '16G', '']
['黑', '16G', '电信']
观察以上,其实数组第一个选项要不就是黑
,要不就是''
,第二个选项要不就是16G
,要不就是''
,第三个选项要不就是电信
,要不就是''
。
这样就很好理解了,数组的每一项要不就是当前的标签,要不就是空,那么我们设置一个横向遍历的数组来递归进行组合就可以了,如下所示:
[
['黑', '']
['16G', ''],
['电信', '']
]
核心递归遍历方法:
recombine(arr) {
const labelArr = arr;
// 获取重新组合的数组格式 例如[['黑', ''],['16G', ''],['电信', '']]
// 进行横向遍历,填充组合数组
const newLabelArr = labelArr.map((item) => [item, '']);
// 进行横向遍历,填充组合数组,从0开始递归
let resultArr = this.getCombineArr(newLabelArr,0);
return resultArr;
},
getCombineArr(arr,index) {
let resultArr = [];
let newArr = [];
const recursion = (arr,index) => {
for(let i=0;i<arr[index].length;i++) {
newArr[index] = arr[index][i]
if(index===(arr.length-1)) {
resultArr.push(JSON.parse(JSON.stringify(newArr)));
} else {
// 递归
recursion(arr,index+1)
recursion(arr,index)
return resultArr;
},
解决到这一步后,后面的工作就相对轻松了。
我们已经能根据 黑;16G;电信
得到左边这样一组数据,黑;16G;移动
得到右边这样的数据。以此类推。
观察上面的数据,我们发现有相同属性的集合,比如['', '', '']
['', '16G', '']
['黑', '16G', '']
等等……
这时候只需把属性数据一样的库存进行累加,同时把价格存到一个数组里。这样把 数据集
里所有的 sku 都循环一遍后,对应的加工以后数据集的库存数就统计出来了。比如每个 sku 都会出现 ['', '', '']
,那累计得出的自然也就是该商品的总库存数量;再比如 sku 里有出现过 ['黑', '', '']
,最终累计得出的就是该商品颜色为黑色的库存数量。数据格式如下图所示:
展示的时候直接取数组里的最大和最小价格展示即可。
const maxPrice = Math.max(...priceArr);
const minPrice = Math.min(...priceArr);
至此,我们已经能实现用户选择一个或多个属性时,均能展示当前的库存和价格信息,演示如下:
演示地址:https://codepen.io/louyanping723/pen/wvojPKd
3.3 关联 SKU 验证
现在只差最后一步了,就是希望当用户点击属性选择的时候,程序能去验证一些可能点击的属性,提前把 0 库存的属性设为禁止选中状态。
判断流程:
- 属性集遍历,按钮渲染绑定disabled状态::disabled="!checkSku(label,index)
- 传入当前渲染的属性标签 和 当前属性标签 所在属性集 的索引值。
- 根据属性集的itemIndex属性 获取当前已经 点击选中的属性标签。组成数组choosedLabelArr。(如['白', '', ''])
- 假设当前渲染的属性标签 选中,替换3中的数组choosedLabelArr索引为当前标签 : choosedLabelArr[index] = str;
- 遍历加工完的数据集,是否有库存可选,有就可点击,没有就禁用。
核心代码如下图:
checkSku(str,index) {
// 先获取已经选中的标签
let choosedLabelArr = [];
const key = this.key;
key.forEach((item) => {
choosedLabelArr.push(
item.itemIndex>-1 ? item.item[item.itemIndex] : ''
)
});
// 假设当前标签已选中
choosedLabelArr[index] = str;
let nowChooseStr = choosedLabelArr.join(';');
// 遍历加工完的数据集,是否有库存可选,有就可点击,没有就禁用
let canChoose = false;
const resultSKU = this.resultSKU;
for(let skuLey in resultSKU) {
if(skuLey.indexOf(nowChooseStr)>-1) {
if(resultSKU[skuLey].count>0) {
canChoose = true;
break;
}
}
}
return canChoose;
}
演示地址:https://codepen.io/louyanping723/pen/gOLzXoR
附录:
参考思路文档:https://hooray.github.io/posts/8b2bd6f8/(核心算法已自己全部重新编写)
更多推荐
所有评论(0)