# Detailed explanation of sku algorithm and demo continued

### Preface

Those who have done the front-end sales of e-commerce projects should have encountered the problem of calculating the inventory of products of different specifications. The industry term is `sku(stock Keeping Unit)`that the inventory unit corresponds to the specific specifications we sell, such as the specific model and specifications of a mobile phone, one of which `iphone6s 4G `is one `sku`. Here we distinguish `spu(Standard Product Unit)`, standardized product units, such as a mobile phone model `iphone6s`is one `spu`.

### sku algorithm

When displaying products on the front-end `sku`, we need to calculate different inventory levels and dynamically display them to users according to different user choices . Here is an `sku`algorithm derived .

#### data structure

Let's first take a look at the general data structure of storing inventory on the back-end server:

``````//
const skuList = [
{
skuId: "0",
skuGroup: [" ", " "],
remainStock: 7,
price: 2,
picUrl: "https://dummyimage.com/100x100/ff00b4/ffffff&text= ",
},
{
skuId: "1",
skuGroup: [" ", " "],
remainStock: 3,
price: 4,
picUrl: "https://dummyimage.com/100x100/ff00b4/ffffff&text= ",
},
{
skuId: "2",
skuGroup: [" ", " "],
remainStock: 0,
price: 0.01,
picUrl: "https://dummyimage.com/100x100/0084ff/ffffff&text= ",
},
{
skuId: "3",
skuGroup: [" ", " "],
remainStock: 1,
price: 1,
picUrl: "https://dummyimage.com/100x100/0084ff/ffffff&text= ",
},
];

//
const skuNameList = [
{
skuName: " ",
skuValues: [" ", " "],
},
{
skuName: " ",
skuValues: [" ", " "],
},
];
``````

#### Algorithm demo

After the front-end user selects a single specification or multiple specifications, we need to dynamically calculate whether other buttons can be clicked at this time (combination has inventory), and the total inventory corresponding to the current state, the cover image and the price range.

Take the above data as an example

Nothing was selected at the beginning, the default picture is displayed, the picture corresponding to the first combination (['Red-Big']) in the specification list, the inventory is the total inventory of the product, and the price is the price range of the product. Then when the user selects a certain attribute or several attributes, the corresponding picture, inventory, and price range are calculated in real time.

At the same time, according to the currently selected attributes, the unselectable attributes are grayed out. In this example, ` `the corresponding inventory of the product is 0, so when we choose one of them to be blue or large, we need to gray out the other attribute option.

#### Implementation ideas-the second algorithm

Ideas

` skuList skuNameList skuNameList-skuValues skuNameList `

• First define the variable `skuStock`(inventory object), `skuPartNameStock`(used to cache non-full-name inventory, such as {'small': 4})

• Take the selected attribute set under the specification list as the input parameter `selected`. If the relevant attribute is not selected in the current specification, an empty string is passed in, that is, at the beginning`selected === ['', '']`

• Determine the currently selected attribute `selected`Are there buffer stock, there is a direct return buffer stock

• Judge whether all are selected currently, if all are selected, the inventory read from skuStock will be returned, and the inventory will be cached in time before then

• Define the inventory variable remainStock, and the attribute array willSelected will be selected

• Traverse inventory specifications to determine whether the current specification attribute has been selected, and if selected, the current attribute will be pushed into willSelected

• If not selected, traverse the attribute array, combine the attribute array and the selected array selected, recursively obtain the current combined inventory, and accumulate the inventory

• Finally, the accumulated inventory is returned as the corresponding inventory when the selected attribute is selected, and cached in the skuPartNameStock object in time

``````//sku
const skuStock = skuList.forEach(sku => {
this.skuStock[sku.skuGroup && sku.skuGroup.join("-")] = sku.remainStock;
});
//
const skuPartNameStock = {};

/**
*
* @param {Array} selected
* @return {Object} skuInfo
*
*/
function getRemainByKey(selected) {
const selectedJoin = selected.join("-");

//
if (typeof skuPartNameStock[selectedJoin] !== "undefined") {
return skuPartNameStock[selectedJoin];
}

// skuStock
if (selected.length === skuNameList.length) {
skuPartNameStock[selectedJoin] = skuStock[selectedJoin]
? skuStock[selectedJoin]
: 0;
return skuPartNameStock[selectedJoin];
}

let remainStock = 0;
const willSelected = [];

for (let i = 0; i < skuNameList.length; i += 1) {
// sku
const exist = skuNameList[i].skuValues.find(
name => name === selected[0]
);
if (exist && selected.length > 0) {
willSelected.push(selected.shift());
} else {
// sku sku
for (let j = 0; j < skuNameList[i].skuValues.length; j += 1) {
remainStock += this.getRemainByKey(
willSelected.concat(skuNameList[i].skuValues[j], selected)
);
}
break;
}
}
//
skuPartNameStock[selectedJoin] = remainStock;
return skuPartNameStock[selectedJoin];
}
``````

### demo

Using this algorithm wrote `skuModal`of vue demo, stick the next code, we can see the effect cited as a component to facilitate understanding

