商品管理

1. 商品分类

创建子级路由

创建categories,goods,params子级路由组件并设置路由规则

import Goods from '@/views/goodsManagement/Goods.vue'
import Params from '@/views/goodsManagement/Params.vue'
import Categories from '@/views/goodsManagement/Categories.vue'

path: '/home', component: Home, redirect: '/welcome', children: [
    { path: "/welcome", component: Welcome },
    { path: "/users", component: Users },
    { path: "/rights", component: Rights },
    { path: "/roles", component: Roles  },
    { path: 'goods', component: Goods },
    { path: 'params', component: Params },
    { path: 'categories', component: Categories }
]

添加组件基本布局

在Categories.vue组件中添加面包屑导航以及卡片视图中的添加分类按钮

<template>
  <div>
    <Breadcrumb :item="item" :subItem="subItem"></Breadcrumb>
    <Categories></Categories>
  </div>
</template>

<script>
  import Breadcrumb from '../../components/Breadcrumb.vue'
  import Categories from '../../components/CategoriesList.vue'
  export default {
    data() {
      return {
        item: '商品管理',
        subItem: '商品分类'
      }
    },
    components: {
      Breadcrumb,
      Categories
    }
  }
</script>

<style lang="scss" scoped>
</style>

在components目录下创建CategoriesList.vue

<template>
  <div class="main">
    <!-- 卡片视图区域 -->
    <el-card>
    </el-card>
  </div>
</template>

请求分类数据

创建 api/categories.js

import http from '../utils/http.js'

// get请求 获取商品分类信息
export function getCategoriesList(params) {
  return http.get('categories', params)
}
export function addCategories(params) {
  return http.post('categories', params)
}
// 根据id获取商品分类信息
export function getCategories(cid) {
  return http.get(`categories/${cid}`, null)
}
// put请求 修改商品分类信息
export function updateCategories(cid, params) {
  return http.put(`categories/${cid}`, params)
}
// 删除用户信息
export function deleteCategories(cid) {
  return http.delete(`categories/${cid}`, null)
}

// 获取参数信息列表
export function getAttributes(cid, params) {
  return http.get(`categories/${cid}/attributes`, params)
}
// 添加参数信息
export function addAttribute(cid, params) {
  return http.post(`categories/${cid}/attributes`, params)
}
// 删除参数信息
export function delAttribute(cid, attrid) {
  return http.delete(`categories/${cid}/attributes/${attrid}`, null)
}
// 获取参数信息
export function getAttribute(cid, attrid, params) {
  return http.get(`categories/${cid}/attributes/${attrid}`, params)
}
// 修改参数信息
export function updateAttribute(cid, attrid, params) {
  return http.put(`categories/${cid}/attributes/${attrid}`, params)
}

请求分类数据并将数据保存在data中

<script>
import {
    getCategoriesList
  } from '../api/categories.js'
  export default {
    data() {
      return {
        // 商品分类数据列表
        cateList: [],
        // 查询分类数据的条件
        queryInfo: {
          type: 3,
          pagenum: 1,
          pagesize: 5
        },
        // 保存总数据条数
        total: 0
    },
    created() {
      this.getCateList()
    },
    methods: {
      getCateList() {
        // 获取商品分类数据
        getCategoriesList(this.queryInfo).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取商品列表数据失败')
          }
          // 将数据列表赋值给cateList
          this.cateList = data.data.result
          // 保存总数据条数
          this.total = data.data.total
        })
      }
    }
  }
</script>

使用插件展示数据

使用第三方插件vue-table-with-tree-grid展示分类数据
1).在vue 控制台中点击依赖->安装依赖->运行依赖->输入vue-table-with-tree-gird->点击安装
2).打开main.js,导入vue-table-with-tree-grid
import TreeTable from ‘vue-table-with-tree-grid’

Vue.config.productionTip = false

//全局注册组件
Vue.component('tree-table', TreeTable)

3).使用组件展示分类数据

<!-- 分类表格
:data(设置数据源) :columns(设置表格中列配置信息) :selection-type(是否有复选框)
:expand-type(是否展开数据) show-index(是否设置索引列) index-text(设置索引列头)
border(是否添加纵向边框) :show-row-hover(是否鼠标悬停高亮) -->
<tree-table class="table" :data="cateList" :columns="columns" :selection-type="false"
:expand-type="false" show-index index-text="#" border :show-row-hover="false">

</tree-table>
<script>
    // 在data数据中添加columns:
    columns: [
        {label:'分类名称',prop:'cat_name'}
    ]
</script>

自定义数据列

使用vue-table-with-tree-grid定义模板列并添加自定义列

// type:'template'(将该列设置为模板列),template:'isok'(设置该列模板的名称为isok)
          {
            label: '是否有效',
            prop: '',
            type: 'template',
            template: 'isok'
          },
          {
            label: '排序',
            prop: '',
            type: 'template',
            template: 'order'
          },
          {
            label: '操作',
            prop: '',
            type: 'template',
            template: 'opt'
          }
<!-- 是否有效区域, 设置对应的模板列: v-slot:isok(与columns中设置的template一致) -->
        <template  v-slot:isok="scope">
          <i class="el-icon-success" v-if="scope.row.cat_deleted === false" style="color:lightgreen"></i>
          <i class="el-icon-error" v-else style="color:red"></i>
        </template>
        <!-- 排序 -->
        <template v-slot:order="scope">
          <el-tag size="mini" v-if="scope.row.cat_level===0">一级</el-tag>
          <el-tag size="mini" type="success" v-else-if="scope.row.cat_level===1">二级</el-tag>
          <el-tag size="mini" type="warning" v-else>三级</el-tag>
        </template>
        <!-- 操作 -->
        <template v-slot:opt>
          <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
          <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
        </template>
      </tree-table>

完成分页功能

<!-- 分页 -->
<el-pagination class="page" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize" layout="total, sizes, prev, pager, next, jumper" :total="total">
</el-pagination>

<script>
//添加对应的事件函数
methods:{
  .......
  handleSizeChange(newSize){
    //当pagesize发生改变时触发
    this.queryInfo.pagesize = newSize;
    this.getCateList();
  },
  handleCurrentChange(newPage){
    //当pagenum发生改变时触发
    this.queryInfo.pagenum = newPage;
    this.getCateList();
  }
}
</script>

完成添加分类

......
<!-- 添加分类按钮区域 -->
<el-row>
  <el-col>
    <el-button type="primary" @click="showAddCateDialog">添加分类</el-button>
  </el-col>
</el-row>
......
<!-- 添加分类对话框 -->
<el-dialog title="添加分类" :visible.sync="addCateDialogVisible" width="50%"  @close="addCateDialogClosed">
  <!-- 添加分类表单 -->
  <el-form :model="addCateForm" :rules="addCateFormRules" ref="addCateForm" label-width="100px">
    <el-form-item label="分类名称" prop="cat_name">
      <el-input v-model="addCateForm.cat_name"></el-input>
    </el-form-item>
    <el-form-item label="父级分类" prop="cat_pid">
      
    </el-form-item>
  </el-form>
  <span slot="footer" class="dialog-footer">
    <el-button @click="addCateDialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCate">确 定</el-button>
  </span>
</el-dialog>


