目录

1.什么是SKU?

2.问题描述

3.解决办法

3.1 场景还原

演示地址:https://codepen.io/louyanping723/pen/gOLeqWb

3.2 数据加工

演示地址:https://codepen.io/louyanping723/pen/wvojPKd

3.3 关联 SKU 验证

演示地址: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 库存的属性设为禁止选中状态

判断流程:

  1. 属性集遍历,按钮渲染绑定disabled状态::disabled="!checkSku(label,index)
  2. 传入当前渲染的属性标签 和 当前属性标签 所在属性集 的索引值。
  3. 根据属性集的itemIndex属性 获取当前已经 点击选中的属性标签。组成数组choosedLabelArr。(如['白', '', ''])
  4. 假设当前渲染的属性标签 选中,替换3中的数组choosedLabelArr索引为当前标签 :  choosedLabelArr[index] = str;
  5. 遍历加工完的数据集,是否有库存可选,有就可点击,没有就禁用。

 

核心代码如下图:

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/(核心算法已自己全部重新编写)

 

 

 

Logo

前往低代码交流专区

更多推荐