``````<template>
<div v-if="visible" class="modal">
<div class="content">
<div class="title">
{{ skuInfo.specName }}
<span class="close" @click="close">
<svg
t="1590840102842"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="1264"
width="32"
height="32"
>
<path
d="M810.666667 273.493333L750.506667 213.333333 512 451.84 273.493333 213.333333 213.333333 273.493333 451.84 512 213.333333 750.506667 273.493333 810.666667 512 572.16 750.506667 810.666667 810.666667 750.506667 572.16 512z"
p-id="1265"
fill="#666666"
></path>
</svg>
</span>
</div>
<div class="info">
<img :src="skuInfo.pic" class="pic"/>
<div class="sku-info">
<span class="price">
{{
skuInfo.minPrice === skuInfo.maxPrice
? skuInfo.minPrice
: skuInfo.minPrice + "-" + skuInfo.maxPrice
}}
</span>
<span class="selected">{{ skuInfo.selectedTip }}</span>
<span class="stock"> {{ skuInfo.remainStock }} </span>
</div>
</div>

<div v-for="(sku, index) in skuStatusGroup" :key="index" class="spec">
<span class="name">{{ sku.name }}</span>
<div class="group">
<span
v-for="(keyInfo, idx) in sku.list"
:key="idx"
class="spec-name"
:class="{
active: keyInfo.status === 1,
disabled: keyInfo.status === -1
}"
@click="selectSku(index, idx)"
>{{ keyInfo.key }}</span
>
</div>
</div>
<div class="footer">
<button
class="btn"
:class="skuInfo.isSelectedAll ? 'active' : ''"
type="button"
@click="confirm"
>

</button>
</div>
</div>
</div>
</template>

<script>
export default {
props: {
visible: Boolean
},
data() {
return {
skuInfo: {
// sku
minPrice: 0,
maxPrice: 0,
pic: "",
selected: [], // sku   ''
realSelectd: [],
selectedTip: "",
specName: "",
stock: 0,
isSelectedAll: false
},
skuStatusGroup: [], // sku
skuStock: {}, //sku   -
skuPartNameStock: {}, //sku ( )
skuList: [], // sku
skuInfoCache: {} // sku skuInfo
};
},
methods: {
initSku(data) {
const { skuList, skuNameList } = data;

// sku
this.clearOldSku();

skuNameList.forEach(({ skuName, skuValues }) => {
this.skuStatusGroup.push({
name: skuName,
list: skuValues.map(value => ({
key: value,
status: 0 //0   -1   1
}))
});
});

this.skuNameList = skuNameList;

//
this.skuInfo.specName = skuNameList.map(item => item.skuName).join(" | ");

//sku
skuList.forEach(sku => {
this.skuStock[sku.skuGroup && sku.skuGroup.join("-")] = sku.remainStock;
});

//sku
this.skuList = skuList || [];

// sku
this.filterSkuKey();
},

// sku
clearOldSku() {
this.skuStatusGroup = [];
this.skuStock = {};
this.skuPartNameStock = {};
this.skuList = [];
this.skuInfoCache = {};
},

close() {
this.\$emit("update:visible", false);
},

// skuInfo
const { skuStatusGroup } = this;
const realSelectd = selected.filter(item => item);

const priceInfo = this.getskuInfoByKey(selected);
const stock = this.getRemainByKey(realSelectd);
const isSelectedAll = realSelectd.length === selected.length;
const selectedTip = isSelectedAll
? `  \${realSelectd.join(" ")}`
: `  \${selected
.map((item, idx) => {
if (!item) {
return skuStatusGroup[idx].name;
}
return null;
})
.filter(item => item)
.join(" ")}`;

this.skuInfo = Object.assign({}, this.skuInfo, priceInfo, {
selected,
stock,
realSelectd,
isSelectedAll,
selectedTip
});
},

// sku sku
filterSkuKey() {
const { skuStatusGroup } = this;
const selected = [];

// sku
skuStatusGroup.forEach(sku => {
let pos = 0;
const isInSelected = sku.list.some((skuInfo, idx) => {
pos = idx;
return skuInfo.status === 1;
});

selected.push(isInSelected ? sku.list[pos].key : "");
});

// skuInfo

// sku
skuStatusGroup.forEach((sku, skuIdx) => {
const curSelected = selected.slice();

//
sku.list.forEach(skuInfo => {
if (skuInfo.status === 1) {
return;
}

// sku
const cacheKey = curSelected[skuIdx];
curSelected[skuIdx] = skuInfo.key;
const stock = this.getRemainByKey(curSelected.filter(item => item));
curSelected[skuIdx] = cacheKey;

// sku
if (stock <= 0) {
//eslint-disable-next-line no-param-reassign
skuInfo.status = -1;
} else {
//eslint-disable-next-line no-param-reassign
skuInfo.status = 0;
}
});
});
},

//sku   sku
selectSku(listIdx, keyIdx) {
const { list } = this.skuStatusGroup[listIdx];
const { status } = list[keyIdx];

//status -1   0   1
if (status === -1) {
return;
}

// sku
list.forEach((keyInfo, idx) => {
if (keyInfo.status !== -1) {
if (idx === keyIdx) {
//eslint-disable-next-line no-param-reassign
keyInfo.status = 1 - status;
} else {
//eslint-disable-next-line no-param-reassign
keyInfo.status = 0;
}
}
});

// sku
this.filterSkuKey();
},

/**
*  sku
* @param {Array} selected  sku
*/
getskuInfoByKey(selected = []) {
const { skuList } = this;
const cacheInfo = this.skuInfoCache[
selected.filter(item => item).join("-")
];

//
if (cacheInfo) {
return cacheInfo;
}

const info = {
minPrice: -1,
maxPrice: -1,
pic: ""
};

skuList.forEach(sku => {
const group = sku.skuGroup;

//  key => key
const isInclude = selected.every(
(name, index) => name === "" || name === group[index]
);

if (isInclude) {
const { minPrice, maxPrice } = info;
//  -1
info.minPrice =
minPrice === -1 ? sku.price : Math.min(minPrice, sku.price);
info.maxPrice =
maxPrice === -1 ? sku.price : Math.max(maxPrice, sku.price);
info.pic = sku.picUrl;
}
});

// sku
if (selected[0] === "") info.pic = skuList[0].picUrl;

this.skuInfoCache[selected.filter(item => item).join("-")] = info;

return info;
},

/**
* sku   sku
* @param {Array} selected  sku
*/
getRemainByKey(selected = []) {
const { skuStock, skuPartNameStock, skuNameList } = this;
const selectedJoin = selected.join("-");

//
if (typeof skuPartNameStock[selectedJoin] !== "undefined") {
return skuPartNameStock[selectedJoin];
}

// sku
if (selected.length === skuNameList.length) {
skuPartNameStock[selectedJoin] = skuStock[selectedJoin]
? skuStock[selectedJoin]
: 0;
return skuPartNameStock[selectedJoin];
}

let remainStock = 0;
const willSelected = [];

for (let i = 0; i < skuNameList.length; i += 1) {
// sku
const exist = skuNameList[i].skuValues.find(
_item => _item === selected[0]
);
if (exist && selected.length > 0) {
willSelected.push(selected.shift());
} else {
// sku sku
for (let j = 0; j < skuNameList[i].skuValues.length; j += 1) {
remainStock += this.getRemainByKey(
willSelected.concat(skuNameList[i].skuValues[j], selected)
);
}
break;
}
}
//
skuPartNameStock[selectedJoin] = remainStock;
return skuPartNameStock[selectedJoin];
},

//
confirm() {
const { skuList } = this;

if (skuList.length > 1 && !this.skuInfo.isSelectedAll) {
return;
}

const { skuId } = this.skuList.filter(item => {
if (item.skuGroup.join("-") === this.skuInfo.realSelectd.join("-")) {
return true;
}
return false;
})[0];

this.\$emit("confirm", skuId);
}
}
};
</script>