<script>
		// 用来显示或隐藏添加分类对话框
        addCateDialogVisible: false,
        // 添加分类的表单数据对象
        addCateForm: {
          // 分类名称
          cat_name: '',
          // 添加分类的父级id,0则表示父级为0.添加一级分类
          cat_pid: 0,
          // 添加分类的等级,0则表示添加一级分类
          cat_level: 0
        },
        // 添加分类校验规则
        addCateFormRules: {
          // 验证规则
          cat_name: [{
            required: true,
            message: '请输入分类名称',
            trigger: 'blur'
          }]
        },
        // 保存1,2级父级分类的列表
        parentCateList: []
.......
	showAddCateDialog() {
        // 调用getParentCateList获取分类列表
        this.getParentCateList()
        // 显示添加分类对话框
        this.addCateDialogVisible = true
      },
      getParentCateList() {
        // 获取商品分类数据
        getCategoriesList({
          type: 2
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取商品分类列表数据失败')
          }
          // 将数据列表赋值给cateList
          this.parentCateList = data.data
        })
      }
</script>

添加级联菜单显示父级分类
先导入Cascader组件,并注册
然后添加使用级联菜单组件:

<el-form-item label="父级分类" prop="cat_pid">
  <!-- expandTrigger='hover'(鼠标悬停触发级联) v-model(设置级联菜单绑定数据) :options(指定级联菜单数据源)  :props(用来配置数据显示的规则) 
  clearable(提供“X”号完成删除文本功能) change-on-select(是否可以选中任意一级的菜单) -->
  <el-cascader expandTrigger='hover' v-model="selectedKeys" :options="parentCateList" :props="cascaderProps" @change="parentCateChange" clearable change-on-select></el-cascader>
</el-form-item>

添加数据
<script>
		// 配置级联菜单中数据如何展示
        cascaderProps: {
          value: 'cat_id',
          label: 'cat_name',
          children: 'children',
          expandTrigger: 'hover'
        },
        // 绑定用户选择的分类值
        selectedKeys: []
.....
	methods:{
  	.....
    parentCateChange() {
        // 如果用户选择了父级分类
        if (this.selectedKeys.length > 0) {
          // 则将数组中的最后一项设置为父级分类
          this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
          // level也要跟着发生变化
          this.addCateForm.cat_level = this.selectedKeys.length
        } else {
          this.addCateForm.cat_pid = 0
          this.addCateForm.cat_level = 0
        }
      },
      addCateDialogClosed() {
        // 当关闭添加分类对话框时,重置表单
        this.$refs.addCateFormRef.resetFields()
        this.selectedKeys = []
        this.addCateForm.cat_pid = 0
        this.addCateForm.cat_level = 0
      },
      addCate() {
        // 表单验证
        this.$refs.addCateFormRef.validate(valid => {
          if (!valid) return this.$message.error('输入有误,请重新填写')
          addCategories(this.addCateForm).then(res => {
            const data = res.data
            if (data.meta.status !== 201) {
              return this.$message.error('添加分类失败')
            }
            this.$message.success('添加分类成功')
            this.getCateList()
            this.addCateDialogVisible = false
          })
        })
      }
	}
</script>

注意: 后台返回列表数据较多,通过 Element-UI 中 el-cascader 组件渲染导致数据混乱与丢失,且侧边出现无滚动条、子菜单空白等情况。

可以尝试通过设置高度样式进行修正。

.el-cascader-menu {
      height: 260px;
  }
  .el-cascader-panel {
      height: 260px;
  }

修改商品分类

给修改按钮添加点击事件,以及添加修改对话框

<!-- 操作 -->
        <template v-slot:opt="scope">
          <el-button size="mini" type="primary" icon="el-icon-edit" @click="editDialogShow(scope.row.cat_id)">编辑
          </el-button>
          <el-button size="mini" type="danger" icon="el-icon-delete" >删除</el-button>
        </template>


<!-- 修改分类对话框 -->
    <el-dialog title="修改分类" :visible.sync="editCateDialogVisible" width="50%" @close="editCateDialogClosed">
      <!-- 添加分类表单 -->
      <el-form :model="editCateForm" :rules="editCateFormRules" ref="editCateFormRef" label-width="100px">
        <el-form-item label="分类名称" prop="cat_name">
          <el-input v-model="editCateForm.cat_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editCateDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="saveCate">保 存</el-button>
      </span>
    </el-dialog>

<script>
	......
    editCateDialogVisible: false,
    // 添加分类的表单数据对象
    editCateForm: {
      // 分类名称
      cat_name: '',
      cat_id: ''
    },
    // 添加分类校验规则
    editCateFormRules: {
      // 验证规则
      cat_name: [{
         required: true,
         message: '请输入分类名称',
         trigger: 'blur'
      }]
    }
    ......
    editDialogShow(cateId) {
        getCategories(cateId).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取分类数据失败')
          }
          this.editCateForm = data.data
          this.editCateDialogVisible = true
        })
      },
      saveCate() {
        updateCategories(this.editCateForm.cat_id, {
          cat_name: this.editCateForm.cat_name
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('修改分类失败')
          }
          this.$message.success('修改分类成功')
          this.editCateDialogVisible = false
          this.getCateList()
        })
      }
</script>

删除商品分类

给删除按钮添加点击事件

<el-button size="mini" type="danger" icon="el-icon-delete" @click="delCate(scope.row.cat_id)">删除</el-button>

<script>
......
	delCate(cateId) {
        deleteCategories(cateId).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('删除分类失败')
          }
          this.$message.success('删除分类成功')
          this.getCateList()
        })
      }
</script>

2.参数管理

只允许给三级分类内容设置参数,参数分为动态参数和静态参数属性

添加子级组件

添加Params.vue子组件,并在router.js中引入该组件并设置路由规则

import Goods from '@/views/goodsManagement/Goods.vue'
import Params from '@/views/goodsManagement/Params.vue'
import Categories from '@/views/goodsManagement/Categories.vue'

path: '/home', component: Home, redirect: '/welcome', children: [
    { path: "/welcome", component: Welcome },
    { path: "/users", component: Users },
    { path: "/rights", component: Rights },
    { path: "/roles", component: Roles  },
    { path: 'goods', component: Goods },
    { path: 'params', component: Params },
    { path: 'categories', component: Categories }
]

完成组件基本布局

完成Params.vue组件的基本布局

<template>
  <div>
    <Breadcrumb :item="item" :subItem="subItem"></Breadcrumb>
    <ParamsList></ParamsList>
  </div>
</template>

<script>
  import Breadcrumb from '../../components/Breadcrumb.vue'
  import ParamsList from '../../components/ParamsList.vue'
  export default {
    data() {
      return {
        item: '商品管理',
        subItem: '分类参数'
      }
    },
    components: {
        Breadcrumb,
        ParamsList
    }
  }
</script>

<style>
</style>

在components目录下创建ParamsList.vue,其中警告提示信息使用了el-alert,在element.js引入该组件并注册

<template>
  <div class="params">
    <el-card>
      <!-- 警告区域 :closable="false"(是否展示“X”号) show-icon(显示图标) -->
      <el-alert title="注意:只允许为第三级分类设置相关参数" type="warning" :closable="false" show-icon>
      </el-alert>
      <!-- 选择商品分类区域 -->
      <el-row class="cat_opt">
        <el-col>
          <span>选择商品分类:</span>
          <!-- 选择商品分类的级联选择框 -->
        </el-col>
        <el-col></el-col>
      </el-row>
    </el-card>
  </div>
</template>

<script>
  export default {}
</script>

<style lang="scss" scoped>
  .params,
  .table,
  .page,
  .cat_opt{
    margin-top: 10px;
  }
