前端商城商品sku设计和实现

2023-02-21  本文已影响0人  遇见wn

后台模型


需求与实现(模拟实现)

sku
tip:在后台中添加的规格参数,最终会在请求接口后返回规格参数,本次实例有3个规格,每个规格分别有3个选项,其中有一个sku组合是默认项,可随意在后台设置,然后就是禁用项,禁用项主要的目的就是当前规格可能紧急没有库存或其他原因下架的商品,用户是无法选择禁用的sku组合的。

后台返回的规格列表参数数据(模拟数据):

//attributeList 为所有规格list   skuName为规格名,skuValue为规格项名
//defaultSpec:默认选中的规格
data:{
    attributeList: '[{"skuName":"颜色","skuValue":["红色","白色","绿色"]},{"skuName":"大小","skuValue":["大","中","小"]},{"skuName":"规格","skuValue":["5L","10L","15L"]}]',
    defaultSpec: '{"颜色":"红色","大小":"大","规格":"5L"}',
}

每次点击规格项时调用接口,返回禁用的sku组合(模拟数据):

//禁用组合字段反参为数组,每一项是json格式的键值对
data:{
  cdMallSkusList: ['{"颜色":"红色","大小":"小","规格":"5L"}', '{"颜色":"白色","大小":"中","规格":"10L"}', '{"颜色":"绿色","大小":"中","规格":"5L"}', '{"颜色":"绿色","大小":"小","规格":"10L"}']
}

基本实现:

sku
html:
<view class="sku">
//外层遍历规格名
        <view class="content1"  v-for="(item, index) in skuList" :key="item[id]">
            <text class="t1">{{ item.name }}</text>
            <view class="list flex-wrap">
              //内层遍历规格项名
                <text
                    v-for="(items, indexs) in item.list"
                    :key="indexs"
                    @tap="formatXY(index,indexs) ? '' : changeTab(index, indexs)"
                    :class="{ selectColor: indexs == item.sidx, disColor: formatXY(index,indexs) }"
                    >
                    {{ items }}
                </text>
            </view>
        </view>
</view>
js(核心):
data(){
  return {
       skuList:[],     //sku列表
       disSkus:[],    //禁用的sku组合
       disBtn:[]       //禁用组合的下标集合
    }
},

//当前使用的是uniapp的生命周期,vue原生项目可以做适当修改。逻辑都是一样的
onLoad(){
      this.getMallDetail()
},

//init初始数据
getMallDetail(){
        //res.data.data.attributeList为调用后端接口返回的字段,字段内容见上方模拟数据
         this.skuList = JSON.parse(res.data.attributeList).map((u, index) => {
        //处理成对象型数组格式,更好处理和数据渲染
                return {
                    name: u.skuName,    //规格名
                    id: index,          //每一项的key
                    list: u.skuValue,   //数组,规格项的名称的集合
                    sidx:0           //主要为每一个规格做一个标记,为实现后面的点击变色并且不互相冲突的记录值
                };
        });
         let selectSkus = [];               //得到默认选中项,并处理成 ['红色', '大', '5L']格式
         for (let key in JSON.parse(res.data.defaultSpec)) {
            selectSkus.push(JSON.parse(res.data.defaultSpec)[key]);
        }
         this.getSpecList(selectSkus)             //初始执行一次处理禁用关系函数
 },

//点击切换选项方法:
changeTab(index, indexs) {
            this.skuList[index].sidx = indexs;
        //得到点击选择的规格组合
            const selectInfo = this.skuList.reduce((prev, cur) => {
                if (prev) {
                    return prev + ',' + cur['list'][cur.sidx];
                } else {
                    return cur['list'][cur.sidx];
                }
            }, '');
        this.getSpecList(selectInfo.split(','));
},

// 获取禁用的sku组合和点击后得到的选中的组合
getSpecList(selectSkus) {
          //调用接口获取禁用的sku组合
            this.api.getSpecList().then(res => {
                    this.disSkus = res.data.data.cdMallSkusList.map(a => {
                        let dis = [];
                        for (let key in JSON.parse(a)) {
                            dis.push(JSON.parse(a)[key]);
                        }
                        return dis;
                    });
              //调用下方格式化规格方法
                this.formatSkuValue(selectSkus);
            });
},

