整体和用户列表 类似 功能步骤有:
不一样的点:之前编辑信息 新增信息是 弹出对话框编辑 但商品信息内容较多 我们跳转到一个组件、并且进行商品编辑的时候要进行路由传参 来渲染初始数据
点击添加商品按钮时跳转到新增商品组件对应路径:
addGoods(){
this.$router.push('/goods/add')
}
点击编辑商品按钮时跳转到编辑商品组件对应路径 并且传入id
ToEditGoods(id){
this.$router.push(`/goods/edit/${id}`)
}
新增商品和编辑商品组件布局一致只是新增商品 不用 传参请求数据
我们以编辑商品为例
在设置路由对应关系的时候 预留占位符
{ path:'/goods', component:GoodsList }, { path:'/goods/add', component:GoodsAdd }, { path:'/goods/edit/:id', component:GoodsEdit }
第一步 先使用组件进行页面布局:
主要使用到了 el-steps 步骤条组件 和 el-tabs 标签页组件的联动 使他们步长一致 使用共同的
active 步骤条的active 动态绑定 到 activeIndex上
当标签页发生切换时 根据name 赋值给 activeIndex
async handleClick(){ this.activeIndex = this.activeName * 1 // 选择了商品(动态)参数选项 },
这样 两个组件就可以联动展示了
标签页组件其实是包裹在 el-form 当中方便统一提交给服务器
接下来就是表单内部 组件渲染 表单验证了
组件渲染el-input 数据绑定 v-model 类型限制 tpye=‘number’ prop合法值验证
这里需要自定义验证的是 商品价格不能小于0 商品数量必须是整数
必填就可以直接使用自带的
基本信息中还有一个要点:分类选择
- <el-form-item label="选择商品分类">
- el-cascader 级联选择器
- <el-cascader
- 默认选定的值是根据id请求过来的分类数组
- v-model="AddGoodsForm.goods_cat"
- style="width: 400px"
- 数据来源:cateLists 一进入页面请求过来的
- :options="cateLists"
- 有清空按钮
- clearable
- 禁用 编辑页面 不让修改分类
- disabled
- 级联选择器的相关规则
- :props="CSet"
- 选择发生改变时 执行的回调
- @change="handleChange">el-cascader>
- el-form-item>
-
-
- <script>
- 数据来源:
- async getAllCate(){
- const {data:res} = await this.$http.get('categories')
- if (res.meta.status !==200) return this.$Msg.error('获取商品分类列表失败!')
- this.cateLists = res.data
- }
-
- 级联选择器的规则
- CSet:{
- 展示下一级触发方式 鼠标悬浮
- expandTrigger: 'hover',
- 指定选项的子选项为选项对象的某个属性值
- children:'children',
- 显示的文本
- label:'cat_name',
- 文本对应的value
- value:'cat_id',
- }
-
-
- 选择发生改变时 执行的回调 只让选择第三级 不是的话就清空 选择不进去
- handleChange(){
- if (this.AddGoodsForm.goods_cat.length !== 3){
- this.AddGoodsForm.goods_cat = []
- }
- console.log(this.AddGoodsForm.goods_cat)
- }
-
-
-
如果是新增商品页面的话 也大体一致 把 disabled 去掉即可
并且在切换标签页时可以验证 AddGoodsForm.goods_cat 的长度
- leaveTabs(activeName, oldActiveName){
- if(oldActiveName === '0' && this.AddGoodsForm.goods_cat.length !== 3){
- this.$Msg.error('请先选择商品分类!')
- return false
- }
根据服务器返回的数据
渲染商品参数-attr.many 和商品属性- attr.only
分别渲染 多选框组和输入框组来v-for 循环
- <el-tab-pane label="4.商品图片" name="3">
- <el-upload
- class="upload-demo"
- :action="actionToUrl"
- :on-preview="handlePreview2"
- :on-remove="handleRemove"
- :on-success="handleSuccess"
- :headers="UploadHeaders"
- list-type="picture-card">
- <el-button size="small" type="primary">点击上传el-button>
- <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kbdiv>
- el-upload>
- el-tab-pane>
-
-
-
- <el-dialog
- title="预览图片"
- :visible.sync="Preview"
- width="45%">
- <img :src="PreviewPic" alt="" style="width: 100%">
- <span slot="footer" class="dialog-footer">
- <el-button type="primary" @click="Preview = false">确 定el-button>
- span>
- el-dialog>
-
- <script>
- action 必选参数,上传的地址 这里用的是本地服务器
- actionToUrl:'http://127.0.0.1:8888/api/private/v1/upload'
-
- on-preview 点击文件列表中已上传的文件时的钩子 点击出现对话框显示放大预览图
- handlePreview2(file){
- this.PreviewPic=file.response.data.url // 显示图片的地址
- this.Preview = true // 决定对话框显示的布尔值
- }
-
- on-remove 文件列表移除文件时的钩子
- handleRemove(file){
- //1.获取将要删除的图片临时路径
- const fileUrl = file.response.data.tmp_path
- //2.从pics 数组中,找到这个图片对应的索引值
- let aaa = this.AddGoodsForm.pics.findIndex(value => value === fileUrl)
- console.log(aaa)
- //3.调用数组 splice 方法 把图片信息对象从pics 数组中移除
- this.AddGoodsForm.pics.splice(aaa,1)
- console.log(this.AddGoodsForm.pics)
- }
-
-
- on-success 文件上传成功时的钩子
- async handleSuccess(response){
- // 找出定义一下 新上传文件的路径地址
- const NewPicUrl = response.data.tmp_path
- // push 到预留表单位中
- this.AddGoodsForm.pics.push(NewPicUrl)
- console.log(this.AddGoodsForm.pics)
- // const {data:res} = await this.$http.put(`goods/${this.NowEditId}/pics`,this.AddGoodsForm.pics)
- // if (res.meta.status !==200) return this.$Msg.error('更新主图失败!')
- // this.$Msg.success('更新主图成功!')
- }
-
- headers 设置上传的请求头部
- UploadHeaders:{
- Authorization:window.sessionStorage.getItem('token')
- },
- script>
-
先执行安装语句:
在main.js 中注册 并引入样式
- npm install vue-quill-editor
-
- import VueQuillEditor from 'vue-quill-editor'
-
- import 'quill/dist/quill.core.css' // import styles
- import 'quill/dist/quill.snow.css' // for snow theme
- import 'quill/dist/quill.bubble.css' // for bubble theme
-
- Vue.use(VueQuillEditor, /* { default global options } */)
在组件中使用
- <el-tab-pane label="5.商品内容" name="4">
- <quill-editor
- ref="myQuillEditor"
- 数据双向绑定 便于发送请求
- v-model="AddGoodsForm.goods_introduce"
- 富文本编辑器的核心配置
- :options="editorOption"
- />
- el-tab-pane>
-
-
-
-
- <script>
-
- // 此处定义在data外
- const toolbarOptions = [
- ['insertMetric'],
- ['bold', 'italic', 'underline', 'strike'], // 加粗,斜体,下划线,删除线
- ['blockquote', 'code-block'], //引用,代码块
- [{ 'header': 1 }, { 'header': 2 }], // 几级标题
- [{ 'list': 'ordered' }, { 'list': 'bullet' }], // 有序列表,无序列表
- [{ 'script': 'sub' }, { 'script': 'super' }], // 下角标,上角标
- [{ 'indent': '-1' }, { 'indent': '+1' }], // 缩进
- [{ 'direction': 'rtl' }], // 文字输入方向
- [{ 'size': ['small', false, 'large', 'huge'] }], // 字体大小
- [{ 'header': [1, 2, 3, 4, 5, 6, false] }], // 标题
- [{ 'color': [] }, { 'background': [] }], // 颜色选择
- [{ 'font': ['SimSun', 'SimHei', 'Microsoft-YaHei', 'KaiTi', 'FangSong', 'Arial'] }], // 字体
- [{ 'align': [] }], // 居中
- ['clean'], // 清除样式,
- ['link', 'image'] // 上传图片、上传视频
- ]
-
- // toolbar标题
- const titleConfig = [
- { Choice: '.ql-insertMetric', title: '跳转配置' },
- { Choice: '.ql-bold', title: '加粗' },
- { Choice: '.ql-italic', title: '斜体' },
- { Choice: '.ql-underline', title: '下划线' },
- { Choice: '.ql-header', title: '段落格式' },
- { Choice: '.ql-strike', title: '删除线' },
- { Choice: '.ql-blockquote', title: '块引用' },
- { Choice: '.ql-code', title: '插入代码' },
- { Choice: '.ql-code-block', title: '插入代码段' },
- { Choice: '.ql-font', title: '字体' },
- { Choice: '.ql-size', title: '字体大小' },
- { Choice: '.ql-list[value="ordered"]', title: '编号列表' },
- { Choice: '.ql-list[value="bullet"]', title: '项目列表' },
- { Choice: '.ql-direction', title: '文本方向' },
- { Choice: '.ql-header[value="1"]', title: 'h1' },
- { Choice: '.ql-header[value="2"]', title: 'h2' },
- { Choice: '.ql-align', title: '对齐方式' },
- { Choice: '.ql-color', title: '字体颜色' },
- { Choice: '.ql-background', title: '背景颜色' },
- { Choice: '.ql-image', title: '图像' },
- { Choice: '.ql-video', title: '视频' },
- { Choice: '.ql-link', title: '添加链接' },
- { Choice: '.ql-formula', title: '插入公式' },
- { Choice: '.ql-clean', title: '清除字体格式' },
- { Choice: '.ql-script[value="sub"]', title: '下标' },
- { Choice: '.ql-script[value="super"]', title: '上标' },
- { Choice: '.ql-indent[value="-1"]', title: '向左缩进' },
- { Choice: '.ql-indent[value="+1"]', title: '向右缩进' },
- { Choice: '.ql-header .ql-picker-label', title: '标题大小' },
- { Choice: '.ql-header .ql-picker-item[data-value="1"]', title: '标题一' },
- { Choice: '.ql-header .ql-picker-item[data-value="2"]', title: '标题二' },
- { Choice: '.ql-header .ql-picker-item[data-value="3"]', title: '标题三' },
- { Choice: '.ql-header .ql-picker-item[data-value="4"]', title: '标题四' },
- { Choice: '.ql-header .ql-picker-item[data-value="5"]', title: '标题五' },
- { Choice: '.ql-header .ql-picker-item[data-value="6"]', title: '标题六' },
- { Choice: '.ql-header .ql-picker-item:last-child', title: '标准' },
- { Choice: '.ql-size .ql-picker-item[data-value="small"]', title: '小号' },
- { Choice: '.ql-size .ql-picker-item[data-value="large"]', title: '大号' },
- { Choice: '.ql-size .ql-picker-item[data-value="huge"]', title: '超大号' },
- { Choice: '.ql-size .ql-picker-item:nth-child(2)', title: '标准' },
- { Choice: '.ql-align .ql-picker-item:first-child', title: '居左对齐' },
- { Choice: '.ql-align .ql-picker-item[data-value="center"]', title: '居中对齐' },
- { Choice: '.ql-align .ql-picker-item[data-value="right"]', title: '居右对齐' },
- { Choice: '.ql-align .ql-picker-item[data-value="justify"]', title: '两端对齐' }
- ]
-
-
-
-
- // 此处书写在data当中
- editorOption: {
- placeholder: '请在这里输入......',
- theme: 'snow', //主题 snow/bubble
- modules: {
- history: {
- delay: 1000,
- maxStack: 50,
- userOnly: false
- },
- toolbar: {
- container: toolbarOptions,
- handlers: {
- insertMetric: this.showHandle
- }
- }
- }
- }
-
-
-
- // 此处书写在 methods 中
- // 配置富文本编辑器
- initTitle () {
- document.getElementsByClassName('ql-editor')[0].dataset.placeholder = ''
- for (let item of titleConfig) {
- let tip = document.querySelector('.quill-editor ' + item.Choice)
- if (!tip) continue
- tip.setAttribute('title', item.title)
- }
- },
- showHandle () {
- this.$Msg.error('这是自定义工具栏的方法!')
- },
- // 自定义按钮内容初始化
- initButton () {
- const editorButton = document.querySelector('.ql-insertMetric')
- editorButton.innerHTML = ''
- },
- // 失去焦点
- onEditorBlur (editor) { },
- // 获得焦点
- onEditorFocus (editor) { },
- // 开始
- onEditorReady (editor) { },
- // 值发生变化
- onEditorChange (editor) {
- // 如果需要手动控制数据同步,父组件需要显式地处理changed事件
- // this.content = editor.html;
- console.log(editor);
- },
- script>
- <el-tab-pane label="6.提交商品" name="5">
- <el-empty image="http://www.wsg3096.com/gangback/pub/asdc1.png" :image-size="320" description="确定所有数据添加完毕后就可以提交啦!">
- <el-button type="primary" icon="el-icon-success" @click="ToGoods">上传商品el-button>
- el-empty>
- el-tab-pane>
-
-
- <script>
-
-
- // 确定上传的按钮
- async ToGoods(){
- this.$refs.AddGoodsFormRef.validate(async valid =>{
- if (!valid) return this.$Msg.error('请检查下各项数据是否规范!')
- // 执行添加业务的逻辑 先深拷贝一下 防止改变 级联选择器
- const form = _.cloneDeep(this.AddGoodsForm)
- // 处理当前商品所属ID 服务器要求 ,分割的字符串
- form.goods_cat = form.goods_cat.join(',')
- // 请求过来的数据保存到ManyData OnlyData 展示 返回去的时候 还用服务器的数据就行
- form.attrs = [...this.ManyData,...this.OnlyData]
- // console.log(form)
- const {data : res} = await this.$http.put(`goods/${this.NowEditId}`,form)
- if (res.meta.status !== 200) return this.$Msg.error('编辑商品失败!')
- this.$Msg.success('编辑商品成功!')
- await this.$router.push('/goods')
- })
- }
-
-
-
- script>