目录
3.4 商品描述信息
商品描述信息比较复杂,而且图文并茂,甚至包括视频。
这样的内容,一般都会使用富文本编辑器。
3.4.1 富文本编辑器
通俗来说:富文本,就是比较丰富的文本编辑器。普通的框只能输入文字,而富文本还能给文字加颜色样式等。
富文本编辑器有很多,例如:KindEditor、Ueditor。但并不原生支持vue,所以要使用vue-quill-editor,它
是一款支持Vue的富文本编辑器。
3.4.2 Vue-Quill-Editor
GitHub的主页:https://github.com/surmon-china/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即可: