实现效果如下
需求解析
1)转换接口返回的sku数据 初始化一部分必要的变量数据
labels(当前规格的所有规格名称 如途中的 颜色和尺寸) curProps (items===>所有规格条,result====>所有规格和对应的规格值) disabledSelected (不能进行点击的规格) curSelected (当前已经选择的规格) spliter(规格map中分割的符号) curresult(当前选中的规格拼接字符串) res (根据可用的子集sku组合成的map数据)
combineAttr methods (得到所有可能的规格组合) buildResult methods (得到所有的子集合,并与sku组成一个map格式的对象 一一匹配) clickSkuItem methods (点击规格进行的操作) a)判断当前是否能点击 不能点击的直接返回; b)判断当前点击的内容是否存在 如果存在 直接将存在的删除 不存在 添加到已选项上;
showResult methods (展示结果==是否完整的选择了一条sku数据,是?可以进行对应价格的更新等操作) filterDisabledSku methods (根据当前点击的操作进行disabledSelected数据的更新,以此达到用户当前是否能点击某种规格的判断)
a)只有一种规格的类型 eg:颜色(值可以有多个)此时设置disabledSelected=[] b)所有的值都存在 eg:颜色和规格各有两个 且sku的条数为4条 此时设置disabledSelected=[] c)并不是包含所有的sku eg:上图中的有四个规格 却只有两个配对的sku 此时过滤单个子集放入到数组中
isContained method (判断两个数组中是否有交集) export function isContained(aa,bb){ if(!(aa instanceof Array) || !(bb instanceof Array)||aa.length < bb.length) { return false; } let c = aa.filter((item)=>{ return bb.indexOf(item) > -1 }) if(c.length>0){ return true } return false }
<template>
<div class="demosku">
<div v-for="(item,key,index) in curProps.result">
<div class="textleft">
<div style="display: flex">
<div>{{labels[index]}}:</div>
<div v-for="it in item"
class="curskuval"
:class="{active:labels[index]=='颜色'?curSelected[index]==it.value:curSelected[index]==it,
disabled:labels[index]=='颜色'?JSON.stringify(disabledSelected).indexOf(it.value)!=-1:JSON.stringify(disabledSelected).indexOf(it)!=-1}"
@click="clickSkuItem(it,index)">
{{labels[index]=='颜色'?it.value:it}}</div>
</div>
</div>
</div>
<p>已选</p>
<div>{{curresult}}</div>
</div>
</template>
<script>
import {isContained } from '@/utils/auth'
export default{
name:'demosku',
data(){
return{
curdata:[
{
"marketPrice": 458200,
"stock": 100,
"skuUuid": "1128953008617558016",
"properties": [
{
"name": "颜色",
"value": {
"img": "",
"icon": "",
"remark": "",
"value": "酒红"
}
},
{
name: "尺寸",
value: "尺寸1"
}
],
"salesPrice": 458200,
"num": ""
},
{
"marketPrice": 152800,
"stock": 100,
"skuUuid": "1128953008625946624",
"properties": [
{
"name": "颜色",
"value": {
"img": "",
"icon": "",
"remark": "",
"value": "绿色"
}
},
{
name: "尺寸",
value: "尺寸2"
}
],
"salesPrice": 152800,
"num": ""
},
{
"marketPrice": 458200,
"stock": 100,
"skuUuid": "1128953008617558025",
"properties": [
{
"name": "颜色",
"value": {
"img": "",
"icon": "",
"remark": "",
"value": "酒红"
}
},
{
name: "尺寸",
value: "尺寸2"
}
],
"salesPrice": 458200,
"num": ""
},
],
curProps:[],
labels:[],
curSelected:[],
disabledSelected:[],
curfilterlabelVal:[],
spliter :'\u2299',
curresult:'',
res:{}
}
},
created(){
this.initData(this.curdata);
},
methods:{
filterColorObj(it){
//判断是否是object对象
if(Object.prototype.toString.call(it)==="[object Object]") {
return true
}
return false
},
trimSpliter(str, spliter) {
// ⊙abc⊙ => abc
// ⊙a⊙⊙b⊙c⊙ => a⊙b⊙c
var reLeft = new RegExp('^' + spliter + '+', 'g');
var reRight = new RegExp(spliter + '+$', 'g');
var reSpliter = new RegExp(spliter + '+', 'g');
return str.replace(reLeft, '')
.replace(reRight, '')
.replace(reSpliter, spliter)
},
handleNormalClick(key,curval){
this.curSelected.splice(key,1,curval)
},
clickSkuItem(obj,key){
//选中某一个sku 当前选中的值 判断是否能够点击
//判断当前点击的内容是否已经选择
let curtype='';
if(this.filterColorObj(obj)){
curtype=obj.value
}else{
curtype=obj
}
//判断当前是否能点击 不能点击的直接返回
if(JSON.stringify(this.disabledSelected).indexOf(curtype)!=-1){
return
}
//如果存在 直接将存在的删除
if(JSON.stringify( this.curSelected).indexOf(curtype)!==-1){
this.curSelected.splice(key,1,'')
//当前选中的结果清除
this.curresult=''
}else{
this.curSelected.splice(key,1,curtype)
}
this.filterDisabledSku();
this.showResult();
},
filterDisabledSku(){
//过滤获取不可点击的内容
//默认不可选的内容
this.disabledSelected=[];
let k=0;
this.curSelected.forEach((item)=>{
if(item===''){
k++
}
})
if(k===this.curSelected.length){
//所有的不可选置空
return
}
let curlen=1;
for(let i in this.curProps.result){
curlen*=this.curProps.result[i].length
}
if(this.curProps.items.length==curlen){
return;
}
//过滤出当前选中的sku的值
let curselectsku=this.curSelected.filter((item)=>{
return item!==''
})
let curselectstr=curselectsku.join(this.spliter)
let curskus=this.res[curselectstr]
//判断后者是否是一个长度大于一的数组
//过滤出与当前选中的sku不一致的labelVal 只要包含
for(let i in this.res){
let its=this.res[i];
if(i!==''&&i.indexOf(this.spliter)==-1){
console.log(curskus.skus)
console.log(i)
console.log(its.skus)
if(its.skus.length>curskus.skus.length){
if(!isContained(its.skus,curskus.skus)){
this.disabledSelected.push(i)
}
}else{
if(!isContained(curskus.skus,its.skus)){
this.disabledSelected.push(i)
}
}
}
}
},
getSelectedItem() {
/**
* 获取当前选中的属性
*/
//遍历当前所有的规格 result 获取当前选中的规格对应的值
let k=0;
for(let i in this.curProps.result){
if(!this.curSelected[k]){
this.curSelected[k]=''
}
k++
}
return this.curSelected
},
showResult() {
//展示结果
var result = this.getSelectedItem()
var s = []
for (var i = 0; i < result.length; i++) {
var item = result[i];
if (!!item) {
s.push(item)
}
}
//所有的规格都填写完成
if (s.length == this.labels.length) {
var curr = this.res[s.join(this.spliter)]
if (curr) {
s = s.concat(curr.skus)
}
this.curresult=s.join('\u3000-\u3000')
//展示对应的价格
this.curSelectSkuObj=this.filterPrice(curr.skus[0])
}
},
filterPrice(sku){
return this.curdata.filter((item)=>{
return item.skuUuid==sku
})
},
initData(data){
//初始化数据
//将源数据中的规格的名称放入keys数组中保存
let keys=[];
for(let attr_key in data[0]){
if (!data[0].hasOwnProperty(attr_key)) continue;
if (attr_key == 'properties'){
for(let a=0;a<data[0]['properties'].length;a++){
let it=data[0]['properties'][a]
keys.push(it.name)
}
}
}
this.labels=keys;
this.curProps = this.combineAttr(data, keys)
// 获取当前sku的所有的子集合
this.buildResult(this.curProps.items);
this.showResult();
},
combineAttr(data, keys) {
/**
* 计算组合数据
*/
var allKeys = []
var result = {}
//当前的data表示的直接是sku对应的每条记录
for (var i = 0; i < data.length; i++) {
var item = data[i]
var values = []
for (var j = 0; j < keys.length; j++) {
var key = keys[j]
if (!result[key]) result[key] = []
//判断是否是颜色
if(key==='颜色'){
if (JSON.stringify(result[key]).indexOf(JSON.stringify(item.properties[j].value)) < 0) result[key].push(item.properties[j].value)
values.push(item.properties[j].value.value)
this.curfilterlabelVal.push(item.properties[j].value.value)
}else{
if (result[key].indexOf(item.properties[j].value) < 0) result[key].push(item.properties[j].value)
values.push(item.properties[j].value)
this.curfilterlabelVal.push(item.properties[j].value)
}
}
allKeys.push({
path: values.join(this.spliter),
sku: item['skuUuid']
})
}
return {
result: result,
items: allKeys
}
},
getAllKeys(arr) {
var result = []
for (var i = 0; i < arr.length; i++) {
result.push(arr[i].path)
}
return result
},
buildResult(items) {
/**
* 生成所有子集是否可选、库存状态 map
*/
var allKeys = this.getAllKeys(items);
for (var i = 0; i < allKeys.length; i++) {
var curr = allKeys[i]
var sku = items[i].sku
var values = curr.split(this.spliter)
// var allSets = getAllSets(values)
var allSets = this.powerset(values)
// 每个组合的子集
for (var j = 0; j < allSets.length; j++) {
var set = allSets[j]
var key = set.join(this.spliter)
if (this.res[key]) {
this.res[key].skus.push(sku)
} else {
this.res[key] = {
skus: [sku]
}
}
}
}
},
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;
}
}
}
</script>
<style scoped lang="scss" rel="stylesheet/scss">
.demosku{
margin:0 auto;
max-width:800px;
text-align: left;
>div{
margin: 20px 0;
.curskuval{
padding: 5px 10px;
margin-right:10px;
border:1px solid #ddd;
&:hover{
cursor: pointer;
}
&.active{
background-color: red;
color: #fff;
}
&.disabled{
border-style:dashed;
background-color: #ddd;
&:hover{
cursor: default;
}
}
}
}
}
</style>