纠结哈,还能再简单点嘛?...SKU算法

当时某团购项目需要匹配sku,看了同事写的sku,看着看着懵圈了。后面自行找了哈。发现sku算法大多基本归于笛卡尔积,邻接矩阵等。
参考文献:sku 多维属性状态判断算法
商品多种规格属性的选择(sku 算法)
https://github.com/zaxlct/vue-sku

SKU

作用:让用户选择商品的规格,选择的时候,组件的选中状态要进行更新,没有库存的要禁用。

- 步骤一:初始化规格渲染

- 步骤二:点击规格更新选中状态。(seleted)

激活当前规格,取消同排其他规格。

- 步骤三:点击规格更新禁用状态。(重点)

核心原理:当前的规格SKU,或者组合起来的规格Sku,**库存为0时禁用**。

生成**有效路径字典**是为了协助和简化这个过程。

实现思路: 1.根据库存字段得到有效的SKU .
           2.根据有效的SKU数组使用幂等算法powerSet算法得到所有子集。
		   3.根据子集生成路径字典对象pathMap,匹配不上则禁用。
点击规格更新禁用状态: 使用name字段作为key去路径字典pathMap做匹配

已经选择的规格放在对应索引的数组里,当无undefined,那么用户匹配了所有有效规格。此时可以产出sku数  据。

- 步骤四:产出选择的sku数据

把产出的sku信息,拼接为路径字典的key,去路径字典pathMap找。

image

<template>
    <h1>SKUccA</h1>
    <ul class="goods-sku">
        <li class="group" v-for="(item, index) in goods.specs">
            <span class="lab">{{ item.name }}</span>
            <div class="guige">
                <template v-for="(val, sindex) in item.values">
                    <span :class="['guige__items', { 'selected': val.selected ,'disabled':val.disabled}]"
                        @click="changeSku(item, val)">{{ val.name }}</span>
                </template>
            </div>
        </li>
    </ul>
</template>

<script setup>
/*
 * sku步骤:
 * 1.展示规格列表
 * 2.点击更新选中状态 。  点击则选中(添加Class的selected),并同排取消选中状态
 * 3.点击规格更新禁用状态 - 生成有效路径字典
 *   3.1  使用name字段作为key去路径字典pathMap做匹配
  4.产出sku数据
 */
// http://pcapi-xiaotuxian-front-devtest.itheima.net/goods?id=1369155859933827074
import { ref, reactive, onMounted } from 'vue'
// 商品数据
import { goodsData } from './data.js'
import powerSet from './power-set.js'

let goods = ref(goodsData);
console.log('goods.value---',goods.value);

let pathMap = {}

onMounted(() => {
    pathMap = getPathMap(goods.value.skus);
    console.log('pathMap---', pathMap);
    initDisabledStatus(goods.value.specs,pathMap);
})
// 切换选中状态
/**
 * @param {同一排的对象} item 
 * @param {当前点击项} val 
 */
const changeSku = (item, val) => {
    if(val.disabled) return
    console.log('切换选中状态');
    // 点击的是未选中,把同一个规格的其他取消选中,当前点击项选中。 点击的已选中则直接取消
    if (val.selected) {
        val.selected = false
    } else {
        item.values.forEach(val => val.selected = false)
        val.selected = true;
    }
    // console.log(item);
    // const arr = getSelectedValus(goods.value.specs);
    // console.log('选中的规格',arr);
    // 点击按钮时更新
    updateDisabledStatus(goods.value.specs,pathMap);
    // 产出SKU对象数据
    const index = getSelectedValues(goods.value.specs).findIndex(item=>item===undefined)
    if(index>-1){
        console.log('找到了,信息不完整--555');
    }else{
        console.log('没有找到,信息完整,可以产出');
    }
    // 获取sku对象
    const  key = getSelectedValues(goods.value.specs).join('-');
    console.log('key11111----',key);
    const skuIds =  pathMap[key];
    console.log('skuIds-------',skuIds);
    // 以skuIds作为匹配项去goods.value.skus数组中查找
    let skuObj = goods.value.skus.find(item=>item.id === skuIds[0]);
    console.log('sku对象为',skuObj);
}
/** 
 * 根据skus生成有效路径字典对象 (根据库存)
 * @param {*} skus 
 */
