最近接到一個需求類似於商城選購的一個sku列表,大致要實現的效果如下:
sku的專業名詞解釋爲:
庫存保有單位即庫存進出計量的單位,
可以是以件、盒、托盤等爲單位。SKU是物理上不可分割的最小存貨單元。在使用時要根據不同業態,不同管理模式來處理
但我個人理解則爲:當你選擇到某一個屬性,與這個相關的屬性應該會發生相對應的變化,這裏的變化指的是這個選項是否是可選。
假設我現在的數據結構如下:
data: [ // 庫存
{ id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
{ id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
{ id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
{ id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
{ id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
{ id: "6", specs: ["紅色", "套餐一", "64G"], total: 120 }
],
commoditySpecs: [ // 商品類型 ["紅色", "紫色", "白色", "黑色"] ["套餐一", "套餐二", "套餐三", "套餐四"] ["64G", "128G", "256G"]
{ key: "color", title: "顏色", list: [{ id: "red", name: "紅色", disable: false, active: false }, { id: "zise", name: "紫色" }, { id: "white", name: "白色" }, { id: "black", name: "黑色" }, { id: "blue", name: "藍色" }] },
{ key: "status", title: "套餐", list: [{ id: "one", name: "套餐一", disable: false }, { id: "two", name: "套餐二" }, { id: "three", name: "套餐三" }, { id: "four", name: "套餐四" }, { id: "five", name: "套餐五" }] },
{ key: "size", title: "內存", list: [{ id: "small", name: "64G" }, { id: "mini", name: "128G" }, { id: "big", name: "256G" }] }
],
如果我們現在選擇到的顏色類型爲"紅色",那麼這個顏色可以選擇的套餐和內存爲,“套餐一"和"64G”,效果如下:
同理如果你繼續往下面選擇"套餐一",則只會有"64G"這個選項。
思路捋清楚之後,我就思考一下代碼如何實現,其實sku的列表的難點在於它們之間狀態的聯動。那麼我們可以把data中的specs數據看作爲一個"頂點",那麼這個"頂點"會與其它的元素進行一個相關聯。
比如現在的庫存data爲:
data: [ // 庫存
{ id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
{ id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
{ id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
{ id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
{ id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
{ id: "6", specs: ["紅色", "套餐一", "64G"], total: 120 }
],
我們就可以找出與"紫色"相關聯的相鄰節點,那麼在這裏與“紫色”相關聯的節點爲,[“套餐一”,“64G”,“套餐一”,“128G”,“套餐二”,“128G”],以此類推"黑色"相關聯的節點爲:[“套餐三”,“256G”]等等,如果我們能夠獲取相鄰的節點則可以判斷出它是否可以進行點擊。但是在這個過程中,我們需要主要數組去重這樣的一個小細節即可。
initData () {
this.data.forEach(v => {
let specs = v.specs;
specs.forEach(s => {
if (!this.obj[s]) {
this.obj[s] = specs;
} else {
this.obj[s] = this.obj[s].concat(specs);
}
});
});
console.log(this.obj);
},
getVertex (name) {
if (!this.obj[name]) {
return [];
}
return Array.from(new Set(this.obj[name].filter(v => v !== name)));
},
console.log(this.getVertex("紅色")); // ["套餐一","64G"]
那麼還有一個技術難點就是再次點擊某個列表取消選擇的時候,我的思路如下:
(1)取消選擇的時候應去判斷當前是否還有被選擇到元素,如果沒有則讓它回到初始的狀態,則是爲全部都沒有被選擇的情況
(2)如果取消選擇存在有其它元素被選擇的情況,則需要獲取到其它元素關聯的並集。從而決定屬性是否可選。
完整代碼如下:
<template>
<div class="sku">
<div class="list" v-for="(item,index) in commoditySpecs" :key="index">
<div class="title">{{item.title}}</div>
<div class="shopList">
<span
v-for="(shopItem,sIndex) in item.list"
:key="sIndex"
class="shopList-item"
:class="{disable:shopItem.disable,active:shopItem.active}"
@click="handClickFun(shopItem,sIndex,item.title,item.key)"
>{{shopItem.name}}</span>
</div>
</div>
<div class="tips">
<p>選擇的顏色是:{{colorName}}</p>
<p>選擇的套餐是:{{statusName}}</p>
<p>選擇的內存是:{{sizeName}}</p>
<p>總數爲:{{cTotal}}件</p>
</div>
</div>
</template>
<script>
export default {
name: "sku",
data () {
return {
data: [ // 庫存
{ id: "1", specs: ["紫色", "套餐一", "64G"], total: 50 },
{ id: "2", specs: ["紫色", "套餐一", "128G"], total: 60 },
{ id: "3", specs: ["紫色", "套餐二", "128G"], total: 160 },
{ id: "4", specs: ["黑色", "套餐三", "256G"], total: 40 },
{ id: "5", specs: ["白色", "套餐三", "64G"], total: 480 },
{ id: "6", specs: ["紅色", "套餐一", "64G"], total: 120 }
],
commoditySpecs: [ // 商品類型 ["紅色", "紫色", "白色", "黑色"] ["套餐一", "套餐二", "套餐三", "套餐四"] ["64G", "128G", "256G"]
{ key: "color", title: "顏色", list: [{ id: "red", name: "紅色", disable: false, active: false }, { id: "zise", name: "紫色" }, { id: "white", name: "白色" }, { id: "black", name: "黑色" }, { id: "blue", name: "藍色" }] },
{ key: "status", title: "套餐", list: [{ id: "one", name: "套餐一", disable: false }, { id: "two", name: "套餐二" }, { id: "three", name: "套餐三" }, { id: "four", name: "套餐四" }, { id: "five", name: "套餐五" }] },
{ key: "size", title: "內存", list: [{ id: "small", name: "64G" }, { id: "mini", name: "128G" }, { id: "big", name: "256G" }] }
],
item: {},
obj: {}
// colorName: "", // 顏色名稱
// statusName: "", // 套餐名稱
// sizeName: "" // 內存名稱
};
},
computed: {
cTotal () { // 計算總數
let color = this.colorName;
let status = this.statusName;
let sizeName = this.sizeName;
let str = color + "" + status + "" + sizeName;
let obj = this.data.find(v => v.specs.join("") === str);
if (obj) {
return obj.total;
}
return 0;
},
colorName () {
let data = this.commoditySpecs[0].list.find(v => v.active);
if (data) {
return data.name;
}
return "";
},
statusName () {
let data = this.commoditySpecs[1].list.find(v => v.active);
if (data) {
return data.name;
}
return "";
},
sizeName () {
let data = this.commoditySpecs[2].list.find(v => v.active);
if (data) {
return data.name;
}
return "";
}
},
methods: {
// 設置相關元素的狀態
dfs (data, relation) {
if (!data || data.length === 0) {
return;
}
let stack = [];
data.forEach(v => {
stack.push(v);
});
while (stack.length) {
const result = stack.shift();
if (relation.includes(result.name)) {
this.$set(result, "disable", false);
} else {
this.$set(result, "disable", true);
if (result.disable && result.active) {
this.$set(result, "active", false);
}
}
if (result.list && result.list.length > 0) {
stack = [...stack, ...result.list];
}
}
return data;
},
// 初始化所有都是可以選擇
init () {
this.commoditySpecs.forEach(v => {
let list = v.list;
list.forEach(s => {
this.$set(s, "disable", false);
this.$set(s, "active", false);
});
});
},
// 初始化數據
initData () {
this.data.forEach(v => {
let specs = v.specs;
specs.forEach(s => {
if (!this.obj[s]) {
this.obj[s] = specs;
} else {
this.obj[s] = this.obj[s].concat(specs);
}
});
});
console.log(this.obj);
},
getVertex (name) {
if (!this.obj[name]) {
return [];
}
return Array.from(new Set(this.obj[name].filter(v => v !== name)));
},
handClickFun (item, sIndex, title, key) {
this.item = item;
if (item.disable) {
return;
}
if (!item.active) { // 沒有選擇的情況
// 當前列取消選擇
let currentData = this.commoditySpecs.filter(v => v.title === title);
currentData.forEach(v => {
let list = v.list;
list.forEach(s => {
this.$set(s, "active", false);
});
});
let relation = this.getVertex(item.name);
console.log(relation);
if (relation.length === 0) { // 沒有庫存了
let restData = this.commoditySpecs.filter(v => v.title !== title);
restData.forEach(v => {
let list = v.list;
list.forEach(s => {
this.$set(s, "disable", true);
});
});
} else { // 有庫存
let restData = this.commoditySpecs.filter(v => v.title !== title);
this.dfs(restData, relation);
}
this.$set(item, "active", true);
} else { // 取消選擇的情況
this.$set(item, "active", false);
let choseData = this.commoditySpecs.reduce(
(total, current) => total.concat(current.list.filter(v => v.active)),
[]
);
if (choseData.length === 0) { // 當前沒有選中的元素
this.init();
} else { // 當前存在選中的元素
let choseDataName = choseData.map(v => v.name);
let arr = [];
choseData.forEach(v => {
let currentData = this.getVertex(v.name);
arr = arr.concat(currentData);
});
arr = arr.concat(choseDataName);
this.dfs(this.commoditySpecs, arr);
}
}
}
},
mounted () {
this.init();
this.initData();
console.log(this.getVertex("紅色"));
}
};
</script>
<style lang="scss" scoped>
.shopList {
display: flex;
text-indent: 10px;
.shopList-item {
margin-right: 8px;
background: #ffffff;
cursor: pointer;
&.active {
background: red;
}
&.disable {
background: #dddddd;
}
}
}
</style>
完整的github地址爲:https://github.com/whenTheMorningDark/vue-kai-admin/blob/master/src/views/sku/index.vue,如果對你有幫助,請點一下star。