vue中使用sku ,一个简单便捷的实现手段

255 阅读3分钟

SKU(Stock Keeping Unit)最小存货单位. 目前电商平台的业务中,只要有商品,就不可避免的会遇到sku方面功能,那么怎么更好的来实现呢?

网上随便找一张效果图

VW2QZ%4OW1PSY@E2I2L03B4.png

思想:1.记住三个概念:规格属性列表,库存列表,选中列表

2.第一步将选中的列表处理成键值对的格式,长这样:

this.selectList:{
    "颜色""",
    "套餐""",
    "内存""",
}
  1. 第二步,将规格属性列表处理成我们需要的样子,渲染的数据也是处理后的数据(一般后端给的都有很多属性,这里就简单提取一下):
  • 提取之后大概长这样:
const specList = [    { title: "颜色", list: ["红色","紫色","白色","黑色" ] },
    { title: "套餐", list: [ "套餐一","套餐二","套餐三", "套餐四",] },
    { title: "内存", list: ["64G","128G","256G" ] }
]
  • 经过处理(将提取后的规格属性列表中list数组中属性变成对象并动态添加一个able属性,able属性用来判断是否可以点击)后长这样:
const specList = [
    { title: "颜色", list: [ {name:'红色',able:true,},
                            { name:'紫色',able:true,},
                            { name:'白色',able:true,},
                            { name:'黑色',able:true,} 
                           ] },
    { title: "套餐", list: [ {name:'套餐一',able:true,},
                            { name:'套餐二',able:true,},
                            { name:'套餐三',able:true,},
                            { name:'套餐四',able:true,} 
                            ] },
    { title: "内存", list: [ {name:'64G',able:true,},
                            { name:'128G',able:true,},
                            { name:'256G',able:true,},                 
                            ] },
]
  1. 第三步,点击事件,拿到点击的值,每点击一次都要将拿到的值(选中列表)和库存列表相比较,重新渲染able的属性值
 methods:{
        skuclick(key,name,able){
               // console.log(key,name,able);
            if(!able) return
              //再次点击取消
            if(this.selectList[key]===name){
                this.selectList[key]=''
                
            }else{
                this.selectList[key]=name
                
            }
            // console.log(this.selectList[key]);
            // console.log(this.selectList);
         
            //更改渲染的数据属性,重新渲染最新的数据
            this.mydata2.forEach(item=>{
                item.list.forEach(it=>{
                    it.able=this.isAble(item.title,it.name)  //这里的isAble()方法就是用来比较得到able属性值
                })
            })
            
        },
 }

好了,思路大概就是这样,不理解的也没关系,看下面完整代码你就明白了,下面有 含有注释 和 没有注释的 两套代码,看情况自取。

所需数据长这样:

const specList = [    { title: "颜色", list: ["红色","紫色","白色","黑色" ] },
    { title: "套餐", list: [ "套餐一","套餐二","套餐三", "套餐四",] },
    { title: "内存", list: ["64G","128G","256G" ] }
]

//库存列表,这里只是所有组合中的一部分
const skuList = [    { id: 1608188117177, specs: ["红色", "套餐一", "64G"] },
    { id: 1608188117178, specs: ["红色", "套餐一", "128G"] },
    { id: 1608188117179, specs: ["红色", "套餐一", "256G"] },
    { id: 1608188117186, specs: ["红色", "套餐四", "64G"] },
    { id: 1608188117187, specs: ["红色", "套餐四", "128G"] },
    { id: 1608188117188, specs: ["红色", "套餐四", "256G"] },
    { id: 1608188117207, specs: ["白色", "套餐三", "64G"] },
    { id: 1608188117208, specs: ["白色", "套餐三", "128G"] },
    { id: 1608188117209, specs: ["白色", "套餐三", "256G"] },
    { id: 1608188117210, specs: ["白色", "套餐四", "64G"] },
    { id: 1608188117219, specs: ["黑色", "套餐三", "64G"] },
    { id: 1608188117220, specs: ["黑色", "套餐三", "128G"] },
    { id: 1608188117221, specs: ["黑色", "套餐三", "256G"] },
    { id: 1608188117222, specs: ["黑色", "套餐四", "64G"] },
        
]