const getPathMap = (skus) => {
    console.log(1222, skus);
    let pathMap = {};
    // 1.根据skus字段生成有效的sku数组
    const effectiveSkus = skus.filter(sku => sku.inventory > 0);
    console.log('有效的sku', effectiveSkus);
    //2.根据有效的sku数组,使用算法 [1,2]=>[[1],[2],[1,2]]
    effectiveSkus.forEach(sku => {
        // 2.1 获取匹配的valueName组成的数组
        const selectedValArr = sku.specs.map(val => val.valueName);
        // console.log('selectedValArr--', selectedValArr);
        // 2.2  使用算法获取子集
        const valueArrPowerSet = powerSet(selectedValArr);
        // console.log('算法获得子集--', valueArrPowerSet);
        //3. 把得到的子集生成最终的路径字典对象
        valueArrPowerSet.forEach(arr => {
            //初始化key ,数组join 
            const key = arr.join('-')
            //如果已经存在当前key了,就往数组中直接添加skuId,如果不存在key,直接赋值
            // 如果没有就先初始化一个空数组
            // console.log('pathMap[key]----',pathMap[key]);
            if (pathMap[key]) {
                pathMap[key].push(sku.id)
            } else {
                pathMap[key] = [sku.id]
            }

        })
    });
    return pathMap
}

/**
 * 初始化规格时禁用状态
 * @param {*} specs 
 * @param {*} pathMap 
 */
const initDisabledStatus =(specs,pathMap)=>{
    specs.forEach(spec => {
        spec.values.forEach(val=>{
            // console.log('规格',pathMap[val.name]);
            if(pathMap[val.name]){
                val.disabled = false
            }else{
                val.disabled = true
            }
        })
    });
}
// 获取当前选中项的匹配数组(规格集合)
const getSelectedValues = (specs) =>{
    // 选中规格的数组
    const selectedArr = []
    specs.forEach((spec, index) => {
        // 目标:找到values中selected为true的项,然后把它的name字段添加到数组对应的位置
        const selectedVal = spec.values.find(val => val.selected)
        selectedArr.push(selectedVal ? selectedVal.name:undefined);
        // if (selectedVal) {
        // selectedArr[index] = selectedVal.name
        // } else {
        // selectedArr[index] = undefined
        // }
    })
    return selectedArr

}
/**
 * 切换时更新按钮禁用状态
 * @param {*} specs 
 * @param {*} pathMap 
 */
const updateDisabledStatus =(specs,pathMap)=>{
    // 遍历每一种规格
  specs.forEach((item, i) => {
    // 拿到当前选择的项目
    const selectedArr = getSelectedValues(specs)
    // 遍历每一个按钮
    item.values.forEach(val => {
      if (!val.selected) {
        selectedArr[i] = val.name
        // 去掉undefined之后组合成key
        const key = selectedArr.filter(value => value).join('-')
        // val.disabled = !pathMap[key]
        if(pathMap[key]){
            val.disabled = false;
        }else{
            val.disabled = true;
        }
      }
    })
  })
}


</script>

<style scoped>
.goods-sku {
    padding: 50px;
}

.lab {
    color: blue;
}

.group {
    display: flex;
    margin-bottom: 20px;
}

.lab {
    margin: 10px 30px;
}

.guige {
    padding: 0;
    margin: 0;
    display: flex;
    flex: 1;
}

.guige__items {
    padding: 10px 20px;
    font-size: 20px;
    margin:0 20px;
}

.guige__items.selected {
    color: lightseagreen;
    border: 1px solid lightseagreen;
}
.guige__items.disabled{
    background: gray;
    opacity: .7;   
    cursor: not-allowed;
}
</style>

