详解矩阵算法在电商sku组件中的应用二

file

本文首发于:https://github.com/bigo-frontend/blog/ 欢迎关注、转载。

前言

上一篇我们讲到了 sku 选择组件利用矩阵算法解决动态变化选项的需求,依旧遗留了一个大问题:
就是当规格大于 2 个的时候,你会发现这个算法存在 bug

举个例子

"skuList": [
	    {
	        "showPrice": "6.00",//优惠展示价格
            "favType": 1,// 优惠类型
		    "skuId": "1234",
		    "originalSalePrice": "12.01", // 商品原价
		    //"originalSaleCurrency": "", // 商品原价货币
		    "salePrice": "11.01", // 商品售卖价
		    "discount": "-40%", // 商品折扣文案
		    "saleCurrency": "USD", // 商品售卖价货币
		    "skuMinVolume": 1, // SKU 商品最小起售数量
		    "skuIncreaseVolume": 1, // SKU 商品最小增加数量
		    "maxBuyVolume": 99, // 最大购买数量
		    "skuStatus": 1, // SKU状态 0:已下架;1:在售
		    "skuImg":"https://s4.forcloudcdn.com/item/images/dmc/7cdb5ab0-88b5-45a4-a5af-d683ccbd9336-750x1000.jpeg_750x0c.jpeg",//sku图片
		    "skuAttrList": [ // SKU属性信息
		        {
                  "attrNameId": "1", // 属性名ID
                  "attrName": "颜色",//属性名
                  "attrValue": "红"//属性值
                },
                {
                  "attrNameId": "2", // 属性名ID
                  "attrName": "码数",//属性名
                  "attrValue": "XL"//属性值
                },
                {
                  "attrNameId": "3", // 属性名ID
                  "attrName": "套餐",//属性名
                  "attrValue": "套餐2"//属性值
                }
	        ],
	  	}, {
	        "showPrice": "6.00",//优惠展示价格
            "favType": 1,// 优惠类型
		    "skuId": "2345",
		    "originalSalePrice": "12.01", // 商品原价
		    //"originalSaleCurrency": "", // 商品原价货币符号 废弃
		    "salePrice": "11.01", // 商品售卖价
		    "saleCurrency": "USD", // 商品售卖价货币符号
		    "discount": "-40%", // 商品折扣文案
		    "skuMinVolume": 1, // SKU 商品最小起售数量
		    "skuIncreaseVolume": 1, // SKU 商品最小增加数量
		    "maxBuyVolume": 99, // 最大购买数量
		    "skuStatus": 1, // SKU状态
		    "skuImg":"https://s4.forcloudcdn.com/item/images/dmc/4a085065-0f92-4406-a55e-ae4150b91def-779x974.jpeg_750x0c.jpeg",//sku图片
		    "skuAttrList": [ // SKU属性信息
		        {
                  "attrNameId": "1", // 属性名ID
                  "attrName": "颜色",//属性名
                  "attrValue": "红"//属性值
                },
                {
                  "attrNameId": "2", // 属性名ID
                  "attrName": "码数",//属性名
                  "attrValue": "XXL"//属性值
                },
                {
                  "attrNameId": "3", // 属性名ID
                  "attrName": "套餐",//属性名
                  "attrValue": "套餐1"//属性值
                }
	        ],
	  	}, {
		    "skuId": "3456",
		    "originalSalePrice": "12.01", // 商品原价
		    //"originalSaleCurrency": "", // 商品原价货币符号
		    "salePrice": "11.01", // 商品售卖价
		    "saleCurrency": "USD", // 商品售卖价货币符号
		    "discount": "-40%", // 商品折扣文案
		    "skuMinVolume": 1, // SKU 商品最小起售数量
		    "skuIncreaseVolume": 1, // SKU 商品最小增加数量
		    "maxBuyVolume": 99, // 最大购买数量
		    "skuStatus": 1, // SKU状态
		    "skuImg":"https://s4.forcloudcdn.com/item/images/dmc/4a085065-0f92-4406-a55e-ae4150b91def-779x974.jpeg_750x0c.jpeg",//sku图片
		    "skuAttrList": [ // SKU属性信息
		        {
                  "attrNameId": "1", // 属性名ID
                  "attrName": "颜色",//属性名
                  "attrValue": "红"//属性值
                },
                {
                  "attrNameId": "2", // 属性名ID
                  "attrName": "码数",//属性名
                  "attrValue": "XXL"//属性值
                },
                {
                  "attrNameId": "3", // 属性名ID
                  "attrName": "套餐",//属性名
                  "attrValue": "套餐2"//属性值
                }
	        ],
	  	},
	  	{
		    "skuId": "4567",
		    "originalSalePrice": "12.01", // 商品原价
		    //"originalSaleCurrency": "", // 商品原价货币符号
		    "salePrice": "11.01", // 商品售卖价
		    "saleCurrency": "USD", // 商品售卖价货币符号
		    "discount": "-40%", // 商品折扣文案
		    "skuMinVolume": 1, // SKU 商品最小起售数量
		    "skuIncreaseVolume": 1, // SKU 商品最小增加数量
		    "maxBuyVolume": 99, // 最大购买数量
		    "skuStatus": 1, // SKU状态
		    "skuImg":"https://s4.forcloudcdn.com/item/images/dmc/4a085065-0f92-4406-a55e-ae4150b91def-779x974.jpeg_750x0c.jpeg",//sku图片
		    "skuAttrList": [ // SKU属性信息
		        {
                  "attrNameId": "1", // 属性名ID
                  "attrName": "颜色",//属性名
                  "attrValue": "白"//属性值
                },
                {
                  "attrNameId": "2", // 属性名ID
                  "attrName": "码数",//属性名
                  "attrValue": "XL"//属性值
                },
                {
                  "attrNameId": "3", // 属性名ID
                  "attrName": "套餐",//属性名
                  "attrValue": "套餐1"//属性值
                }
	        ],
	  	}
    ],
