vue中SKU实现

通过发送请求获取商品详情数据,包括商品规格(specs)和库存信息(skus)。

选中状态更新:根据当前状态进行激活或取消激活的逻辑,通过为每个规格项添加的“selected”字段来标识是否激活,同时利用样式处理,通过动态类属性来直观地显示选中状态。

禁用状态更新:基于库存情况来确定规格是否禁用,通过生成有效路径字典来简化匹配过程。具体步骤包括获取有效 Sku 数组、生成子集以及构建路径字典对象。

初始化规格禁用:通过遍历规格对象,利用“name”字段与路径字典进行匹配来确定是否禁用,并通过增加“disabled”字段和动态类名来实现显示上的禁用效果。

点击时组合禁用更新:当用户点击规格时,通过特定步骤获取选中项数组并进行匹配来更新禁用状态。

产出有效 SKU 信息:以已选择项数组中不存在“undefined”来判断用户已选择所有有效规格,然后通过拼接已选数组为路径字典的键来获取相应的 SKU 信息对象。

vue

<script setup>
import { onMounted, ref } from "vue";
import axios from "axios";
// 导入幂级算法
import bwPowerSet from "./bwPowerSet";

// 存储用于在页面中展示的规格数据
const specs = ref([]);
// 创建 UI 状态 (选中、禁用)
const UIState = ref([]);

// 声明规格查询字典
let pathMap = {};
 
//获取商品详情数据
function requestGoodsApi(id) {
  return axios.get("https://apipc-xiaotuxian-front.itheima.net/goods", {
    params: { id },
  });
}

// 根据规格数据创建其对应的界面状态
function createUIStatus(specs) {
  // UI 状态数组
  const UIStatus = [];
  // 遍历源规格分组
  specs.forEach((spec) => {
    // 创建规格分组
    const group = [];
    // 遍历具体的规格选项
    spec.values.forEach(() => {
      // 设置每一个规格选项的选中状态和禁用状态(初始值)
      group.push({ selected: false, disabled: false });
    });
    // 将规格组对象添加到拷贝结果数组中
    UIStatus.push(group);

    
  });
  // 返回 UI 状态数组
  return UIStatus;
  
}

// 设置规格的选中状态
function setSelected(index, i) {
  // 获取当前用户点击的规格
  const current = UIState.value[index][i];
  // 获取当前用户点击的规格对应的规格组
  const group = UIState.value[index]; 
  // 如果当前规格已经是禁用状态, 不能被选择, 所以阻止代码继续执行
  if (current.disabled) return;
  // 如果用户点击的规格已经是选中的
  if (current.selected) {
    // 让其取消选中
    current.selected = false;
  } else {
    // 先将该规格中的所有规格取消选中
    group.forEach((item) => (item.selected = false));
    // 将当前用户点击的规格设置为选中
    current.selected = true;
  }
  // 用户选择规格后更新规格的禁用状态
  setDisabled();
}

// 设置规格的禁用状态
function setDisabled() {
  // 遍历每一组规格数据
  specs.value.forEach((spec, index) => {
    // 获取用户选择的规格名称数组
    const selected = getUserSelected();
    // 遍历这一组规格数据中的具体规格
    spec.values.forEach((value, i) => {
      // 如果当前规格已经被选中了, 说明它可以选, 不需要被禁用
      if (UIState.value[index][i].selected) return;
      // 将当前规格名称放入用户选择的规格数组名称中, 待匹配
      selected[index] = value.name;
      // 检测当前规格是否可以选择
      const key = selected.filter((name) => name).join("_");
      // 如不能选择, 设置当前规格的 disabled 为 true
      UIState.value[index][i].disabled = !(key in pathMap);
    });
  });
}

// 获取用户选择的规格名称数组
function getUserSelected() {
  // 声明用于存储用户选择的规格名称数组
  const names = [];
  // 遍历规格组
  specs.value.forEach((spec, index) => {
    // 查找当前规格组中被选中的规格的索引
    const selectedIndex = UIState.value[index].findIndex(
      (item) => item.selected
    );
    // 如果找到了
    if (selectedIndex !== -1) {
      // 将该规格放在它自己的位置上
      names[index] = spec.values[selectedIndex].name;
    } else {
      // 如果没有找到, 当前规格使用 undefined 进行占位
      names[index] = undefined;
    }
    //获取到选中的规格
  console.log(names);

  });
  // 返回用户选择的规格名称数组
  return names;
  
}

// 创建规格查询字典
function createPathMap(skus) {
  // 过滤出有库存的商品规格组合
  skus
    .filter((sku) => sku.inventory > 0)
    // 遍历有库存的商品规格组合
    .forEach((sku) => {
      // 将当前遍历的规格组合中的规格名称临时存到一个数组中
      // ['蓝色', '20cm', '中国']
      const valueNames = sku.specs.map((spec) => spec.valueName);
      // 获取用户可以选择的所有可能的规格及规格组合
      // ['蓝色', '20cm']
      // [['蓝色'], ['20cm'], ['蓝色', '20cm']]
      const sets = bwPowerSet(valueNames).filter((set) => set.length > 0);
      // 获取当前商品的规格数量, 将用于判断某个规格是否是完整的
      const max = valueNames.length;
      // 遍历用户可以选择的所有可能的规格及规格组合
      sets.forEach((set) => {
        // 将规格名称以 _ 进行拼接
        const key = set.join("_");
        // 用于判断当前规格是否是完整的
        const isCompleted = set.length === max;
        // 判断规格查询对象中是否已经存储了当前规格
        if (!(key in pathMap)) {
          // 如果当前规格是完整的
          if (isCompleted) {
            // 将当前规格或规格组合添加到规格查询对象中并赋值 sku id
            pathMap[key] = sku.id;
          } else {
            // 将当前规格或规格组合添加到规格查询对象中
            pathMap[key] = null;
          }
        }
      });
     
    });
  return pathMap;

}

