1. 程式人生 > >樂優商城(十)商品管理

樂優商城(十)商品管理

目錄

3.4 商品描述資訊

商品描述資訊比較複雜,而且圖文並茂,甚至包括視訊。

這樣的內容,一般都會使用富文字編輯器。

3.4.1 富文字編輯器

通俗來說:富文字,就是比較豐富的文字編輯器。普通的框只能輸入文字,而富文字還能給文字加顏色樣式等。

富文字編輯器有很多,例如:KindEditor、Ueditor。但並不原生支援vue,所以要使用vue-quill-editor,它是一款支援Vue的富文字編輯器。

3.4.2 Vue-Quill-Editor

Vue-Quill-Editor是一個基於Quill的富文字編輯器:

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的資料大小都是有限制的,這樣的話有可能導致提交失敗,或者是使用者體驗很差,資料要傳遞很久才全部傳送到伺服器。因此,在富文字編輯的過程中,對於圖片的處理,更合理的做法是將圖片上傳到伺服器,再將圖片連結插入到富文字中,以達到最優的體驗。所以要進行小小的改造,使其支援上傳圖片到伺服器的功能,請參考

《改造vue-quill-editor:實現圖片上傳到伺服器再插入富文字》

使用也非常簡單:

<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即可: