1. SKU和SPU概念
SPU代表一个商品,这个商品可以拥有很多属性
SKU代表这个商品可选规格的任意组合,是库存单位唯一标识
2. 路径字典
大致步骤
-
根据后台返回的sku数据得到有效sku组合(inventory > 0)
-
处理sku数组,使用power-set算法得到sku数组的子集
-
比如:['蓝色', '中国'] => ['蓝色'], ['中国'], ['蓝色', '中国']
-
-
将子集内容作为key,对应查找到的skuId作为value存入路径对象中
-
比如:['蓝色','中国'] => '蓝色☆中国': 1238978971
-
import bwPowerSet from '@/vender/power-set'
const spliter = '★'
const pathMap = {}
export const usePathMap = (skus) => {
skus.forEach((sku) => {
if (sku.inventory > 0) {
// 3.处理skus数组,参考例子:['蓝色','中国'] => ['蓝色'], ['中国'], ['蓝色','中国']
// 3.1 要先处理skus数组为上面例子的形式,将属性名映射成数组
const valueArr = sku.specs.map((value) => value.valueName)
// 3.2 使用power-set算法得到skus数组的子集
const valuePowerSet = bwPowerSet(valueArr)
// 4.1 处理子集的形式,把数组映射成字符串得到key值
valuePowerSet.forEach((arr) => {
const key = arr.join(spliter)
// 4.2 把key和skuId作为key-value存储到路径字典
// 4.2.1 如果pathMap[key]已存在,则继续Push skuID
// 4.2.2 如果pathMap[key]不存在,则设置成数组
if (key !== '') {
if (pathMap[key]) {
pathMap[key].push(sku.id)
} else {
pathMap[key] = [sku.id]
}
}
})
}
})
return pathMap
}
3. 规格组件的选中效果
大致步骤
-
// 关键数据:goods.specs = [spec, spec, spec]
// spec = {name: '颜色', values: [{valueName: '黑色'}, {valueName: '蓝色'}]}
// 选中和禁用的状态:selected和disabled,这两个状态都是加到每个按钮上的
-
绑定按钮点击事件,完成选中和取消选中
-
当前点的是选中,取反
-
当前点的是未选中,先遍历当前规格的所有按钮,全部取消选中,再当前按钮选中。
-
const changeSku = (goods, specs, spec, value) => {
if (value.disabled) return
if (value.selected) {
value.selected = false
} else {
spec.values.forEach((val) => {
if (val.name !== value.name) {
val.selected = false
}
})
value.selected = true
。。。
}
4. 规格组件-按钮禁用状态
目的:在组件初始化的时候,以及点击规格中按钮的时候,去更新其他按钮的禁用状态
-
获取当前选中的值的数组:['黑色', undefined, undefined]
-
遍历按钮:先遍历每一种规格,取出当前选中的值的数组:['黑色', undefined, undefined],再遍历每一个按钮
-
如果这个按钮已经选中,忽略判断禁用状态
-
如果这个按钮没被选中,则拿着这个按钮的值按顺序去套入,得到套入后的数组,过滤掉undefined,加入☆拼接成key,比如:蓝色☆中国,拿这个key去路径字典中查找,没有查找到就设置这个按钮为禁用状态
-
// 获取规格参数数组,参考数据:如果点击了蓝色,其他未点 => ['蓝色', undefined, undefined]
export const getSelectedArr = (specs) => {
const selectedArr = []
specs.forEach((spec) => {
const selectedVal = spec.values.find((val) => val.selected)
selectedArr.push(selectedVal ? selectedVal.name : undefined)
})
return selectedArr
}
// 更新按钮的禁用状态
export const updateDisable = (specs, pathMap) => {
// 先遍历每一种规格,取出当前选中的值的数组
specs.forEach((spec, i) => {
const selectedArr = getSelectedArr(specs)
// 再遍历规格中的每一个按钮,拿按钮的值按顺序套入
spec.values.forEach((val) => {
if (val.name !== selectedArr[i]) {
selectedArr[i] = val.name
// 去除undefined得到key值,到路径字典查找是否有该key,无则设置该按钮disabled为true
const key = selectedArr.filter((item) => item).join(spliter)
val.disabled = pathMap[key] ? false : true
}
})
})
}
const changeSku = (goods, specs, spec, value) => {
if (value.disabled) return
if (value.selected) {
value.selected = false
} else {
spec.values.forEach((val) => {
if (val.name !== value.name) {
val.selected = false
}
})
value.selected = true
// 点击按钮时更新按钮的禁用状态
updateDisable(specs, pathMap)
// 当选择了完整的sku之后,触发change事件让父组件收到更新的sku
const validSelectedArr = getSelectedArr(specs).filter((v) => v)
if (validSelectedArr.length === specs.length) {
// 完整的sku
const key = validSelectedArr.join('★')
const skuIds = pathMap[key]
const sku = goods.skus.find((sku) => sku.id === skuIds[0])
emit('change', {
skuId: sku.id,
price: sku.price,
oldPrice: sku.oldPrice,
inventory: sku.inventory,
specsText: sku.specs
.reduce((p, c) => `${p} ${c.name}:${c.valueName}`, '')
.trim()
})
} else {
emit('change', {})
}
}
}
父组件:
// 监听sku组件的自定义事件,拿到更新的sku
const changeSku = (sku) => {
console.log(sku)
// 用更新的sku去修改goods的现价和原价还有库存
goods.value.price = sku.price
goods.value.oldPrice = sku.oldPrice
goods.value.inventory = sku.inventory
}
// 组件初始化的时候根据传来的skuid默认选中按钮
export const initSelected = (goods, skuId) => {
const sku = goods.skus.find((sku) => sku.id === skuId)
// 根据sku中的spec描述信息,遍历specs数组,找到对应的按钮并选中
if (sku) {
goods.specs.forEach((spec, i) => {
// 找到sku描述的规格名
if (spec.name === sku.specs[i].name) {
spec.values.forEach((value) => {
// 找到sku描述的属性名
if (value.name === sku.specs[i].valueName) {
value.selected = true
}
})
}
})
}
}
标签:02,sku,const,鲜儿,value,详情页,key,按钮,specs From: https://www.cnblogs.com/jzhFlash/p/16660129.html