"skuAttrSortedList":[ // sku属性展示顺序
      {
        "attrName": "颜色",
        "attrNameId": "1",
        "attrValues": ["红", "白"]
      },{
        "attrName": "码数",
        "attrNameId": "2",
        "attrValues": ["XL", "XXL"]
      },
      {
        "attrName": "套餐",
        "attrNameId": "3",
        "attrValues": ["套餐1", "套餐2"]
      }
    ]

当 skulist 和 skuAttrSortedList 为上面数组时,我们尝试填充数组,会有以下矩阵:
image

当我们选择红色、XL后,交集求出来,6 个属性值居然都是剩余可选的,但是我们看 skulist,并没有 红色、XL、套餐 1 这个组合,这就存在问题了,我们选出了一个不存在的 sku,这是为什么呢?
因为我们填充 1 的依据只是单单两个点之间是否连通,但实际上 A 和 B 连通、B 和 C 连通 并不代表 A 和 C 也是连通的,那问题来了,我们怎么保证 A 和 B 和 C 是在同个 sku 里连通的呢?
这时候我们需要使用加权标记!

我们需要定义不连通则填充 0,同级连通填充 1,sku 连通填充 2、3、4···同个 sku 下连通填充同个数字。

我们来修改一下原来填充 1 的方法 setAdjoinVertexs:

setAdjoinVertexs(side, sides, weight) {
      let pIndex = '';
      for (let i = 0; i < this.vertex.length; i += 1) {
        if (side === this.vertex[i]) {
          pIndex = i;
          break;
        }
      }
      sides.forEach((item) => {
        let index = '';
        for (let i = 0; i < this.vertex.length; i += 1) {
          if (item === this.vertex[i]) {
            index = i;
            break;
          }
        }
        // this.adjoinArray[pIndex * this.len + index] = 1;
        const cur = this.adjoinArray[pIndex * this.len + index];
        if (typeof cur !== 'number') { // specList.length > 3时,存在单边多权的情况
          this.adjoinArray[pIndex * this.len + index].push(weight);
        } else if (cur > 1) {
          this.adjoinArray[pIndex * this.len + index] = [cur, weight];
        } else {
          this.adjoinArray[pIndex * this.len + index] = weight;
        }
      });
    }

