preface

Recently, our mall project designed to a specification configuration, it followed the goods is calculated according to specifications of the inventory, and restrict the user to select the specifications of the inventory, such a great degree of optimizing the user experience, even liver two days I will back office goods finally specification design and displays of the specifications of the small program, this article mainly introduces the small program on the design, The main design ideas are as follows:

  • Initialize, set a default specification parameter, and then calculate the inventory count for the other options
  • Click the specification trigger to iterate over the unselected specification and calculate whether it is in stock

The data structure passed to me from the back end

Both sets of data are trueproductDetailThe following array

Product Specification data (xqsGoodsSpecVos)

[{" id ": 1," specName ":" taste ", "specVoValues" : [{" id ": 53," optionName ":" five kernel flavor "}, {" id ": 54," optionName ": "Black sesame flavor"}, {" id ": 55," optionName ":" salt and pepper taste "}, {" id ": 63," optionName ":" black sesame flavor + five kernel flavor "}, {" id ": 66," optionName ": "Black sesame seed, salt and pepper and five ren"}}, {" id ": 5," specName ":" weight ", "specVoValues" : [{" id ": 56," optionName ":" 100 gb "}, {" id ": 57, "optionName": "200g" }, { "id": 58, "optionName": "300g" } ] } ]Copy the code

Commodity specification price data (xqsGoodsSkus)

[ { "gmtModified": null, "gmtCreate": null, "id": 448, "goodsId": 210, "skuId": null, "store": 1, "price": 25, "supplyPrice": 10, "historyPrice": 30, "specId": "53-56", "specName": "Wurenwei-100g ", "isDefault": 1 }, { "gmtModified": null, "gmtCreate": null, "id": 449, "goodsId": 210, "skuId": null, "store": 0, "price": 30, "supplyPrice": 20, "historyPrice": 40, "specId": "53-57", "specName": "Wurenwei-200g ", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 450, "goodsId": 210, "skuId": null, "store": 1, "price": 40, "supplyPrice": 30, "historyPrice": 50, "specId": "53-58", "specName": "Wurenwei-300g ", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 451, "goodsId": 210, "skuId": null, "store": 1000, "price": 20, "supplyPrice": 10, "historyPrice": 30, "specId": "54-56", "specName": "Black sesame flavor -100g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 452, "goodsId": 210, "skuId": null, "store": 1000, "price": 30, "supplyPrice": 20, "historyPrice": 40, "specId": "54-57", "specName": "Black sesame flavor -200g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 453, "goodsId": 210, "skuId": null, "store": 1000, "price": 40, "supplyPrice": 30, "historyPrice": 50, "specId": "54-58", "specName": "black sesame -300g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 454, "goodsId": 210, "skuId": null, "store": 1000, "price": 20, "supplyPrice": 10, "historyPrice": 30, "specId": "55-56", "specName": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 455, "goodsId": 210, "skuId": null, "store": 1000, "price": 30, "supplyPrice": 20, "historyPrice": 40, "specId": "55-57", "specName": "200g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 456, "goodsId": 210, "skuId": null, "store": 1000, "price": 30, 40, "supplyPrice" : "historyPrice" : 50, "specId" : "55-58," "specName" : "salt and pepper taste - 300 - g", "isDefault" : 0 }, { "gmtModified": null, "gmtCreate": null, "id": 457, "goodsId": 210, "skuId": null, "store": 1000, "price": 40, "supplyPrice": 20, "historyPrice": 60, "specId": "56-63", "specName": "Black Sesame flavor + Five kernel flavor -100g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 458, "goodsId": 210, "skuId": null, "store": 1000, "price": 60, "supplyPrice": 40, "historyPrice": 80, "specId": "57-63", "specName": "Black sesame flavor + Five kernel flavor -200g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 459, "goodsId": 210, "skuId": null, "store": 1000, "price": 80, "supplyPrice": 60, "historyPrice": 100, "specId": "58-63", "specName": "Black sesame flavor + Five kernel flavor -300g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 460, "goodsId": 210, "skuId": null, "store": 1000, "price": 60, "supplyPrice": 30, "historyPrice": 90, "specId": "56-66", "specName": "Black sesame + Pepper + Five-kernel + 100g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 461, "goodsId": 210, "skuId": null, "store": 1000, "price": 90, "supplyPrice": 60, "historyPrice": 120, "specId": "57-66", "specName": "Black sesame + Salt + Five kernel -200g", "isDefault": 0 }, { "gmtModified": null, "gmtCreate": null, "id": 462, "goodsId": 210, "skuId": null, "store": 1000, "price": 232, "supplyPrice": 121, "historyPrice": 427, "specId": "58-66", "specName": "black sesame + Salt + wuren-300g ", "isDefault": 0}]Copy the code

Specification popup page implementation

Here I mainly use the vuant-WEAP related components to assist in development

<! <van-popup round class="spec-popup" closeable show="{{specPopup}}" bind:close="showSpecPopup" close-icon="clear" position="bottom" custom-style="min-height:50%; max-height:80%" > <view> <! <van-card title="{{productdetail. goodsTitle}}"> <view slot="thumb"> <van-image use-error-slot use-loading-slot width="88px" height="88px" src="{{productDetail.images[0]}}" lazy-loader="true" > <text slot="error">XXX</text> <text slot="loading">XXX</text> </van-image> </view> <view slot="price" style="color:#ed2856; The font - size: 24 the RPX "> $< text style =" font - size: 36 the RPX;" >{{ productDetail.price }}</text> </view> <view style="color:#7b7f84" slot="tags" >{{ productDetail.specName ? productDetail.specName : '' }}</view> </van-card> <! - specification information select - > < block wx: for = "{{productDetail. XqsGoodsSpecVos}}" wx: key = "index" wx: the for - index = "specIndex" wx:for-item="spec" > <view class="spec-name">{{ spec.specName }}</view> <view class="option"> <block wx:for="{{spec.specVoValues}}" wx:for-item="option" wx:key="index" wx:for-index="optionIndex" > <! -- Three status values, selected, unselected, unavailable,TODO -- some options are not selected, <van-button class="select-btn" plain data-spec-index="{{specIndex}}" data-option-index="{{optionIndex}}" bindtap="selectOptionName" size="mini" color="#ed2856" wx:if="{{option.visible && option.store>0 }}" >{{ option.optionName }}</van-button> <van-button plain class="un-select-btn" size="mini" color="#424549" data-spec-index="{{specIndex}}" data-option-index="{{optionIndex}}" bindtap="selectOptionName" wx:if="{{! option.visible && option.store>0}}" >{{ option.optionName }}</van-button> <van-button color="#CBCED1" disabled class="disable-btn" size="mini" plain wx:if="{{option.store == 0}}" >{{ option.optionName }}</van-button> </block> </view> </block> </view> </van-popup>Copy the code

The specific implementation UI is as follows

Initialize related operations

After initialization, I observed the data of inventory 0 in the specification price, which we do not choose

Filter specification price data with inventory 0

this.productDetail.xqsGoodsSkus = this.productDetail.xqsGoodsSkus.filter(item => {
     return item.store > 0;
});
Copy the code

Initializes a selected state for a specification parameter

The main purpose of this operation is to record the index of the currently selected specification for my later selected and unselected states, instead of going through the number group each time I click. Change visible for the selected specification to False

let store = 0; this.selectSpecArr = this.productDetail.xqsGoodsSkus[0].specId.split('-'); store = this.productDetail.xqsGoodsSkus[0].store; for (let i = 0; i < this.productDetail.xqsGoodsSpecVos.length; i++) { for (let j = 0; j < this.productDetail.xqsGoodsSpecVos[i].specVoValues.length; j++) { for (let k = 0; k < this.selectSpecArr.length; k++) { if (this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].id == this.selectSpecArr[k]) { this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].visible = true; / / to this. ProductDetail. XqsGoodsSpecVos [I] records the currently selected index j, Click behind the specification is convenient for the last time the specifications of the visible set to false (not selected) enclosing productDetail. XqsGoodsSpecVos [I] curSelectIndex = j; break; } } } } }Copy the code