幂等算法power-set.js , 获取某集合所有子集

export default function bwPowerSet (originalSet) {
    const subSets = []
  
    //我们将有 2^n 种可能的组合(其中 n 是原始集合的长度)。
    //这是因为对于原始集合的每个元素我们都会决定是否包含
    //是否(每个集合元素有 2 个选项)。
    const numberOfCombinations = 2 ** originalSet.length
  
    //0 到 2^n 范围内的每个二进制表示的数字都完全符合我们的需要:
    //它通过其位(0 或 1)显示是否包含集合中的相关元素。
    //例如,对于集合 {1, 2, 3},二进制数 0b010 意味着我们需要
    //仅包含“2”到当前集合中。
    for (let combinationIndex = 0; combinationIndex < numberOfCombinations; combinationIndex += 1) {
      const subSet = []
  
      for (let setElementIndex = 0; setElementIndex < originalSet.length; setElementIndex += 1) {
       //决定是否需要将当前元素包含到子集中。
        if (combinationIndex & (1 << setElementIndex)) {
          subSet.push(originalSet[setElementIndex])
        }
      }
  
     //将当前子集添加到所有子集列表中。
      subSets.push(subSet)
    }
  
    return subSets
  }

数据提供:data.js

//data.js
export const goodsData = {
	// 规格
	"specs": [
		{
			"name": "颜色",
			"id": "1369139574067957762",
			"values": [
				{
					"name": "黑色",
				},
				{
					"name": "蓝色",
				}
			]
		},
		{
			"name": "尺寸",
			"id": "1369141324204216321",
			"values": [
				{
					"name": "30cm",

				},
				{
					"name": "20cm",

				},
				{
					"name": "10cm",

				}
			]
		},
		{
			"name": "产地",
			"id": "1369141916305723393",
			"values": [
				{
					"name": "日本",
				},
				{
					"name": "中国",
				}
			]
		}
	],
	// 所有存在的skus
	"skus": [{
		"id": "1369155862131642369",
		"skuCode": "goods-sku-001",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "20cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155863389933570",
		"skuCode": "goods-sku-007",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "20cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155864430120962",
		"skuCode": "goods-sku-003",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 20,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "10cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155865461919746",
		"skuCode": "goods-sku-009",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "10cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155866451775490",
		"skuCode": "goods-sku-005",
		"price": "128.00",
		"oldPrice": "210.00",
		"inventory": 99918,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "30cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155867420659714",
		"skuCode": "goods-sku-011",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 999,
		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "30cm"
		}, {
			"name": "产地",
			"valueName": "中国"
		}]
	}, {
		"id": "1369155868389543937",
		"skuCode": "goods-sku-002",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 99814,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "20cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}, {
		"id": "1369155869354233857",
		"skuCode": "goods-sku-008",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "20cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}, {
		"id": "1369155870306340866",
		"skuCode": "goods-sku-004",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "10cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}, {
		"id": "1369155871241670658",
		"skuCode": "goods-sku-010",
		"price": "128.00",
		"oldPrice": "200.00",
		"inventory": 0,

		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "10cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}, {
		"id": "1369155872197971970",
		"skuCode": "goods-sku-006",
		"price": "148.00",
		"oldPrice": "190.00",
		"inventory": 99952,
		"specs": [{
			"name": "颜色",
			"valueName": "蓝色"
		}, {
			"name": "尺寸",
			"valueName": "30cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}, {
		"id": "1369155873162661889",
		"skuCode": "goods-sku-012",
		"price": "148.00",
		"oldPrice": "210.00",
		"inventory": 0,
		"specs": [{
			"name": "颜色",
			"valueName": "黑色"
		}, {
			"name": "尺寸",
			"valueName": "30cm"
		}, {
			"name": "产地",
			"valueName": "日本"
		}]
	}]
}
posted @ 2024-05-05 23:55  邪儿莫  阅读(132)  评论(0编辑  收藏  举报