</style>

完成级联选择框

完成商品分类级联选择框

<template>
  <div class="params">
    <el-card>
      <!-- 警告区域 :closable="false"(是否展示“X”号) show-icon(显示图标) -->
      <el-alert title="注意:只允许为第三级分类设置相关参数" type="warning" :closable="false" show-icon>
      </el-alert>
      <!-- 选择商品分类区域 -->
      <el-row class="cat_opt">
        <el-col>
          <span>选择商品分类:</span>
           <!-- 选择商品分类的级联选择框 -->
                  <el-cascader expandTrigger='hover' v-model="selectedCateKeys"
                  :options="cateList" :props="cateProps" @change="handleChange" clearable></el-cascader>
        </el-col>
        <el-col></el-col>
      </el-row>
    </el-card>
  </div>
</template>

<script>
  import {
    getCategoriesList
  } from '../api/categories.js'
  export default {
     data() {
        return {
            // 分类列表
            cateList: [],
            // 用户在级联下拉菜单中选中的分类id
            selectedCateKeys: [],
            // 配置级联菜单中数据如何展示
            cateProps: {
                value: 'cat_id',
                label: 'cat_name',
                children: 'children'
            }
        }
      },
      created() {
          this.getCateList()
      },
      methods: {
          getCateList() {
            // 获取所有的商品分类列表
            getCategoriesList().then(res => {
              const data = res.data
              if (data.meta.status !== 200) {
                return this.$message.error('获取商品分类列表数据失败')
              }
              // 将数据列表赋值给cateList
              this.cateList = data.data
            })
          },
          handleChange() {
            // 当用户在级联菜单中选择内容改变时触发
            console.log(this.selectedCateKeys)
          }
      }
  }
</script>

展示参数

展示动态参数数据以及静态属性数据

<!-- tab页签区域 -->
      <el-tabs v-model="activeName" @tab-click="handleTabClick">
        <!-- 添加动态参数的面板 将标签页改为many -->
        <el-tab-pane label="动态参数" name="many">
          <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加参数</el-button>
          <!-- 动态参数表格 -->
          <el-table :data="manyTableData" border stripe class="table">
            <!-- 展开行 -->
            <el-table-column type="expand"></el-table-column>
            <!-- 索引列 -->
            <el-table-column type="index"></el-table-column>
            <el-table-column label="参数名称" prop="attr_name"></el-table-column>
            <el-table-column label="操作">
              <template>
                <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
                <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
        <!-- 添加静态属性的面板 将标签页改为only -->
        <el-tab-pane label="静态属性" name="only">
          <el-button size="mini" type="primary" :disabled="isButtonDisabled">添加属性</el-button>
          <!-- 静态属性表格 -->
          <el-table :data="onlyTableData" border stripe class="table">
            <!-- 展开行 -->
            <el-table-column type="expand"></el-table-column>
            <!-- 索引列 -->
            <el-table-column type="index"></el-table-column>
            <el-table-column label="属性名称" prop="attr_name"></el-table-column>
            <el-table-column label="操作">
              <template>
                <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
                <el-button size="mini" type="danger" icon="el-icon-delete">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
      </el-tabs>
<script>
export default {
  data() {
    return {
      ......
      //tab页签激活显示的页签项
      activeName: 'many',
      //用来保存动态参数数据
      manyTableData: [],
      //用来保存静态属性数据
      onlyTableData: []  
    }
  methods: {
    .......
          // 当用户在级联菜单中选择内容改变时触发
      handleChange() {
        if (this.selectedCateKeys.length !== 3) {
          // 将动态参数和静态属性重置为空
          this.manyTableData = null
          this.onlyTableData = null
          return this.$message.error('选择商品分类')
        }
        // 发送请求,根据用户选择的三级分类和面板获取参数数据
        getAttributes(this.cateId, {
          sel: this.activeName
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取参数列表数据失败')
          }
          if (this.activeName === 'many') {
            // 获取的是动态参数
            this.manyTableData = data.data
          } else if (this.activeName === 'only') {
            // 获取的是静态属性
            this.onlyTableData = data.data
          }
        })
      },
      handleTabClick() {
        this.handleChange()
      }
    },
    computed: {
      // 添加计算属性用来获取按钮禁用与否
      isButtonDisabled() {
        return this.selectedCateKeys.length !== 3
      },
      // 获取选中的三级分类id
      cateId() {
        if (this.selectedCateKeys.length === 3) {
          return this.selectedCateKeys[this.selectedCateKeys.length - 1]
        }
        return null
      }
    }
  }
</script>

添加参数

完成添加参数或属性

<!-- 添加参数或属性对话框 -->
    <el-dialog :title="'添加'+titleText" :visible.sync="addDialogVisible" width="50%" @close="addDialogClosed">
      <!-- 添加表单 -->
      <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px">
        <el-form-item :label="titleText" prop="attr_name">
          <el-input v-model="addForm.attr_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="addDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="addParams">确 定</el-button>
      </span>
    </el-dialog>

<script>
export default {
  data() {
    return {
      .......
      // 控制添加参数.属性对话框的显示或隐藏
        addDialogVisible: false,
        // 添加参数的表单数据对象
        addForm: {
          attr_name: ''
        },
        // 添加表单验证规则
        addFormRules: {
          attr_name: [{
            required: true,
            message: '请输入名称',
            trigger: 'blur'
          }]
        }
    }
  },
  methods: {
    .......
    addDialogShow() {
        this.addDialogVisible = true
      },
      addDialogClosed() {
        this.$refs.addFormRef.resetFields()
        this.addDialogVisible = false
      },
      addParams() {
        // 当用户点击对话框中的确定时,校验表单
        this.$refs.addFormRef.validate(valid => {
          // 校验不通过,return
          if (!valid) return
          addAttributes(this.cateId, {
            attr_name: this.addForm.attr_name,
            attr_sel: this.activeName
          }).then(res => {
            const data = res.data
            if (data.meta.status !== 201) {
              return this.$message.error('添加' + this.titleText + '数据失败')
            }
            this.$message.success('添加' + this.titleText + '数据成功')
            this.addDialogVisible = false
            this.getCateList()
          })
        })
      }
  },
    computed: {
      ......
      titleText() {
        return this.activeName === 'many' ? '动态参数' : '静态属性'
      }
    }
</script>

编辑参数

完成编辑参数或属性

<!-- 修改参数或属性对话框 -->
    <el-dialog :title="'修改'+titleText" :visible.sync="editDialogVisible" width="50%" @close="editDialogClosed">
      <!-- 添加表单 -->
      <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px">
        <el-form-item :label="titleText" prop="attr_name">
          <el-input v-model="editForm.attr_name"></el-input>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button @click="editDialogVisible = false">取 消</el-button>
        <el-button type="primary" @click="editParams">确 定</el-button>
      </span>
    </el-dialog>
<script>

export default {
  data() {
    return {
      .......
      // 控制修改参数.属性对话框的显示或隐藏
        editDialogVisible: false,
        // 修改参数.属性对话框中的表单
        editForm: {
          attr_name: ''
        },
        // 修改表单的验证规则
        editFormRules: {
          attr_name: [{
            required: true,
            message: '请输入名称',
            trigger: 'blur'
          }]
        }
    }
  },
 methods: {
    .......
     editDialogShow(attrid) {
        getAttribute(this.cateId, attrid, {
          attr_sel: this.activeName
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取参数数据失败')
          }
          this.editForm = data.data
          // 显示修改参数.属性对话框
          this.editDialogVisible = true
        })
      },
      editParams() {
        // 验证表单
        this.$refs.editFormRef.validate(valid => {
          if (!valid) return
          // 发送请求完成修改
          updateAttribute(this.cateId, this.editForm.attr_id, {
            attr_name: this.editForm.attr_name,
            attr_sel: this.activeName
          }).then(res => {
            const data = res.data
              if (data.meta.status !== 200) {
                return this.$message.error('获取参数数据失败')
              }
              this.$message.success('修改' + this.titleText + '数据成功')
              this.editDialogVisible = false
              this.handleChange()
          })
        })
      },
      editDialogClosed() {
        this.$refs.editFormRef.resetFields()
        this.editDialogVisible = false
      }
  }
</script>

删除参数

删除参数或属性

给两个删除按钮添加事件
<template v-slot="scope">
   <el-button size="mini" type="primary" icon="el-icon-edit">编辑</el-button>
   <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeParams(scope.row.attr_id)">删除</el-button>
</template>

添加对应的事件处理函数
<script>
	async removeParams(attrid) {
        // 根据id删除对应的参数或属性
        // 弹窗提示用户是否要删除
        const confirmResult = await this.$confirm(
          '请问是否要删除该' + this.titleText,
          '删除提示', {
            confirmButtonText: '确认删除',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).catch(err => err)
        // 如果用户点击确认,则confirmResult 为'confirm'
        // 如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'
        if (confirmResult !== 'confirm') {
          return this.$message.info('已经取消删除')
        }
        delAttributes(this.cateId, attrid).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('删除参数数据失败')
          }
          this.$message.success('删除' + this.titleText + '数据成功')
          this.handleChange()
        })
      }
