# Sku 组件封装

存货单元的英文原名为 Stock keeping unit (SKU),在某些企业中可以译成存货储备单元,简称存货单元,即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。存货单元是对于大型连锁超市 DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的存货单元号。

# 生成有效路径字典对象

image

// 生成有效路径字典对象
const getPathMap = (goods) => {
  const pathMap = {}
  // 找到有库存数据形成一个数组  1. 根据 skus 字段生成有效的 sku 数组
  const effectiveSkus = goods.skus.filter(sku => sku.inventory > 0)
  // 通过这个新的数组可以找到数组中的 valueName 形成一个新数组  2. 根据有效的 sku 使用子集算法 [1,2] => [1],[2],[1,2]
  effectiveSkus.forEach(sku => {
    // 2.1 获取匹配的 valueName 组成的数组
    const selectedValArr = sku.specs.map(arr => arr.valueName)
    // 对这新数组进行求子集 获取到一个都是子集的数组
    const valueArrPowerSet = powerSet(selectedValArr)
    valueArrPowerSet.forEach(arr => {
      // 先将数组转成字符串
      const key = arr.join('-')
      // 进行对象添加
      if (pathMap[key]) {
        pathMap[key].push(sku.id)
      } else {
        pathMap[key] = [sku.id]
      }
    })
  })
  return pathMap
}

1. 首先从获取到的 goods 中获取 skus 这个整个数组,对其进行 filter 过滤,过滤掉库存为 0 的数,这里是 sku.inventory 这个字段代表库存。过滤后获取到一个新的数组 effectiveSkus。

2. 对这个 effectiveSkus 数组(过滤后的 skus)进行遍历,其中的 specs 里面存这是这个完整商品选择时的规格。在每一个 sku 中遍历整个 specs 数组,将其里面的的 valueName 进行映射,形成一个只有由 valueName 的组成的数组 selectedValArr,每一个 sku 都有这样一个数组 selectedValArr。

3. 接着对这个数组 selectedValArr 进行求子集,这里用到一个求子集的算法,直接导入使用即可。求子集就是例如 [1,2] => [1],[2],[1,2] 这样。然后获取到一个新的数组 valueArrPowerSet,这个数组里面存的都是子集。例如原本数组是 [红色,中国],求完子集后具返回 [[红色],[中国],[红色,中国]]。这里就是相对应的商品的名称。

4. 生成的一个新的数据 valueArrPowerSet 是包含子集的数组,也就是数组里面包数组,使用再对这个数据进行遍历,因为是要生成一个字典对象,使用需要将数组使用 join 转成字符串,将 valueArrPowerSet (子集数组) 中的每一个数组变成一个字符串 key。

5. 接着就可以通过这个由子集数组转成字符串的 key 作为属性名进行添加属性,使用 pathMap [key],来进行属性的添加,如果 pathMap [key] 存在的话就将当前的 sku 的 id 直接 push 到这个属性值中,因为一个子集可能在多处会使用到(子集越小,相关联的 skuId 越多,例如只有一个规格,那么他的组合方式可能很多),所以属性是一个数组的形式,如果 pathMap [key] 不存在则的则把 sku 的 id 当成数组进行赋值。最后将有效路径字典对象返回出去。

# 切换时更新禁用状态

1. 先封装获取选中匹配数组。首先先获取选中匹配数组,例如 [' 黑色 ',undefined,undefined] 这样。对传进来 specs (商品的规格数组就是每一个数组就是一个一种规格), 对这个大的规格数组进行遍历,每一个小规格数组进行 find 查找数组中的哪个元素被选中,然后返回那个被选中的元素 selectedVal。如果当前这个规格有被选中则将这个元素的 name,也就是 selectedVal.name,push 到数组 arr 中,如果没有则 push undefined。最后存储总的规格将数组 arr 返回出去。

2. 先调用获取选中匹配数组的函数,获取选中匹配的数据。然后对总规格里面的小规格 spec 进行遍历,把小规格中的 name 字段填充到对应的位置,这里可以使用 index 来确定位置,例如 selectedValues [index] = val.name

3. 对填充后的数组进行过滤,筛选掉 undefined 并且使用 join 方法形成一个有效的 key。

4. 使用这个 key 在 pathMap 中进行查找,如果在 pathMap 中查找到该属性有值的话则使用不禁用,否则则进行禁用。

// 初始化禁用状态
const initDisabledStatus = (specs, pathMap) => {
  specs.forEach(spec => {
    spec.values.forEach(val => {
      if (pathMap[val.name]) {
        val.disabled = false
      } else {
        val.disabled = true
      }
    })
  })
}
// 获取选中匹配数组 [' 黑色 ',undefined,undefined]
const getSelectedValues = (specs) => {
  const arr = []
  specs.forEach(spec => {
    const selectedVal = spec.values.find(val => val.selected)
    arr.push(selectedVal ? selectedVal.name : undefined)
  })
  return arr
}
// 切换时更新禁用状态
const updateDisabledState = (specs, pathMap) => {
  specs.forEach((spec, index) => {
    const selectedValues = getSelectedValues(specs)
    spec.values.forEach(val => {
      selectedValues[index] = val.name
      const key = selectedValues.filter(value => value).join('-')
      if (pathMap[key]) {
        val.disabled = false
      } else {
        val.disabled = true
      }
    })
  })
}

思路是这样的:由于在之前已经执行过初始化禁用状态。所以现在看到的都是可以选择的。比如我选择了第一行中的一个,然后代码中他进行了一次 spec.values.forEach 遍历,这个既然能选中了就是说明是不被禁用的。接着就是对第二行进行遍历,如果第二行出现有一个 key 在 path 中找不到则会被禁用。就是他会根据你现在已经选择的规格形成的数组,然后对里面每一种规格进行遍历,每一种规格又会对里面的小规格进行遍历,然后过滤形成一个 key,再从 pathMap 中获取。

他每次从大规格(规格种类)开始遍历,进入遍历后先获取到当前的选中规格数组。接着遍历小规格(一个规格中的不同选项),将每一个小规格都拿到当前的选中规格数组中进行填充(说白了将当前这种规格的小规格就是拿进去选中规格数组中然后不断靠之前选中的和当前这一行(这一种规格)的不断拿去 pathMap 试),然后进行过滤生成一个 key 字段,拿这个 key 字段在 pathMap 中看有没有值,有就不禁用,没有就禁用。