// 格式化规格,找出禁选坐标
formatSkuValue(selectSkus) {
            this.disBtn = [];
            let noSelectSkusAll = []
            let skuGroupAll = []

            // selectSkus 默认选中的规格组合
            // skuList 每个规格的各项,所有的规格
            // noSelectSkus 每个规格没有被选中的各项(二维数组,每一个数组对应每一个规格)
            let noSelectSkus = this.skuList.map((a, index) => {
                return a.list
                    .map(b => {
                        if (b != selectSkus[index]) {
                            return b;
                        }
                    })
                    .filter(Boolean);
            });
            
            // noSelectSkusAll 二维数组,过滤出排除当前规格项的其他已选中的规格项.例如:
            // ['红','大','5L']为选中项,那么过滤后noSelectSkusAll的值就是:[['大','5L'],['红','5L'],['红','大']]
            
            for(let i = 0;i<selectSkus.length;i++){
                let arr = selectSkus.filter(item => {
                    return item != selectSkus[i];
                });
                noSelectSkusAll.push(arr)
            }
            
            // skuGroupAll 三维数组或二维数组,取决于规格有多少,大于2个规格时就是三维数组,
            // 组合出每一个规格选中的项和上述 noSelectSkusAll得到的值进行组合.例如:
            // [['绿','紫'],['中','小'],['10L','15L']]是noSelectSkusAll的值,['红','大','5L']是选中的组合项,那么组合就是[[['绿','大','5L'],['紫','大','5L']],[['红','小','5L'],['红','中','5L']],[['红','大','10L'],['红','大','15L']]]
            
            for(let i = 0;i<noSelectSkus.length;i++){
                let arr = []
                noSelectSkus[i].forEach(item => {
                    noSelectSkusAll[i].splice(i, 0, item);
                    arr.push(JSON.parse(JSON.stringify(noSelectSkusAll[i])));
                    noSelectSkusAll[i].splice(i, 1);
                });
                skuGroupAll.push(arr)
            }
            
            //disSkus 是提前设定好的禁选的sku组合
            //把skuGroupAll中的组合和禁选的组合进行比对,完全相同的就是需要禁选的,
            //通过遍历skuList所有的规格的每一项,比如第0下标的规格和h[0]比对,一一对比,等到相同项就是最终的禁选项,如果相同就可以得出0下标的第几项了,比如第2项是禁选的那么坐标就是[0,1]
            //最终把有禁用项的下标存储到数组中,有多少个规格就存多少长度的数组,就可以准确的知道每一个规格哪一项是禁选的准确坐标
            //例如:[[1],[0,1],[2]]为最终得到的禁选的下标,那么第1个规格的第2个禁选,第3个规格的第3个禁选,....
            for(let j = 0;j<skuGroupAll.length;j++){
                let arr = []
                skuGroupAll[j].map((v, i) => {
                    this.disSkus.map((h, s) => {
                        if (v.toString() == h.toString()) {
                            console.log('禁用');
                            this.skuList[j].list.map((a, index) => {
                                if (a == h[j]) {
                                    arr.push(index);
                                }
                            });
                        }
                    });
                });
                this.disBtn.push(arr)
            }
        console.log('disBtn',this.disBtn);
},

// index:每个规格的下标,相当于y轴
// indexs:每个规格里每项的下标,相当于x轴
// 获取禁用项具体坐标
formatXY(index,indexs){
        let disStr = ''
        this.disBtn.map((a, index1) => {
            // disBtn:保存的是最终需要被禁选的下标,二维数组组成,每个数组的小标为y轴即规格,数组中的数字就是x轴即规格的每项,例如:[[2],[0]] 就是(0,2)和(1,0)被禁选
            // 当y轴的规格名和x轴的规格项名都相等时,就找到了需要禁选的那一项
                a.map(b => {
                    disStr = (index == index1 && indexs == b) || disStr
                })
            });
        console.log('disStr',disStr);
        // disStr返回true则当前项是禁选的
        return disStr
},
css(scss):
//创建一个scss 的minix用作公用
@mixin str($size,$weight,$color) {
  font-size: $size;
  font-family: PingFang SC;
  font-weight: $weight;
  color:$color;
  opacity: 1;
}

.sku {
    .content1,
    .content2 {
      .t1 {
          @include str(28px, bold, #333);
      }

      .flex_warp {
          display: flex;
          flex-wrap: wrap;
      }

      .list {
          display: flex;
          flex-wrap: wrap;

        p {
          display: inline-block;
          width: 218px;
          height: 60px;
          border-radius: 8px;
          background: rgba(237, 237, 237, 1);
          text-align: center;
          line-height: 60px;
          cursor: pointer;
          @include str(28px, 400, #444);
        }

        p:nth-child(n + 4) {
          margin-top: 20px;
        }

        p:nth-child(n + 2) {
          margin-left: 10px;
        }
      }
    }
    //选择时的按钮颜色变化
    .selectColor {
      background: #fc6d18 !important;
      color: #fff !important;
    }

    //禁用项的样式
    .disColor {
      text-decoration: line-through !important;
      background: rgba(237, 237, 237, 1) !important;
      color: #ccc !important;
      opacity: 0.1;
    }
  }
上一篇 下一篇

猜你喜欢

热点阅读