</script>

展示动态参数可选项

动态参数可选项展示及操作
在获取动态参数的方法中进行处理。

//将获取到的数据中的attr_vals字符串转换为数组
data.data.forEach(item => {
  item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
  //添加一个bool值控制文本框的显示或者隐藏
  item.inputVisible = false
  //添加一个inputValue保存文本框值
  item.inputValue = ''
})

//然后再修改展开行中的代码,生成el-tag和文本框以及添加按钮
<!-- 展开行 -->
<el-table-column type="expand">
  <template slot-scope="scope">
    <!-- 循环生成的el-tag -->
    <el-tag v-for="(item,i) in scope.row.attr_vals" :key="i" closable>{{item}}</el-tag>
    <!-- 输入框 -->
    <el-input class="input-new-tag" v-if="scope.row.inputVisible" v-model="scope.row.inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm(scope.row)" @blur="handleInputConfirm(scope.row)">
    </el-input>
    <!-- 添加按钮 -->
    <el-button v-else class="button-new-tag" size="small" @click="showInput(scope.row)">+ New Tag</el-button>
  </template>
</el-table-column>


<script>
......
	// 最后对应文本框的事件和按钮的事件添加处理函数
      handleInputConfirm(row) {
        // 当用户在文本框中按下enter键或者焦点离开时都会触发执行
        // 判断用户在文本框中输入的内容是否合法
        if (row.inputValue.trim().length === 0) {
          row.inputValue = ''
          row.inputVisible = false
        }
        // row.inputVisible = false
        // 如果用户输入了真实合法的数据,需要保存起来
        
      },
      showInput(row) {
        // 用户点击添加按钮时触发
        row.inputVisible = true
        // $nextTick:在页面上元素被重新渲染之后,调用回调函数的代码
        this.$nextTick(_ => {
          // 让文本框自动获得焦点
          this.$refs.saveTagInput.$refs.input.focus()
        })
      }
</script>

添加/删除可选项

添加/删除动态参数可选项
给el-tag添加删除事件

<el-tag v-for="(item,i) in scope.row.attr_vals" :key="i" closable @close="handleClose(i,scope.row)">{{item}}</el-tag>

<script>
	handleInputConfirm(row) {
        // 当用户在文本框中按下enter键或者焦点离开时都会触发执行
        // 判断用户在文本框中输入的内容是否合法
        if (row.inputValue.trim().length === 0) {
          row.inputValue = ''
          row.inputVisible = false
          return
        }
        // 如果用户输入了真实合法的数据,需要保存起来
        row.attr_vals.push(row.inputValue.trim())
        row.inputValue = ''
        row.inputVisible = false
        this.saveAttrVals(row)
      },
      handleClose(index, row) {
        // 删除对应索引的参数可选项
        row.attr_vals.splice(index, 1)
        // 调用函数,完成保存可选项的操作
        this.saveAttrVals(row)
      },
      saveAttrVals(row) {
        updateAttribute(this.cateId, row.attr_id, {
          attr_name: row.attr_name,
          attr_sel: row.attr_sel,
          attr_vals: row.attr_vals.join(' ')
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('操作失败')
          }
        })
      }
</script>

补充:当用户在级联选择框中选中了非三级分类时,需要清空表格中数据

