樂優商城(十)商品管理
目錄
3.4 商品描述資訊
商品描述資訊比較複雜,而且圖文並茂,甚至包括視訊。
這樣的內容,一般都會使用富文字編輯器。
3.4.1 富文字編輯器
通俗來說:富文字,就是比較豐富的文字編輯器。普通的框只能輸入文字,而富文字還能給文字加顏色樣式等。
富文字編輯器有很多,例如:KindEditor、Ueditor。但並不原生支援vue,所以要使用vue-quill-editor,它
是一款支援Vue的富文字編輯器。
3.4.2 Vue-Quill-Editor
Vue-Quill-Editor是一個基於Quill的富文字編輯器:
3.4.3 使用方法
使用非常簡單:
第一步:安裝,使用npm命令:
npm install vue-quill-editor --save
第二步:載入,在js中引入:
全域性使用:
import Vue from 'vue'
import VueQuillEditor from 'vue-quill-editor'
const options = {}; /* { default global options } */
Vue.use(VueQuillEditor, options); // options可選
區域性使用:
import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' import 'quill/dist/quill.bubble.css' import {quillEditor} from 'vue-quill-editor' var vm = new Vue({ components:{ quillEditor } })
第三步:頁面引用:
<quill-editor v-model="goods.spuDetail.description" :options="editorOption"/>
3.4.4 自定義富文字編輯器
vue-quill-editor預設的處理方式是直接將圖片轉成base64編碼,這樣的結果是整個富文字的html片段十分冗餘,通常來講,每個伺服器端接收的post的資料大小都是有限制的,這樣的話有可能導致提交失敗,或者是使用者體驗很差,資料要傳遞很久才全部傳送到伺服器。因此,在富文字編輯的過程中,對於圖片的處理,更合理的做法是將圖片上傳到伺服器,再將圖片連結插入到富文字中,以達到最優的體驗。所以要進行小小的改造,使其支援上傳圖片到伺服器的功能,請參考
使用也非常簡單:
<v-stepper-content step="2">
<v-editor v-model="goods.spuDetail.description" upload-url="/upload/image"/>
</v-stepper-content>
-
upload-url:是圖片上傳的路徑
-
v-model:雙向繫結,將富文字編輯器的內容繫結到goods.spuDetail.description
3.4.5 效果
3.5 規格引數
商品規格引數與商品分類繫結,因此我們需要在使用者選擇商品分類後,去後臺查詢對應的規格引數模板。
3.5.1 查詢商品規格
首先,在data中定義變數,用來分別記錄特有和普通規格引數模板:
然後,通過watch監控goods.categories的變化,然後去查詢規格:
查詢得到資料後,將資料傳入到dataProcess函式中進行處理,將普通屬性和特有屬性分開。特有屬性儲存在specialSpecs中,普通屬性儲存在specifications中。
//新增商品時資料處理
dataProces(temp){
let commonTemplate = [];
let specialTemplate = [];
let count = 0;
temp.forEach(({params}) => {
params.forEach(({k, options, global}) => {
if (!global) {
specialTemplate.push({
k, options, selected: []
})
}
})
});
for (let i=0;i<temp.length;i++){
if (temp[i].params.length > 0){
let temp2 = temp[i].params;
let param = [];
for (let j = 0;j < temp2.length; j++){
if (temp2[j].global){
param.push(temp2[j]);
}
}
if (count !== temp2.length && param.length !== 0) {
commonTemplate.push({
group: temp[i].group,
params: param,
});
}
}
}
this.specialSpecs = specialTemplate;
this.specifications = commonTemplate;
}
檢視是否查詢到:
3.5.2 頁面展示規格屬性
獲取到了規格引數,還需要把它展示到頁面中。
現在查詢到的規格引數只有key,並沒有值。值需要使用者來根據SPU資訊填寫,因此規格引數最終需要處理為表單。
整體結構
整體來看,規格引數是陣列,每個元素是一組規格的集合。我們需要分組來展示。比如每組放到一個card中。
注意事項:
規格引數中的屬性有一些需要我們特殊處理:
-
global:是否是全域性屬性,規格引數中一部分是SPU共享,屬於全域性屬性,另一部是SKU特有,需要根據SKU來填寫。因此,在當前版面中,只展示global為true的,即全域性屬性。sku特有屬性放到最後一個面板
-
numerical:是否是數值型別,如果是,把單位補充在頁面表單,不允許使用者填寫,並且要驗證使用者輸入的資料格式
-
options:是否有可選項,如果有,則使用下拉選框來渲染。
頁面程式碼:
<v-stepper-content step="3">
<v-flex class="xs10 mx-auto px-3">
<v-card v-for="spec in specifications" :key="spec.group" class="my-2">
<v-card-title class="subheading"><h4>{{spec.group}}</h4></v-card-title>
<v-divider></v-divider>
<v-card-text v-for="param in spec.params" :key="param.key" v-if="param.global" class="px-5">
<!--判斷是否有可選項,如果沒有,則顯示文字框。還要判斷是否是數值型別,如果是則把unit顯示到字尾-->
<v-text-field v-if="param.options.length <= 0" :label="param.k" v-model="param.v" :suffix="param.unit || ''"></v-text-field>
<!--否則,顯示下拉選項-->
<v-select v-else :label="param.k" v-model="param.v" :items="param.options"/>
</v-card-text>
</v-card>
</v-flex>
</v-stepper-content>
效果:
3.6 SKU特有屬性
3.6.1 篩選特有屬性
特有屬性已經在dataProces函式中處理過了,儲存到了specialSpecs中,而且專門添加了一個selected屬性,用來儲存使用者填寫的資訊。
3.6.2 頁面渲染特有屬性
接下來,需要把篩選出的特有規格引數,渲染到SKU頁面:
預期目標效果是這樣的:
可以看到,
-
每一個特有屬性自成一組,都包含標題和選項。我們可以使用card達到這個效果。
-
無options選項的特有屬性,展示一個文字框,有options選項的,展示多個checkbox,讓使用者選擇
頁面程式碼實現:
<!--4、SKU屬性-->
<v-stepper-content step="4">
<v-flex class="mx-auto">
<!--遍歷特有規格引數-->
<v-card flat v-for="spec in specialSpecs" :key="spec.k">
<!--特有引數的標題-->
<v-card-title class="subheading">{{spec.k}}:</v-card-title>
<!--特有引數的待選項,需要判斷是否有options,如果沒有,展示文字框,讓使用者自己輸入-->
<v-card-text v-if="spec.options.length <= 0" class="px-5">
<v-text-field :label="'輸入新的' + spec.k" v-model="spec.selected"/>
</v-card-text>
<!--如果有options,需要展示成多個checkbox-->
<v-card-text v-else class="container fluid grid-list-xs">
<v-layout row wrap class="px-5">
<v-checkbox color="primary" v-for="o in spec.options" :key="o" class="flex xs3"
:label="o" v-model="spec.selected" :value="o"/>
</v-layout>
</v-card-text>
</v-card>
</v-flex>
</v-stepper-content>
3.6.3 自由新增和刪除文字框
剛才的實現中,普通文字項只有一個,如果使用者想新增更多值就不行。我們需要讓使用者能夠自由新增新的文字框,而且還能刪除。這裡有個取巧的方法:
1.藉助selected屬性,因為它是一個數組,所以可以把文字框的個數與陣列的長度繫結
2.獲取selected的長度,點選增加按鈕,則把selected.length++複製給count
3.然後用v-for遍歷count,顯示出對應個數的文字框,然後文字框繫結對應的selected陣列下標,因為v-for遍歷是從1開始的, 所以count+1,然後文字框與陣列下標繫結的時候下標為i-1
4.刪除,是把文字框對應的陣列下標中所包含的內容從陣列中移除,使用splice函式,從i-1開始
具體程式碼如下:
<v-card v-for="spec in specialSpecs" :key="spec.k">
<v-card-title v-if="spec.options.length <= 0" class="subheading">
<h4 class="xs3">{{spec.k}}</h4>
<v-spacer></v-spacer>
<v-btn flat icon color="primary" @click="count=spec.selected.length++">
<v-icon>add</v-icon>
</v-btn>
</v-card-title>
<v-card-title v-else class="subheading">
<h4>{{spec.k}}</h4>
</v-card-title>
<v-card-text v-if="spec.options.length <= 0" class="px-5">
<div v-for="i in count+1" :key="i">
<v-layout row>
<v-text-field :label="'輸入新的'+spec.k" v-model="spec.selected[i-1]"></v-text-field>
<v-btn :disabled="spec.selected.length === 1 || spec.selected.length === 0" flat icon color="error" @click="spec.selected.splice(i-1,1),count=spec.selected.length-1">
<v-icon>delete</v-icon>
</v-btn>
</v-layout>
</div>
</v-card-text>
<v-card-text v-else class="container fluid grid-list-xs">
<v-layout row wrap class="px-5">
<v-checkbox color="primary" v-for="p in spec.options" :key="p" class="flex xs3" :label="p" v-model="spec.selected" :value="p">
</v-checkbox>
</v-layout>
</v-card-text>
</v-card>
效果展示:
3.7 展示SKU列表
3.7.1 效果預覽
當我們選定SKU的特有屬性時,就會對應出不同排列組合的SKU。
當你選擇了上圖中的這些選項時:
-
顏色共2種:土豪金,絢麗紅
-
記憶體共2種:2GB,4GB
-
機身儲存1種:64GB
此時會產生多少種SKU呢? 應該是 2 * 2 * 1 = 4種。
因此,接下來應該由使用者來對這4種sku的資訊進行詳細填寫,比如庫存和價格等。而多種sku的最佳展示方式,是表格(淘寶、京東都是這麼做的),如圖:
而且這個表格應該隨著使用者選擇的不同而動態變化。
3.7.2 求笛卡兒積
N個數組的笛卡爾積
思路:
-
先拿其中兩個陣列求笛卡爾積
-
然後把前面運算的結果作為新陣列,與第三個陣列求笛卡爾積
方法:
reduce函式:
reduce(callback,initvalue)
callback:是一個回撥函式。這個callback可以接收2個引數:arg1,arg2
-
arg1代表的上次運算得到的結果
-
arg2是陣列中正要處理的元素
initvalue,初始化值。第一次呼叫callback時把initvalue作為第一個引數,把陣列的第一個元素作為第二個引數運算。如果未指定,則第一次運算會把陣列的前兩個元素作為引數。
reduce會把陣列中的元素逐個用這個函式處理,然後把結果作為下一次回撥函式的第一個引數,陣列下個元素作為第二個引數,以此類推。
因此,可以把想要求笛卡爾積的多個數組先放到一個大陣列中。形成二維陣列,然後再來運算。
業務需求
首先,假設已經有了一個特有引數的規格模板:
[
{
"k": "機身顏色",
"selected": ["紅色","黑色"]
},
{
"k": "記憶體",
"selected": ["8GB","6GB"]
},
{
"k": "機身儲存",
"selected": ["64GB","256GB"]
}
]
可以看做是一個二維陣列。
一維是引數物件。
二維是引數中的selected選項。
最終想要的結果:
[
{"機身顏色":"紅色","記憶體":"6GB","機身儲存":"64GB"},
{"機身顏色":"紅色","記憶體":"6GB","機身儲存":"256GB"},
{"機身顏色":"紅色","記憶體":"8GB","機身儲存":"64GB"},
{"機身顏色":"紅色","記憶體":"8GB","機身儲存":"256GB"},
{"機身顏色":"黑色","記憶體":"6GB","機身儲存":"64GB"},
{"機身顏色":"黑色","記憶體":"6GB","機身儲存":"256GB"},
{"機身顏色":"黑色","記憶體":"8GB","機身儲存":"64GB"},
{"機身顏色":"黑色","記憶體":"8GB","機身儲存":"256GB"},
]
思路是這樣:
-
我們的啟點是一個空的物件陣列:
[{}]
, -
然後先與第一個規格求笛卡爾積
-
然後再把結果與下一個規格求笛卡爾積,依次類推
程式碼:
在Vue中新增一個計算屬性,按照上面所講的邏輯,計算所有規格引數的笛卡爾積,同時要對資料進行優化,因為SKU陣列只包含規格引數是不夠的,還需要以下欄位:
-
price:價格
-
stock:庫存
-
enable:是否啟用。雖然笛卡爾積對應了9個SKU,但使用者不一定會需要所有的組合,用這個欄位進行標記。
-
images:商品的圖片
-
indexes:特有屬性的索引拼接得到的字串
需要在已經生成好的笛卡爾積的基礎上新增上述欄位,最終程式碼如下:
computed:{
skus(){
// 過濾掉使用者沒有填寫資料的規格引數
const arr = this.specialSpecs.filter(s => s.selected.length > 0);
// 通過reduce進行累加笛卡爾積
return arr.reduce((last, spec, index) => {
const result = [];
last.forEach(o => {
for(let i = 0; i < spec.selected.length; i++){
const option = spec.selected[i];
const obj = {};
Object.assign(obj, o);
obj[spec.k] = option;
// 拼接當前這個特有屬性的索引
obj.indexes = (o.indexes||'') + '_'+ i
if(index === arr.length - 1){
// 如果發現是最後一組,則添加價格、庫存等欄位
Object.assign(obj, { price:0, stock:0,enable:false, images:[]})
// 去掉索引字串開頭的下劃線
obj.indexes = obj.indexes.substring(1);
}
result.push(obj);
}
})
return result
},[{}])
}
}
檢視生成的資料:
3.7.3 頁面實現
頁面展現是一個表格。之前已經用過。表格需要以下資訊:
-
items:表格內的資料
-
headers:表頭資訊
剛才的計算屬性skus得到的就是表格資料了。還差頭:headers
頭部資訊也是動態的,使用者選擇了一個屬性,就會多出一個表頭。與skus是關聯的。
既然如此,需要再次編寫一個計算屬性,來計算得出header陣列:
headers(){
if(this.skus.length <= 0){
return []
}
const headers = [];
// 獲取skus中的任意一個,獲取key,然後遍歷其屬性
Object.keys(this.skus[0]).forEach(k => {
let value = k;
if(k === 'price'){
// enable,表頭要翻譯成“價格”
k = '價格'
}else if(k === 'stock'){
// enable,表頭要翻譯成“庫存”
k = '庫存';
}else if(k === 'enable'){
// enable,表頭要翻譯成“是否啟用”
k = '是否啟用'
} else if(k === 'indexes' || k === 'images'){
// 圖片和索引不在表格中展示
return;
}
headers.push({
text: k,
align: 'center',
sortable: false,
value
})
})
return headers;
}
接下來編寫頁面,實現table。
需要注意的是,price、stock欄位需要使用者填寫數值,不能直接展示。enable要展示為checkbox,讓使用者選擇,如圖:
程式碼:
<v-card>
<!--標題-->
<v-card-title class="subheading">SKU列表</v-card-title>
<!--SKU表格,hide-actions因此分頁等工具條-->
<v-data-table :items="skus" :headers="headers" hide-actions item-key="indexes">
<template slot="items" slot-scope="props">
<!--價格和庫存展示為文字框-->
<td v-for="(v,k) in props.item" :key="k" v-if="['price', 'stock'].includes(k)"
class="text-xs-center">
<v-text-field single-line v-model.number="props.item[k]"/>
</td>
<!--enable展示為checkbox-->
<td class="text-xs-center" v-else-if="k === 'enable'">
<v-checkbox v-model="props.item[k]"/>
</td>
<!--indexes和images不展示,其它展示為普通文字-->
<td class="text-xs-center" v-else-if="!['indexes','images'].includes(k)">{{v}}</td>
</template>
</v-data-table>
</v-card>
效果:
3.7.4 圖片上傳列表
這個表格中只展示了基本資訊,當用戶需要上傳圖片時,該怎麼做呢?
Vuetify的table有一個展開功能,可以提供額外的展示空間:
用法也非常簡單,新增一個template,把其slot屬性指定為expand即可: