vue项目中商品规格判断是否可以选取

★规格组件-SKU&SPU概念

官方话术:

  • SPU(Standard Product Unit):标准化产品单元。是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
  • SKU(Stock Keeping Unit)库存量单位,即库存进出计量的单位, 可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。

总结一下:

  • spu代表一种商品,拥有很多相同的属性。
  • sku代表该商品可选规格的任意组合,他是库存单位的唯一标识。

在这里插入图片描述

  • 如何判断组合选择的规格参数是否可以选中?
  1. 从后端可以得到所有的SKU数据
  2. 我们需要过滤出有库存的SKU数据
  3. 为了方便进行组合判断,需要计算每个SKU规格的集合数据的【笛卡尔集】
  4. 为了方便判断是否可以选择规格,可以基于笛卡尔集生成规格的【l路径字典】
  5. 此时当点击规格标签时,把选中的规格进行组合,然后去字典中判断,只要有一个存在,就证明这种组合是有效的(点击组合点击)
<template>
  <div class="goods-sku">
    <dl v-for='(item, index) in specs' :key='index'>
      <!-- 规格类型 -->
      <dt>{{item.name}}</dt>
      <dd>
        <template v-for='(tag, i) in item.values' :key='i'>
          <!-- 规格下的选项:图片或者文字 -->
          <img @click='toggle(tag, item.values)' :class='{selected: tag.selected, disabled: tag.disabled}' v-if='tag.picture' :src="tag.picture" alt="">
          <span @click='toggle(tag, item.values)' :class='{selected: tag.selected, disabled: tag.disabled}' v-else>{{tag.name}}</span>
        </template>
      </dd>
    </dl>
  </div>
</template>
<script>
import powerSet from '@/verdor/power-set.js'
const spliter = '#'
// 生成路径字典
const usePathMap = skus => {
  // 最终的字典
  const pathMap = {}
  skus.forEach(sku => {
    // 过滤掉库存为0的sku
    if (sku.inventory === 0) return
    // 把sku中的规格数据转换为普通数组
    // attrs = [蓝色,中国,10cm]
    const attrs = sku.specs.map(item => item.valueName)
    // 计算集合的笛卡尔集
    const subsets = powerSet(attrs)
    // 把集合转换为字典
    subsets.forEach(subset => {
      // 排除空集
      if (subset.length === 0) return
      // 把规格的组合拼接为路径字典的key
      const pathKey = subset.join(spliter)
      // 把结果添加到路径字典中即可
      if (pathMap[pathKey]) {
        // 已经存在该key了
        pathMap[pathKey].push(sku.id)
      } else {
        // 尚未存在,添加一个新的
        pathMap[pathKey] = [sku.id]
      }
    })
  })
  return pathMap
}

// 获取选中的所有的规格的值
const getSelectedValues = specs => {
  // result = [蓝色, undefinde, 10cm]
  const result = []
  specs.forEach((item, index) => {
    // 获取当前规格选中的标签
    const tag = item.values.find(tag => tag.selected)
    if (tag) {
      // 有选中的
      result[index] = tag.name
    } else {
      // 没有选中
      result[index] = undefined
    }
  })
  return result
}

// 更新每一个规格选项的禁用状态
const updateDisabledStatus = (specs, pathMap) => {
  // 把每一种规格的每一个选项遍历一屏
  specs.forEach((item, index) => {
    // 获取选中的值 result = [蓝色, 中国, 10cm]
    const result = getSelectedValues(specs)
    item.values.forEach(tag => {
      if (tag.selected) {
        // 如果标签本身就是选中的,不需要处理
        return
      } else {
        // 没有选中,就把当前标签的名称添加到选中的值中单独判断即可
        result[index] = tag.name
      }
      // 判断此时选中的值是否在字典中
      // 把选中的值拼接为字符串(字典的key): 过滤掉undefined
      // const pathKey = result.filter(item => item).join(spliter)
      // 获取选中的值
      console.log(result)
      const seletedValues = result.filter(item => item)
      if (seletedValues.length > 0) {
        // 把选中的值拼接为字符串(字典的key): 过滤掉undefined
        const pathKey = seletedValues.join(spliter)
        // 根据路径字典控制标签的禁用状态
        tag.disabled = !pathMap[pathKey]
      }
      // 根据pathKey去路径字典中判断是否存在
      // if (!pathMap[pathKey]) {
      //   // 此时的pathKey不在路径字典中,应该被禁用
      //   tag.disabled = true
      // }
    })
  })
}

// 初始化规格的选中状态: 根据skuId控制规格的选中
const initSkuSeletedStatus = (skuId, specs, skus) => {
  // 1、获取需要选中的规格
  const selectedSpecs = skus.find(item => item.id === skuId).specs
  if (selectedSpecs) {
    selectedSpecs.forEach(spec => {
      // 2、找到需要选中的规格列表(所有的规格选项)
      const allSpecs = specs.find(item => item.name === spec.name).values
      // 3、根据需要选中的规格获取对应的数据选项
      const ret = allSpecs.find(item => item.name === spec.valueName)
      // 4、控制规格选中
      if (ret) ret.selected = true
    })
  }
}

export default {
  name: 'GoodsSku',
  props: {
    specs: {
      type: Array,
      default: () => []
    },
    skus: {
      type: Array,
      default: () => []
    },
    skuId: {
      type: String,
      default: ''
    }
  },
  setup(props, { emit }) {
    if (props.skuId) {
      initSkuSeletedStatus(props.skuId, props.specs, props.skus)
    }
    const pathMap = usePathMap(props.skus)
    // console.log(pathMap)
    // const ret = getSelectedValues(props.specs)
    // console.log(ret)
    // 把每一个单独的选项判断一遍,并处理禁用状态
    updateDisabledStatus(props.specs, pathMap)
    // 控制选中和反选
    const toggle = (tag, list) => {
      // 判断标签是否为禁用状态
      if (tag.disabled) return
      // 当前的选中,其他的取消选中
      tag.selected = !tag.selected
      list.forEach(item => {
        if (item !== tag) {
          // 其他标签设置为不选中状态
          item.selected = false
        }
      })
      // 触发更新所有规格选项禁用状态的操作
      updateDisabledStatus(props.specs, pathMap)
      // 获取选中规格的相关商品信息(必须选中所有的规格)排除undefined
      const spec = getSelectedValues(props.specs).filter(item => item)
      if (spec.length === props.specs.length) {
        // 所有的规格都选择了,获取当前规格的详细商品信息
        // 根据选中的规格生成路径字典的key
        const pathKey = spec.join(spliter)
        // 根据路径查找路径字典中的对应的skuId
        const currentSkuId = pathMap[pathKey][0]
        // 根据当前的skuId获取sku中记录的商品信息
        const currentSku = props.skus.find(item => item.id === currentSkuId)
        // 准备传递给父组件的数据
        // specsText -> 颜色:黑色 产地:日本 尺寸:30cm
        // let specsText = ''
        // currentSku.specs.forEach(item => {
        //   specsText += item.name + ':' + item.valueName + ' '
        // })
        const specsText = currentSku.specs.reduce((result, item) => result + item.name + ':' + item.valueName + ' ', '')
        const skuInfo = {
          skuId: currentSku.id,
          price: currentSku.price,
          oldPrice: currentSku.oldPrice,
          inventory: currentSku.inventory,
          specsText: specsText
        }
        // 把获取的规格相关数据传递给父组件
        emit('sku-info', skuInfo)
      }
    }
    return { toggle }
  }
}
</script>
<style scoped lang="less">
.sku-state-mixin () {
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;
  &.selected {
    border-color: @xtxColor;
  }
  &.disabled {
    opacity: 0.6;
    border-style: dashed;
    cursor: not-allowed;
  }
}
.goods-sku {
  padding-left: 10px;
  padding-top: 20px;
  dl {
    display: flex;
    padding-bottom: 20px;
    align-items: center;
    dt {
      width: 50px;
      color: #999;
    }
    dd {
      flex: 1;
      color: #666;
      > img {
        width: 50px;
        height: 50px;
        .sku-state-mixin ();
      }
      > span {
        display: inline-block;
        height: 30px;
        line-height: 28px;
        padding: 0 20px;
        .sku-state-mixin ();
      }
    }
  }
}
</style>

将集合转化为笛卡尔集的组件

/**
 * Find power-set of a set using BITWISE approach.
 *
 * @param {*[]} originalSet
 * @return {*[][]}
 */
export default function bwPowerSet(originalSet) {
  const subSets = []

  // We will have 2^n possible combinations (where n is a length of original set).
  // It is because for every element of original set we will decide whether to include
  // it or not (2 options for each set element).
  const numberOfCombinations = 2 ** originalSet.length

  // Each number in binary representation in a range from 0 to 2^n does exactly what we need:
  // it shows by its bits (0 or 1) whether to include related element from the set or not.
  // For example, for the set {1, 2, 3} the binary number of 0b010 would mean that we need to
  // include only "2" to the current set.
  for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
    const subSet = []

    for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
      // Decide whether we need to include current element into the subset or not.
      if (combinationIndex & (1 << setElementIndex)) {
        subSet.push(originalSet[setElementIndex])
      }
    }

    // Add current subset to the list of all subsets.
    subSets.push(subSet)
  }

  return subSets
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值