基于Vue3 Sku的设计

Sku如何进行设计

欢迎各位小伙伴来观看这篇文章!最近做项目需要对sku进行设计并实现,以下是实现的效果。
在这里插入图片描述接下来我们开始吧!!!

数据库的设计

数据库中我们总共有两个表,一个商品表,一个sku表。sql语句

商品表

CREATE TABLE `goods` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `brand` varchar(50) DEFAULT NULL,
  `thumb` varchar(100) DEFAULT NULL,
  `category_id` int(8) DEFAULT NULL,
  `state` tinyint(1) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=36 DEFAULT CHARSET=utf8;

sku表

CREATE TABLE `sku` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `price` int(10) DEFAULT NULL,
  `stock` int(10) DEFAULT NULL,
  `marketPrice` int(10) DEFAULT NULL,
  `costPrice` int(10) DEFAULT NULL,
  `specs` varchar(100) DEFAULT NULL,
  `goods_id` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8;

里面的一些数据
商品表的信息
在这里插入图片描述
sku表的信息
在这里插入图片描述
sku的specs字段我的是varchar类型因为我的mysql是5.6版本的,5.7版本及以上可以使用json类型。此处我是按照我的实际情况走的,向sku插入数据时

//item是服务器得到前端传过来的数据
'insert into sku (price,stock,marketPrice,costPrice,specs,goods_id)
 values (?,?,?,?,?,?)',[item.price, item.stock, item.sPrice, item.cPrice, JSON.stringify(item.specs), result.insertId]

以上是对数据库设计的解释,以防小伙伴接下来对拿到的数据不明白。
以上就涉及了两个表,但是我的项目涉及到了其他的数据所以这是按照我的项目拿的数据,但是这个sku的设计就涉及到了上边的两个表

从后端拿数据

接下来是后端给前端的数据。

//利用node搭建的服务器
//在这里后端拿到前端发过来的数据商品id,有关商品某个sku的id.
async function selectGoodsSpecs(ctx) {
    let id = ctx.query.id
    let sku_id = ctx.query.sku_id
    if (!id || !sku_id) {
        return ctx.body = { code: 0, msg: '该商品不存在', data: null }
    }
    // 此处应该加上是否已将上架,此搜索为某个商品的信息
    let [[good]] = await db.query('select * from goods where id=?', [id])
    // 搜索该商品的轮播图的照片
    let [imgs] = await db.query('select * from goods_imgs where goods_id=?', [id])
    let imgsLength = imgs.length
    // 搜索已存在的商品规格组合sku
    let [skusRaw] = await db.query('select * from sku where goods_id=?', [id])
    for (const it of skusRaw) {
        it.specs = JSON.parse(it.specs)
    }
    // 获取当前的商品规格
    let [[currentSku]] = await db.query('select * from sku where id=?', [sku_id])
    currentSku.specs = JSON.parse(currentSku.specs)
    good.imgs = imgs
    good.imgsLength = imgsLength
    good.skusRaw = skusRaw
    good.currentSku = currentSku
    ctx.body = { code: 1, msg: '查找成功', data: good }
}

前端拿到的数据是华为电视这个商品的

