后臺模型
需求與實現(模擬實現)
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(){
let selectSkus = []; //得到默認選中項,并處理成 ['紅色', '大', '5L']格式
for (let key in JSON.parse(res.data.defaultSpec)) {
selectSkus.push(JSON.parse(res.data.defaultSpec)[key]);
}
//res.data.data.attributeList為調用后端接口返回的字段,字段內容見上方模擬數據
this.skuList = JSON.parse(res.data.attributeList).map((u, index) => {
let sidx = u.skuValue.findIndex(item => item == selectSkus[index]) //計算出默認規格的下標,做選中效果使用
//處理成對象型數組格式,更好處理和數據渲染
return {
name: u.skuName, //規格名
id: index, //每一項的key
list: u.skuValue, //數組,規格項的名稱的集合
sidx:sidx //主要為每一個規格做一個標記,為實現后面的點擊變色并且不互相沖突的記錄值
};
});
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;
}
}