首先說(shuō)一說(shuō)什么是SKU。匀奏。鞭衩。。娃善。论衍。。自己百度去聚磺。坯台。。
類似京東上面瘫寝,未來(lái)人類S5這個(gè)臺(tái)筆記本
都是S5這個(gè)型號(hào)蜒蕾,但是因?yàn)镃PU,顯卡,內(nèi)存焕阿,硬盤(pán)等不同咪啡,價(jià)格也不一樣。CPU,顯卡暮屡,內(nèi)存撤摸,硬盤(pán)等屬性組合成的一個(gè)唯一的商品,就可以用一個(gè)SKU來(lái)表示,像圖上就有10個(gè)SKU褒纲。一系列的SKU可以歸到一個(gè)SPU下進(jìn)行管理准夷。
那么一個(gè)SKU是怎么生成的呢?下面結(jié)合自己的一些經(jīng)驗(yàn)莺掠,說(shuō)說(shuō)一些電商平臺(tái)的大致產(chǎn)品結(jié)構(gòu)以及SKU的生成方式衫嵌。
1.阿里速賣通平臺(tái),阿里國(guó)際站
這兩個(gè)平臺(tái)同一個(gè)爸爸,基本差不多彻秆。要?jiǎng)?chuàng)建一個(gè)商品需要先選一個(gè)類目楔绞,類目下面掛了一堆的屬性,屬性上又掛了一堆的屬性值唇兑。屬性分為銷售屬性和非銷售屬性(銷售屬性就是類似顏色墓律,尺寸這些單個(gè)SKU獨(dú)有的,非銷售屬性就是多個(gè)SKU共有的幔亥,比如同一個(gè)品牌型號(hào)“未來(lái)人類S5”)耻讽。非銷售屬性有必填和非必填,可以是單選帕棉,多選针肥,文本等。銷售屬性就是構(gòu)成SKU的關(guān)鍵香伴。比如說(shuō)有銷售屬性顏色和尺寸慰枕,顏色屬性下有很多屬性值(紅,黃即纲,藍(lán)等等)具帮,尺寸(1,2,3,4等等)也是。當(dāng)顏色選了紅,黃蜂厅,尺寸選了1,2匪凡,那么就應(yīng)該生成2x2=4個(gè)SKU,每個(gè)SKU有各自的價(jià)格掘猿,庫(kù)存等病游。保存SKU的時(shí)候會(huì)與對(duì)應(yīng)的銷售屬性相關(guān)聯(lián)。
大致的數(shù)據(jù)模型如下
當(dāng)然稠通,實(shí)際比這更加復(fù)雜(比如產(chǎn)品的圖片衬衬,單個(gè)SKU的圖片,多個(gè)SKU共同的圖片,非銷售屬性可以自定義添加分類不具有的改橘。滋尉。。)
2.eBay
跟上面兩個(gè)平臺(tái)類似飞主,創(chuàng)建一個(gè)產(chǎn)品也要先選一個(gè)分類兼砖,分類下面也是有很多屬性,屬性有很多屬性值既棺。讽挟。。丸冕,不同的地方是eBay沒(méi)有區(qū)分銷售屬性和非銷售屬性(或者說(shuō)全部是非銷售屬性)耽梅,也允許添加自定義屬性和屬性值。eBay上SKU是手動(dòng)添加的胖烛,SKU上的屬性(SKU上的屬性暫且都叫做銷售屬性)也是自定義的眼姐。比如說(shuō)添加了一個(gè)SKU A,價(jià)格和數(shù)量這兩個(gè)是必須的,還可以手動(dòng)加個(gè)顏色屬性佩番,然后填上屬性值紅色众旗。當(dāng)然,若果增加一個(gè)SKU B趟畏,那么這個(gè)SKU也會(huì)有顏色這個(gè)屬性贡歧,屬性值可以自定義。最后校驗(yàn)這些SKU的屬性值組合起來(lái)是否唯一赋秀。
這樣的優(yōu)勢(shì)就是可以隨意控制SKU的數(shù)量利朵,如果按照上面兩個(gè)平臺(tái)的規(guī)則,這里應(yīng)該有4x4x1=16個(gè)SKU猎莲。這樣自由度更高會(huì)不會(huì)使結(jié)構(gòu)更加復(fù)雜呢绍弟?當(dāng)然是NO,用上面的數(shù)據(jù)模型就可以搞定
3.Lazada,Linio平臺(tái)
國(guó)際慣例著洼,先選一個(gè)類目樟遣,類目下面一堆屬性而叼,有必填和非必填,屬性下一堆屬性值豹悬。最大的區(qū)別就是葵陵,一個(gè)產(chǎn)品只有一個(gè)SKU,屬性不支持自定義屿衅。比如說(shuō)要添加一個(gè)商品埃难,有兩種顏色莹弊,那么只能創(chuàng)建兩個(gè)產(chǎn)品涤久,這兩個(gè)產(chǎn)品可能只有圖片不一樣(顏色不一樣,可能沒(méi)有顏色這個(gè)屬性來(lái)選)
4.Wish平臺(tái)
比較奇特忍弛,沒(méi)有分類响迂,產(chǎn)品有固定的屬性,比如標(biāo)題细疚,描述蔗彤,運(yùn)費(fèi)等。一個(gè)產(chǎn)品下可以有多個(gè)SKU疯兼,SKU的生成取決于固定的兩大類屬性然遏,兩個(gè)大類為:顏色和尺寸。比如顏色這個(gè)屬性就是歸類于顏色這個(gè)類吧彪,其他的屬性(品牌待侵,型號(hào),容量等等歸為尺寸這個(gè)大類)姨裸。尺寸類的屬性值支持自定義秧倾,但只能選一個(gè)尺寸類屬性(比如選了品牌就不能選容量,選了品牌后可以添加任意值)傀缩。兩類屬性不是必須同時(shí)存在那先,比如顏色選了紅,可以不選尺寸類的屬性赡艰,反之也一樣售淡。
忘記說(shuō)了,一個(gè)SKU是靠一串唯一編碼來(lái)標(biāo)識(shí)的慷垮,比如1234A勋又,1234B。一般來(lái)說(shuō)一個(gè)平臺(tái)下不會(huì)存在兩個(gè)相同的SKU换帜,或這一個(gè)店鋪下不會(huì)存在兩個(gè)相同的SKU楔壤。
大致的邏輯和數(shù)據(jù)模型就這些,接下來(lái)說(shuō)說(shuō)開(kāi)發(fā)實(shí)現(xiàn)方面惯驼。
數(shù)據(jù)庫(kù)大致就依靠上面的數(shù)據(jù)模型進(jìn)行設(shè)計(jì)蹲嚣,編輯的時(shí)候递瑰,后端按照這些關(guān)聯(lián)關(guān)系取出數(shù)據(jù)給到前端(我這邊前后端未分離,頁(yè)面還是后端渲染隙畜,但我還是把數(shù)據(jù)格式化為JSON再渲染到前端)抖部,保存的時(shí)候再進(jìn)行相關(guān)的邏輯校驗(yàn)。因?yàn)楹蠖说囊恍┻壿嫴僮魃婕昂蠊緝?nèi)部的業(yè)務(wù)议惰,這里就不細(xì)說(shuō)了慎颗。說(shuō)說(shuō)前端的具體細(xì)節(jié),以速賣通的為例言询,用的是vue俯萎,前端拿到的數(shù)據(jù)如下
實(shí)現(xiàn)后的粗糙界面
data: {
properties: properties,
skus: skus
}
遍歷properties运杭,得到材質(zhì)夫啊,顏色,發(fā)貨地辆憔,套餐這些屬性對(duì)象撇眯,接著遍歷這些對(duì)象里的values屬性,得到屬性值對(duì)象,根據(jù)屬性對(duì)象的selectedValues判斷屬性值是否選上(因?yàn)槲沂呛蠖虽秩镜膉s變量,所以初始化的時(shí)候selectedValues里的數(shù)據(jù)直接引用的屬性值對(duì)象虱咧,如果是非后端渲染的話熊榛,要根據(jù)skus里的屬性和屬性值去初始化selectedValues的數(shù)據(jù),并且存的是屬性值對(duì)象的引用)
<tr v-for="(index,item) in properties">
<td><strong>{{item.Name}}:</strong></td>
<td>
<label v-for="value in item.values"><input type="checkbox" :value="value" v-model="item.selectedValues"/>{{value.Name}}</label>
<table class="list_table" v-if="item.Name!='發(fā)貨地'&&item.selectedValues.length>0">
<tbody>
<tr>
<th>{{item.Name}}</th>
<th>自定義名稱</th>
<th v-if="item.Name=='顏色'">圖片(無(wú)圖片可以不填)</th>
</tr>
<tr v-for="selectedValue in item.selectedValues">
<td>{{selectedValue.Name}}</td>
<td>
<input type="text" v-model="selectedValue.DefinitionName" maxlength="20"/>
</td>
<td v-if="item.Name=='顏色'">
<div style="float: left">
<input type="file" style="width: 63px;"/>
</div>
<div style="float: right">
<a href="" rel="link" target="_blank">
![](selectedValue.ImageUrl)
</a>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
因?yàn)閟electedValues通過(guò)v-model綁定腕巡,當(dāng)選中或取消一個(gè)屬性值的時(shí)候后玄坦,selectedValues也會(huì)隨著改變,selectedValues里的數(shù)據(jù)是直接引用屬性值而不是拷貝一份數(shù)據(jù)逸雹,所以修改selectedValues中的數(shù)據(jù)也會(huì)直接反映到屬性值上营搅,實(shí)現(xiàn)了屬性值的自定義。
那么怎么根據(jù)選中的屬性值生成SKU呢梆砸?
SKU表格處的表頭是要根據(jù)選中的屬性動(dòng)態(tài)更新的转质,可以這樣做
<tr>
<th v-for="item in properties" v-if="item.selectedValues.length>0">{{item.Name}}</th>
<th><span class="c_red">*</span>零售價(jià)</th>
<th><span class="c_red">*</span>庫(kù)存</th>
<th>商品編碼</th>
</tr>
如果屬性里的屬性值都沒(méi)有被選中(selectedValues.length==0),就不在表頭顯示這個(gè)屬性帖世。
SKU的初始顯示
<tr v-for="sku in skus">
<td v-for="item in properties" v-if="item.selectedValues.length>0">{{getValueName(sku,item)}}</td>
<td>US $<input type="text" v-model="sku.SkuPrice" class="w50" maxlength="9"/><span name="productUnitTips"></span></td>
<td><input type="text" v-model="sku.StockQuantity" class="w50" maxlength="9"/></td>
<td><input type="text" v-model="sku.SkuCode" class="w180" maxlength="20"/></td>
</tr>
也是利用selectedValues.length讓SKU的屬性值列數(shù)與表頭列數(shù)保持一致休蟹。因?yàn)镾KU對(duì)象里的保存的是屬性值Id和屬性Id,需要一個(gè)方法去獲取屬性值的值
getValueName: function (sku, property) {
var valueName = "";
$.each(sku.values,
function () {
var _this = this;
if (this.propertyId == property.Id) {
$.each(property.selectedValues, function () {
if (_this.valueId == this.Id) {
valueName = this.Name;
return false;
}
});
}
});
return valueName;
}
你沒(méi)有看錯(cuò),這是JQ日矫。赂弓。。
接下來(lái)就是SKU表格的更新了哪轿,我的做法是變更整塊區(qū)域盈魁,就是給skus重新賦值。賦的新值從哪來(lái)呢窃诉?
將選中的屬性值放到一個(gè)數(shù)組中
var ori = [];
$.each(vm.properties,
function (index, item) {
var selectValues = this.selectedValues;
if (selectValues.length > 0) {
ori.push(selectValues);
}
});
得到這種結(jié)構(gòu)的數(shù)組
[
[
{
'PropertyId': 10,
'Id': 477,
'Name': '鋁',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 10,
'Id': 529,
'Name': '帆布',
'DefinitionName': '',
'ImageUrl': ''
}
],
[
{
'PropertyId': 200000828,
'Id': 201655809,
'Name': '殼+貼膜',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 200000828,
'Id': 201655810,
'Name': '殼+掛繩',
'DefinitionName': '',
'ImageUrl': ''
}
]
]
求笛卡爾積后(后面有求笛卡爾積參考鏈接)
var ret = descartes(ori);
[
[
{
'PropertyId': 10,
'Id': 477,
'Name': '鋁',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 200000828,
'Id': 201655809,
'Name': '殼+貼膜',
'DefinitionName': '',
'ImageUrl': ''
}
],
[
{
'PropertyId': 10,
'Id': 477,
'Name': '鋁',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 200000828,
'Id': 201655810,
'Name': '殼+掛繩',
'DefinitionName': '',
'ImageUrl': ''
}
],
[
{
'PropertyId': 10,
'Id': 529,
'Name': '帆布',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 200000828,
'Id': 201655809,
'Name': '殼+貼膜',
'DefinitionName': '',
'ImageUrl': ''
}
],
[
{
'PropertyId': 10,
'Id': 529,
'Name': '帆布',
'DefinitionName': '',
'ImageUrl': ''
},
{
'PropertyId': 200000828,
'Id': 201655810,
'Name': '殼+掛繩',
'DefinitionName': '',
'ImageUrl': ''
}
]
]
大前端也用上了算法有木有杨耙,這里需要弄明白拿到的是什么數(shù)據(jù)赤套,需要的是什么數(shù)據(jù),然后就去想實(shí)現(xiàn)就OK了珊膜。
想要的數(shù)據(jù)已經(jīng)拿到容握,重新構(gòu)建skus
for (var i = 0; i < ret.length; i++) {
var sku = {SkuCode: "", SkuPrice: "", StockQuantity: ""};
sku.values = [];
$.each(ret[i],
function () {
sku.values.push({propertyId: this.PropertyId, valueId: this.Id});
});
vmSkus.push(sku);
}
到此,更新SKU表格的代碼已經(jīng)實(shí)現(xiàn)车柠,數(shù)據(jù)驅(qū)動(dòng)視圖更新剔氏,很清晰。但是什么時(shí)候去觸發(fā)這個(gè)更新呢(何時(shí)去重新構(gòu)建skus)? 很簡(jiǎn)單嘛竹祷,就是勾選或取消勾選屬性值的時(shí)候去觸發(fā)更新操作谈跛。勾選或取消勾選我們能直接從selectedValues.length上得到反饋,然后使用vue 的watch就可以實(shí)現(xiàn)了溶褪。但是selectedValues是properties數(shù)組中元素的一個(gè)屬性币旧,vue的watch是無(wú)法用在數(shù)組元素的某一個(gè)字段上的(至少目前我發(fā)現(xiàn)是這樣的)践险,那么暴力一點(diǎn)猿妈,直接watch整個(gè)properties數(shù)組并且加上deep:true。這樣是可以實(shí)現(xiàn)巍虫,但是當(dāng)修改自定義屬性的時(shí)候也會(huì)觸發(fā)變更(業(yè)務(wù)會(huì)提刀來(lái)見(jiàn)的)彭则。
最終解決方案
computed:{
allCheckedLength:function(){
var length=0;
$.each(this.properties,function(){
length+=this.selectedValues.length;
});
return length;
}
}
watch: {
'allCheckedLength': {
handler: 'reBuild'
}
}
reBuild就是重新構(gòu)建的方法。
Java 笛卡爾積算法的簡(jiǎn)單實(shí)現(xiàn)
Cartesian product of multiple arrays in JavaScript