//id:商品id
//name:商品名字
//brand:品牌
//thumb:封面图
//category_id:分类id
//state:商品状态
//imgs:商品轮播图
//imgsLength:商品轮播图的个数
//skusRaw:该商品目前拥有的规格sku
//currentSku:默认的商品sku
{
    "id": 35,
    "name": "华为智慧屏 SE 65英寸 畅连通话版 超薄电视 4K超高清智能液晶电视机 HD65DESY 2+16GB 【搭载HarmonyOS 2】  ",
    "brand": "华为",
    "thumb": "2021/11/10/1639137174811华为1.dpg",
    "category_id": 38,
    "state": 1,
    "imgs": [
        {
            "id": 8,
            "url": "2021/11/10/1639137289124华为1-1.dpg.webp",
            "goods_id": 35
        },
        {
            "id": 9,
            "url": "2021/11/10/1639137294323华为1-2.dpg.webp",
            "goods_id": 35
        },
        {
            "id": 10,
            "url": "2021/11/10/1639137299589华为1-3.dpg.webp",
            "goods_id": 35
        },
        {
            "id": 11,
            "url": "2021/11/10/1639137306225华为1-4.dpg.webp",
            "goods_id": 35
        },
        {
            "id": 12,
            "url": "2021/11/10/1639137312438华为1-5.dpg.webp",
            "goods_id": 35
        }
    ],
    "imgsLength": 5,
    "skusRaw": [
        {
            "id": 27,
            "price": 329900,
            "stock": 111,
            "marketPrice": 299900,
            "costPrice": 200000,
            "specs": {
                "颜色": "红色",
                "运行内存": "4G",
                "存储": "64G"
            },
            "goods_id": 35
        },
        {
            "id": 28,
            "price": 259900,
            "stock": 222,
            "marketPrice": 199900,
            "costPrice": 149900,
            "specs": {
                "颜色": "金色",
                "运行内存": "6G",
                "存储": "128G"
            },
            "goods_id": 35
        },
        {
            "id": 29,
            "price": 269900,
            "stock": 333,
            "marketPrice": 189900,
            "costPrice": 129900,
            "specs": {
                "颜色": "褐色",
                "运行内存": "4G",
                "存储": "256G"
            },
            "goods_id": 35
        },
        {
            "id": 30,
            "price": 289500,
            "stock": 4444,
            "marketPrice": 179900,
            "costPrice": 149900,
            "specs": {
                "颜色": "黑色",
                "运行内存": "4G",
                "存储": "128G"
            },
            "goods_id": 35
        },
        {
            "id": 31,
            "price": 312600,
            "stock": 555,
            "marketPrice": 159900,
            "costPrice": 148500,
            "specs": {
                "颜色": "白色",
                "运行内存": "4G",
                "存储": "64G"
            },
            "goods_id": 35
        },
        {
            "id": 32,
            "price": 245600,
            "stock": 666,
            "marketPrice": 195600,
            "costPrice": 111100,
            "specs": {
                "颜色": "红色",
                "运行内存": "6G",
                "存储": "128G"
            },
            "goods_id": 35
        },
        {
            "id": 33,
            "price": 327800,
            "stock": 777,
            "marketPrice": 169800,
            "costPrice": 145800,
            "specs": {
                "颜色": "金色",
                "运行内存": "8G",
                "存储": "64G"
            },
            "goods_id": 35
        },
        {
            "id": 34,
            "price": 369900,
            "stock": 888,
            "marketPrice": 219900,
            "costPrice": 129900,
            "specs": {
                "颜色": "褐色",
                "运行内存": "8G",
                "存储": "256G"
            },
            "goods_id": 35
        },
        {
            "id": 35,
            "price": 378400,
            "stock": 999,
            "marketPrice": 184500,
            "costPrice": 112200,
            "specs": {
                "颜色": "白色",
                "运行内存": "8G",
                "存储": "256G"
            },
            "goods_id": 35
        },
        {
            "id": 36,
            "price": 358900,
            "stock": 1010,
            "marketPrice": 190000,
            "costPrice": 150000,
            "specs": {
                "颜色": "黑色",
                "运行内存": "4G",
                "存储": "64G"
            },
            "goods_id": 35
        }
    ],
    "currentSku": {
        "id": 27,
        "price": 329900,
        "stock": 111,
        "marketPrice": 299900,
        "costPrice": 200000,
        "specs": {
            "颜色": "红色",
            "运行内存": "4G",
            "存储": "64G"
        },
        "goods_id": 35
    }
}

我们将得到的这个对象赋值给goodsInfo.data

    let route = useRoute();
    let goodsInfo = reactive({
      data: "",
    });
    // 生命周期函数获取数据库数据
    onBeforeMount(async function () {
      let id = route.query.id;
      let sku_id = route.query.sku_id;
      let res = await selectGoodsSpecs({ id, sku_id });
      //console.log(res.data)
      goodsInfo.data = res.data;
      // 默认当前选中的值
      changeSkuGoodsSpecs(goodsInfo.data.currentSku.specs);
    });