// 组件挂载完成之后
onMounted(async () => {
  // 获取商品详情数据
  const response = await requestGoodsApi("1369155859933827074");
  // 保存规格数据
  specs.value = response.data.result.specs;
  // 为规则数据附加 UI 状态
  UIState.value = createUIStatus(specs.value);
  // 创建规格查询对象
  pathMap = createPathMap(response.data.result.skus);
  
  // 设置规格的初始禁用效果
  setDisabled();
});
</script>

<template>
  <div class="goods-sku">
    <dl v-for="(spec, index) in specs" :key="spec.id">
      <dt>{{ spec.name }}</dt>
      <dd>
        <template v-for="(item, i) in spec.values">
          <img
            v-if="item.picture"
            :class="{
              selected: UIState[index][i].selected,
              disabled: UIState[index][i].disabled,
            }"
            @click="setSelected(index, i)"
            :src="item.picture"
            :alt="item.name"
          />
          <span
            v-else
            :class="{
              selected: UIState[index][i].selected,
              disabled: UIState[index][i].disabled,
            }"
            @click="setSelected(index, i)"
            >{{ item.name }}</span
          >
        </template>
      </dd>
    </dl>
  </div>
</template>

<style scoped>
.goods-sku {
  padding-left: 10px;
  padding-top: 20px;
}

.goods-sku dl {
  display: flex;
  align-items: center;
}

.goods-sku dl dt {
  width: 50px;
  color: #999;
}

.goods-sku dl dd {
  flex: 1;
  color: #666;
}

.goods-sku dl dd > img {
  width: 50px;
  height: 50px;
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;
  display: block;
  float: left;
}

.goods-sku dl dd > img.selected {
  border-color: #27ba9b;
}

.goods-sku dl dd > img.disabled {
  opacity: 0.6;
  border-style: dashed;
  cursor: not-allowed;
}

.goods-sku dl dd > span {
  display: inline-block;
  height: 30px;
  line-height: 28px;
  padding: 0 20px;
  border: 1px solid #e4e4e4;
  margin-right: 10px;
  cursor: pointer;
}

.goods-sku dl dd > span.selected {
  border-color: #27ba9b;
}

.goods-sku dl dd > span.disabled {
  opacity: 0.6;
  border-style: dashed;
  cursor: not-allowed;
}
</style>

bowPowerSet.js

export default function bwPowerSet(originalSet) {
  // 初始化一个空数组用于存储子集
  const subSets = [];
  // 计算原始集合的元素个数
  const numberOfCombinations = 2 ** originalSet.length;
  // 循环生成所有可能的组合
  for (
    let combinationIndex = 0;
    combinationIndex < numberOfCombinations;
    combinationIndex += 1
  ) {
    // 初始化一个空数组用于存储当前组合的子集
    const subSet = [];
    // 遍历原始集合的每个元素
    for (
      let setElementIndex = 0;
      setElementIndex < originalSet.length;
      setElementIndex += 1
    ) {
      // 检查当前元素是否在当前组合中
      if (combinationIndex & (1 << setElementIndex)) {
        // 如果是,则将该元素添加到子集中
        subSet.push(originalSet[setElementIndex]);
      }
    }
    // 将当前子集添加到子集数组中
    subSets.push(subSet);
  }
  // 返回所有生成的子集
  return subSets;
}

相关推荐

  1. vueSKU实现

    2024-06-06 13:58:09       8 阅读
  2. vue实现锚点定位功能

    2024-06-06 13:58:09       35 阅读
  3. Vue的 keep-alive 实现原理

    2024-06-06 13:58:09       47 阅读
  4. vue2实现记住密码功能

    2024-06-06 13:58:09       7 阅读
  5. Vue 实践的理解

    2024-06-06 13:58:09       21 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-06 13:58:09       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-06 13:58:09       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-06 13:58:09       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-06 13:58:09       20 阅读

热门阅读

  1. 2024前端面试准备-HTML&CSS

    2024-06-06 13:58:09       10 阅读
  2. 深度学习中训练集、验证集和测试集的不同作用

    2024-06-06 13:58:09       9 阅读
  3. Linux学习—Linux服务和守护进程

    2024-06-06 13:58:09       8 阅读
  4. Overall timing accuracy 和Edge placement accuracy 理解

    2024-06-06 13:58:09       8 阅读
  5. flutter 适配屏幕宽高工具

    2024-06-06 13:58:09       10 阅读
  6. 通过nginx弄一个滑块加图片的人机验证

    2024-06-06 13:58:09       10 阅读
  7. 渗透测试之Web安全系列教程(二)

    2024-06-06 13:58:09       8 阅读
  8. 计算机网络——网络安全

    2024-06-06 13:58:09       9 阅读
  9. 【TB作品】msp430f5529单片机,dht22,烟雾传感器

    2024-06-06 13:58:09       10 阅读
  10. Selenium自动化测试入门:设置等待时间

    2024-06-06 13:58:09       8 阅读