handleChange() {
      //如果用户选择的不是三级分类
      if(this.selectedCateKeys.length !== 3){
        this.selectedCateKeys = []
        this.manyTableData = []
        this.onlyTableData = []
        return
      }
      ......

补充2:当完成了动态参数可选项的功能之后,我们也需要一样的方式完成静态属性可选项的功能。
此时我们只需要将动态参数可选项中的展开行复制到静态属性的表格中即可。

3.商品列表

制作商品列表基本结构

添加子级路由组件以及对应的规则,并设置组件的基本机构
打开router.js,添加下面的代码

import Goods from '@/views/goodsManagement/Goods.vue'

path: '/home', component: Home, redirect: '/welcome', children: [
  { path: "/welcome", component: Welcome },
  { path: "/users", component: Users },
  { path: "/rights", component: Rights },
  { path: "/roles", component: Roles  },
  { path: "/categories", component: Cate  },
  { path: "/params", component: Params  },
  { path: "/goods", component: Goods }
]

打开Goods.vue组件,添加下列代码

<template>
  <div>
    <Breadcrumb :item="item" :subItem="subItem"></Breadcrumb>
    <GoodsList></GoodsList>
  </div>
</template>

<script>
  import Breadcrumb from '../../components/Breadcrumb.vue'
  import GoodsList from '../../components/goods/List.vue'
  export default {
    data() {
      return {
        item: '商品管理',
        subItem: '商品列表'
      }
    },
    components: {
        Breadcrumb,
        GoodsList
    }
  }
</script>

<style>
</style>

在api目录下创建goods.js的接口

import http from '../utils/http.js'

// get请求 获取商品分类信息
export function getGoodsList(params) {
  return http.get('goods', params)
}
// 删除
export function delGoods(id) {
  return http.delete(`goods/${id}`, null)
}
// 添加
export function addGoods(params) {
  return http.post('goods', params)
}
// 根据id获取
export function getGoods(id) {
  return http.get(`goods/${id}`, null)
}
// 修改
export function updateGoods(id, params) {
  return http.put(`goods/${id}`, params)
}

数据展示

添加数据表格展示数据以及分页功能的实现,搜索功能的实现

创建 api/goods.js

import http from '../utils/http.js'

// get请求 获取商品信息
export function getGoodsList(params) {
  return http.get('goods', params)
}
// 删除
export function delGoods(id) {
  return http.delete(`goods/${id}`, null)
}
// 添加
export function addGoods(params) {
  return http.post('goods', params)
}
// 根据id获取
export function getGoods(id) {
  return http.get(`goods/${id}`, null)
}
// 修改
export function updateGoods(id, params) {
  return http.put(`goods/${id}`, params)
}

日期时间过滤器:

在filter目录下创建dateFormat.js

// 创建过滤器将秒数过滤为年月日,时分秒
export function dateFormat(originVal) {
  originVal = originVal * 1000
  const date = new Date(originVal)
  const y = date.getFullYear()
  const m = (date.getMonth() + 1 + '').padStart(2, '0')
  const d = (date.getDate() + '').padStart(2, '0')

  const hh = (date.getHours() + '').padStart(2, '0')
  const mm = (date.getMinutes() + '').padStart(2, '0')
  const ss = (date.getSeconds() + '').padStart(2, '0')

  return `${y}-${m}-${d} ${hh}:${mm}:${ss}`
}

在main.js中注册过滤器(全局过滤器)

import { dateFormat } from './filter/dateFormat.js'
// 全局注册组件
Vue.component('tree-table', TreeTable)
<template>
  <div class="goods">
    <!-- 卡片视图区域 -->
    <el-card>
      <el-row :gutter="20">
        <el-col :span="8">
          <el-input placeholder="请输入内容" v-model="queryInfo.query" clearable @clear="getGoodsList">
            <el-button slot="append" icon="el-icon-search" @click="getGoodsList"></el-button>
          </el-input>
        </el-col>
        <el-col :span="4">
          <el-button type="primary" @click='goAddPage'>添加商品</el-button>
        </el-col>
      </el-row>
      <!-- 表格区域 -->
      <el-table class="table" :data="goodsList" border stripe>
        <el-table-column type="index"></el-table-column>
        <el-table-column label="商品名称" prop="goods_name"></el-table-column>
        <el-table-column label="价格(元)" prop="goods_price" width="85px"></el-table-column>
        <el-table-column label="重量" prop="goods_weight" width="80px"></el-table-column>
        <el-table-column label="创建时间" prop="add_time" width="160px">
          <template v-slot="scope">
            {{scope.row.add_time | dateFormat}}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120px">
          <template>
            <el-button size="mini" type="primary" icon="el-icon-edit"></el-button>
            <el-button size="mini" type="danger" icon="el-icon-delete" ></el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <el-pagination class="page" @size-change="handleSizeChange" @current-change="handleCurrentChange"
        :current-page="queryInfo.pagenum" :page-sizes="[3, 5, 10, 15]" :page-size="queryInfo.pagesize"
        layout="total, sizes, prev, pager, next, jumper" :total="total">
      </el-pagination>
    </el-card>
  </div>
</template>

<script>
  import {
    getGoodsList,
    delGoods
  } from '../api/goods.js'
  export default {
    data() {
      return {
        // 查询参数
        queryInfo: {
          query: '',
          pagenum: 1,
          pagesize: 10
        },
        // 保存商品列表信息
        goodsList: [],
        // 总数据条数
        total: 0
      }
    },
    created() {
      this.getGoodsList()
    },
    methods: {
      getGoodsList() {
        // 根据分页获取对应的商品列表
        getGoodsList(this.queryInfo).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error(data.meta.msg)
          }
          this.goodsList = data.data.goods
          this.total = data.data.total
        })
      },
      handleSizeChange(newSize) {
        // 当页号发生改变时,更改pagesize,重新请求
        this.queryInfo.pagesize = newSize
        this.getGoodsList()
      },
      handleCurrentChange(newPage) {
        // 当页码发生改变时,更改pagesize,重新请求
        this.queryInfo.pagenum = newPage
        this.getGoodsList()
      }
    }
  }
</script>

<style lang="scss" scoped>
  .goods,
  .table,
  .page {
    margin-top: 10px;
  }
</style>

实现删除商品

<template v-slot="scope">
    <el-button size="mini" type="primary" icon="el-icon-edit"></el-button>
    <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeGoods(scope.row.goods_id)"></el-button>
</template>

<script>
	... ...
	async removeGoods(id) {
        // 根据id删除对应的参数或属性
        // 弹窗提示用户是否要删除
        const confirmResult = await this.$confirm(
          '请问是否要删除该商品',
          '删除提示', {
            confirmButtonText: '确认删除',
            cancelButtonText: '取消',
            type: 'warning'
          }
        ).catch(err => err)
        // 如果用户点击确认,则confirmResult 为'confirm'
        // 如果用户点击取消, 则confirmResult获取的就是catch的错误消息'cancel'
        if (confirmResult !== 'confirm') {
          return this.$message.info('已经取消删除')
        }
        delGoods(id).then(res => {
          this.$message.error(res.data.meta.msg)
          this.getGoodsList()
        })
      }

</script>

添加商品

1.添加编程式导航

在List.vue中添加编程式导航,并创建添加商品路由组件及规则

//在List.vue中添加编程式导航
<el-col :span="4">
    <el-button type="primary" @click="goAddPage">添加商品</el-button>
</el-col>

<script>
    ... ...
	goAddPage() {
        this.$router.push('goodsAdd')
      }
</script>

在router.js中引入goodsAdd.vue,并添加路由规则

import GoodsAdd from '@/views/goodsManagement/GoodsAdd.vue'
{ path: 'goodsAdd', component: GoodsAdd }

2.布局Add.vue组件

布局过程中需要使用Steps组件,在element.js中引入并注册该组件,并在global.css中给组件设置全局样式

import {Steps,Step} from 'element-ui'
Vue.use(Step)
Vue.use(Steps)

//global.css
.el-steps{
    margin:15px 0;
}
.el-step__title{
    font-size: 13px;
}

然后再在view\GoodsAdd.vue中进行页面布局

<template>
  <div>
    <Breadcrumb :item="item" :subItem="subItem"></Breadcrumb>
    <Add></Add>
  </div>
</template>

<script>
  import Breadcrumb from '../../components/Breadcrumb.vue'
  import Add from '../../components/goods/Add.vue'
  export default {
    data() {
      return {
        item: '商品管理',
        subItem: '商品添加'
      }
    },
    components: {
        Breadcrumb,
        Add
    }
  }
</script>

<style>
</style>

封装组件,在components/goods/Add.vue

<template>
  <div class="main">
    <!-- 卡片视图区域 -->
    <el-card>
      <!-- 消息提示 -->
      <el-alert title="添加商品信息" type="info" center show-icon :closable="false">
      </el-alert>

      <!-- 步骤条组件 -->
      <!-- align-center(居中效果) -->
      <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>

      <!-- tab栏区域:el-tab-pane必须是el-tabs的子节点
                :tab-position="'left'"(设置tab栏为左右结构tab栏) -->
      <!-- 表单:label-position="top"(设置label在文本框上方) -->
      <el-form :model="addForm" :rules="addFormRules" ref="addFormRef" label-width="100px" label-position="top">
        <el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTabLeave" @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 expandTrigger='hover' v-model="addForm.goods_cat" :options="cateList" :props="cateProps"
                @change="handleChange" clearable class="cascader"></el-cascader>
            </el-form-item>
          </el-tab-pane>
          <el-tab-pane label="商品参数" name="1">
          </el-tab-pane>
          <el-tab-pane label="商品属性" name="2">

          </el-tab-pane>
          <el-tab-pane label="商品图片" name="3">

          </el-tab-pane>
          <el-tab-pane label="商品内容" name="4">

          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>
  </div>
</template>