changeSkuGoodsSpecs函数

 //通过匹配到的值改变goodsInfo.skuStatus的状态值
    function changeSkuGoodsSpecs(defaultSkuSpecsPart) {
      let skusRaw = goodsInfo.data.skusRaw;
      let skuStatus = {};
      for (const it of skusRaw) {
        let specs = it.specs;
        for (const key in specs) {
          if (!skuStatus[key]) {
            skuStatus[key] = {};
          }
          skuStatus[key][specs[key]] = { clicked: false, disabled: true };
        }
      }
      // 得到skuStatus,规格的具体信息,并且渲染到了界面上
      goodsInfo.data.skuStatus = skuStatus;
      // 通过默认值修改goodsInfo.skuStatus,为点击的状态
      for (const key in defaultSkuSpecsPart) {
        goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
      }

      // 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
      let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
      //对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
      let selectSkuSpecsPartArr = [];
      selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
      toStrSkuSpecsChild(selectSkuSpecsPartArr);
      let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
      console.log(selectSkuSpecsPartArrToOne)
      // 需要得到已经存在的skusRaw组合的specs值的子组合
      let skuSpecsChild = [];
      for (const it in skusRaw) {
        let specs = skusRaw[it].specs;
        skuSpecsChild.push(powerset(Object.values(specs)));
      }
      // 已经得到skuRaw每条信息的specs的子组合
      // 对skuSpecsChild进行进一步处理
      toStrSkuSpecsChild(skuSpecsChild);
      console.log(skuSpecsChild)
      // 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
      // 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
      let selectAboultIndex = [];
      for (const i in skuSpecsChild) {
        for (const item of selectSkuSpecsPartArrToOne) {
          if (selectSkuSpecsPartArrToOne.length > 1) {
            if (
              item.includes("-") &&
              skuSpecsChild[i].includes(item) &&
              !selectAboultIndex.includes(i)
            ) {
              selectAboultIndex.push(i);
            }
          } else {
            if (skuSpecsChild[i].includes(item)) {
              selectAboultIndex.push(i);
            }
          }
        }
      }
      //selectAboultIndex得到了匹配结果
      // 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
      // 修改goodsInfo.skuStatus里的值对应的状态
      for (const value of selectAboultIndex) {
        for (const key in skusRaw[value].specs) {
          // key键值
          // console.log(key)
          // key键值对应的值
          // 变成可点状态
          goodsInfo.data.skuStatus[key][
            skusRaw[value].specs[key]
          ].disabled = false;
        }
      }
    }

首先先渲染上去

首先考虑的问题是将得到的商品的所有规格渲染上去。从数据中可以看出。该商品有是三种规格。颜色,运行内存,存储。

颜色:[“红色”,“金色”,“褐色”,“黑色”,“白色”]
运行内存:[“4G”,“6G”,“8G”]
存储:[“64G”,“128G”,“256G”]

但是我们还要考虑规格按钮的状态。是否可以点,是否不可以点。是否点击了,所以我们需要得到这样的数据

      let skusRaw = goodsInfo.data.skusRaw;
      let skuStatus = {};
      for (const it of skusRaw) {
        let specs = it.specs;
        for (const key in specs) {
          if (!skuStatus[key]) {
            skuStatus[key] = {};
          }
          skuStatus[key][specs[key]] = { clicked: false, disabled: true };
        }
      }
      // 得到skuStatus,规格的具体信息,并且渲染到了界面上
      goodsInfo.skuStatus = skuStatus;
//得到的skuStatus是这样的
{
    "颜色": {
        "红色": {
            "clicked": false,
            "disabled": true
        },
        "金色": {
            "clicked": false,
            "disabled": true
        },
        "褐色": {
            "clicked": false,
            "disabled": true
        },
        "黑色": {
            "clicked": false,
            "disabled": true
        },
        "白色": {
            "clicked": false,
            "disabled": true
        }
    },
    "运行内存": {
        "4G": {
            "clicked": false,
            "disabled": true
        },
        "6G": {
            "clicked": false,
            "disabled": true
        },
        "8G": {
            "clicked": false,
            "disabled": true
        }
    },
    "存储": {
        "64G": {
            "clicked": false,
            "disabled": true
        },
        "128G": {
            "clicked": false,
            "disabled": true
        },
        "256G": {
            "clicked": false,
            "disabled": true
        }
    }
}