<style lang="less" scoped>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;

&:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.2);
}

.content {
position: absolute;
top: 50%;
left: 50%;
max-height: 900px;
overflow: auto;
background: #fff;
transform: translate(-50%, -50%);
z-index: 1;

.title {
display: flex;
justify-content: space-between;
color: #666;
font-size: 32px;
line-height: 60px;
text-align: left;
border-bottom: 1px solid #eee;

.close {
display: flex;
align-items: center;
}
}

.info {
display: flex;
margin-top: 10px;

.pic {
width: 180px;
height: 180px;
}

.sku-info {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-left: 30px;
color: #999;
font-size: 26px;

span {
margin-bottom: 20px;
}

.price {
color: #333;
}
}
}

.spec {
display: flex;

.name {
color: #999;
font-size: 24px;
line-height: 54px;
}

.group {
margin-left: 20px;

.spec-name {
display: inline-block;
height: 54px;
margin: 0 30px 10px 0;
line-height: 54px;
color: #333;
font-size: 28px;
background: rgba(245, 245, 245, 1);
border: 1px solid rgba(204, 204, 204, 1);

&.active {
color: #ff981a;
background: #ffeeeb;
border: 1px solid #ff981a;
}

&.disabled {
color: #cccccc;
background: #f5f5f5;
border: 1px solid transparent;
}
}
}
}

.btn {
width: 690px;
height: 80px;
color: rgba(255, 255, 255, 1);
font-size: 32px;
background: rgba(204, 204, 204, 1);
outline: none;

&.active {
color: #fff;
background: #ff981a;
}
}
}
}
</style>
``````

How to use

``````<!--   -->
<skuModal ref="sku" :visible.sync="visible" @confirm="confirm"></skuModal>
``````
``````// sku
this.\$refs.sku.initSku({
skuNameList, //
skuList //
});
``````

### summary

Those who have done e-commerce projects should have dealt with or heard of sku. Learning related concepts and a true understanding of how to calculate sku can help us become more familiar with the business and improve our ability to handle related businesses. In the future, the interviewer's questions will be more stable. The first sku algorithm can refer to the previous blog.

### reference

Welcome to the front-end learning group to study together~ 516913974