閒聊
最近做了一個電商專案,用了vue-cli搭建了一個vuejs SPA應用。電商嘛,都會涉及到商品選擇這個模組,剛好之前沒有實現過這個模組的經驗,這次寫了一下和大家分享一下。
開始
html
<div class="popBox" id="app">
<div class="sku-list" v-if="specs.length">
<div class="sku-item" v-for="(spec, index) in specs" :key="index">
<label>{{ spec.name }}</label>
<div class="spces-item">
<button
v-for="(entry, keys) in spec.entries"
:key="keys.id"
v-if="entry.isSelected"
:disabled="!isSpecEntryAvailable(spec.name, entry.id)"
:class="{ active: isSpecEntrySelected(spec.name, entry.id) }"
@click="entry.isSelected && isSpecEntryAvailable(spec.name, entry.id) ? selectSpec(spec.name, entry.id) : ''">
{{ entry.value }}
</button>
</div>
</div>
<div class="quantity">
<div class="btn">
<button :disabled="!canDec()" @click="decQuantity()" class="btn-minus"></button>
</div>
<input type="tel" name="quantity" ref="input" :value="quantity" />
<div class="btn">
<button :disabled="!canInc()" @click="incQuantity()" class="btn-plus"></button>
</div>
</div>
</div>
</div>
複製程式碼
javascript
// sku列表
const SKU_LIST = '[{"id":1,"price":12,"specificationValues":[{"id":1,"value":"原味"},{"id":4,"value":"red"}],"stock":3},{"id":2,"price":33,"specificationValues":[{"id":2,"value":"棕色小熊"},{"id":5,"value":"green"}],"stock":0},{"id":3,"price":33,"specificationValues":[{"id":2,"value":"綠色狐狸(杯蓋同紅色款"},{"id":4,"value":"red"}],"stock":4}]';
// 規格列表
const SPEC_LIST = '[{"name":"口味","entries":[{"id":1,"value":"原味","isSelected":true},{"id":2,"value":"綠色狐狸(杯蓋同紅色款)","isSelected":true},{"id":3,"value":"棕色小熊","isSelected":false}]},{"name":"顏色","entries":[{"id":4,"value":"red","isSelected":true},{"id":5,"value":"green","isSelected":true}]}]';
new Vue({
el: '#app',
data() {
return {
sku: JSON.parse(SKU_LIST),
specs: JSON.parse(SPEC_LIST),
selectedSpecs: [],
quantity: 1,
};
},
methods: {
getStockNum() {
return this.skuList.reduce((acc, value) => acc + value.stock, 0);
},
addDefaultSelectedSpecs() {
this.quantity = Number(Cookies.get('cartNum')) || 1;
if (!this.selectedSpec) return;
this.selectedSpec.forEach((spec) => {
if (this.isSpecEntryAvailable(spec.specName, spec.entryId)) {
this.selectSpec(spec.specName, spec.entryId);
}
});
},
selectSpec(specName, entryId) {
const index = this.selectedSpecs.findIndex(spec => spec.specName === specName);
if (index === -1) {
this.selectedSpecs.push({ specName, entryId });
} else {
const [spec] = this.selectedSpecs.splice(index, 1);
if (spec.entryId !== entryId) {
this.selectedSpecs.push({ specName, entryId });
}
}
this.productPrice = (this.skuPrice() / 100).toFixed(2);
this.quantity = Math.min(this.quantity, this.skuStock());
},
isSpecEntrySelected(specName, entryId) {
const match = spec => spec.specName === specName && spec.entryId === entryId;
return this.selectedSpecs.find(match);
},
isSpecEntryAvailable(specName, entryId) {
const combination = this.selectedSpecs.filter(spec => spec.specName !== specName);
combination.push({ specName, entryId });
return this.sku.filter(sku => this.isMatchSku(sku, combination) && sku.stock > 0).length > 0;
},
isSkuSelected() {
return this.selectedSpecs.length === this.specs.length;
},
isMatchSku(sku, specs) {
let skuSpecEntryIds = [];
let specEntryIds = [];
if (!sku.specificationValues || !specs) {
skuSpecEntryIds.push(sku.id);
} else {
skuSpecEntryIds = sku.specificationValues.map(value => value.id);
specEntryIds = specs.map(spec => spec.entryId);
}
return specEntryIds.length === this.intersection(specEntryIds, skuSpecEntryIds).length;
},
intersection(specs, skus) {
return skus.filter(sku => specs.indexOf(sku) > -1);
},
canInc() {
return this.isSkuSelected() && this.quantity < this.skuStock();
},
canDec() {
return this.isSkuSelected() && this.quantity > 1;
},
incQuantity() {
const quantity = Number(this.quantity);
if (this.canInc()) {
this.quantity = quantity + 1;
}
},
decQuantity() {
const quantity = Number(this.quantity);
if (this.canDec()) {
this.quantity = quantity - 1;
}
},
skuStock() {
const sku = this.findSelectedSku();
return sku ? sku.stock : this.totalStock;
},
skuPrice() {
const sku = this.findSelectedSku();
return sku ? sku.price : this.price;
},
findSelectedSku() {
if (!this.isSkuSelected()) {
return null;
}
return this.sku.find(sku => this.isMatchSku(sku, this.selectedSpecs));
},
},
computed: {
totalStock() {
return this.sku.reduce((acc, val) => {
return acc + Number(val.stock);
}, 0);
}
}
});
複製程式碼