赞
踩
目录
演示地址:https://codepen.io/louyanping723/pen/gOLeqWb
演示地址:https://codepen.io/louyanping723/pen/wvojPKd
演示地址:https://codepen.io/louyanping723/pen/gOLzXoR
附录:参考思路文档
在连锁零售门店中有时称单品为一个SKU(中文译为最小存货单位,英文全称为Stock Keeping Unit,简称SKU,定义为保存库存控制的最小可用单位,例如纺织品中一个SKU通常表示规格、颜色、款式)。
通俗一点就是如下图所示:男款+41+黑灰,就构成了一个SKU。
拿手机举例,假设一台手机有颜色和内存和运营商 3 种可选属性,颜色有黑色和白色,内存只有 16G 和 32G 。我们把属性组合一下,列举出所有的 SKU ,同时也显示出库存数量和价格:
颜色 | 内存 | 库存 | 价格 |
黑色 | 16G | 0 | 5999 |
黑色 | 32G | 11 | 5999 |
白色 | 16G | 12 | 6299 |
白色 | 32G | 13 | 6299 |
要解决这个问题,我们先模拟一个商品购买选择 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;电信 ,再根据这个字符串去 数据集 里获取库存和价格,演示如下:
这里的属性集我加了一个itemIndex的属性,标识当前选中的属性索引值,用于选中高亮。
上面这个演示有个最大的问题就是,必须把每个属性都选择后,才能获取到对应的库存和价格,如果没有选择完整,就无法获取对应的数据。
原因也很简单,因为 数据集
里没有提供。比如只选择了 白
,那么当前拼接出来的 sku 则是 白;;
,自然找不到这条 sku 的相关数据。那要怎么解决呢?那就把 数据集
加工一下。
我拿 数据集
里某一条 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);
至此,我们已经能实现用户选择一个或多个属性时,均能展示当前的库存和价格信息,演示如下:
现在只差最后一步了,就是希望当用户点击属性选择的时候,程序能去验证一些可能点击的属性,提前把 0 库存的属性设为禁止选中状态。
判断流程:
核心代码如下图:
- 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://hooray.github.io/posts/8b2bd6f8/(核心算法已自己全部重新编写)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。