'clicked’为false则没有被点击状态,为true则是被点击状态
'disabled’为false则可以点击,为true则不可以被点击。
通过goodsInfo.skuStatus就可以渲染上去了。

<template>
  <div class="specs" v-for="(item, index) in goodsInfo.skuStatus" :key="index">
    <div class="title">{{ index }}</div>
    <div class="specs-list">
      <template v-for="(it, index2) in item" :key="index2">
        <div @click="changeSpecs(index, index2)" class="specs-item" :class="it">
          {{ index2 }}
        </div>
      </template>
    </div>
  </div>
</template>

通过动态绑定:class=‘it’.当it为{
“clicked”: false,
“disabled”: true
}
则class=‘disabled’.剩下的组合我就不用解释了吧!!!!

CSS样式

//这是less的写法
.specs {
  .title {
    font-size: 0.72rem;
    padding: 10px 0;
  }
  .specs-list {
    display: flex;
    flex-wrap: wrap;
    font-size: 12px;
    line-height: 20px;
    .specs-item {
      padding: 3px 12px;
      background-color: #f7f7f7;
      color: #333;
      margin: 5px 10px 5px 0;
      height: 20px;
      border-radius: 20px;
      border: 1px solid #ddd;

      &.disabled {
        // background-color: cornflowerblue;
        border: 1px dotted #ccc;
        background-color: #fefefe;
        color: #ccc;
      }

      &.clicked {
        background-color: #f81818;
        color: #fff;
        border: 1px solid #f81818;
      }
    }
  }
}

思路最重要

目前走到这里应该是已经渲染上去,并且样式都是灰色的
但是接下来该怎么办呢,首先是了解到我们已经拥有了可以控制每个按钮的可不可以被点击,被点击了吗。

第一步:当拿到数据时,先得到所有的规格(上边已经拿到了),通过当前的默认规格去找出和它有关系的规格,例如:
此时默认红色,4G,64G.这个规格与skuRaw的所有规格去匹配。
组合有红色-4G,红色-64G,4G-64G,红色-4G-64G.skuRaw里边的规格凡是匹配到这些组合的都可点,将匹配到的skuRaw下标存到一个数组里。
组合为黑色,4G,64G存在,白色,4G,64G也存在
在这里插入图片描述
当点击黑色
此时为黑色,4G,64G.组合有黑色-4G,黑色-64G,4G-64G,黑色-4G-64G。去匹配skuRaw.
在这里插入图片描述
黑色-4G,可以匹配到 存储的64G和128G.黑色-64G可以匹配到运行内存的4G.4G-64G可以匹配到红色,白色。所以我们要得到当前组合的子组合。
我们要得到这样的数据

 ['红色', '4G', '红色-4G', '64G', '红色-64G', '4G-64G', '红色-4G-64G']

1.第一步处理

 // 求数组的所有子组合(求幂集)
    function powerset(arr) {
      var ps = [[]];
      for (var i = 0; i < arr.length; i++) {
        for (var j = 0, len = ps.length; j < len; j++) {
          ps.push(ps[j].concat(arr[i]));
        }
      }
      return ps;
    }

2.第二步处理

//对skuSpecsChild进行进一步处理,将数组里边的数组转换成字符串
    function toStrSkuSpecsChild(arr) {
      for (const i in arr) {
        let childArr = arr[i];
        for (const j in childArr) {
          if (childArr[j] != []) {
            childArr[j] = childArr[j].join("-");
          }
        }
        //删除数组的第一个元素,因为它是空的
        childArr.shift();
        // 原数组被改变childArr已经没有了空值
      }
    }

3.这是得到当前默认规格的子组合

 // 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
 //defaultSkuSpecsPart为{"颜色": "红色","运行内存": "4G","存储": "64G" }这样的数据
      let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
      //对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
      let selectSkuSpecsPartArr = [];
      selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
      toStrSkuSpecsChild(selectSkuSpecsPartArr);
      let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
      console.log(selectSkuSpecsPartArrToOne)

