10-★规格组件-禁用效果-思路分析
目标:大致了解禁用效果的整体思路,注意只是了解。
大致步骤:
- 1.根据后台返回的skus数据得到有效sku组合
- 2.根据有效的sku组合得到所有的子集集合
- 3.根据子集集合组合成一个路径字典,也就是对象。
- 4.在组件初始化的时候去判断每个规格是否点击
- 5.在点击规格的时候去判断其他规格是否可点击
- 6.判断的依据是,拿着说有规格和现在已经选中的规则取搭配,得到可走路径。
- 1.如果可走路径在字典中,可点击
- 2.如果可走路径不在字典中,禁用
11-★规格组件-禁用效果-路径字典
目的:根据后台skus数据得到可走路径字典对象
- js算法库 https://github.com/trekhleb/javascript-algorithms
- 幂集算法 https://raw.githubusercontent.com/trekhleb/javascript-algorithms/master/src/algorithms/sets/power-set/bwPowerSet.js
src/vender/power-set.js
/**
* 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;
}
src/views/goods/components/goods-sku.vue
import getPowerSet from '@/vender/power-set'
const spliter = '★'
// 根据skus数据得到路径字典对象
const getPathMap = (skus) => {
const pathMap = {}
skus.forEach(sku => {
// 1. 过滤出有库存有效的sku
if (sku.inventory) {
// 2. 得到sku属性值数组
const specs = sku.specs.map(spec => spec.valueName)
// 3. 得到sku属性值数组的子集
const powerSet = getPowerSet(specs)
// 4. 设置给路径字典对象
powerSet.forEach(set => {
const key = set.join(spliter)
if (pathMap[key]) {
// 已经有key往数组追加
pathMap[key].push(sku.id)
} else {
// 没有key设置一个数组
pathMap[key] = [sku.id]
}
})
}
})
return pathMap
}
+ setup (props) {
+ const pathMap = getPathMap(props.goods.skus)
+ console.log(pathMap)
参照示例
12-★规格组件-禁用效果-设置状态
目的:在组件初始化的时候,点击规格的时候,去更新其他按钮的禁用状态。
大致的步骤:
- 再需要更新状态的时候获取当前选中的规格数组
- 遍历所有的规格按钮,拿出按钮的值设置给规格数组,让后得到key
- 拿着key去路径字典中查找,有就可点击,没有禁用即可。
src/views/goods/components/goods-sku.vue
// 得到当前选中规格集合
const getSelectedArr = (specs) => {
const selectedArr = []
specs.forEach(spec => {
const selectedVal = spec.values.find(val => val.selected)
selectedArr.push(selectedVal ? selectedVal.name : undefined)
})
return selectedArr
}
// 更新按钮的禁用状态
const updateDisabledStatus = (specs, pathMap) => {
specs.forEach((spec, i) => {
const selectedArr = getSelectedArr(specs)
spec.values.forEach(val => {
// 已经选中的按钮不用判断
if (val.name === selectedArr[i]) return false
// 未选中的替换对应的值
selectedArr[i] = val.name
// 过滤无效值得到key
const key = selectedArr.filter(v => v).join(spliter)
// 设置禁用状态
val.disabled = !pathMap[key]
})
})
}
setup (props) {
const pathMap = getPathMap(props.goods.skus)
// 组件初始化的时候更新禁用状态
+ updateDisabledStatus(props.goods.specs, pathMap)
const clickSpecs = (item, val) => {
// 如果是禁用状态不作为
+ if (val.disabled) return false
// 1. 选中与取消选中逻辑
if (val.selected) {
val.selected = false
} else {
item.values.find(bv => { bv.selected = false })
val.selected = true
}
// 点击的时候更新禁用状态
+ updateDisabledStatus(props.goods.specs, pathMap)
}
return { clickSpecs }
}
13-★规格组件-数据通讯
目的:根据传入的skuId进行默认选中,选择规格后触发change事件传出选择的sku数据。
大致步骤:
- 根据传入的SKUID选中对应规格按钮
- 选择规格后传递sku信息给父组件
- 完整规格,传 skuId 价格 原价 库存 规格文字
- 不完整的,传 空对象
落的代码:
- 根据传人的sku设置默认选中的规格
src/views/goods/components/goods-sku.vue
skuId: {
type: String,
default: ''
}
// 初始化选中状态
const initSelectedStatus = (goods, skuId) => {
const sku = goods.skus.find(sku => sku.id === skuId)
if (sku) {
goods.specs.forEach((spec, i) => {
const value = sku.specs[i].valueName
spec.values.forEach(val => {
val.selected = val.name === value
})
})
}
}
setup (props, { emit }) {
const pathMap = getPathMap(props.goods.skus)
// 根据传入的skuId默认选中规格按钮
+ initSelectedStatus(props.goods, props.skuId)
// 组件初始化的时候更新禁用状态
updateDisabledStatus(props.goods.specs, pathMap)
- 根据选择的完整sku规格传出sku信息
- 其中传出的specsText是提供给购物车存储使用的。
src/views/goods/components/goods-sku.vue
+ setup (props, { emit }) {
const clickSpecs = (item, val) => {
// 如果是禁用状态不作为
if (val.disabled) return false
// 1. 选中与取消选中逻辑
if (val.selected) {
val.selected = false
} else {
item.values.find(bv => { bv.selected = false })
val.selected = true
}
// 点击的时候更新禁用状态
updateDisabledStatus(props.goods.specs, pathMap)
+ // 触发change事件将sku数据传递出去
+ const selectedArr = getSelectedArr(props.goods.specs).filter(v => v)
+ if (selectedArr.length === props.goods.specs.length) {
+ const skuIds = pathMap[selectedArr.join(spliter)]
+ const sku = props.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, n) => `${p} ${n.name}:${n.valueName}`, '').replace(' ', '')
+ })
+ } else {
+ emit('change', {})
+ }
+ }
src/views/goods/index.vue
<GoodsSku :goods="goods" @change="changeSku"/>
setup () {
const goods = useGoods()
// sku改变时候触发
+ const changeSku = (sku) => {
+ if (sku.skuId) {
+ goods.value.price = sku.price
+ goods.value.oldPrice = sku.oldPrice
+ goods.value.inventory = sku.inventory
+ }
+ }
+ return { goods, changeSku }
}