<script>
  import {
    getCategoriesList,
    getAttributes
  } from '../api/categories.js'
  export default {
    data() {
      return {
        // 保存步骤条激活项索引
        activeIndex: '0',
        // 添加商品的表单数据对象
        addForm: {
          goods_name: '',
          goods_price: 0,
          goods_weight: 0,
          goods_number: 0,
          goods_cat: []
        },
        // 验证规则
        addFormRules: {
          goods_cat: [{
            required: true,
            message: '请选择商品分类',
            trigger: 'blur'
          }],
          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'
          }]
        },
        // 用来保存分类数据
        cateList: [],
        // 配置级联菜单中数据如何展示
        cateProps: {
          value: 'cat_id',
          label: 'cat_name',
          children: 'children'
        }
      }
    },
    created() {
      this.getCateList()
    },
    methods: {
      getCateList() {
        // 获取商品分类数据
        getCategoriesList().then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error('获取商品列表数据失败')
          }
          // 将数据列表赋值给cateList
          this.cateList = data.data
        })
      },
      handleChange() {
        // 如果用户选择的不是三级分类,该次选择无效,因为必须选择三级分类
        if (this.addForm.goods_cat.length !== 3) {
          this.addForm.goods_cat = []
        }
      }
    }
  }
</script>

<style lang="scss">
  .cascader {
    width: 100%;
  }
</style>

3. 添加tab栏切换验证

也就是说不输入某些内容,无法切换到别的tab栏

//首先给tabs添加tab切换前事件
<el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTabLeave">
......
</el-tabs>

<script>
	... ...
	  // 再到methods编写事件函数beforeTabLeave
      beforeTabLeave(activeName, oldActiveName) {
        // 在tab栏切换之前触发,两个形参为切换前,后的tab栏name
        if (oldActiveName === '0') {
          // 在第一个标签页的时候
          if (this.addForm.goods_cat.length !== 3) {
            this.$message.error('请选择商品的分类')
            return false
          } else if (this.addForm.goods_name.trim() === '') {
            this.$message.error('请输入商品名称')
            return false
          } else if (this.addForm.goods_price === 0) {
            this.$message.error('请输入商品价格')
            return false
          } else if (this.addForm.goods_weight === 0) {
            this.$message.error('请输入商品重量')
            return false
          } else if (this.addForm.goods_number === 0) {
            this.$message.error('请输入商品数量')
            return false
          }
        }
      }
</script>

4.展示信息

展示商品参数信息,商品属性信息
在商品参数信息展示中使用的el-checkbox,el-checkbox-group组件,打开element.js引入组件并注册组件

<!-- 在用户点击tab栏时触发事件 -->
<el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTabLeave" @tab-click="tabClicked">
........

<!-- 在参数信息,商品属性面板中添加循环生成结构的代码 -->
		<el-tab-pane label="商品参数" name="1">
            <!-- 渲染表单item项 -->
            <el-form-item :label="item.attr_name" :key="item.attr_id" v-for="(item, index) in manyTableDataAll">
              <!-- 使用数组渲染复选框组 -->
              <el-checkbox-group v-model="manyTableData[index].attr_vals" @change="hanldleChange">
                <el-checkbox border :label="val" v-for="(val,i) in item.attr_vals" :key="i"></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>

<script>
// 在data数据中添加保存动态参数和静态属性的数组
export default {
  data() {
    return {
      ......
      // 所有动态参数列表
      manyTableDataAll: [],
      // 选中动态参数列表
      manyTableData: [],
      // 静态属性列表
      onlyTableData: []
      }
  },methods: {
    .......
  	tabClicked() {
        // 当用户点击切换tab栏时触发
        if (this.activeIndex === '1') {
          // 发送请求获取动态参数
          getAttributes(this.cateId, {
            sel: 'many'
          }).then(res => {
            const data = res.data
            if (data.meta.status !== 200) {
              return this.$message.error(data.meta.msg)
            }
            // 将attr_vals字符串转换为数组
            data.data.forEach(item => {
              console.log(item.attr_vals)
              item.attr_vals =
                item.attr_vals.length === 0 ? [] : item.attr_vals.split(' ')
            })
            this.manyTableData = data.data
          })
        } else if (this.activeIndex === '2') {
          // 发送请求获取静态属性
          getAttributes(this.cateId, {
            sel: 'only'
          }).then(res => {
            const data = res.data
            if (data.meta.status !== 200) {
              return this.$message.error(data.meta.msg)
            }
            this.onlyTableData = data.data
          })
        }
      }
  },
  //添加 计算属性获取三级分类
  computed: {
    cateId() {
      if (this.addForm.goods_cat.length === 3) {
        return this.addForm.goods_cat[2]
      }
      return null
    }
  }
}
</script>

5.完成图片上传

使用upload组件完成图片上传
在element.js中引入upload组件,并注册
因为upload组件进行图片上传的时候并不是使用axios发送请求
所以,我们需要手动为上传图片的请求添加token,即为upload组件添加headers属性

//在页面中添加upload组件,并设置对应的事件和属性

<el-tab-pane label="商品图片" name="3">
  <!-- 商品图片上传
  action:指定图片上传api接口 
  :on-preview : 当点击图片时会触发该事件进行预览操作,处理图片预览
  :on-remove : 当用户点击图片右上角的X号时触发执行
  :on-success:当用户点击上传图片并成功上传时触发
  list-type :设置预览图片的方式
  :headers :设置上传图片的请求头 -->
  <el-upload :action="uploadURL" :on-preview="handlePreview" :on-remove="handleRemove" :on-success="handleSuccess" list-type="picture" :headers="headerObj">
    <el-button size="small" type="primary">点击上传</el-button>
  </el-upload>
</el-tab-pane>


//在el-card卡片视图下面添加对话框用来预览图片

<!-- 预览图片对话框 -->
<el-dialog title="图片预览" :visible.sync="previewVisible" width="50%">
  <img :src="previewPath" class="previewImg" />
</el-dialog>

<script>
//在data中添加数据
data(){
  return {
    ......
    //添加商品的表单数据对象
    addForm: {
      goods_name: '',
      goods_price: 0,
      goods_weight: 0,
      goods_number: 0,
      goods_cat: [],
      //上传图片数组
      pics: []
    },
    //上传图片的url地址
    uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload',
    //图片上传组件的headers请求头对象
    headerObj: { Authorization: window.sessionStorage.getItem('token') },
    //保存预览图片的url地址
    previewPath: '',
    //控制预览图片对话框的显示和隐藏
    previewVisible:false
  }
},
//在methods中添加事件处理函数
methods:{
  .......
  handlePreview(file) {
    //当用户点击图片进行预览时执行,处理图片预览
    //形参file就是用户预览的那个文件
    this.previewPath = file.response.data.url
    //显示预览图片对话框
    this.previewVisible = true
  },
  handleRemove(file) {
    //当用户点击X号删除时执行
    //形参file就是用户点击删除的文件
    //获取用户点击删除的那个图片的临时路径
    const filePath = file.response.data.tmp_path
    //使用findIndex来查找符合条件的索引
    const index = this.addForm.pics.findIndex(item => item.pic === filePath)
    //移除索引对应的图片
    this.addForm.pics.splice(index, 1)
  },
  handleSuccess(response) {
    //当上传成功时触发执行
    //形参response就是上传成功之后服务器返回的结果
    //将服务器返回的临时路径保存到addForm表单的pics数组中
    this.addForm.pics.push({ pic: response.data.tmp_path })
  }
}
</script>