4.还需得到skuRaw所有规格的子组合

 // 需要得到已经存在的skusRaw组合的specs值的子组合
      let skuSpecsChild = [];
      for (const it in skusRaw) {
        let specs = skusRaw[it].specs;
        skuSpecsChild.push(powerset(Object.values(specs)));
      }
      // 已经得到skuRaw每条信息的specs的子组合
      // 对skuSpecsChild进行进一步处理
      toStrSkuSpecsChild(skuSpecsChild);

当前总共十条数据

[
    [
        "红色",
        "4G",
        "红色-4G",
        "64G",
        "红色-64G",
        "4G-64G",
        "红色-4G-64G"
    ],
    [
        "金色",
        "6G",
        "金色-6G",
        "128G",
        "金色-128G",
        "6G-128G",
        "金色-6G-128G"
    ],
    [
        "褐色",
        "4G",
        "褐色-4G",
        "256G",
        "褐色-256G",
        "4G-256G",
        "褐色-4G-256G"
    ],
    [
        "黑色",
        "4G",
        "黑色-4G",
        "128G",
        "黑色-128G",
        "4G-128G",
        "黑色-4G-128G"
    ],
    [
        "白色",
        "4G",
        "白色-4G",
        "64G",
        "白色-64G",
        "4G-64G",
        "白色-4G-64G"
    ],
    [
        "红色",
        "6G",
        "红色-6G",
        "128G",
        "红色-128G",
        "6G-128G",
        "红色-6G-128G"
    ],
    [
        "金色",
        "8G",
        "金色-8G",
        "64G",
        "金色-64G",
        "8G-64G",
        "金色-8G-64G"
    ],
    [
        "褐色",
        "8G",
        "褐色-8G",
        "256G",
        "褐色-256G",
        "8G-256G",
        "褐色-8G-256G"
    ],
    [
        "白色",
        "8G",
        "白色-8G",
        "256G",
        "白色-256G",
        "8G-256G",
        "白色-8G-256G"
    ],
    [
        "黑色",
        "4G",
        "黑色-4G",
        "64G",
        "黑色-64G",
        "4G-64G",
        "黑色-4G-64G"
    ]
]

两者进行匹配
接下来就是最重要且是难点的部分。
如何匹配得到匹配结果

// 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
      // 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
      let selectAboultIndex = [];
      for (const i in skuSpecsChild) {
        for (const item of selectSkuSpecsPartArrToOne) {
        //当子组合大于1个的时候,还有子组合为1的时候
          if (selectSkuSpecsPartArrToOne.length > 1) {
            if (
              item.includes("-") &&
              skuSpecsChild[i].includes(item) &&
              !selectAboultIndex.includes(i)
            ) {
              selectAboultIndex.push(i);
            }
          } else {
            if (skuSpecsChild[i].includes(item)) {
              selectAboultIndex.push(i);
            }
          }
        }
      }

得到一个数组,里边存的是skuRaw的(总共10条)第几条数据的下标。
还有一个难点。如何通过这个数组改变按钮和颜色。那就需要操作skuStatus

1.得到的界面结果哪些可点,哪些不可点

// 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
      // 修改goodsInfo.skuStatus里的值对应的状态
      for (const value of selectAboultIndex) {
        for (const key in skusRaw[value].specs) {
        //key键
          goodsInfo.data.skuStatus[key][
            skusRaw[value].specs[key]
          ].disabled = false;
        }
      }

2.得到的结果是哪些被点了,哪些还是正常状态

for (const key in defaultSkuSpecsPart) {
        goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
      }
处理点击事件

首先有两种结果,当你点正常状态下的时候,其他规格不变,和它同一规格下的,之前的红色变为正常状态,被点击的变成红色。另一种结果当点击的为不能被点击的,被点击变为红色。正常调用changeSkuGoodsSpecs函数
在这里插入图片描述
点击事件的代码

