商品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选择的基本方法了~

相关推荐
七灵微1 小时前
【后端】单点登录
服务器·前端
持久的棒棒君5 小时前
npm安装electron下载太慢,导致报错
前端·electron·npm
渔舟唱晚@6 小时前
大模型数据流处理实战:Vue+NDJSON的Markdown安全渲染架构
vue.js·大模型·数据流
crary,记忆7 小时前
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
前端·webpack·angular·angular.js
漂流瓶jz7 小时前
让数据"流动"起来!Node.js实现流式渲染/流式传输与背后的HTTP原理
前端·javascript·node.js
SamHou08 小时前
手把手 CSS 盒子模型——从零开始的奶奶级 Web 开发教程2
前端·css·web
我不吃饼干8 小时前
从 Vue3 源码中了解你所不知道的 never
前端·typescript
开航母的李大8 小时前
【中间件】Web服务、消息队列、缓存与微服务治理:Nginx、Kafka、Redis、Nacos 详解
前端·redis·nginx·缓存·微服务·kafka
Bruk.Liu8 小时前
《Minio 分片上传实现(基于Spring Boot)》
前端·spring boot·minio
鱼樱前端9 小时前
Vue3+d3-cloud+d3-scale+d3-scale-chromatic实现词云组件
前端·javascript·vue.js