商品SKU选择器实现思路,包简单

背景

又又又做了个商城,这回的商城规格发现不是个全相乘的关系。例如,假设有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选择的基本方法了~

相关推荐
崔庆才丨静觅6 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60617 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了7 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅7 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅7 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅8 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment8 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅8 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊8 小时前
jwt介绍
前端
爱敲代码的小鱼8 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax