SKU(Stock Keeping Unit)最小存货单位. 目前电商平台的业务中,只要有商品,就不可避免的会遇到sku方面功能,那么怎么更好的来实现呢?
网上随便找一张效果图:
思想:1.记住三个概念:规格属性列表,库存列表,选中列表
2.第一步将选中的列表处理成键值对的格式,长这样:
this.selectList:{
"颜色":"",
"套餐":"",
"内存":"",
}
- 第二步,将规格属性列表处理成我们需要的样子,渲染的数据也是处理后的数据(一般后端给的都有很多属性,这里就简单提取一下):
- 提取之后大概长这样:
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,},
] },
]
- 第三步,点击事件,拿到点击的值,每点击一次都要将拿到的值(选中列表)和库存列表相比较,重新渲染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>