加购商品sku选择算法方案 (vue)(淘宝)

6 篇文章 0 订阅
1 篇文章 0 订阅

目录

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

 

 

 

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
首先,我们需要设计一个多SKU商品的数据结构,可以使用以下方式: ``` { "name": "商品名称", "description": "商品描述", "skus": [ { "id": "sku1", "name": "SKU1名称", "price": 100, "stock": 10, "attributes": [ { "name": "颜色", "value": "红色" }, { "name": "尺寸", "value": "XL" } ] }, { "id": "sku2", "name": "SKU2名称", "price": 120, "stock": 5, "attributes": [ { "name": "颜色", "value": "蓝色" }, { "name": "尺寸", "value": "L" } ] } ] } ``` 其中,`name`、`description` 是商品的基本信息,`skus` 是商品的多个SKU列表。每个SKU由一个唯一的`id`、`name`、`price`、`stock` 和 `attributes` 组成。`attributes`包含了SKU的属性,比如颜色、尺寸等等。 接下来,我们可以使用 Vue.js 来实现这个多SKU商品的页面。首先,我们需要使用`v-for`指令来循环渲染所有的SKU。 ``` <template> <div> <h1>{{ product.name }}</h1> <p>{{ product.description }}</p> <div v-for="sku in product.skus" :key="sku.id"> <h2>{{ sku.name }}</h2> <p>价格: {{ sku.price }}</p> <p>库存: {{ sku.stock }}</p> <div v-for="attribute in sku.attributes" :key="attribute.name"> <p>{{ attribute.name }}: {{ attribute.value }}</p> </div> </div> </div> </template> ``` 然后,我们需要添加一个购买按钮,当用户点击购买按钮时,弹出一个弹窗,让用户选择SKU的属性,然后输入购买数量。 ``` <template> <div> <h1>{{ product.name }}</h1> <p>{{ product.description }}</p> <div v-for="sku in product.skus" :key="sku.id"> <h2>{{ sku.name }}</h2> <p>价格: {{ sku.price }}</p> <p>库存: {{ sku.stock }}</p> <div v-for="attribute in sku.attributes" :key="attribute.name"> <p>{{ attribute.name }}: {{ attribute.value }}</p> </div> <button @click="showBuyDialog(sku)">购买</button> </div> <div v-if="showDialog"> <h2>购买</h2> <p>请选择属性:</p> <div v-for="attribute in selectedAttributes" :key="attribute.name"> <p>{{ attribute.name }}: {{ attribute.value }}</p> <select v-model="attribute.selectedValue"> <option v-for="value in attribute.values" :key="value">{{ value }}</option> </select> </div> <p>数量:</p> <input type="number" v-model.number="quantity" min="1"> <button @click="buy">购买</button> </div> </div> </template> <script> export default { data() { return { product: { name: '商品名称', description: '商品描述', skus: [ { id: 'sku1', name: 'SKU1名称', price: 100, stock: 10, attributes: [ { name: '颜色', value: '红色' }, { name: '尺寸', value: 'XL' } ] }, { id: 'sku2', name: 'SKU2名称', price: 120, stock: 5, attributes: [ { name: '颜色', value: '蓝色' }, { name: '尺寸', value: 'L' } ] } ] }, showDialog: false, selectedSku: null, selectedAttributes: [], quantity: 1 } }, methods: { showBuyDialog(sku) { this.selectedSku = sku this.selectedAttributes = sku.attributes.map(attribute => ({ name: attribute.name, value: attribute.value, selectedValue: attribute.value, values: attribute.values })) this.quantity = 1 this.showDialog = true }, buy() { const selectedAttributeValues = this.selectedAttributes.map(attribute => attribute.selectedValue) // 根据选择的属性值和数量,生成订单 // ... this.showDialog = false } } } </script> ``` 在上面的代码中,当用户点击购买按钮时,我们调用了`showBuyDialog`方法,该方法会将当前选择SKU保存到`selectedSku`中,并将该SKU的属性保存到`selectedAttributes`中。`selectedAttributes`是一个数组,它保存了所有的属性,每个属性包含了属性名称、属性值、用户选择的属性值以及该属性的所有可选值。在弹出的弹窗中,我们使用`v-for`指令循环渲染所有的属性,并使用`v-model`指令绑定用户选择的属性值。用户选择完属性值和购买数量后,我们可以根据这些信息生成订单,并将弹窗关闭。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值