完整代码(不含注释)

<template>
    <div>
        <div class="father" v-for="(a,k1) in mydata2" :key="k1">
            <div>{{a.title}}</div>
            <div class="child">
                <div v-for="(b,k2) in a.list" :key="k2">
                    <div  @click="skuclick(a.title,b.name,b.able)" :class="[selectList[a.title]===b.name ? 'active' : '' ,b.able ? '' : 'disabled' ]">{{b.name}}</div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import {skuList,specList} from '../data/data.js'
export default {
    data(){
        return{
            mydata1:[],
            mydata2:[],
            selectList:{}, //选中列表
        }
    },
    methods:{
        skuclick(key,name,able){
            if(!able) return
            if(this.selectList[key]===name){
                this.selectList[key]=''
                
            }else{
                this.selectList[key]=name
                
            }
            //更改渲染的数据属性,重新渲染最新的数据
            this.mydata2.forEach(item=>{
                item.list.forEach(it=>{
                    it.able=this.isAble(item.title,it.name)
                })
            })
            
        },
        //选中的和库存列表比较,判断是否可以被选择
        isAble(key,name){
            var copySelectList=JSON.parse(JSON.stringify(this.selectList))
            copySelectList[key]=name
        
            let falg=this.mydata1.some(item=>{
                var j = 0;
                for(let i in copySelectList){
                    if(copySelectList[i]!== '' && item.specs.includes(copySelectList[i])){
                        j++;
                    }else if(copySelectList[i]==''){
                        j++;
                    }
                }
                return j===specList.length
            })
            return falg
        }
    },

    created(){
        this.mydata1=skuList
        this.numarr=skuList
        specList.forEach(item => {
            this.$set(this.selectList,item.title,'')
        });
        this.mydata2=specList.map(item=>{
            return {
                title:item.title,
              
                list:item.list.map(it=>{
                    return {
                        name:it,
                        able:this.isAble(item.title,it)
                    }
                })
            }
        })
    }
}
</script>

<style>
    .sty{
        background-color: #e5e5e5;
        padding: 5px
    }
    .colorsty{
        background-color: red;
        color: #fff;
        padding: 5px
    }
    .father{
        margin: 10px;
    }
    .child{
        display: flex;
        justify-content: space-around
    }
    .active{
        background-color: red;
        color: #fff;
    }
    .disabled{
        color: #c0c4cc;
        cursor: not-allowed;
        background-image: none;
        background-color: #fff;
        border-color: #ebeef5;
    }
</style>

完整代码(含注释)

<template>
  <div class="hello">
    <div v-for="(item,index) in specList" :key="index">
      <div class='title'>{{item.title}}</div>
      <div class='spec'>
        <div class='spec-item' v-for="(its,ins) in item.list" :key="its.name + ins">
          <span @click="changeSpec(item.title, its.name, its.able)" :class="[selectSpec[item.title] === its.name ? 'active' : '' , its.able? '' : 'disabled']">{{its.name}}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
   import {skuList,specList} from "../data/data.js" 

