(1)通过编程式导航跳转到商品添加页面
1. 在List.vue页面。点击添加商品,实现页面跳转
2. 创建商品添加组件Add.vue
3. 为Add组件添加路由
(2) 渲染添加页面的基本UI结构
使用到Alter警告和Steps 步骤条组件
1. 引入组件
2. add组件的基本UI结构
(3)美化步骤条组件
1. 增加进度,并修改文字
2. css样式
(4)渲染tab栏区域
(5)实现步骤条和tab栏的数据联动效果
通过让进度条和tab标签共用activeIndex的值,实现联动效果:
1. 当选择商品参数时,v-model绑定该选项对应的name值1,
2. 从而修改activeIndex的值为1
3. 进而使进度条当前所在的进度转到下标为1的项:商品参数
注意: v-model 绑定的是当前选中项的name值;
active 绑定的是当前进度的下标值
(6)绘制“基本信息”面板的UI结构
补充:在el-form标签中,添加下面属性,使输入框标题和输入框分行显示
(7)获取商品分类的数据
(8)绘制商品分类的级联选择器
1. 创建级联选择器
2. 相关的数据
3. 方法
(9)只允许选中三级分类
(10)阻止页签切换
1. 首先为tabs标签页添加属性before-leave,其值为一个事件函数。
1.1 该函数有两个参数(activeName将要跳转的标签项name,oldActiveName现在的标签项name)。
1.2 若该事件函数的返回值为false,则阻止跳转到下一个标签项
2. 在事件函数中,
如果当前页的name为0(在“基本信息”标签项)并且 选中项id的数组长度不等于3,则禁止跳转,返回fals
目的:实现只有选中三级分类后,才可跳转
(11)获取动态参数列表数据(当点击商品参数页签时)
1. 为tabs页签添加点击事件,选择页签时触发该方法
2. 在该方法中,判断当前所在的页签是不是商品参数。
3. 如果是,则进行数据请求,获取动态参数列表
4. 为了便于获取当前选中分类的id,定义计算属性cateId
点击商品参数时,获取到的数据为:
(12)绘制商品参数面板中的复选框组
1. 引入复选框组件
2. 首先在获取动态参数列表的方法中,先将每个参数所对应的标签字符串attr_vals 转化为 数组
3. 通过第一个for循环,依次显示每一个参数名
4. 再通过嵌套的第二个for循环,以复选框的形式,依次显示当前参数对应的所有标签
补充:优化复选框的样式,使复选框开头对齐
(13)获取静态属性列表数据
(14)渲染商品属性面板的UI结构
(15)初步使用upload上传组件
1. 实现以下功能:
1.1 点击上传,选择本地图片进行上传
1.2 上传成功后,显示:(也可以点击右上角的x,进行移除)
1.3 点击上传的图片,进行预览
2. 用到upload上传组件,引入该组件
3. 使用upload上传组件
效果图:
(16)手动为upload绑定headers请求头
问题:在Network中显示是无效的ttoken。
说明上传图片时没有使用axios发送请求,而是使用其内部的封装的一套ajax。其内部封装的ajax没有Authorization字段。
解决:通过headers为其指定Authorization字段
(17)监听upload组件的on-success事件(上传成功事件)
1. API文档中相关的信息
2. 为upload组件添加on-success(上传成功时,触发该方法)
3. 上传成功后,返回的数据中有图片的临时路径。将该路径保存到pics数组中
上传成功后返回的数据:
上传图片临时路径的pics数组:
(18)监听upload上传组件的on-remove事件(删除图片)
删除图片时,会返回数据:
(19)实现图片的预览效果
1. 监听图片的预览事件on-preview
2. 点击图片时,触发预览事件,该事件会传入数据。
3. 将数据中url(图片地址)赋值给变量previewPath,并且显示对话框(对话框中存放图片)
4. 使用对话框组件,显示图片预览。内容部分放当前点击的图片
(20)安装并配置vue-quill-editor(富文本编辑器)
1. 安装插件vue-quill-editor
2. 导入该插件(富文本编辑器),并全局注册
// 导入富文本编辑器对应的样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
// 3.将富文本编辑器注册为全局可用的组件
Vue.use(VueQuillEditor)
3. 使用富文本编辑器
在全局css文件中
4. 添加商品按钮,查看addForm中的商品介绍是否与富文本编辑器输入的内容双向绑定
5. 效果图
(21)实现表单数据的预验证
1. 点击添加按钮,通过add方法,对提交的表单数据进行验证
(22)把选中分类的id数组(goods_cat)转化为字符串
1. 安装运行依赖lodash,调用方法cloneDeep(要拷贝的对象),实现深拷贝(对赋值的addForm操作,避免与级联选择器中绑定的goods_cat发生冲突)
2. 导入lodash
3. 使用其方法cloneDeep(要拷贝的对象)进行深拷贝,并将拷贝对象中的goods_cat数组转化为字符串
将上图中修改为打印form
(23)在add方法中处理attrs数组(商品的参数(包括动态参数和静态属性))
addForm. attrs:表示商品的参数(包括动态参数和静态属性)
1. 首先添加为addForm添加attrs属性
2. 在add方法中,获取动态参数和静态属性的attr_id和attr_value,存到addForm. attrs中
(24)完成添加商品的操作
Add.vue
<template>
<div>
<!-- 面包屑 导航区 -->
<el-breadcrumb separator="/">
<el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
<el-breadcrumb-item>商品管理</el-breadcrumb-item>
<el-breadcrumb-item>商品列表</el-breadcrumb-item>
</el-breadcrumb>
<!-- 卡片视图区 -->
<el-card>
<!-- alert警告 -->
<el-alert
title="添加商品信息"
type="info"
center
show-icon>
</el-alert>
<!-- 进度条 -->
<el-steps :space="200" :active="activeIndex-0" finish-status="success" align-center>
<el-step title="基本信息"></el-step>
<el-step title="商品参数"></el-step>
<el-step title="商品属性"></el-step>
<el-step title="商品图片"></el-step>
<el-step title="商品内容"></el-step>
<el-step title="完成"></el-step>
</el-steps>
<!-- tabs标签页区域. 在外面添加表单便于数据的显示
注意:tabs和tabs-pane标签之间不能有其它标签,所以将form放在最外面
-->
<el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px" label-position="top">
<el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTagLeave" @tab-click="tabClicked">
<el-tab-pane label="基本信息" name="0">
<el-form-item label="商品名称" prop="goods_name">
<el-input v-model="addForm.goods_name"></el-input>
</el-form-item>
<el-form-item label="商品价格" prop="goods_price">
<el-input v-model="addForm.goods_price" type="number"></el-input>
</el-form-item>
<el-form-item label="商品重量" prop="goods_weight">
<el-input v-model="addForm.goods_weight" type="number"></el-input>
</el-form-item>
<el-form-item label="商品数量" prop="goods_number">
<el-input v-model="addForm.goods_number" type="number"></el-input>
</el-form-item>
<el-form-item label="商品分类" prop="goods_cat">
<el-cascader
expand-trigger="hover"
:options="catelist"
:props="cateProp"
v-model="addForm.goods_cat"
@change="handleChange">
</el-cascader>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品参数" name="1">
<!-- 通过for循环,显示当前选中分类下的每个参数名item.attr_name -->
<el-form-item :label="item.attr_name" v-for="item in manyTableData" :key="item.attr_id">
<!-- 通过for循环,显示当前参数下所对应的所有取值attr_vals -->
<el-checkbox-group v-model="item.attr_vals">
<el-checkbox :label="cb" v-for="(cb,i) in item.attr_vals" :key="i" border></el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品属性" name="2">
<el-form-item :label="item.attr_name" v-for="item in onlyTableData" :key="item.attr_id">
<el-input v-model="item.attr_vals"></el-input>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="商品图片" name="3">
<!-- 1. action:图片要上传到的后台API地址
2. on-preview 预览图片操作
3. on-remove 移除图片操作
4. list-type="picture" 以图片的形式显示
-->
<el-upload
:action="uploadUrl"
:on-preview="handlePreview"
:on-remove="handleRemove"
list-type="picture"
:headers="headerObj"
:on-success="handleSuccess">
<el-button size="small" type="primary">点击上传</el-button>
</el-upload>
</el-tab-pane>
<el-tab-pane label="商品内容" name="4">
<!-- 富文本编辑器组件 -->
<quill-editor v-model="addForm.goods_introduce">
</quill-editor>
<!-- 添加商品按钮 -->
<el-button type="primary" class="btnAdd" @click="add">添加商品</el-button>
</el-tab-pane>
</el-tabs>
</el-form>
</el-card>
<!-- 图片预览框 -->
<el-dialog
title="图片预览"
:visible.sync="previewVisible"
width="50%">
<img :src="previewPath" class="previewImg">
</el-dialog>
</div>
</template>
<script>
// 导入lodash,将其命名为_。以进行深拷贝
import _ from 'lodash'
export default {
data () {
return {
// 默认选中进度条第一项(第一项对应0),也是选中tab标签中的name值
activeIndex: '0',
// 添加商品的数据对象
addForm: {
goods_name: '',
goods_price: 0,
goods_weight: 0,
goods_number: 0,
goods_cat: [], // 添加商品所属的分类数组(存放选中的分类id)
pics: [], // 图片临时地址的对象数组
goods_introduce: '', // 商品的详情描述
attrs: [] // 商品参数(包括动态参数和静态属性的attr_id和attr_value)
},
// 表单的验证规则
addFormRules: {
goods_name: [{ required: true, message: '请输入商品名称', trigger: 'blur' }],
goods_price: [{ required: true, message: '请输入商品价格', trigger: 'blur' }],
goods_weight: [{ required: true, message: '请输入商品重量', trigger: 'blur' }],
goods_number: [{ required: true, message: '请输入商品数量', trigger: 'blur' }],
goods_cat: [{ required: true, message: '请选中商品分类', trigger: 'blur' }]
},
// 商品分类列表
catelist: [],
// 级联选择框的属性配置
cateProp: {
value: 'cat_id', // 选中的是什么
label: 'cat_name', // 看到的是什么
children: 'children' // 具有父子关系的数组
},
// 动态参数列表数据
manyTableData: [],
// 静态属性列表数据
onlyTableData: [],
// 图片要上传到的后台API地址(/upload前面的地址为根路径)
uploadUrl: 'http://127.0.0.1:8888/api/private/v1/upload',
// 图片上传组件的headers请求头对象
headerObj: {
Authorization: window.sessionStorage.getItem('token')
},
// 被点击图片的url地址
previewPath: '',
// 控制图片预览框的显示与隐藏
previewVisible: false
}
},
created () {
this.getCateList()
},
methods: {
// 1. 获取商品分类数据(以在商品选择中显示)
async getCateList() {
const { data: res } = await this.$http.get('categories')
if (res.meta.status !== 200) {
return this.$message.error('获取商品分类数据失败!')
}
this.catelist = res.data
console.log(res.data)
},
// 2.级联选择器选中项发生变化时,会触发这个函数
handleChange() {
console.log(this.addForm.goods_cat)
// 没有选中三级分类。将存放选中id的数组置空
if (this.addForm.goods_cat.length !== 3) {
this.addForm.goods_cat = []
}
},
// 3.控制标签页的跳转(只有选中第一个标签页的三级分类后,才可跳转)
beforeTagLeave(activeName, oldActiveName) {
// 当前在第一个标签页,但是没有选中三级分类
if (oldActiveName === '0' && this.addForm.goods_cat.length !== 3) {
this.$message.error('请选择商品分类!')
return false
}
},
// 4. 点击页签时触发该方法
async tabClicked() {
// 4.1 选中商品参数页签, 获取动态参数列表数据
if (this.activeIndex === '1') {
// 发送数据请求
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`,
{ params: { sel: 'many' } })
if (res.meta.status !== 200) {
return this.$message.error('获取参数列表失败')
}
// 先将获取的参数列表中每一个参数所对应的标签字符串,转化为 标签数组
res.data.forEach(item => {
item.attr_vals = item.attr_vals.length === 0 ? [] : item.attr_vals.split(' ')
})
// 将获取的动态参数列表保存
this.manyTableData = res.data
console.log(res.data)
} else if (this.activeIndex === '2') { // 4.2 选中商品属性页签,获取静态属性列表数据
// 发送数据请求
const { data: res } = await this.$http.get(`categories/${this.cateId}/attributes`,
{ params: { sel: 'only' } })
if (res.meta.status !== 200) {
return this.$message.error('获取属性列表失败')
}
// 将获取的属性列表保存
this.onlyTableData = res.data
console.log(res.data)
}
},
// 5. 处理预览图片效果
handlePreview(file) {
// 1.获取点击图片的url地址
this.previewPath = file.response.data.url
// 2.使对话框(图片预览)可见
this.previewVisible = true
},
// 6. 处理移除图片的操作
handleRemove(file) {
// 6.1 获取将要删除的图片的临时路径
const filePath = file.response.data.tmp_path
// 6.2 从pics 数组中,找到这个图片对应的索引值(使用foreach依次遍历pics数组,寻找filePath所对应的索引值)
const i = this.addForm.pics.forEach(item =>
item.pic === filePath)
// 6.3 调用数组的splice方法,把图片信息对象,从pics数组中移除
this.addForm.pics.splice(i, 1)
console.log(this.addForm.pics)
},
// 7. 监听图片上传成功的事件(response:上传成功后返回的数据,包括图片的临时路径)
handleSuccess(response) {
// 1.拼接得到一个图片信息对象(图片临时路径)
const pinInfo = { pic: response.data.tmp_path }
// 2.将获取到的图片对象 加入到 pics数组
this.addForm.pics.push(pinInfo)
console.log(this.addForm.pics)
},
// 8.点击添加商品按钮,触发
add() {
this.$refs.addFormRef.validate(async valid => {
// 8.1 预验证未通过
if (!valid) {
return this.$message.error('请填写必要的表单项')
}
// 8.2 预验证通过。执行添加的业务逻辑
// 8.2.1 首先将选中的分类id数组( goods_cat),转化为字符串
// 为了避免与级联选择器中绑定的 goods_cat 产生冲突,对原始的addForm进行深拷贝
const form = _.cloneDeep(this.addForm)
// 将选中分类id数组,转化为字符串
form.goods_cat = form.goods_cat.join(',')
// 8.3 获取动态参数数组中每个参数的attr_id和 attr_value
this.manyTableData.forEach(item => {
const newInfo = {
attr_id: item.attr_id,
attr_value: item.attr_vals.join(' ') // 对应参数的标签是用空格连起来的字符串
}
this.addForm.attrs.push(newInfo)
})
// 8.4 获取静态属性数组中每个属性的attr_id和 attr_value
this.onlyTableData.forEach(item => {
const newInfo = {
attr_id: item.attr_id,
attr_value: item.attr_vals
}
this.addForm.attrs.push(newInfo)
})
// 8.5 将获取的addForm的attrs赋值给form的attrs
form.attrs = this.addForm.attrs
console.log(form)
// 8.6 发送添加商品请求(注意:商品名称是唯一的)
const {data : res} = await this.$http.post('goods',form)
if (res.meta.status !== 201) {
return this.$message.error('添加商品失败!')
}
// 8.7 添加商品成功,回到商品列表页面
this.$message.success('添加商品成功')
this.$router.push('/goods')
})
}
},
computed: {
// 1.获取当前选中分类的id
cateId() {
// 选中三级分类,返回三级分类的id
if (this.addForm.goods_cat.length === 3) {
return this.addForm.goods_cat[2]
}
// 没有选中
return null
}
}
}
</script>
<style lang="less" scoped>
.el-checkbox {
margin: 0 10px 0 0 !important;
}
.previewImg {
width: 100%;
}
/* 为添加商品按钮添加外边距*/
.btnAdd {
margin-top: 15px;
}
</style>