使用富文本插件

想要使用富文本插件vue-quill-editor,就必须先从依赖安装该插件

cnpm install  vue-quill-editor

引入并注册vue-quill-editor,打开main.js,编写如下代码

//导入vue-quill-editor(富文本编辑器)
import VueQuillEditor from 'vue-quill-editor'
//导入vue-quill-editor的样式
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
......
//全局注册组件
Vue.component('tree-table', TreeTable)
//全局注册富文本组件
Vue.use(VueQuillEditor)

使用富文本插件vue-quill-editor

<el-tab-pane label="商品内容" name="4">
  <!-- 富文本编辑器组件 -->
  <quill-editor v-model="addForm.goods_introduce"></quill-editor>
  <!-- 添加商品按钮 -->
  <el-button type="primary" class="btnAdd">添加商品</el-button>
</el-tab-pane>

<script>
//在数据中添加goods_introduce
//添加商品的表单数据对象
addForm: {
  goods_name: '',
  goods_price: 0,
  goods_weight: 0,
  goods_number: 0,
  goods_cat: [],
  //上传图片数组
  pics: [],
  //商品的详情介绍
  goods_introduce:'',
  // 商品的动态参数和静态属性
  attrs: []
}
</script>

在global.css样式中添加富文本编辑器的最小高度

.ql-editor{
    min-height: 300px;
}
//给添加商品按钮添加间距
.btnAdd{
  margin-top:15px;
}

添加商品

完成添加商品的操作
在添加商品之前,为了避免goods_cat数组转换字符串之后导致级联选择器报错
我们需要打开vue控制条,点击依赖,安装lodash,把addForm进行深拷贝

安装命令:cnpm install lodash

为什么选择 Lodash ?

Lodash 通过降低 array、number、objects、string 等等的使用难度从而让 JavaScript 变得更简单。 Lodash 的模块化方法 非常适用于:

  • 遍历 array、object 和 string
  • 对值进行操作和检测
  • 创建符合功能的函数
//给添加商品按钮绑定点击事件
<!-- 添加商品按钮 -->
<el-button type="primary" class="btnAdd" @click="add">添加商品</el-button>

<script>
import { addGoods } from '../api/goods.js'
//官方推荐将lodash导入为_
import _ from 'lodash'
... ...
	//编写点击事件完成商品添加
 	add() {
        this.$refs.addFormRef.validate(valid => {
          if (!valid) return this.$message.error('请填写必要的表单项!')
          // 将addForm进行深拷贝,避免goods_cat数组转换字符串之后导致级联选择器报错
          const form = _.cloneDeep(this.addForm)
           console.log(form)
          // 将goods_cat从数组转换为"1,2,3"字符串形式
          form.goods_cat = form.goods_cat.join(',')
          // 处理attrs数组,数组中需要包含商品的动态参数和静态属性
          // 将manyTableData(动态参数)处理添加到attrs
          this.manyTableData.forEach(item => {
            form.attrs.push({
              attr_id: item.attr_id,
              attr_value: item.attr_vals.join(' ')
            })
          })
          // 将onlyTableData(静态属性)处理添加到attrs
          this.onlyTableData.forEach(item => {
            form.attrs.push({
              attr_id: item.attr_id,
              attr_value: item.attr_vals
            })
          })
          addGoods(form).then(res => {
            const data = res.data
            if (data.meta.status !== 201) {
              return this.$message.error(data.meta.msg)
            }
            this.$message.success(data.meta.msg)
            // 编程式导航跳转到商品列表
            this.$router.push('goods')
          })
        })
      }
</script>

编辑商品

1. 添加编程式导航

<template v-slot="scope">
       <el-button size="mini" type="primary" icon="el-icon-edit"  @click="goEditPage(scope.row.goods_id)"></el-button>
       <el-button size="mini" type="danger" icon="el-icon-delete" @click="removeGoods(scope.row.goods_id)"></el-button>
</template>
<script>
	.....
    goEditPage(id) {
       this.$router.push({ name: 'goodsEdit', params: { id } })
    }
</script>

2.Edit组件

在view\goodsManagement下创建视图:GoodsEdit.vue

<template>
  <div>
    <Breadcrumb :item="item" :subItem="subItem"></Breadcrumb>
    <Edit></Edit>
  </div>
</template>

<script>
  import Breadcrumb from '../../components/Breadcrumb.vue'
  import Edit from '../../components/goods/Edit.vue'
  export default {
    data() {
      return {
        item: '商品管理',
        subItem: '商品修改'
      }
    },
    components: {
        Breadcrumb,
        Edit
    }
  }
</script>

<style>
</style>

在components\goods下创建组件:Edit.vue

<template>
  <div class="main">
    <!-- 卡片视图区域 -->
    <el-card>
      <!-- 消息提示 -->
      <el-alert title="修改商品信息" type="info" center show-icon :closable="false">
      </el-alert>

      <!-- 步骤条组件 -->
      <!-- align-center(居中效果) -->
      <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>

      <!-- tab栏区域:el-tab-pane必须是el-tabs的子节点
                :tab-position="'left'"(设置tab栏为左右结构tab栏) -->
      <!-- 表单:label-position="top"(设置label在文本框上方) -->
      <el-form ref="editFormRef" label-width="100px" label-position="top">
        <el-tabs v-model="activeIndex" :tab-position="'left'" >
          </el-tab-pane>
          <el-tab-pane label="商品参数" name="1">
          </el-tab-pane>
          <el-tab-pane label="商品属性" name="2">
          </el-tab-pane>
          <el-tab-pane label="商品图片" name="3">
          </el-tab-pane>
          <!-- 富文本编辑器组件 -->
          <el-tab-pane label="商品内容" name="4">
          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>
  </div>
</template>

在router.js中添加路由规则

......
children: [
	... ...
 	{ path: ':id', component: GoodsEdit, name: 'goodsEdit' }
 ]

显示商品信息

<template>
  <div class="main">
    <!-- 卡片视图区域 -->
    <el-card>
      <!-- 消息提示 -->
      <el-alert title="修改商品信息" type="info" center show-icon :closable="false">
      </el-alert>

      <!-- 步骤条组件 -->
      <!-- align-center(居中效果) -->
      <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>

      <!-- tab栏区域:el-tab-pane必须是el-tabs的子节点
                :tab-position="'left'"(设置tab栏为左右结构tab栏) -->
      <!-- 表单:label-position="top"(设置label在文本框上方) -->
      <el-form :model="editForm" :rules="editFormRules" ref="editFormRef" label-width="100px" label-position="top">
        <el-tabs v-model="activeIndex" :tab-position="'left'" :before-leave="beforeTabLeave">
          <el-tab-pane label="基本信息" name="0">
            <el-form-item label="商品名称" prop="goods_name">
              <el-input v-model="editForm.goods_name"></el-input>
            </el-form-item>
            <el-form-item label="商品价格" prop="goods_price">
              <el-input v-model="editForm.goods_price" type="number"></el-input>
            </el-form-item>
            <el-form-item label="商品重量" prop="goods_weight">
              <el-input v-model="editForm.goods_weight" type="number"></el-input>
            </el-form-item>
            <el-form-item label="商品数量" prop="goods_number">
              <el-input v-model="editForm.goods_number" type="number"></el-input>
            </el-form-item>
          </el-tab-pane>
          <el-tab-pane label="商品参数" name="1">
            <!-- 渲染表单item项 -->
            <el-form-item :label="item.attr_name" :key="item.attr_id" v-for="(item, index) in manyTableDataAll">
              <!-- 使用数组渲染复选框组 -->
              <el-checkbox-group v-model="manyTableData[index].attr_vals">
                <el-checkbox border :label="val" v-for="(val,i) in item.attr_vals" :key="i">{{val}}</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">
            <el-upload class="upload-demo" drag :action="uploadURL" :on-preview="handlePreview"
              :on-remove="handleRemove" :on-success="handleSuccess" list-type="picture" :headers="headerObj"
              :file-list="previewPath">
              <i class="el-icon-upload"></i>
              <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
              <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
            </el-upload>
          </el-tab-pane>
          <!-- 富文本编辑器组件 -->
          <el-tab-pane label="商品内容" name="4">
            <!-- 富文本编辑器组件 -->
            <quill-editor v-model="editForm.goods_introduce"></quill-editor>
            <el-button type="primary" class="btnAdd">保存</el-button>
          </el-tab-pane>
        </el-tabs>
      </el-form>
    </el-card>
    <!-- 预览图片对话框 -->
    <el-dialog title="图片预览" :visible.sync="previewVisible">
      <img :src="previewPath" width="100%" />
    </el-dialog>
  </div>
