电商平台spu和sku的完整设计

一、关于数据库表的设计

  • 1、商品属性表

    比如一个衣服有颜色、尺码、款式这个叫属性表

    -- ------------------------
    -- 商品属性表
    -- ------------------------
    DROP TABLE IF EXISTS `attribute`;
    CREATE TABLE `attribute` (
        `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT  COMMENT '主键id',
        `name` varchar(50) not null  COMMENT '属性名',
        `status` tinyint(4) DEFAULT 0 COMMENT '状态,0表示正常,1表示禁用',
        `remark` varchar(100) default  null comment '备注',
        `created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
        `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
        `deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '软删除时间',
        UNIQUE KEY `UK_name_deleted_at` (`name`,`deleted_at`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT = "商品属性表";
    
  • 2、商品属性值表

    上面说的颜色,可能是红色、黄色、绿色,尺码可能是S、M、L

    -- ------------------------
    -- 商品属性值表
    -- ------------------------
    DROP TABLE IF EXISTS `attribute_value`;
    CREATE TABLE `attribute_value` (
        `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT  COMMENT '主键id',
        `attribute_id` int(11) not null comment '关联到attribute表主键id',
        `name` varchar(50) not null  COMMENT '属性值',
        `status` tinyint(4) DEFAULT 0 COMMENT '状态,0表示正常,1表示禁用',
        `remark` varchar(100) default  null comment '备注',
        `created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
        `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
        `deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '软删除时间',
        UNIQUE KEY `UK_attribute_id_name_deleted_at` (`attribute_id`,`name`,`deleted_at`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT = "商品属性值表";
    
  • 3、spu表,或者直接叫商品表也可以的

    spu表比如我们说的苹果手机、华为手机、戴尔笔记本这样的叫spu

    -- ------------------------
    -- 商品spu表
    -- ------------------------
    DROP TABLE IF EXISTS `spu`;
    CREATE TABLE `spu` (
          `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT  COMMENT '主键id',
          `name` varchar(50) not null  COMMENT '商品名称',
          `keyword` varchar(50) not null  COMMENT '关键词',
          `introduction` varchar(100) not null  COMMENT '简介',
          `category_id` int(11) not null  COMMENT '关联到category表主键id',
          `brand_id` int(11) default null comment  '关联到brand表主键id',
          `pic_url` varchar(200) default  null comment  '封面图',
          `video_url` varchar(200) default  null comment  '视频地址',
          `slider_pic_urls` varchar(500) default null COMMENT '商品轮播图地址',
          `unit` int(11) default  null comment  '单位,关联到dict表主键id',
          `spec_type` tinyint(4) default 0 comment '单双规格,0表示单规格,1表示多规格(sku)',
          `price` decimal(10,2) default '0.00' comment '商品单价',
          `market_price` decimal(10,2) default '0.00' comment '商品市场价',
          `discount_price` decimal(10,2) default '0.00' comment '商品折扣价',
          `vip_price` decimal(10,2) default '0.00' comment '商品vip价',
          `cost_price` decimal(10,2) default '0.00' comment '商品成本价',
          `stock` int(11) default 0 comment '库存(如果是多规格求和)',
          `sort` int(11) DEFAULT 1 COMMENT '排序',
          `status` tinyint(4) DEFAULT 0 COMMENT '状态,0表示正常,1表示禁用',
          `is_hot` tinyint(4) DEFAULT 0 COMMENT '是否热销,0表示不是,1表示是',
          `is_benefit` tinyint(4) DEFAULT 0 COMMENT '是否优惠推荐,0表示不是,1表示是',
          `is_best` tinyint(4) DEFAULT 0 COMMENT '是否精品,0表示不是,1表示是',
          `is_new` tinyint(4) DEFAULT 0 COMMENT '是否新品,0表示不是,1表示是',
          `is_good` tinyint(4) DEFAULT 0 COMMENT '是否优品推荐,0表示不是,1表示是',
          `give_integral` int(11) DEFAULT 0 COMMENT '赠送积分',
          `sales_count` int(11) DEFAULT 0 COMMENT '销量',
          `browse_count` int(11) DEFAULT 0 COMMENT '浏览数',
          `content` text NOT NULL COMMENT '商品详情',
          `attribute` varchar(1000) default null COMMENT '销售属性数组,JSON 格式',
          `remark` varchar(100) default  null comment '备注',
          `created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
          `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
          `deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '软删除时间',
          UNIQUE KEY `UK_name_deleted_at` (`name`,`deleted_at`)
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT = "商品spu表";
    
  • 4、sku

    上面说的苹果手机,可能是【黄色-128GB-大陆版】,【白色-256GB-港版】这样的叫sku

    -- ------------------------
    -- 商品sku表
    -- ------------------------
    DROP TABLE IF EXISTS `sku`;
    CREATE TABLE `sku` (
           `id` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT  COMMENT '主键id',
           `spu_id` int(11) NOT NULL comment '关联到spu主键id',
           `attribute_value` varchar(200) default null comment '销售属性值:用英文,拼接',
           `price` decimal(10,2) default '0.00' comment '商品单价',
           `market_price` decimal(10,2) default '0.00' comment '商品市场价',
           `discount_price` decimal(10,2) default '0.00' comment '商品折扣价',
           `vip_price` decimal(10,2) default '0.00' comment '商品vip价',
           `cost_price` decimal(10,2) default '0.00' comment '商品成本价',
           `bar_code` varchar(64)  DEFAULT NULL COMMENT 'SKU 的条形码',
           `pic_url` varchar(200)  NOT NULL COMMENT '图片地址',
           `stock` int DEFAULT NULL COMMENT '库存',
           `weight` double DEFAULT NULL COMMENT '商品重量,单位:kg 千克',
           `volume` double DEFAULT NULL COMMENT '商品体积,单位:m^3 平米',
           `sales_count` int DEFAULT NULL COMMENT '商品销量',
           `created_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
           `updated_at` timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间',
           `deleted_at` timestamp(6) NULL DEFAULT NULL COMMENT '软删除时间'
    ) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT = "商品sku表";
    

二、前端选择销售属性

  • 1、最后效果图

    在这里插入图片描述

  • 2、上面的数据提前手动插入到数据库中的

在这里插入图片描述


在这里插入图片描述

  • 3、后端提供一个接口先查询全部的商品属性,在根据商品属性的id去查询商品属性值,这块可以自己见代码

三、选择商品属性生成SKU

  • 1、效果展示

在这里插入图片描述

  • 2、由销售属性来生成下面的表格的方法

    const checkPropertyList = ref([]);
      /**
       * 生成sku数据
       * @param {*} skuAttribute 选中的商品属性
       */
      const generateSku = (skuAttribute) => {
        console.log(JSON.stringify(skuAttribute));
        const attrValue = [];
        // 获取选中的属性
        const checkList = [];
        for (const item of skuAttribute) {
          attrValue.push(item.attributeItem.filter((it) => item.checkList.includes(it.id)));
          checkList.push(item.id); // 选中的主键id
        }
        checkPropertyList.value = checkList;
        console.log(attrValue, '???');
        if (attrValue.length == 0) {
          tableData.value = [];
          return;
        }
        // 处理添加一个属性的时候表格置空
        if (!attrValue[attrValue.length - 1].length) {
          return;
        }
        // 循环组成sku数据
        const skuList = attrValue
          .reduce((pre, cur) => {
            let res = [];
            for (const item of pre) {
              for (const it of cur) {
                let t = item.name + ',' + it.name;
                res.push({
                  name: t,
                  url: item.url || it.url || '',
                });
              }
            }
            return res;
          })
          .map((it) => {
            const oldData = afterSku.value.find((item) => item.name == it.name);
            return {
              ...it,
              id: oldData ? oldData.id : '',
              price: oldData ? oldData.price : '', // 单价
              stock: oldData ? oldData.stock : '', // 库存
            };
          });
        tableData.value = skuList;
      };
    
  • 3、在什么时候调用上面这个方法呢?,直接监听销售属性的变化就可以

    watch(
        () => skuAttributes.value,
        (newValue) => {
          generateSku(cloneDeep(newValue));
        },
        {
          deep: true,
        }
    );
    // 监听sku表格的变化,并将当前sku进行备份
    const afterSku = ref([]);
    watch(
        () => tableData.value,
        (value) => {
          afterSku.value = cloneDeep(value);
        },
        { deep: true }
    );
    
  • 4、点击按钮提交数据给后端

    const submitHandler = async () => {
        ElMessage.success('请查看浏览器控制台');
        console.log('销售属性:', JSON.stringify(skuAttributes.value));
        console.log('表格数据:', JSON.stringify(tableData.value));
        const postData = {
          name: '苹果13',
          keyword: '苹果手机',
          introduction: 'laboris sint in',
          categoryId: 10,
          brandId: 50,
          picUrl: 'http://dummyimage.com/400x400',
          videoUrl: 'http://voanozyj.vg/suuadzgv',
          sliderPicUrls: 'http://dummyimage.com/400x400',
          unit: 25,
          specType: 79,
          price: 90,
          marketPrice: 11,
          discountPrice: 82,
          vipPrice: 32,
          costPrice: 87,
          stock: 19,
          sort: 33,
          isHot: 0,
          isBenefit: 0,
          isBest: 0,
          isNew: 0,
          isGood: 0,
          giveIntegral: 43,
          content: '手机详情',
          remark: 'magna eu laboris',
          // 过滤掉没有选择的sku属性
          attribute: JSON.stringify(skuAttributes.value),
          skuList: tableData.value.map((item) => {
            return {
              ...item,
              attributeValue: item.name,
              barCode: item.name,
              discountPrice: item.price,
              costPrice: item.price,
              picUrl: 'http://dummyimage.com/400x400',
              vipPrice: item.price,
              volume: 85,
              marketPrice: 26,
              weight: 50,
            };
          }),
        };
        console.log(JSON.stringify(postData), '提交数据');
        const data = await SkuService.createSpuApi(postData);
        console.log(data);
      };
    
  • 5、完整代码如下

    <template>
      <div class="sku">
        <el-card shadow="never">
          <el-form>
            <el-form-item label="销售属性">
              <el-card
                shadow="never"
                v-for="(item, index) of skuAttributes"
                :key="index"
                :gutter="20"
                class="sku-row"
              >
                <el-button type="danger" class="delete-row-btn" @click="deleteRowHandler(index)"
                  >删除</el-button
                >
                <el-row :gutter="10">
                  <el-col :span="2">属性名称:</el-col>
                  <el-col :span="8">
                    <el-select
                      v-model="item.id"
                      placeholder="请选择属性"
                      @change="changeAttributeHandler"
                    >
                      <el-option
                        v-for="item in attributeItem"
                        :key="item.id"
                        :label="item.name"
                        :value="item.id"
                        :disabled="checkPropertyList.includes(item.id)"
                      />
                    </el-select>
                  </el-col>
                </el-row>
                <el-row :gutter="10">
                  <el-col :span="2"> 属性值: </el-col>
                  <el-col :span="22">
                    <el-checkbox-group v-model="item.checkList">
                      <el-checkbox
                        :label="value.id"
                        v-for="(value, i) of item.attributeItem"
                        :key="i"
                        >{{ value.name }}</el-checkbox
                      >
                    </el-checkbox-group>
                  </el-col>
                </el-row>
              </el-card>
              <div>
                <el-button type="primary" @click="addSkuAttrHandler">增加销售属性</el-button>
              </div>
            </el-form-item>
          </el-form>
        </el-card>
        <!-- 下面表格 -->
        <el-card shadow="never" style="margin-top: 20px">
          <el-table :data="tableData" style="width: 100%" border>
            <el-table-column prop="name" label="销售规格" width="180" />
            <el-table-column prop="price" label="单价">
              <template #default="scope">
                <el-input v-model="scope.row.price" placeholder="单价"></el-input>
              </template>
            </el-table-column>
            <el-table-column prop="stock" label="库存">
              <template #default="scope">
                <el-input v-model="scope.row.stock" placeholder="库存"></el-input>
              </template>
            </el-table-column>
          </el-table>
          <el-button type="primary" style="margin-top: 20px" @click="submitHandler">提交数据</el-button>
        </el-card>
      </div>
    </template>
    
    <script setup>
      import { SkuService } from '@/services';
      import { ref, onMounted, watch } from 'vue';
      import { cloneDeep } from 'lodash';
      import { ElMessage } from 'element-plus';
      const skuAttributes = ref([]);
      // 添加属性
      const addSkuAttrHandler = () => {
        skuAttributes.value.push({
          id: null,
          attributeItem: [],
          checkList: [],
        });
      };
      // 删除
      const deleteRowHandler = (index) => {
        skuAttributes.value.splice(index, 1);
      };
      // 1.获取全部的销售属性
      const attributeItem = ref([]);
      const getAllAttrApi = async () => {
        const data = await SkuService.getAttributeListApi();
        attributeItem.value = data.result;
      };
      // 切换的时候
      const changeAttributeHandler = async (item) => {
        let currentItem = skuAttributes.value.find((it) => it.id == item);
        const currentIndex = skuAttributes.value.findIndex((it) => it.id == item);
        const data = await SkuService.getAttributeValueByAttributeIdApi(item);
        currentItem.attributeItem = data.result;
        currentItem.name = attributeItem.value.find((it) => it.id == item)?.name;
        // 替换之前的
        skuAttributes.value.splice(currentIndex, 1, currentItem);
      };
      // 表格数据
      const tableData = ref([]);
      const submitHandler = async () => {
        ElMessage.success('请查看浏览器控制台');
        console.log('销售属性:', JSON.stringify(skuAttributes.value));
        console.log('表格数据:', JSON.stringify(tableData.value));
        const postData = {
          name: '苹果13',
          keyword: '苹果手机',
          introduction: 'laboris sint in',
          categoryId: 10,
          brandId: 50,
          picUrl: 'http://dummyimage.com/400x400',
          videoUrl: 'http://voanozyj.vg/suuadzgv',
          sliderPicUrls: 'http://dummyimage.com/400x400',
          unit: 25,
          specType: 79,
          price: 90,
          marketPrice: 11,
          discountPrice: 82,
          vipPrice: 32,
          costPrice: 87,
          stock: 19,
          sort: 33,
          isHot: 0,
          isBenefit: 0,
          isBest: 0,
          isNew: 0,
          isGood: 0,
          giveIntegral: 43,
          content: '手机详情',
          remark: 'magna eu laboris',
          // 过滤掉没有选择的sku属性
          attribute: JSON.stringify(skuAttributes.value),
          skuList: tableData.value.map((item) => {
            return {
              ...item,
              attributeValue: item.name,
              barCode: item.name,
              discountPrice: item.price,
              costPrice: item.price,
              picUrl: 'http://dummyimage.com/400x400',
              vipPrice: item.price,
              volume: 85,
              marketPrice: 26,
              weight: 50,
            };
          }),
        };
        console.log(JSON.stringify(postData), '提交数据');
        const data = await SkuService.createSpuApi(postData);
        console.log(data);
      };
      watch(
        () => skuAttributes.value,
        (newValue) => {
          generateSku(cloneDeep(newValue));
        },
        {
          deep: true,
        }
      );
      // 监听sku表格的变化,并将当前sku进行备份
      const afterSku = ref([]);
      watch(
        () => tableData.value,
        (value) => {
          afterSku.value = cloneDeep(value);
        },
        { deep: true }
      );
      const checkPropertyList = ref([]);
      /**
       * 生成sku数据
       * @param {*} skuAttribute 选中的商品属性
       */
      const generateSku = (skuAttribute) => {
        console.log(JSON.stringify(skuAttribute));
        const attrValue = [];
        // 获取选中的属性
        const checkList = [];
        for (const item of skuAttribute) {
          attrValue.push(item.attributeItem.filter((it) => item.checkList.includes(it.id)));
          checkList.push(item.id); // 选中的主键id
        }
        checkPropertyList.value = checkList;
        console.log(attrValue, '???');
        if (attrValue.length == 0) {
          tableData.value = [];
          return;
        }
        // 处理添加一个属性的时候表格置空
        if (!attrValue[attrValue.length - 1].length) {
          return;
        }
        // 循环组成sku数据
        const skuList = attrValue
          .reduce((pre, cur) => {
            let res = [];
            for (const item of pre) {
              for (const it of cur) {
                let t = item.name + ',' + it.name;
                res.push({
                  name: t,
                  url: item.url || it.url || '',
                });
              }
            }
            return res;
          })
          .map((it) => {
            const oldData = afterSku.value.find((item) => item.name == it.name);
            return {
              ...it,
              id: oldData ? oldData.id : '',
              price: oldData ? oldData.price : '', // 单价
              stock: oldData ? oldData.stock : '', // 库存
            };
          });
        tableData.value = skuList;
      };
      onMounted(() => {
        getAllAttrApi();
      });
    </script>
    
    <style lang="scss" scoped>
      .sku-row {
        width: 100%;
        margin-bottom: 10px;
        position: relative;
        .delete-row-btn {
          position: absolute;
          right: 10px;
          top: 10px;
          cursor: pointer;
          z-index: 30;
        }
        .sku-value-item {
          margin-bottom: 10px;
          position: relative;
          .close-icon {
            position: absolute;
            right: 0;
            top: 0;
            z-index: 10;
            cursor: pointer;
            opacity: 0;
          }
          &:hover {
            .close-icon {
              opacity: 1;
            }
          }
        }
      }
    </style>
    

四、前端实现sku选择

  • 1、效果图如下

    在这里插入图片描述

  • 2、实现代码见如下

    <template>
      <div class="sku">
        <h3>iphone 13</h3>
        <div v-for="(item, index) of processAttribute" :key="index">
          <div class="title" style="margin-bottom: 10px; margin-top: 10px">{{ item.title }}</div>
          <template v-for="(item1, index1) of item.attributeItem" :key="index1">
            <el-tag
              type="success"
              :class="[
                {
                  active: item1.activity,
                  disabled: item1.disabled,
                },
              ]"
              style="margin-right: 10px; cursor: pointer"
              @click="skuClickHandler(index, index1)"
              >{{ item1.name }}</el-tag
            >
          </template>
        </div>
        <div>当前选中的库存:{{ stock }}</div>
        <div
          >价格范围:
          <span v-if="minPrice == maxPrice">{{ maxPrice }}</span>
          <span v-else>{{ minPrice }}-{{ maxPrice }}</span>
        </div>
      </div>
    </template>
    
    <script setup>
      import { onMounted } from 'vue';
      import { SkuService } from '@/services';
      import { useRoute } from 'vue-router';
      const route = useRoute();
      const skuAttribute = ref([]);
      const skuList = ref([]);
      const processAttribute = ref([]);
      const processSkuMap = ref({});
      const initData = () => {
        for (const item of skuAttribute.value) {
          let temp = {
            id: item.id,
            title: item.title,
          };
          temp.attributeItem = item.attributeItem
            .filter((it) => item.checkList.includes(it.id)) // 过滤有的属性
            .map((it) => {
              return {
                ...it,
                activity: false,
                disabled: itemquantity(it.name) <= 0, // 判断当前是否小于0的库存
                stock: itemquantity(it.name),
              };
            });
          processAttribute.value.push(temp);
        }
        // 对 skuList 数据进行加工,并存入 processSkuMap 中
        for (const item of skuList.value) {
          let combArr = arrayCombine(item.attributeValue.split(','));
          for (let j = 0; j < combArr.length; j++) {
            var key = combArr[j].join(',');
            if (processSkuMap.value[key]) {
              // 库存累加,价格添加进数组
              processSkuMap.value[key].stock += +item.stock;
              processSkuMap.value[key].prices.push(item.price);
            } else {
              processSkuMap.value[key] = {
                stock: +item.stock,
                prices: [item.price],
              };
            }
          }
        }
        // 计算下
        // skuCheck();
      };
      // 计算当前sku的库存
      const itemquantity = (item) => {
        let quantity = 0;
        skuList.value.forEach((element) => {
          var skuArr = element.attributeValue.split(',');
          if (skuArr.indexOf(item) != -1) {
            quantity += +element.stock;
          }
        });
        return quantity;
      };
      const arrayCombine = (targetArr) => {
        let resultArr = [];
        for (var n = 0; n <= targetArr.length; n++) {
          var flagArrs = getFlagArrs(targetArr.length, n);
          while (flagArrs.length) {
            var flagArr = flagArrs.shift();
            var combArr = Array(targetArr.length);
            for (var i = 0; i < targetArr.length; i++) {
              if (flagArr[i]) {
                combArr[i] = targetArr[i];
              }
            }
            resultArr.push(combArr);
          }
        }
        return resultArr;
      };
    
      const getFlagArrs = (m, n) => {
        let flagArrs = [];
        let flagArr = [];
        let isEnd = false;
        for (let i = 0; i < m; i++) {
          flagArr[i] = i < n ? 1 : 0;
        }
        flagArrs.push(flagArr.concat());
        // 当n不等于0并且m大于n的时候进入
        if (n && m > n) {
          while (!isEnd) {
            var leftCnt = 0;
            for (var i = 0; i < m - 1; i++) {
              if (flagArr[i] == 1 && flagArr[i + 1] == 0) {
                for (var j = 0; j < i; j++) {
                  flagArr[j] = j < leftCnt ? 1 : 0;
                }
                flagArr[i] = 0;
                flagArr[i + 1] = 1;
                var aTmp = flagArr.concat();
                flagArrs.push(aTmp);
                if (aTmp.slice(-n).join('').indexOf('0') == -1) {
                  isEnd = true;
                }
                break;
              }
              flagArr[i] == 1 && leftCnt++;
            }
          }
        }
        return flagArrs;
      };
      // 点击sku
      const skuClickHandler = (key1, key2) => {
        console.log(key1, key2, '点击了', processAttribute.value[key1]);
        // 如果不是被禁用的时候才执行
        if (!processAttribute.value[key1].attributeItem[key2].disabled) {
          // 选择和取消选中
          processAttribute.value[key1].attributeItem.map((item, index) => {
            item.activity = index == key2 ? !item.activity : false;
          });
          // 检查当前的sku是否有库存
          skuCheck();
          // 每次点击的时候判断禁用
          getStockPrice();
        }
      };
      // 当前选中的sku的库存机最小单价最大单价
      const stock = ref(0);
      const minPrice = ref(null);
      const maxPrice = ref(null);
      const skuCheck = () => {
        let sku = [];
        processAttribute.value.map((attr) => {
          let name = '';
          attr.attributeItem.map((item) => {
            console.log(item, '111');
            if (item.activity) {
              name = item.name;
            }
          });
          sku.push(name);
        });
        console.log(sku, '选中的的值', sku.join(','));
        stock.value = processSkuMap.value[sku.join(',')].stock;
        minPrice.value = Math.min.apply(Math, processSkuMap.value[sku.join(',')].prices);
        maxPrice.value = Math.max.apply(Math, processSkuMap.value[sku.join(',')].prices);
      };
      // 点击的时候判断库存禁用
      const getStockPrice = () => {
        processAttribute.value.map((attr) => {
          attr.attributeItem.map((item) => {
            item.disabled = itemquantity(item.name) <= 0;
          });
        });
        let count = 0;
        let i = 0;
        processAttribute.value.map((attr, index) => {
          let flag = false;
          attr.attributeItem.map((item) => {
            if (item.activity) {
              flag = true;
            }
          });
          if (!flag) {
            count += 1;
            i = index;
          }
        });
        // 当只有一组规格没选时
        if (count == 1) {
          processAttribute.value[i].attributeItem.map((item) => {
            let sku = [];
            let text = item.name;
            processAttribute.value.map((attr, index) => {
              if (index != i) {
                attr.attributeItem.map((item2) => {
                  if (item2.activity) {
                    sku.push(item2.name);
                  }
                });
              } else {
                sku.push(text);
              }
            });
            if (processSkuMap.value[sku.join(',')].stock == 0) {
              item.disabled = true;
            }
          });
        }
        // 当所有规格都有选时
        if (count == 0) {
          processAttribute.value.map((attr, index) => {
            let i = index;
            processAttribute.value[index].attributeItem.map((item) => {
              if (!item.activity) {
                let sku = [];
                let text = item.name;
                processAttribute.value.map((list, index) => {
                  if (index != i) {
                    list.attributeItem.map((item2) => {
                      if (item2.activity) {
                        sku.push(item2.name);
                      }
                    });
                  } else {
                    sku.push(text);
                  }
                });
                if (processSkuMap.value[sku.join(',')].stock == 0) {
                  item.disabled = true;
                }
              }
            });
          });
        }
      };
      const initSkuData = async () => {
        const { result } = await SkuService.getSkuByIdApi(route.query.id);
        // console.log(data);
        console.log(result.skuAttribute, '111');
        skuAttribute.value = result.skuAttribute;
        skuList.value = result.skuList;
      };
      onMounted(async () => {
        await initSkuData();
        initData();
      });
    </script>
    
    <style lang="scss" scoped>
      .sku {
        .active {
          background: skyblue;
          color: #fff;
          border: none;
        }
        .disabled {
          background: #ddd;
        }
      }
    </style>
    
    

五、具体代码见

  • 1、[后端代码](shuiping.kuang/sku_demo (gitee.com))
  • 2、[前端代码](shuiping.kuang/vue3-sku (gitee.com))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/329792.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Maven工程 — 继承与聚合 相关知识点详解

简介&#xff1a;这篇帖子主要讲解Maven工程中的继承与聚合的相关知识点&#xff0c;用简洁的语言和小编自己的理解&#xff0c;深入浅出的说明Maven工程的继承与聚合。 目录 1、继承 1.1 继承关系的实现 1.2 版本锁定 2、聚合 2.1 聚合方法 3、总结 1、继承 图 1-1 继承…

阿里云国外服务器价格购买与使用策略

阿里云国外服务器优惠活动「全球云服务器精选特惠」&#xff0c;国外服务器租用价格24元一个月起&#xff0c;免备案适合搭建网站&#xff0c;部署独立站等业务场景&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云国外服务器优惠活动&#xff1a; 全球云服务器精选特惠…

Vue3在点击菜单切换路由时,将页面el-main的内容滚动到顶部

功能&#xff1a;Vue3在点击菜单切换路由时&#xff0c;将页面el-main的内容滚动到顶部&#xff0c;布局如下&#xff0c;使用ui组件为ElementPlus 在网上搜很多都是在route.js中的router.beforeEach中使用window.scrollTop(0,0) 或 window.scrollTo(0,0) 滚动&#xff0c;但是…

springboot-简单测试 前端上传Excel表格后端解析数据

导入依赖 <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxm…

uni-app的数据缓存

数据缓存uni.setStorage 将数据存储在本地缓存中指定的 key 中&#xff0c;会覆盖掉原来该 key 对应的内容&#xff0c;这是一个异步接口。 参数名类型必填说明keyString是本地缓存中的指定的 keydataAny是需要存储的内容&#xff0c;只支持原生类型、及能够通过 JSON.string…

vite和webpack的区别和作用

前言 Vite 和 Webpack 都是现代化的前端构建工具&#xff0c;它们可以帮助开发者优化前端项目的构建和性能。虽然它们的目标是相似的&#xff0c;但它们在设计和实现方面有许多不同之处。 一、Vite详解和作用 vite 是什么 vite —— 一个由 vue 作者尤雨溪开发的 web 开发工…

ArcGIS Pro 标注牵引线问题

ArcGIS Pro 标注 模仿CAD坐标牵引线问题 右键需要标注的要素&#xff0c;进入标注属性。 选择背景样式 在这里有可以选择的牵引线样式 选择这一个&#xff0c;可以根据调整间距来进行模仿CAD标注样式。 此图为cad样式 此为调整后gis样式 此处可以调整牵引线的样式符号 …

【NodeJS】nodejs后端渲染html

背景 Node.js 后端渲染 HTML 在提高网站性能、优化用户体验、简化前端开发流程以及提升内容可抓取性等方面都具有显著的价值。这种模式特别适用于那些不需要复杂交互的网站&#xff0c;例如博客、产品页面或者一些信息发布平台等。然而&#xff0c;对于需要高度交互和动态用户…

华为路由设备DHCPV6配置

组网需求 如果大量的企业用户IPv6地址都是手动配置&#xff0c;那么网络管理员工作量大&#xff0c;而且可管理性很差。管理员希望实现公司用户IPv6地址和网络配置参数的自动获取&#xff0c;便于统一管理&#xff0c;实现IPv6的层次布局。 图1 DHCPv6服务器组网图 配置思路 …

海外软文发稿:谷歌关键词排名与社交媒体互动的联动-大舍传媒

海外软文发稿&#xff1a;谷歌关键词排名与社交媒体互动的联动-大舍传媒 在当今数字化社会&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;已经成为网站蓬勃发展的关键因素。然而&#xff0c;在谷歌这样的搜索引擎中&#xff0c;关键词排名仅仅是成功的一部分。社交媒体…

Leetcode2596. 检查骑士巡视方案

Every day a Leetcode 题目来源&#xff1a;2596. 检查骑士巡视方案 解法1&#xff1a;广度优先搜索 这是有点特殊的广度优先搜索&#xff0c;每个位置需要搜索 8 个方向&#xff0c;但最终只选择其中的一个方向走下去。 所以不需要使用队列&#xff0c;也不需要标记数组&…

leetcode:2283. 判断一个数的数字计数是否等于数位的值(python3解法)

难度&#xff1a;简单 给你一个下标从 0 开始长度为 n 的字符串 num &#xff0c;它只包含数字。 如果对于 每个 0 < i < n 的下标 i &#xff0c;都满足数位 i 在 num 中出现了 num[i]次&#xff0c;那么请你返回 true &#xff0c;否则返回 false 。 示例 1&#xff1a…

IIS 缓存, 更新后前端资源不能更新问题

解决办法: 通常只需要index.html 不缓存即可, 其他文件都是根据index.html 中的引用去加载; 正确的做法是在 站点下增加 web.config 文件, 内容如下: 我这个是因为目录下有个config.js 配置文件, 也不能缓存, 所以加了两个 <?xml version"1.0" encoding&quo…

ARM 1.17

波特率 波特率&#xff08;bandrate&#xff09;,指的是串口通信的速率&#xff0c;也就是串口通信时每秒钟可以传输多少个二进制位。比如每秒钟可以传输9600个二进制&#xff08;传输一个二进制位需要的时间是1/9600秒&#xff0c;也就是104us&#xff09;&#xff0c;波特率就…

nginx+lua配置,一个域名配置https,docker集群使用

没安装kua的先安装lua 没有resty.http模块的&#xff0c;许配置 nginxlua配置&#xff0c;一个域名配置https&#xff0c;docker集群使用&#xff0c;一个域名配置https管理整个集群 lua做转发&#xff08;方向代理&#xff09; 1、ad_load.lua文件 ngx.header.content_typ…

银行数据仓库体系实践(1)--银行数据仓库简介

银行数据仓库简介 数据仓库之父比尔&#xff08;Bill Inmon&#xff09;在1991年出版的“Building the Data Warehouse”&#xff08;《建立数据仓库》&#xff09;一书中所提出的定义被广泛接受&#xff1a;数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的&a…

机器学习之常用激活函数

人工神经网络中最基本的单元叫神经元,又叫感知器,它是模拟人脑神经系统的神经元(分析和记忆)、树突(感知)、轴突(传导)的工作原理,借助计算机的快速计算和存储来实现。它的主体结构如下: 激活函数常用类型有:线性激活函数、符号激活函数、Sigmoid激活函数、tanh激活…

使用arcgis pro是类似的控件样式 WPF

1.资源加载 <controls:ProWindow.Resources><ResourceDictionary><ResourceDictionary.MergedDictionaries><extensions:DesignOnlyResourceDictionary Source"pack://application:,,,/ArcGIS.Desktop.Framework;component\Themes\Default.xaml&quo…

【问题+解决】axios/vue/element/echarts引入报错

缘由 笔者在html页面引用vue来快速实现页面&#xff1b;<head></head>中通过<script>src""></script>方法引入&#xff0c;开始引入&#xff0c;应用都是正常&#xff0c;后来用了也没问题&#xff1b;奇怪的是&#xff0c;前几天发现htm…

docker-compose和docker compose的区别

在docker实际使用中&#xff0c;经常会搭配Compose&#xff0c;用来定义和运行多个 Docker 容器。使用时会发现&#xff0c;有时候的指令是docker-compose&#xff0c;有时候是docker compose&#xff0c;下面给出解释。 docker官方文档&#xff1a;https://docs.docker.com/c…