背景
又又又做了个商城,这回的商城规格发现不是个全相乘的关系。例如,假设有3种规格,每种类型的商品(sku)只需要将这三种规格都填上,且组合起来不重复就行,爱是啥值是啥值,爱有有几个sku有几个,主打一个随性。
所以我们接收到的数据可能是这样的,本文接下来的故事将围绕这个具体的示例展开:
js
const goodsSkus = [
{ 颜色: 黄, 尺码: 1, 形状: 圆 },
{ 颜色: 黄, 尺码: 2, 形状: 圆 },
{ 颜色: 黄, 尺码: 4, 形状: 圆 },
{ 颜色: 黄, 尺码: 1, 形状: 方 },
{ 颜色: 黄, 尺码: 3, 形状: 三角 },
{ 颜色: 蓝, 尺码: 1, 形状: 方 },
{ 颜色: 蓝, 尺码: 3, 形状: 圆 },
{ 颜色: 红, 尺码: 1, 形状: 三角 },
{ 颜色: 红, 尺码: 2, 形状: 方 },
]
所以当规格选中之后,希望能将可选的规格区分出来,用以保证在最后选中的结果是有效可靠的。这是这篇文章主要讨论的内容。
接下来直接是正题
目标是构建一个可选规格的池子,随着选中的规格的变化而变化。
一个简单的选中
现在我们选中了 颜色=黄 的规格,那么我们就可以得出有效的sku为:
js
const validGoodsSkus = [
{ 颜色: 黄, 尺码: 1, 形状: 圆 },
{ 颜色: 黄, 尺码: 2, 形状: 圆 },
{ 颜色: 黄, 尺码: 4, 形状: 圆 },
{ 颜色: 黄, 尺码: 1, 形状: 方 },
{ 颜色: 黄, 尺码: 3, 形状: 三角 },
]
从这里面我们可以得出有效的规格池子长这样:
ini
{
颜色: [黄],
尺码: [1, 2, 3, 4],
形状: [圆, 方, 三角],
}
这样,就可以得到个简单的有效范围了。但这样有一个缺点,我们虽然选择了黄 ,但颜色规格的其他值此时并没有被约束掉,应该也是可选的才对。这就来到了第二个问题,怎么知道某个选中规格的其他有效值是什么?
全有效值的判断
我们来思考一个问题,一个规格哪些值可选,是由什么决定的呢? 没错,是由除了该规格以外的其他规格的已选项来决定的,我们只需要把所有规格都算一遍就好了。
为了方便,现在我们弄了个规格数组:
js
const specs = [颜色, 尺码, 形状]
现在来举个例子,假如我选中了【颜色:黄 + 尺码:1】,这时候应该有哪些可选值呢?
- 颜色 的可选值由除了它以外的尺码、形状 的选中值来确定,这里只有尺码是有效值,我们来寻找尺码=1的数据:
js
const validGoodsSkus = [
{ 颜色: 黄, 尺码: 1, 形状: 圆 },
{ 颜色: 黄, 尺码: 1, 形状: 方 },
{ 颜色: 蓝, 尺码: 1, 形状: 方 },
{ 颜色: 红, 尺码: 1, 形状: 三角 },
]
将这几个sku中的颜色属性值取出来去下重,就可以得到颜色的有效值了:
js
const validSpecs = {
颜色: [黄, 蓝, 红],
}
- 尺码 的可选值由除了它以外的颜色、形状 的选中值来确定,这里只有颜色是有效值,我们来寻找颜色=黄的数据:
js
const validGoodsSkus = [
{ 颜色: 黄, 尺码: 1, 形状: 圆 },
{ 颜色: 黄, 尺码: 2, 形状: 圆 },
{ 颜色: 黄, 尺码: 4, 形状: 圆 },
{ 颜色: 黄, 尺码: 1, 形状: 方 },
{ 颜色: 黄, 尺码: 3, 形状: 三角 },
]
将这几个sku中的尺码属性值取出来去下重,就可以得到尺码的有效值了:
js
const validSpecs = {
颜色: [黄, 蓝, 红],
尺码: [1, 2, 3, 4]
}
- 形状 的可选值由除了它以外的颜色、尺码 的选中值来确定,这里只有颜色是有效值,我们来寻找颜色=黄&&尺码=1的数据:
js
const validGoodsSkus = [
{ 颜色: 黄, 尺码: 1, 形状: 圆 },
{ 颜色: 黄, 尺码: 1, 形状: 方 },
]
将这几个sku中的形状属性值取出来去下重,就可以得到尺码的有效值了:
js
const validSpecs = {
颜色: [黄, 蓝, 红],
尺码: [1, 2, 3, 4],
形状: [圆, 方]
}
由此我们得出了选中【颜色:黄 + 尺码:1】时的完整的规格有效可选值的池子。
其他实现细节(选阅)
其实基本上都说完了,但我比较臭屁,觉得实现也写得不错,所以决定写下来。
记录已选中对象
想了想选中值和规格是一对一的形式,key:value形式的结构正好了~用来储存多个键值对用Object和Map都很合适,他们用key查找插入删除在操作上都很方便(小数据量不考虑效率问题)。但Map还有一个特殊功能,记录存入的顺序,可能会在一些展示位用到它(咱用到了)。
js
const select = ref(new Map())
可选规格合集构造
除了上述的已选项,我们还需要一个规格类型数组和sku列表,就用上面提到过的specs和goodsSkus就好。
js
const specs = [颜色, 尺码, 形状]
const goodsSkus = [...]
ts
// 可选的规格值合集
const validSpecs = computed(() => {
// 所有规格种类循环
return specs.reduce((it, item) => {
// 筛选除了目前循环对应规格种类外的其他规格选中值组成新的Map类型
const currentLastSelect = new Map();
Array.from(select.value.entries())
.filter(([key, value]) => key !== item)
.forEach(([key, value]) => currentLastSelect.set(key, value));
// 筛序除当前循环规格外,其他规格满足条件的商品
const vailSkus = filterSkuBySelectMap(currentLastSelect, goodsSkus);
// 当前循环规格的可选值
it[item] = [...new Set(vailSkus.map((sku) => sku[item]) || [])];
return it;
}, {});
});
// good商品sku过滤器,筛选满足条件的商品
const filterSkuBySelectMap = (targetSelect: Map, skuList: { [key: string]: string }[]) => {
return skuList.filter((item) =>
Array.from(targetSelect.entries()).every(
([key, value]) => value === item[key],
),
);
};
其他方法(不重要,选阅的选阅)
例如选中啊、是否可选、变换成展示用列表一类的,随便写一下就行
js
// 选中or取消选中
const onSelect = (skuValue, specsName) => {
const _select = select.value;
const lastValue = _select.get(specsName);
_select.delete(specsName);
if (lastValue !== skuValue) _select.set(specsName, skuValue);
};
// 规格值是否选中
const isSelect = (specsName: string, skuValue: string, _select: Map) => {
return _select.get(specsName) === skuValue;
};
// 是否无效的规格值
const isInvalid = (specsName: string, skuValue: string, _ableSelectList) => {
return !_ableSelectList[specsName].includes(skuValue);
};
// 展示用的规格选择列表[{ [规格名]: 规格值 }] => [{key: 规格名, value: 规格值[]}]
const showSpecsList = computed(() => {
return specs.reduce((it, item) => ({
...it,
{ key: item, value: [...new Set(goodsSkus.map((skuItem) => skuItem[item]))] }
}), []);
});
// 所有规格都选择完成,筛选出的唯一的sku
const currentSelectSku = computed(() => {
const _select = select.value;
if (_select.size !== specs.length) return false;
return goodsSkus.find((sku) =>
Array.from(_select.entries()).every(
([selectKey, selectValue]) =>
sku[selectKey] === selectValue,
),
);
});
这样,就包含完一个简单的sku选择的基本方法了~