vue實現一個sku列表

最近接到一個需求類似於商城選購的一個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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章