export default {
  data(){
    return{
      specList:[],
      skuList:[],
      selectSpec:{},  // 选择数据的对象 将已选的数据放在这个对象里面记录下来  用对象的好处在下面深拷贝处就能体验到了 
    }
  },
  created() {
            this.skuList = skuList;
            //1.准备选中列表 初始化选择数据的对象 
            specList.forEach(item => {
              this.$set(this.selectSpec, item.title, "");
            })
            // console.log(this.selectSpec);
            // console.log(specList);
            // 将规格数据处理成我们视图所需要的数据类型 (将list改为对象并添加了一个able属性)
            this.specList = specList.map(item => {
              // console.log(item.title);
              return {
                title:item.title,
                list: item.list.map(its => {

                  // console.log(its);
                  return {
                    name:its,
                    //  判断是否可以选择
                    //  这里相当于init 初始化数据  this.isAble() 核心判断逻辑  
                    able: this.isAble(item.title, its)  // 注释的调试看逻辑代码 false 
                  }
                })
              }
            })
            // console.log(this.specList);
            // 注释的调试看逻辑代码
            // this.selectSpec = {
            //   '颜色':'',
            //   '套餐':'套餐一',
            //   '内存':'64G'
            // }
            // this.isAble('颜色', '红色')
          },
  methods:{
            // 3.再看isAble判断
            // 核心判断逻辑 
            // 判断规格是否可以被选择  核心函数 key当前的规格的title   value规格值
            isAble(key, value){
              // 深拷贝 避免被影响  
              // console.log(this.selectSpec);
              var copySelectSpec = JSON.parse(JSON.stringify(this.selectSpec));
              // 用对象的好处就在这了 直接赋值当前验证项
              // console.log(key);
              // console.log(value);
              // console.log(copySelectSpec);
              copySelectSpec[key] = value;
              // console.log(copySelectSpec);
              // 用数组的 some 方法 效率高 符合条件直接退出循环
              let flag = this.skuList.some(item => {
                // 条件判断 核心逻辑判断
                // console.log(item)
                var i = 0 ;  //每次都会被清零
                // 这个for in 循环的逻辑就对底子不深的人来说就看不懂了 原理就是循环已经选中的 和 正在当前对比的数据 和 所有的sku对比 
                // 只有当前验证的所有项满足sku中的规格或者其他规格为空时 即满足条件 稍微有点复杂 把注释的调试代码打开就调试下就可以看懂了
                for(let k in copySelectSpec) {
                  //  console.log(copySelectSpec[k])  // 注释的调试看逻辑代码
                  if(copySelectSpec[k] != "" && item.specs.includes(copySelectSpec[k])){
                    // console.log(item)
                    i++;

                  }else if (copySelectSpec[k] == "") {
                    i++;
                  }
                }
                // 符合下面条件就退出了 不符合会一直循环直到循环结束没有符合的条件就 return false 了 
                // console.log(i) // 注释的调试看逻辑代码  // i每次都会被清零
                return i == specList.length
              })
              // console.log(flag)
              return flag
            },
            // 2.先看点击事件
            changeSpec(key,value,able){
              // console.log(this.specList);
              // console.log(key,value,able);
              // console.log(this.selectSpec[key]);
              if(!able) return
              if(this.selectSpec[key] === value){
                this.selectSpec[key] = ''
              }else {
                this.selectSpec[key] = value
              }
              // console.log(this.selectSpec);
              // forEach循环改变原数组 
              this.specList.forEach(item => {
                item.list.forEach(its => {
                  its.able = this.isAble(item.title, its.name); //颜色 黑色
                  // console.log(its.name, its.able);
                });
              });
            }
          },
        }

</script>

<style scoped>
.title{
  margin:30px 0px 20px 0;
}
.spec-item{
  display: inline-block;
  margin-right: 10px;
}
.spec-item span {
  border:1px solid #eee;
  cursor: pointer;
  padding:5px 10px;
}
.spec-item .active{
  border: 1px solid red;
  background-color: red;
  color:#fff;
}
.spec-item .disabled{
  color: #c0c4cc;
  cursor: not-allowed;
  background-image: none;
  background-color: #fff;
  border-color: #ebeef5;
}

</style>

这里多一嘴啊: 其实我们可以根据规格列表计算出所有的组合

上代码:

<template>
    <div>
        <div>你好,sku</div>
    </div>
</template>

<script>
    //规格数据
    const colors = ["红色","紫色","白色","黑色" ];
    const meal = [ "套餐一","套餐二","套餐三", "套餐四",];
    const storage = ["64G","128G","256G" ];

//计算出所以的排列组合 相当于接口获取的库存列表 
const getSkuList = (attrList) => {
    // console.log(attrList);
  if (attrList.length < 2) return attrList[0] || [];
  return attrList.reduce((total, current) => {
    const res = [];
    // console.log(total);
    // console.log(current);
    total.forEach((t) => {
        // console.log(t);
      current.forEach((c) => {
        const temp = Array.isArray(t) ? [...t] : [t];
        temp.push(c);
        // console.log(temp);
        res.push(temp);
      });
    });
    return res;
  });
}

getSkuList([colors,meal,storage]);
// console.log(getSkuList([colors,meal,storage]));  //打印出所有的组合

export default {

}
</script>

<style>

</style>