</template>
<script>
  import {
    getAttributes
  } from '../../api/categories.js'
  import {
    getGoods
  } from '../../api/goods.js'
  export default {
    data() {
      return {
        // 保存步骤条激活项索引
        activeIndex: '0',
        // 添加商品的表单数据对象
        editForm: {
          goods_id: '',
          cat_id: '',
          goods_name: '',
          goods_price: 0,
          goods_weight: 0,
          goods_number: 0,
          // 上传图片数组
          pics: [],
          // 商品的详情介绍
          goods_introduce: '',
          // 商品的动态参数和静态属性
          attrs: []
        },
        // 验证规则
        editFormRules: {
          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'
          }]
        },
        // 用来保存分类数据
        cateList: [],
        // 配置级联菜单中数据如何展示
        cateProps: {
          value: 'cat_id',
          label: 'cat_name',
          children: 'children'
        },
        // 动态参数列表
        manyTableData: [],
        manyTableDataAll: [],
        // 静态属性列表
        onlyTableData: [],
        // 上传图片的url地址
        uploadURL: 'http://127.0.0.1:8888/api/private/v1/upload',
        // 图片上传组件的headers请求头对象
        headerObj: {
          Authorization: window.localStorage.getItem('token')
        },
        // 保存预览图片的url地址
        previewPath: [],
        previewVisible: false
      }
    },
    created() {
      this.getGoodsInfo()
    },
    methods: {
      getGoodsInfo() {
        const id = parseInt(this.$route.params.id)
        getGoods(id).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error(data.meta.msg)
          }
          this.editForm = data.data
          // 获取商品属性
          this.onlyTableData = this.editForm.attrs.filter(item => {
            return item.attr_sel === 'only'
          })
          // 获取商品参数
          this.manyTableData = this.editForm.attrs.filter(item => {
            return item.attr_sel === 'many'
          })
          // item.attr_vals转换数组
          this.manyTableData.forEach(item => {
            item.attr_vals = item.attr_value.length === 0 ? [] : item.attr_value.split(' ')
          })
          // 获取该商品类型的所有参数
          this.getManyTableDataAll()
          // 获取商品图片
          this.editForm.pics.forEach(item => {
            item.pic = item.pics_mid_url
            this.previewPath = [{
              name: item.pics_mid,
              url: item.pics_mid_url
            }]
          })
        })
      },
      getManyTableDataAll() {
        // 发送请求获取动态参数
        getAttributes(this.editForm.cat_id, {
          sel: 'many'
        }).then(res => {
          const data = res.data
          if (data.meta.status !== 200) {
            return this.$message.error(data.meta.msg)
          }
          // 将attr_vals字符串转换为数组
          data.data.forEach(item => {
            item.attr_vals =
              item.attr_vals.length === 0 ? [] : item.attr_vals.split(' ')
          })
          this.manyTableDataAll = data.data
        })
      },
      // 再到methods编写事件函数beforeTabLeave
      beforeTabLeave(activeName, oldActiveName) {
        // 在tab栏切换之前触发,两个形参为切换前,后的tab栏name
        if (oldActiveName === '0') {
          // 在第一个标签页的时候
          if (this.editForm.goods_name.trim() === '') {
            this.$message.error('请输入商品名称')
            return false
          } else if (this.editForm.goods_price === 0) {
            this.$message.error('请输入商品价格')
            return false
          } else if (this.editForm.goods_weight === 0) {
            this.$message.error('请输入商品重量')
            return false
          } else if (this.editForm.goods_number === 0) {
            this.$message.error('请输入商品数量')
            return false
          }
        }
      },
      handlePreview(file) {
        // 当用户点击图片进行预览时执行,处理图片预览
        // 形参file就是用户预览的那个文件
        this.previewPath = file.url
        // 显示预览图片对话框
        this.previewVisible = true
      },
      handleRemove(file) {
        // 当用户点击X号删除时执行
        // 形参file就是用户点击删除的文件
        // 获取用户点击删除的那个图片的临时路径
        const filePath = file.url
        // 使用findIndex来查找符合条件的索引
        const index = this.editForm.pics.findIndex(item => {
          return item.pic === filePath
        })
        // 移除索引对应的图片
        this.editForm.pics.splice(index, 1)
      },
      handleSuccess(response, file) {
        // 当上传成功时触发执行
        // 形参response就是上传成功之后服务器返回的结果
        // 将服务器返回的临时路径保存到addForm表单的pics数组中
        if (response.meta.status !== 200) {
          return this.$message.error(response.meta.msg)
        } else {
          this.editForm.pics.push({
            pic: file.url
          })
          this.$message.success(response.meta.msg)
        }
      }
    }
  }
</script>

3. 发送更新商品请求

<!-- 给保存按钮添加点击事件 -->
<el-button type="primary" class="btnAdd" @click="edit">保存</el-button>

<script>
  import {
    getGoods,
    updateGoods
  } from '../../api/goods.js'
  // 官方推荐将lodash导入为_
  import _ from 'lodash'
... ...
	edit() {
        this.$refs.editFormRef.validate(valid => {
          if (!valid) return this.$message.error('请填写必要的表单项!')
          // 清空原来的属性
          this.editForm.attrs = []
          // 将addForm进行深拷贝
          const form = _.cloneDeep(this.editForm)
          // 处理attrs数组,数组中需要包含商品的动态参数和静态属性
          // 将manyTableData(动态参数)处理添加到attrs
          this.manyTableData.forEach(item => {
            form.attrs.push({
              attr_id: item.attr_id,
              attr_value: item.attr_vals.join(' ')
            })
          })
          // 反转数组
          form.attrs.reverse()
          // 将onlyTableData(静态属性)处理添加到attrs
          this.onlyTableData.forEach(item => {
            form.attrs.push({
              attr_id: item.attr_id,
              attr_value: item.attr_vals
            })
          })
          updateGoods(this.editForm.goods_id, form).then(res => {
            const data = res.data
            if (data.meta.status !== 200) {
              return this.$message.error(data.meta.msg)
            }
            this.$message.success(data.meta.msg)
            // 编程式导航跳转到商品列表
            this.$router.push('goods')
          })
        })
      }
</script>