// 点击改变事件
    function changeSpecs(key, value) {
      let selectSkuSpecsPart = {};
      // 在此需要判断点击的规格disabled的状态
      if (goodsInfo.data.skuStatus[key][value].disabled) {
        selectSkuSpecsPart[key] = value;
      } else {
        // 将这种规格的点击状态变为false
        for (const item in goodsInfo.data.skuStatus[key]) {
          goodsInfo.data.skuStatus[key][item].clicked = false;
        }
        // 获取当前所选的
        //点击后的状态为红色
        goodsInfo.data.skuStatus[key][value].clicked = true;
        selectSkuSpecsPart = getSelectSpecs();
      }
      //  当点击后需要获取当前的specs并调用changeSkuGoodsSpecs
      changeSkuGoodsSpecs(selectSkuSpecsPart);
      // 计算价格
      if (
        Object.keys(goodsInfo.data.currentSku.specs).length ==
        Object.keys(selectSkuSpecsPart).length
      ) {
        // 更新当前的 sku 信息
        for (const it of goodsInfo.data.skusRaw) {
          if (objectIsEqual(it.specs, selectSkuSpecsPart)) {
            goodsInfo.data.currentSku = it;
          }
        }
      }
      selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
      price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
      ctx.emit("getPrice", price, selectSkuSpecs.value);
    }

总结

该设计持续更新中,有不懂小伙伴可以私信或者评论中留言。该页面我封成了一个组件。 以下为该组件全部代码。

<template>
  <div class="content">
    <van-row>
      <van-col span="6">
        <img class="img" :src="'http://127.0.0.1:3010/' + thumb" alt="" />
      </van-col>
      <van-col offset="1" span="17">
        <span class="money">{{ price }}</span>
        <p>
          <span>已选:</span>
          <span
            class="selected"
            v-for="(value, index) in selectSkuSpecs"
            :key="index"
            >{{ value }}
          </span>
          <span>{{ selectSkuNum }}</span>
        </p>
        <p></p>
      </van-col>
    </van-row>
    <div
      class="specs"
      v-for="(item, index) in goodsInfo.skuStatus"
      :key="index"
    >
      <div class="title">{{ index }}</div>
      <div class="specs-list">
        <template v-for="(it, index2) in item" :key="index2">
          <div
            @click="changeSpecs(index, index2)"
            class="specs-item"
            :class="it"
          >
            {{ index2 }}
          </div>
        </template>
      </div>
    </div>

    <!-- 步进器 -->
    <div class="num">
      <div>数量</div>
      <div>
        <van-stepper
          v-model="selectSkuNum"
          theme="round"
          button-size="22"
          disable-input
          @change="changeSkuNum"
        />
      </div>
    </div>

    <div class="btn">
      <!-- <van-button round type="warning">查看相似商品</van-button>
            <van-button round type="primary">到货通知</van-button> -->
      <van-button @click="addShopping" round type="warning"
        >加入购物车</van-button
      >
      <van-button round type="danger">立即购买</van-button>
    </div>
  </div>
</template>