这个方法变成了未填充的填充权值,已经填充了的则是变成一个数组存放每次连通填充的权值。

然后修改下初始化的几个方法:

initSpec() {
      this.specCombinationList.forEach((item, index) => this.fillInSpec(item.skuAttrValueList, index + 2));
},
initSameLevel() {
      this.product.skuAttrSortedList.forEach((item) => {
        const params = [];
        // 获取同级别顶点
        item.attrValues.forEach((val) => {
          if (this.optionSpecs.includes(val)) params.push(val);
        });
        // 同级点位创建
        this.fillInSpec(params, 1);
      });
},
fillInSpec(params, weight) {
      params.forEach((param) => {
        this.setAdjoinVertexs(param, params, weight);
      });
}

initSpec 方法则变成每个 sku 点与点连通都填充 sku 在 skulist 组合列表对应索引 +2,意思是第一个 sku 权值 2,第二个 sku 权值 3;initSameLevel 则是写死权值 1。
那最终初始化后的矩阵是:
image

关键是我们要如何求所有列的并集和选择列的交集呢?
现在因为采用权值叠加的方式,那原来格子求和的方法就不能用了。
我们先看求并集:
求并集比较简单,也是看每一列里是否有不为 0 的,只要这一列中有一个不为 0 的,则这一列对应的属性在初始时都是可选的。

getUnion(params) {
      const paramsVertex = params.map((id) => this.getVertexCol(id));
      const union = [];
      paramsVertex.forEach((col, index) => {
        if (col.some((item) => item !== 0)) union.push(params[index]);
      });
      return union;
    }

image
所以一开始所有属性都是可选的。

接下来我们看如何选交集:
我们先选中红色这一列,可以看到每一行的数组或者数字都是没有 0 的,则所有属性都是剩余可选的。
既然都可选,那我们再选中 XL 这列的时候,要知道剩余可选有哪些的话我们可以聚焦到每一行,比如我们看套餐 1 这行:
image

看出来,红色和套餐 1 只在第 2 个 sku 里组合了,因为他们两点间权值为 3,而 XL 和套餐 1 则是在第 4 个 sku 里组合了,所以可见红色、XL、套餐 1是不可能在一个 sku 组合里的,所以不存在红色、XL、套餐 1这个 sku,选中红色、XL 后,套餐 1 不是剩余可选的。

我们再看套餐 2 这行,红色和套餐 2 在第 1 个 sku 和第 3 个 sku 里组合了,XL 和套餐 2 在第 1 个 sku 里组合了,所以求交集得出红色、XL、套餐 2是在第 1 个 sku 里组合了,选中红色、XL 后,套餐 2是可选的。

所以我们看这个属性是否可选,需要知道这一行的已选属性列间是否存在同个权值。
我们修改原来求交集的方法 getIntersection:

getIntersection(params) {
      const paramsVertex = params.map((id) => this.getVertexCol(id));
      const intersection = [];
      this.vertex.forEach((type, index) => {
        const row = paramsVertex.map((col) => col[index]).filter((t) => t !== 1);
        if (this.isItemEqual(row)) {
          intersection.push(type);
        }
      });
      return intersection;
},
 /*
   *  @param params
   * 传入一个交集行,判断内部是否互相相等
   */
isItemEqual(params) {
      if (params && params.includes(0)) return false;
      let weight = -1;

      // 找出权值
      if (params.length) {
        params.some((t) => {
          if (typeof t === 'number') weight = t;
          return typeof t === 'number';
        });
        if (weight === -1) {
          return this.isArrayUnions(params);
        }
      }

      return params.every((t) => {
        if (typeof t === 'number') {
          return t === weight;
        }
        return t && t.includes(weight);
      });
},
    /*
     *  @param params
     * 传入多个数组,判断是否有交集
     */
isArrayUnions(params) {
      if (!params.length || !params[0]) return false;
      return params[0].some((t) => params.every((_t) => (_t) && (_t).includes(t)));
}

这样我们就可以兼容规格下的 sku 选择了!

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值