最近接到一个需求类似于商城选购的一个sku列表,大致要实现的效果如下:
在这里插入图片描述
sku的专业名词解释为:

库存保有单位即库存进出计量的单位,
可以是以件、盒、托盘等为单位。SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理

但我个人理解则为:当你选择到某一个属性,与这个相关的属性应该会发生相对应的变化,这里的变化指的是这个选项是否是可选。
假设我现在的数据结构如下:

data: [ // 库存
        { id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
        { id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
        { id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
        { id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
        { id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
        { id: "6", specs: ["红色", "套餐一", "64G"], total: 120 }
      ],
      commoditySpecs: [ // 商品类型 ["红色", "紫色", "白色", "黑色"] ["套餐一", "套餐二", "套餐三", "套餐四"] ["64G", "128G", "256G"]
        { key: "color", title: "颜色", list: [{ id: "red", name: "红色", disable: false, active: false }, { id: "zise", name: "紫色" }, { id: "white", name: "白色" }, { id: "black", name: "黑色" }, { id: "blue", name: "蓝色" }] },
        { key: "status", title: "套餐", list: [{ id: "one", name: "套餐一", disable: false }, { id: "two", name: "套餐二" }, { id: "three", name: "套餐三" }, { id: "four", name: "套餐四" }, { id: "five", name: "套餐五" }] },
        { key: "size", title: "内存", list: [{ id: "small", name: "64G" }, { id: "mini", name: "128G" }, { id: "big", name: "256G" }] }
      ],

如果我们现在选择到的颜色类型为"红色",那么这个颜色可以选择的套餐和内存为,“套餐一"和"64G”,效果如下:
在这里插入图片描述
同理如果你继续往下面选择"套餐一",则只会有"64G"这个选项。

思路捋清楚之后,我就思考一下代码如何实现,其实sku的列表的难点在于它们之间状态的联动。那么我们可以把data中的specs数据看作为一个"顶点",那么这个"顶点"会与其它的元素进行一个相关联。
比如现在的库存data为:

data: [ // 库存
        { id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
        { id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
        { id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
        { id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
        { id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
        { id: "6", specs: ["红色", "套餐一", "64G"], total: 120 }
      ],

我们就可以找出与"紫色"相关联的相邻节点,那么在这里与“紫色”相关联的节点为,[“套餐一”,“64G”,“套餐一”,“128G”,“套餐二”,“128G”],以此类推"黑色"相关联的节点为:[“套餐三”,“256G”]等等,如果我们能够获取相邻的节点则可以判断出它是否可以进行点击。但是在这个过程中,我们需要主要数组去重这样的一个小细节即可。

initData () {
      this.data.forEach(v => {
        let specs = v.specs;
        specs.forEach(s => {
          if (!this.obj[s]) {
            this.obj[s] = specs;
          } else {
            this.obj[s] = this.obj[s].concat(specs);
          }
        });
      });
      console.log(this.obj);
    },
    getVertex (name) {
      if (!this.obj[name]) {
        return [];
      }
      return Array.from(new Set(this.obj[name].filter(v => v !== name)));
    },
    console.log(this.getVertex("红色")); // ["套餐一","64G"]

那么还有一个技术难点就是再次点击某个列表取消选择的时候,我的思路如下:
(1)取消选择的时候应去判断当前是否还有被选择到元素,如果没有则让它回到初始的状态,则是为全部都没有被选择的情况
(2)如果取消选择存在有其它元素被选择的情况,则需要获取到其它元素关联的并集。从而决定属性是否可选。
完整代码如下:

<template>
  <div class="sku">
    <div class="list" v-for="(item,index) in commoditySpecs" :key="index">
      <div class="title">{{item.title}}</div>
      <div class="shopList">
        <span
          v-for="(shopItem,sIndex) in item.list"
          :key="sIndex"
          class="shopList-item"
          :class="{disable:shopItem.disable,active:shopItem.active}"
          @click="handClickFun(shopItem,sIndex,item.title,item.key)"
        >{{shopItem.name}}</span>
      </div>
    </div>
    <div class="tips">
      <p>选择的颜色是:{{colorName}}</p>
      <p>选择的套餐是:{{statusName}}</p>
      <p>选择的内存是:{{sizeName}}</p>
      <p>总数为:{{cTotal}}</p>
    </div>
  </div>
</template>

<script>

export default {
  name: "sku",
  data () {
    return {
      data: [ // 库存
        { id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
        { id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
        { id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
        { id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
        { id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
        { id: "6", specs: ["红色", "套餐一", "64G"], total: 120 }
      ],
      commoditySpecs: [ // 商品类型 ["红色", "紫色", "白色", "黑色"] ["套餐一", "套餐二", "套餐三", "套餐四"] ["64G", "128G", "256G"]
        { key: "color", title: "颜色", list: [{ id: "red", name: "红色", disable: false, active: false }, { id: "zise", name: "紫色" }, { id: "white", name: "白色" }, { id: "black", name: "黑色" }, { id: "blue", name: "蓝色" }] },
        { key: "status", title: "套餐", list: [{ id: "one", name: "套餐一", disable: false }, { id: "two", name: "套餐二" }, { id: "three", name: "套餐三" }, { id: "four", name: "套餐四" }, { id: "five", name: "套餐五" }] },
        { key: "size", title: "内存", list: [{ id: "small", name: "64G" }, { id: "mini", name: "128G" }, { id: "big", name: "256G" }] }
      ],
      item: {},
      obj: {}
      // colorName: "", // 颜色名称
      // statusName: "", // 套餐名称
      // sizeName: "" // 内存名称
    };
  },
  computed: {
    cTotal () { // 计算总数
      let color = this.colorName;
      let status = this.statusName;
      let sizeName = this.sizeName;
      let str = color + "" + status + "" + sizeName;
      let obj = this.data.find(v => v.specs.join("") === str);
      if (obj) {
        return obj.total;
      }
      return 0;
    },
    colorName () {
      let data = this.commoditySpecs[0].list.find(v => v.active);
      if (data) {
        return data.name;

      }
      return "";
    },
    statusName () {
      let data = this.commoditySpecs[1].list.find(v => v.active);
      if (data) {
        return data.name;

      }
      return "";
    },
    sizeName () {
      let data = this.commoditySpecs[2].list.find(v => v.active);
      if (data) {
        return data.name;
      }
      return "";
    }
  },
  methods: {
    // 设置相关元素的状态
    dfs (data, relation) {
      if (!data || data.length === 0) {
        return;
      }
      let stack = [];
      data.forEach(v => {
        stack.push(v);
      });
      while (stack.length) {
        const result = stack.shift();
        if (relation.includes(result.name)) {
          this.$set(result, "disable", false);

        } else {
          this.$set(result, "disable", true);
          if (result.disable && result.active) {
            this.$set(result, "active", false);
          }
        }
        if (result.list && result.list.length > 0) {
          stack = [...stack, ...result.list];
        }
      }
      return data;
    },
    // 初始化所有都是可以选择
    init () {
      this.commoditySpecs.forEach(v => {
        let list = v.list;
        list.forEach(s => {
          this.$set(s, "disable", false);
          this.$set(s, "active", false);
        });
      });
    },
    // 初始化数据
    initData () {
      this.data.forEach(v => {
        let specs = v.specs;
        specs.forEach(s => {
          if (!this.obj[s]) {
            this.obj[s] = specs;
          } else {
            this.obj[s] = this.obj[s].concat(specs);
          }
        });
      });
      console.log(this.obj);
    },
    getVertex (name) {
      if (!this.obj[name]) {
        return [];
      }
      return Array.from(new Set(this.obj[name].filter(v => v !== name)));
    },
    handClickFun (item, sIndex, title, key) {
      this.item = item;
      if (item.disable) {
        return;
      }
      if (!item.active) { // 没有选择的情况
        // 当前列取消选择
        let currentData = this.commoditySpecs.filter(v => v.title === title);
        currentData.forEach(v => {
          let list = v.list;
          list.forEach(s => {
            this.$set(s, "active", false);
          });
        });

        let relation = this.getVertex(item.name);
        console.log(relation);
        if (relation.length === 0) { // 没有库存了
          let restData = this.commoditySpecs.filter(v => v.title !== title);
          restData.forEach(v => {
            let list = v.list;
            list.forEach(s => {
              this.$set(s, "disable", true);
            });
          });
        } else { // 有库存
          let restData = this.commoditySpecs.filter(v => v.title !== title);
          this.dfs(restData, relation);
        }

        this.$set(item, "active", true);
      } else { // 取消选择的情况
        this.$set(item, "active", false);
        let choseData = this.commoditySpecs.reduce(
          (total, current) => total.concat(current.list.filter(v => v.active)),
          []
        );
        if (choseData.length === 0) { // 当前没有选中的元素
          this.init();
        } else { // 当前存在选中的元素
          let choseDataName = choseData.map(v => v.name);
          let arr = [];
          choseData.forEach(v => {
            let currentData = this.getVertex(v.name);
            arr = arr.concat(currentData);
          });
          arr = arr.concat(choseDataName);
          this.dfs(this.commoditySpecs, arr);
        }
      }
    }
  },
  mounted () {
    this.init();
    this.initData();
    console.log(this.getVertex("红色"));
  }
};
</script>

<style lang="scss" scoped>
.shopList {
  display: flex;
  text-indent: 10px;
  .shopList-item {
    margin-right: 8px;
    background: #ffffff;
    cursor: pointer;
    &.active {
      background: red;
    }
    &.disable {
      background: #dddddd;
    }
  }
}
</style>

完整的github地址为:https://github.com/whenTheMorningDark/vue-kai-admin/blob/master/src/views/sku/index.vue,如果对你有帮助,请点一下star。

Logo

前往低代码交流专区

更多推荐