<script>
import { ref, reactive, watch, toRef } from "vue";
import { useRoute } from "vue-router";
import { selectGoodsSpecs } from "../../api/user/goodsSpecs";
export default {
  props: ["close", "thumb"],
  setup(props, ctx) {
    let route = useRoute();
    let goodsInfo = reactive({
      data: "",
    });
    // 定义当前的current
    let price = ref("");
    // 商品已选的内容字符串显示
    let selectSkuSpecs = ref("");
    // 当前sku的数量
    let selectSkuNum = ref(1);
    // 生命周期函数获取数据库数据
    (async function () {
      let id = route.query.id;
      let sku_id = route.query.sku_id;
      let res = await selectGoodsSpecs({ id, sku_id });
      goodsInfo.data = res.data;
      console.log(goodsInfo)
      // 默认当前选中的值
      price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
      ctx.emit("getPrice", price);
      selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
      changeSkuGoodsSpecs(goodsInfo.data.currentSku.specs);
    })();

    // 加入购物车的事件
    function addShopping() {
      ctx.emit("addShopping", goodsInfo.data.currentSku.id, selectSkuNum.value);
    }

    // 点击改变事件
    function changeSpecs(key, value) {
      let selectSkuSpecsPart = {};
      // 在此需要判断点击的规格disabled的状态
      if (goodsInfo.data.skuStatus[key][value].disabled) {
        selectSkuSpecsPart[key] = value;
      } else {
        // 将这种规格的点击状态变为false
        for (const item in goodsInfo.data.skuStatus[key]) {
          goodsInfo.data.skuStatus[key][item].clicked = false;
        }
        // 获取当前所选的
        //点击后的状态为红色
        goodsInfo.data.skuStatus[key][value].clicked = true;
        selectSkuSpecsPart = getSelectSpecs();
      }
      //  当点击后需要获取当前的specs并调用changeSkuGoodsSpecs
      changeSkuGoodsSpecs(selectSkuSpecsPart);
      // 计算价格
      if (
        Object.keys(goodsInfo.data.currentSku.specs).length ==
        Object.keys(selectSkuSpecsPart).length
      ) {
        // 更新当前的 sku 信息
        for (const it of goodsInfo.data.skusRaw) {
          if (objectIsEqual(it.specs, selectSkuSpecsPart)) {
            goodsInfo.data.currentSku = it;
          }
        }
      }
      selectSkuSpecs.value = goodsInfo.data.currentSku.specs;
      price.value = (goodsInfo.data.currentSku.price / 100).toFixed(2);
      ctx.emit("getPrice", price, selectSkuSpecs.value);
    }

    // 每次点击时都要重新获取当前的规格
    function getSelectSpecs() {
      let selectSkuSpecsPart = {};
      for (const it in goodsInfo.data.skuStatus) {
        for (const item in goodsInfo.data.skuStatus[it]) {
          if (goodsInfo.data.skuStatus[it][item].clicked) {
            selectSkuSpecsPart[it] = item;
          }
        }
      }
      return selectSkuSpecsPart;
    }

    //通过匹配到的值改变goodsInfo.skuStatus的状态值
    function changeSkuGoodsSpecs(defaultSkuSpecsPart) {
      let skusRaw = goodsInfo.data.skusRaw;
      let skuStatus = {};
      for (const it of skusRaw) {
        let specs = it.specs;
        for (const key in specs) {
          if (!skuStatus[key]) {
            skuStatus[key] = {};
          }
          skuStatus[key][specs[key]] = { clicked: false, disabled: true };
        }
      }
      // 得到skuStatus,规格的具体信息,并且渲染到了界面上
      goodsInfo.data.skuStatus = skuStatus;
      // 通过默认值修改goodsInfo.skuStatus,为点击的状态
      for (const key in defaultSkuSpecsPart) {
        goodsInfo.data.skuStatus[key][defaultSkuSpecsPart[key]].clicked = true;
      }

      // 得到要选择的规格组合,通过选择的规格,改变按钮的颜色为选中状态
      let selectSkuSpecsPart = Object.values(defaultSkuSpecsPart);
      //对selectSkuSpecsPart进行得到子组合并toStrSkuSpecsChild
      let selectSkuSpecsPartArr = [];
      selectSkuSpecsPartArr.push(powerset(selectSkuSpecsPart));
      toStrSkuSpecsChild(selectSkuSpecsPartArr);
      let [selectSkuSpecsPartArrToOne] = selectSkuSpecsPartArr;
      console.log(selectSkuSpecsPartArrToOne)
      // 需要得到已经存在的skusRaw组合的specs值的子组合
      let skuSpecsChild = [];
      for (const it in skusRaw) {
        let specs = skusRaw[it].specs;
        skuSpecsChild.push(powerset(Object.values(specs)));
      }
      // 已经得到skuRaw每条信息的specs的子组合
      // 对skuSpecsChild进行进一步处理
      toStrSkuSpecsChild(skuSpecsChild);
      console.log(skuSpecsChild)
      // 得到两组数据,selectSkuSpecsPartArr和skuSpecsChild进行匹配
      // 需要得到匹配结果[],selectSkuSpecsPartArr和skuSpecsChild匹配哪几行下标存到空数组了
      let selectAboultIndex = [];
      for (const i in skuSpecsChild) {
        for (const item of selectSkuSpecsPartArrToOne) {
          if (selectSkuSpecsPartArrToOne.length > 1) {
            if (
              item.includes("-") &&
              skuSpecsChild[i].includes(item) &&
              !selectAboultIndex.includes(i)
            ) {
              selectAboultIndex.push(i);
            }
          } else {
            if (skuSpecsChild[i].includes(item)) {
              selectAboultIndex.push(i);
            }
          }
        }
      }
      //selectAboultIndex得到了匹配结果
      // 通过遍历匹配到的结果,得到skusRaw里的specs,将specs里的值,通过里边的值
      // 修改goodsInfo.skuStatus里的值对应的状态
      for (const value of selectAboultIndex) {
        for (const key in skusRaw[value].specs) {
          // key键值
          // console.log(key)
          // key键值对应的值
          // 变成可点状态
          goodsInfo.data.skuStatus[key][
            skusRaw[value].specs[key]
          ].disabled = false;
        }
      }
    }

    // 求数组的所有子组合(求幂集)
    function powerset(arr) {
      var ps = [[]];
      for (var i = 0; i < arr.length; i++) {
        for (var j = 0, len = ps.length; j < len; j++) {
          ps.push(ps[j].concat(arr[i]));
        }
      }
      return ps;
    }

    //对skuSpecsChild进行进一步处理,将数组里边的数组转换成字符串
    function toStrSkuSpecsChild(arr) {
      for (const i in arr) {
        let childArr = arr[i];
        for (const j in childArr) {
          if (childArr[j] != []) {
            childArr[j] = childArr[j].join("-");
          }
        }
        //删除数组的第一个元素,因为他是空的
        childArr.shift();
        // 原数组被改变childArr已经没有了空值
      }
    }

    // 判断两个对象的键值是否完全相等(浅比对)
    function objectIsEqual(obj1, obj2) {
      for (const key in obj1) {
        if (obj1[key] != obj2[key]) {
          return false;
        }
      }
      for (const key in obj2) {
        if (obj1[key] != obj2[key]) {
          return false;
        }
      }
      return true;
    }
    watch(
      function () {
        return props.close;
      },
      function (a) {
        if (a) {
          changeSkuGoodsSpecs(selectSkuSpecs.value);
          ctx.emit("getSpecs", selectSkuSpecs);
        }
      }
    );
    // 修改已选的sku数量
    function changeSkuNum() {
      ctx.emit("getNum", selectSkuNum.value);
    }

    return {
      goodsInfo: toRef(goodsInfo, "data"),
      price,
      changeSpecs,
      selectSkuNum,
      changeSkuNum,
      selectSkuSpecs,
      addShopping,
    };
  },
};
</script>