Calculation of the inventory

In fact, the calculation of inventory is mainly clear, in fact, we are imitating a pre-selection in addition to the selected specification ID combination, at the same time to calculate whether there is inventory, and then to determine a state of the specification button

For (let I = 0; i < this.productDetail.xqsGoodsSpecVos.length; i++) { for (let j = 0; j < this.productDetail.xqsGoodsSpecVos[i].specVoValues.length; j++) { if (! This. ProductDetail. XqsGoodsSpecVos [I] specVoValues [j]. Journal of visible) {/ / traverse is not selected, Assign ([], this.selectSpecarr); let tempIndexArr = Object.assign([], this.selectSpecarr); this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = 0; for (let k = 0; k < tempIndexArr.length; k++) { if (tempIndexArr[k] == this.productDetail.xqsGoodsSpecVos[i].specVoValues[this.productDetail.xqsGoodsSpecVos[i].curSelectIndex].id) { tempIndexArr[k] = this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].id; break; } } tempIndexArr.sort((a, b) => { return Number(a) - Number(b) }); Let tempSelectIndex = tempIndexArr.join('-'); for (let m = 0; m < this.productDetail.xqsGoodsSkus.length; M++) {// all specifications have been selected by specId when initialized. At this point directly to string comparison can be an if (this. ProductDetail. XqsGoodsSkus [m]. SpecId = = tempSelectIndex) { this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = this.productDetail.xqsGoodsSkus[m].store; break; } } } else { this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = store; }}}Copy the code

Click the Specification button method

The following methods are availableselectOptionNameUnder the

selectOptionName(e) {
}
Copy the code

Realize the switch of selected state

Click the specification button to switch between selected and unselected, and obtain the currently selected specification ID combination at the same time

All specifications ID toString() are included below in preparation for later comparison specifications

let optionIndex = e.currentTarget.dataset.optionIndex; let specIndex = e.currentTarget.dataset.specIndex; let specVos = this.productDetail.xqsGoodsSpecVos[specIndex]; Specvos. specVoValues[optionIndex].visible =! specVos.specVoValues[optionIndex].visible; if (specVos.curSelectIndex ! For (let index = 0; let index = 0; index < this.selectSpecArr.length; index++) { if (this.selectSpecArr[index] == specVos.specVoValues[specVos.curSelectIndex].id) { this.selectSpecArr[index]  = specVos.specVoValues[optionIndex].id.toString(); break; } } } else { this.selectSpecArr.push(specVos.specVoValues[optionIndex].id.toString()); } if (specVos.curSelectIndex ! = null) { specVos.specVoValues[specVos.curSelectIndex].visible = false; } if (specVos.curSelectIndex ! == optionIndex) { specVos.curSelectIndex = optionIndex; } else {// selectSpecArr removes the selected id let selectIndex = this.selectSpecArr.indexOf(specVos.specVoValues[optionIndex].id.toString()); if (selectIndex ! = -1) { this.selectSpecArr.splice(selectIndex, 1) } specVos.curSelectIndex = null; } let store = 0; this.selectSpecArr.sort((a, b) => { return Number(a) - Number(b) }); let selectIndex = this.selectSpecArr.join('-');Copy the code

Calculate the price

Here, the combination of the specId in the specification array and the selected ID is compared to obtain the selected inventory and price

For (let index = 0; index < this.productDetail.xqsGoodsSkus.length; index++) { if (this.productDetail.xqsGoodsSkus[index].specId == selectIndex) { this.productDetail.price = this.productDetail.xqsGoodsSkus[index].price.toFixed(2); this.productDetail.costPrice = this.productDetail.xqsGoodsSkus[index].supplyPrice.toFixed(2); store = this.productDetail.xqsGoodsSkus[index].store; this.productDetail.specName = this.productDetail.xqsGoodsSkus[index].specName; this.productDetail.skuId = this.productDetail.xqsGoodsSkus[index].id; break; }} / / modify total price enclosing totalPrice = floatMultiply (this) productDetail) price, enclosing productQuantity);Copy the code

Calculation of the inventory

This is basically the same as the initialization logic, but I make some minor distinctions:

  • Initialization: All specifications are selected
  • At this time, I use the Set Set to judge whether the ID combination in the price specification contains the specification ID combination to be calculated
For (let I = 0; i < this.productDetail.xqsGoodsSpecVos.length; i++) { for (let j = 0; j < this.productDetail.xqsGoodsSpecVos[i].specVoValues.length; j++) { if (! This. ProductDetail. XqsGoodsSpecVos [I] specVoValues [j]. Journal of visible) {/ / traverse is not selected, Assign ([], this.selectSpecarr); let tempIndexArr = Object.assign([], this.selectSpecarr); this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = 0; if (this.productDetail.xqsGoodsSpecVos[i].curSelectIndex ! = null) { for (let k = 0; k < tempIndexArr.length; k++) { if (tempIndexArr[k] == this.productDetail.xqsGoodsSpecVos[i].specVoValues[this.productDetail.xqsGoodsSpecVos[i].curSelectIndex].id) { tempIndexArr[k] = this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].id.toString(); break; } } } else { tempIndexArr.push(this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].id.toString()) } tempIndexArr.sort((a, b) => { return Number(a) - Number(b) }); // let tempSelectIndex = tempIndexArr.join('-'); // console.log(' Calculated spec ID: ', tempIndexArr); for (let m = 0; m < this.productDetail.xqsGoodsSkus.length; M++) {/ / change compare whether the two arrays containing enclosing productDetail. XqsGoodsSkus [m]. SpecId contains tempSelectIndex let idArr = this.productDetail.xqsGoodsSkus[m].specId.split('-'); Let tempSet = new Set([...idArr,...tempIndexArr]); if (tempSet.size == idArr.length) { this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = this.productDetail.xqsGoodsSkus[m].store; break; } } } else { this.productDetail.xqsGoodsSpecVos[i].specVoValues[j].store = 1; }}}}Copy the code

conclusion

In fact, at the beginning of the combination of specifications did not contact the relevant functions, but still as long as clear specific ideas (code written worse, do not spray), according to the correct logic, to debug, debugger can be

The renderings are as follows