<style lang='less' scoped>
.img {
  width: 100%;
}
.custom-indicator {
  position: absolute;
  right: 5px;
  bottom: 5px;
  padding: 2px 5px;
  font-size: 12px;
  background: rgba(0, 0, 0, 0.1);
}

// 点击购买的动作面板的样式
.content {
  padding: 35px 16px 10px;
  .img {
    width: 100%;
  }
  /deep/ .van-col--17 {
    padding-top: 10px;
    .money {
      font-size: 25px;
      color: red;
    }
    p {
      padding-top: 12px;
      margin: 0px !important;
      font-size: 13px;
      .selected {
        margin-right: 15px;
      }
    }
  }
}
.specs {
  .title {
    font-size: 0.72rem;
    padding: 10px 0;
  }
  .specs-list {
    display: flex;
    flex-wrap: wrap;
    font-size: 12px;
    line-height: 20px;
    .specs-item {
      padding: 3px 12px;
      background-color: #f7f7f7;
      color: #333;
      margin: 5px 10px 5px 0;
      height: 20px;
      border-radius: 20px;
      border: 1px solid #ddd;

      &.disabled {
        // background-color: cornflowerblue;
        border: 1px dotted #ccc;
        background-color: #fefefe;
        color: #ccc;
      }

      &.clicked {
        background-color: #f81818;
        color: #fff;
        border: 1px solid #f81818;
      }
    }
  }
}
.num {
  padding: 20px 0;
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
}
.btn {
  display: flex;
  justify-content: space-between;
  .van-button {
    width: 48%;
    height: 36px;
  }
}
</style>
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值