vue电商后台管理系统—实现商品分类以及分类管理功能

商品管理菜单下的商品分类子菜单

商品分类

1. 新建goods_cate子分支

新建分支并推送到码云

vue电商后台管理系统—实现商品分类以及分类管理功能_第1张图片

2. 创建子级路由

在component文件夹中新建一个goods文件夹存放商品管理相关组件,新建Cate.vue子组件;在router/index.js中导入子组件并定义子路由规则,注意,路径是浏览器地址栏中的路径名,这里是/categories

vue电商后台管理系统—实现商品分类以及分类管理功能_第2张图片

3. 绘制商品分类组件的基本页面结构
  • BreadCrumb面包屑导航区域
  • Card卡片视图区域
    1. 一行中的第一列放置 “添加分类”按钮
    2. 商品分类 table 表格区域,为了形成树形结构的表格,所以使用了 tree-table
    3. 底部 Pagination 分页区域
4. 请求分类数据
  data() {
    return {
      // 5.1 商品分类的数据列表
      cateList: [],
      // 5.2
      queryInfo: {
        type: 3,
        pagenum: 1,
        pagesize: 5
      },
      total: 0
    }
  },
  created() {
    // 5.3
    this.getCateList()
  },
  methods: {
    // 5.4 获取商品分类数据
    async getCateList() {
      // 发送请求
      const { data: res } = await this.$http.get('categories', {
        params: this.queryInfo
      })
      if (res.meta.status !== 200) {
        return this.$message.error('获取商品分类数据列表失败')
      }
      console.log(res.data)
      // 把成功获取到的数据保存到数组中
      this.cateList = res.data.result
      // 为总数据条数赋值
      this.total = res.data.total
    }
  }
5. 自定义数据列

官方文档链接:
https://github.com/MisterTaki/vue-table-with-tree-grid/blob/master/example/Example.vue

https://github.com/MisterTaki/vue-table-with-tree-grid

a. 下载依赖 ,在入口文件导入这个插件并全局注册,组件名统一为 tree-table

npm i vue-table-with-tree-grid

API—Table Attributes:

属性 说明 类型 参数 默认值
data 表格各行的数据 Array - []
empty-text 表格数据为空时显示的文字 String - ‘暂无数据’
columns 表格各列的配置(具体见下文:Columns Configs) Array - []
selection-type 是否为多选类型表格 Boolean - false
expand-type 是否为展开行类型表格(为 True 时,需要添加名称为 ‘$expand’ 的作用域插槽, 它可以获取到 row, rowIndex) Boolean - false
show-index 是否显示数据索引 Boolean - false
index-text 数据索引名称 String - ‘序号’
<tree-table
  :data="cateList"
  :columns="columns"
  :selection-type="false"
  :expand-type="false"
  show-index="true"
  index-text="#"
  border
></tree-table>
columns: [
  {
    label: '分类名称',
    prop: 'cat_name',
  }]

vue电商后台管理系统—实现商品分类以及分类管理功能_第3张图片

b. 通过自定义模板列的形式将后三列数据渲染出来

  1. 是否有效cat_deleted:false

    如果值为false,渲染成绿色的✔,如果值为true,则渲染成红色的✘,需要用到作用域插槽

    思路:首先在data中定义一个列“是否有效”,然后在表格的内容节点中使用作用域插槽定义一个模板,添加slot,v-slot 属性,使用指令 v-if 如果scope值为false就显示success图标,否则,error图标

    
    
  2. 排序 cat_level

    同样是要使用 v-if 进行按需渲染,cat_level值为0:一级;值为1:二级;值为2:三级

  3. 操作

    在自定义模板中添加两个按钮,分别是编辑和删除,后续再完成这两者的功能

vue电商后台管理系统—实现商品分类以及分类管理功能_第4张图片

6. 完成分页功能
// 5. 底部 Pagination 分页区域 -->


  1. 监听 pagesize 的改变,获取到最新的 newSize 并赋值给当前页面数据条数 queryInfo.pagesize
  2. 监听 pagenum 的改变,获取到最新的 页码值 newPage 并赋值给当前页码值 queryInfo.pagenum
  3. 给 total 总数进行数据绑定
// 5.5 监听 pagesize 的改变,获取到最新的 newSize
handleSizeChange(newSize) {
  // console.log(newSize)
  this.queryInfo.pagesize = newSize
  this.getCateList()
},
  // 5.6 监听 pagenum 的改变,获取到最新的 页码值 newPage
  handleCurrentChange(newPage) {
    // console.log(newPage)
    this.queryInfo.pagenum = newPage
    this.getCateList()
  }
7. 完成添加分类
  1. addCateDialogVisible 控制添加分类对话框的显示与隐藏,首先在data 中将其设置为false
  2. 给添加分类按钮绑定一个点击事件 showAddDialog,在methods中新增这个事件处理函数
  3. 接下来需要完善对话框内部的表单,父级分类的级联选择框暂时不绘制,添加form表单,表单项“分类名称”中的prop属性需要查看API接口文档1.6.2.添加分类
  4. 获取前两级的父级分类数据列表,查看API接口文档1.6.1. 商品分类数据列表,在methods中定义一个函数 getParentCateList 来获取父级分类的数据列表
  • 请求路径:categories
  • 请求方法:get
  • 请求参数:type,pagenum,pagesize
  1. 点击添加分类按钮弹出对话框前调用函数 getParentCateList,在data中定义一个空数组 parentCateList 用以保存父级分类数据的列表
// 3.1 添加分类的功能对话框 -->
<el-dialog
	title="添加分类"
	:visible.sync="addCateDialogVisible"
	width="50%"
>
  // 3.4.添加分类的表单区域 -->
  <el-form
		:model="addCateForm"
		:rules="addCateFormRules"
		ref="addCateFormRef"
		label-width="80px"
	>
 		<el-form-item label="分类名称" prop="cat_name">
    	<el-input v-model="addCateForm.cat_name"></el-input>
		</el-form-item>
		<el-form-item label="父级分类"></el-form-item>
	</el-form>
	<span slot="footer" class="dialog-footer">
  	<el-button @click="addCateDialogVisible = false">取 消</el-button>
		<el-button type="primary" @click="addCateDialogVisible = false"
		>确 定</el-button>
  </span>
</el-dialog>
<script>
export default {
  data() {
    return{
      ...
      // 3.5 添加分类的表单数据对象
      addCateForm: {
        // 将要添加的分类的名称
        cat_name: '',
        // 父级分类的 id
        cat_pid: 0,
        // 分类的等级,默认要添加的是一级分类
        cat_level: 0
      },
      // 3.6 添加分类的表单验证规则
      addCateFormRules: {
        cat_name: [{ required: true, message: '请输入分类名称', trigger: blur }]
      },
      // 3.8 保存父级分类数据的列表
      parentCateList: []
    }
  },
  methods:{
    // 3.3 点击按钮展示添加分类的对话框
    showAddCateDialog() {
      // 3.7.2 先获取父级分类的数据列表
      this.getParentCateList()
      // 再展示对话框
      this.addCateDialogVisible = true
    },
    // 3.7 定义一个函数 getParentCateList 来获取父级分类的数据列表
    async getParentCateList() {
      // 3.7.1 发送请求
      const { data: res } = await this.$http.get('categories', {
        params: { type: 2 }
      })
      if (res.meta.status !== 200) {
        return this.$message.error('获取父级分类数据列表失败')
      }
      console.log(res.data)
      this.parentCateList = res.data
      // this.getParentCateList()
    }
  }
}
</script>
  1. 添加级联菜单显示父级分类,先导入Cascader组件,并注册,然后添加使用级联菜单组件:

这里遇到一个问题:

原版本是将 expandTrigger="hover":props="cascaderProps" 分开写的,但是官方文档更新之后,写法变为了 :props="{ expandTrigger: 'hover' }" ,导致我不知道 cascaderProps 应该放在什么位置进行配置

// 原版本
<el-cascader>
  v-model="selectedKeys"
  :options="parentCateList"
  expandTrigger="hover"
  :props="cascaderProps"
  @change="parentCateChange"
</el-cascader>

// data函数中
// 3.10 指定级联选择器的配置对象 cascaderProps
cascaderProps: {
  value: 'cat_id',
  label: 'cat_name',
  children: 'children'
},
// 3.11 选中的父级分类的 id 数组
selectedKeys: []
// 官方文档更新后的写法
:props="{ expendTrigger:'hover' }"

// 导致我不知道 cascaderProps 应该放在什么位置进行配置,如果修改为以下写法,页面只显示级联选择器的框架,内部文字无法显示
:props="{ expendTrigger:'hover',cascaderProps}"

// 感谢弹幕,找到了新用法,应该使用扩展运算符更改为...cascaderProps,页面数据才能渲染出来
:props="{ expendTrigger:'hover',...cascaderProps}"

// 查看了文档,props属性可以有很多参数,包括 value,label,children,最后修改为
<el-cascader
	v-model="selectedKeys"
	:options="parentCateList"
	:props="{
	expandTrigger: 'hover',
  value: 'cat_id',
  label: 'cat_name',
  children: 'children',
  checkStrictly: true
	}"
	@change="parentCateChange"
	clearable
></el-cascader>

Cascader Attributes

参数 说明 类型 可选值 默认值
value / v-model 选中项绑定值 -
options 可选项数据源,键名可通过 Props 属性配置 array
props 配置选项,具体见下表 object
clearable 是否支持清空选项 boolean false

Props

参数 说明 类型 可选值 默认值
expandTrigger 次级菜单的展开方式 string click / hover ‘click’
checkStrictly 是否严格的遵守父子节点不互相关联 boolean - false
value 指定选项的值为选项对象的某个属性值 string ‘value’
label 指定选项标签为选项对象的某个属性值 string ‘label’
children 指定选项的子选项为选项对象的某个属性值 string ‘children’
disabled 指定选项的禁用为选项对象的某个属性值 string ‘disabled’
  1. 只要级联选择器的选中项发生了变化,我们就需要监听这个变化的change事件,在事件中,立即把表单绑定的父级分类 cat_id 以及父类的等级 cat_level 进行更新,3.13 通过判断 selectKeys 的 length 是否大于0,来证明是否选中父级分类
  • 如果length等于0的话,证明新添加的分类是一级分类,因为它没有父级分类
  • 如果length大于0的话,证明选中了父级分类,那么以最后一项索引(length - 1)作为父级分类id并赋值给添加分类的表单数据对象;而level值永远和数组的length保持一致

有一个小问题:为当前父级分类id赋值时,下面两种写法的区别

// 错误写法:
this.addCateForm.cat_pid = this.selectedKeys.length - 1

// 正确写法:
this.addCateForm.cat_pid = this.selectedKeys[this.selectedKeys.length - 1]
  1. 关闭或者是取消添加分类后,要重置表单数据,但是要注意,这里监听对话框的关闭事件,只会将表单项“分类名称”中的数据重置,但是不会重置级联选择器中的数据,所以还需要将级联选择器中双向绑定的selectKeys数组清空,父级分类的id 和 level 重置为0
// 3.1 添加分类的功能对话框 -->
<el-dialog
	title="添加分类"
	:visible.sync="addCateDialogVisible"
	width="50%"
	@close="addCateDialogClosed"
></el-dialog>

methods:{
  ...
  // 3.15 监听对话框的关闭事件,重置表单数据
  addCateDialogClosed() {
    this.$refs.addCateFormRef.resetFields()
    // 重置级联选择器的数组,父级分类id以及level
    this.selectedKeys = []
    this.addCateForm.cat_pid = 0
    this.addCateForm.cat_level = 0
  }
}
  1. 完成添加分类功能

    先进行预验证,然后发送post请求完成添加分类的功能

    查看API接口文档1.6.2 添加分类

  • 请求路径:categories
  • 请求方法:post
  • 请求参数

注意:请求发送成功的status = 201,不是200哦!!!!

methods:{
  ...
  // 3.14 addCate 点击按钮,添加新的分类
  addCate() {
    // console.log(this.addCateForm)
    this.$refs.addCateFormRef.validate(async (valid) => {
      // 先进行预验证
      // console.log(valid)
      if (!valid) return
      // 再发送请求
      const { data: res } = await this.$http.post(
        'categories',
        this.addCateForm
      )
      if (res.meta.status !== 201) return this.$message.error('添加分类失败')
      this.$message.success('添加分类成功了')
      this.addCateDialogVisible = false
      this.getCateList()
    })
}

问题点:为什么发送请求判断状态码返回结果时,有时候 return 需要添加{ },有时候又不允许添加{ }呢?

8. 编辑分类
  1. 点击编辑按钮,触发事件,展示修改用户的功能对话框,首先,发送请求根据 id 查询分类,成功获取id 之后,要将查询到的分类信息保存到分类信息对象editForm中,并关闭对话框
  • 请求路径:categories/:id
  • 请求方法:get
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
  1. 监听修改分类名称对话框的关闭事件,重置表单数据
  2. 完成修改分类名称功能,先进行预验证,然后发起修改分类名称的数据请求,请查看API接口文档1.6.4 编辑提交分类,最后隐藏对话框并刷新分类列表
  • 请求路径:categories/:id
  • 请求方法:put
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
cat_name 分类名称 不能为空【此参数,放到请求体中】
// 6.2 展示修改分类的对话框
async showEditCateDialog(id) {
  // console.log(id)
  const { data: res } = await this.$http.get(`categories/${id}`)
  if (res.meta.status !== 200) {
    return this.$message.error('查询分类信息失败')
  }
  this.editCateForm = res.data
  this.editCateDialogVisible = true
},
// 6.5 监听修改分类名称对话框的关闭事件,重置表单数据
editCateDialogClosed() {
	this.$refs.editCateFormRef.resetFields()
},
// 6.6 点击确定按钮,修改分类名称
editCate() {
	this.$refs.editCateFormRef.validate(async (valid) => {
  // 进行预验证
  // console.log(valid)
  if (!valid) return
  // 发送请求
  const { data: res } = await this.$http.put(
    'categories/' + this.editCateForm.cat_id,
    { cat_name: this.editCateForm.cat_name }
  )
  if (res.meta.status !== 200) {
    return this.$message.success('修改分类名称失败')
  }
  this.editCateDialogVisible = false
  this.getCateList()
  // console.log(res)
	})
},
9. 删除分类

根据ID删除用户信息,跳出提示框,利用MessageBox弹框,如果用户确认删除,则返回值为字符串 ‘confirm’;如果用户取消删除,则返回值为字符串 ‘cancel’,如果是 confirm 的话,发送请求确认删除

  • 请求路径:categories/:id
  • 请求方法:delete
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
// 7.1 根据ID删除用户信息,跳出提示框
async removeCateById(id) {
  // console.log(id)
  // MessageBox 弹出框
  const confirmResult = await this.$confirm(
    '此操作将永久删除该文件, 是否继续?',
    '提示',
    {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    }
  ).catch((err) => err)
  // console.log(res)
  if (confirmResult !== 'confirm') return this.$message.info('已取消删除')
  // 如果是 confirm 的话,发送请求确认删除
  const { data: res } = await this.$http.delete(`categories/${id}`)
  if (res.meta.status !== 200) {
    return this.$message.error('删除分类失败')
  }
  this.$message.success('删除分类成功')
  this.getCateList()
}

分类参数

参数管理概述:商品参数用于显示商品的固定特征信息,可以通过电商平台商品详情页面直观的看到

注意:只允许为第三级分类设置相关参数!

  1. 动态参数
  2. 静态属性(只读)
1. 创建子级路由
2. 绘制页面
  1. 面包屑导航区域

  2. 卡片视图区域

  • 3.Alert警告

  • 4.选择商品分类的级联选择器

    • 4.1 获取商品分类列表
3. 获取商品分类数据列表

查看API接口文档 1.6.1. 商品分类数据列表

  • 请求路径:categories
  • 请求方法:get
  • type默认是3
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(this.cateList)
},
4. 渲染商品分类级联选择框

通过监听选择器的change事件,控制只能选择三级分类,如果length = 3 ,表示选中的是三级分类,如果length !== 3,将数组重置为空

// 选择项发生变化触发这个函数
handelChange() {
  // console.log(this.selectKeys)
  if (this.selectedKeys.length !== 3) {
    this.selectedKeys = []
  }
  // console.log(this.selectedKeys)
}
5. 动态参数和静态属性的Tabs标签页
// 5. 动态分类,静态属性Tabs标签页 -->
<el-tabs v-model="activeName" @tab-click="handleClick">
	<el-tab-pane label="动态分类" name="first">用户管理</el-tab-pane>
	<el-tab-pane label="静态属性" name="second">配置管理</el-tab-pane>
</el-tabs>

activeName:'first'

// 5.1 tab 页签点击事件的处理函数
handleClick() {
  console.log(this.activeName)
}
6. 展示动态参数数据以及静态属性数据

渲染添加参数、属性按钮,同时控制禁用和启用的状态,当我们没有选择三级分类商品时,按钮被禁用,

  1. 设置添加参数/添加属性的按钮
  2. 控制按钮的启用和禁用,当选择三级分类商品后(this.selectKeys.length === 3 ),按钮启用
7. 获取参数数据

查看API接口文档 1.7.1.参数列表

  • 请求路径:categories/:id/attributes
  • 请求方法:get
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
sel [only,many] 不能为空,通过 only 或 many 来获取分类静态参数还是动态参数

将pane中的name修改为only和many

问题1:在动态参数和静态属性之间跳转时,不会再次发送请求去获取数据,只有级联选择器事件改变时才触发

解决思路:将获取数据的这些业务逻辑全部抽离出来定义一个新的函数,然后分别进行调用,那么不论是级联选择器中选中项发生变化,还是面板的选中项发生变化,都可以调用这个函数

    // 4.2 选择项发生变化触发这个函数
    handelChange() {
      // console.log(this.selectKeys)
      this.getParamsData()
    },
    // 5.2 tab 页签点击事件的处理函数
    handleClick() {
      console.log(this.activeName)
      this.getParamsData()
    },
    // 5.8 解决面板无法获取数据的问题
    async getParamsData() {
      if (this.selectedKeys.length !== 3) {
        this.selectedKeys = []
      }
      // 证明选中了三级分类
      // console.log(this.selectedKeys)

      // 5.7 在级联选择器事件改变时发起请求
      // 根据所选分类的id和当前所处的面板获取对应的参数
      const { data: res } = await this.$http.get(
        `categories/${this.cateId}/attributes`,
        {
          params: { sel: this.activeName }
        }
      )
      if (res.meta.status !== 200) {
        // this.cateId = this.cateList[this.cateList.length - 1]
        return this.$message.error('获取列表参数失败')
      }
      console.log(res.data)
    }

问题2:获取到的数据 res.data 到底是给动态参数表格使用,还是静态属性表格使用呢?

解决思路:判断 activeName 为 many / only ,分别定义一个专门的数据对象,用来保存 res.data

data() {
  return {
    ...
    // 5.10 动态参数/静态数据数据列表
    manyTableData: [],
    onlyTableData: []
  }
},


// 5.8 解决面板无法获取数据的问题
async getParamsData() {
  ...
  // 5.9 将res.data 分别挂载到对应的数据对象中
  if (this.activeName === 'many') {
    this.manyTableData = res.data
  } else {
    this.onlyTableData = res.data
  }
}
8. 展示参数

绘制表格展示动态参数数据以及静态属性数据

      // 5. 动态分类,静态属性Tabs标签页 -->
      <el-tabs v-model="activeName" @tab-click="handleClick">
        ...
          // 6. 动态参数表格 -->
          <el-table :data="manyTableData" style="width: 100%" border stripe>
            // 展开行 -->
            <el-table-column type="expand"></el-table-column>
            // 索引列 -->
            <el-table-column type="index"></el-table-column>
            <el-table-column prop="attr_name" label="参数名称">
            </el-table-column>
            <el-table-column label="操作">
              <template v-slot="scope">
                // 修改按钮 -->
                <el-button
                  type="primary"
                  icon="el-icon-edit"
                  size="medium"
                  @click="showEditDialog(scope.row.id)"
                  >修改</el-button
                >
                // 删除按钮 -->
                <el-button
                  type="danger"
                  icon="el-icon-delete"
                  size="medium"
                  @click="removeUserById(scope.row.id)"
                  >删除</el-button
                >
              </template>
            </el-table-column>
					 </el-tab-pane>
          </el-table>
				...
          // 7. 静态属性表格
          <el-table :data="onlyTableData" style="width: 100%" border stripe>
            // 展开行
            <el-table-column type="expand"></el-table-column>
            // 索引列
            <el-table-column type="index"></el-table-column>
            <el-table-column prop="attr_name" label="参数名称">
            </el-table-column>
            <el-table-column label="操作">
              <template v-slot="scope">
                // 修改按钮 -->
                <el-button
                  type="primary"
                  icon="el-icon-edit"
                  size="medium"
                  @click="showEditDialog(scope.row.id)"
                  >修改</el-button
                >
                // 删除按钮 -->
                <el-button
                  type="danger"
                  icon="el-icon-delete"
                  size="medium"
                  @click="removeUserById(scope.row.id)"
                  >删除</el-button
                >
              </template>
            </el-table-column>
          </el-table>
        </el-tab-pane>
      </el-tabs>
9. 添加参数/属性

两者除了命名不同以外,其他都一样,所以设置为共用的

  1. title必须设置为动态的,在计算属性中定义一个titleText函数

    注意:为了方便定义内容主体区域form表单的label,函数返回值需是“动态参数”,“静态属性”,而不是“添加动态参数”,”添加静态属性“

  2. 添加功能对话框,在date中定义addDialogVisible为false,直接给面板的添加属性/参数按钮绑定点击事件@click="addDialogVisible = true",那么点击按钮时对话框显示

  3. 完善内容主体区域的form表单 ,设置添加参数的表单数据对象和验证规则以及监听对话框的关闭事件,重置表单

  4. 完成添加参数或属性,给确认按钮绑定点击事件addParams,当点击确认时,进行表单预验证,验证通过后发送请求

    API接口文档1.7.2.添加动态参数和静态属性,注意,成功的状态码是201

    • 请求路径:categories/:id/attributes
    • 请求方法:post
    • 请求参数
    参数名 参数说明 备注
    :id 分类 ID 不能为空携带在url中
    attr_name 参数名称 不能为空
    attr_sel [only,many] 不能为空
    attr_vals 如果是 many 就需要填写值的选项,以逗号分隔 【可选参数】

遇到的小问题:在发送请求时,请求参数我不知道怎么设置

错误写法:

this.$http.post(`categories/${this.cateId}/attributes`, 
                {params: { attr_name, sel: this.activeName })
// 报错,请求体写错了

正确写法:

this.$http.post(`categories/${this.cateId}/attributes`, {
          attr_name: this.addForm.attr_name,
          attr_sel: this.activeName
        })
// 8. 添加参数的功能对话框 -->
<el-dialog
	:title="'添加' + titleText"
	:visible.sync="addDialogVisible"
	width="50%"
	@close="addDialogClosed"
>
  // 内容主体区域 -->
  <el-form
		:model="addForm"
		:rules="addFormRules"
		ref="addFormRef"
		label-width="80px"
	>
  	<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>
data() {
  return {
    ...
    // 8.2 控制添加对话框的显示与隐藏
    addDialogVisible: false,
    // 8.3 添加参数的表单数据对象
    addForm: {
      attr_name: ''
    },
    // 8.4 添加表单的验证规则对象
    addFormRules: {
      attr_name: [
        { required: true, message: '请输入参数名称', trigger: 'blur' }
      ]
    }
  }
}
methods:{
  ...
  // 8.5 监听对话框的关闭事件
  addDialogClosed() {
    this.$refs.addFormRef.resetFields()
  },
  // 8.6 完成添加参数操作
  addParams() {
  // 8.6.1 先进行表单预验证
  	this.$refs.addFormRef.validate(async (valid) => {
    // console.log(valid) // true or false
    if (!valid) return
    // 8.6.2 验证通过后发送请求
    	const { data: res } = await this.$http.post(
        `categories/${this.cateId}/attributes`,
        {
          attr_name: this.addForm.attr_name,
          attr_sel: this.activeName
        }
      )
      if (res.meta.status !== 201) {
        return this.$message.error('添加参数失败')
      }
      this.$message.success('添加参数成功')
      // 关闭添加对话框
      this.addDialogVisible = false
      this.getParamsData()
    })
  }
},
computed:{
  ...
  // 8.1 动态计算标题的文本
  titleText() {
    if (this.activeName === 'many') {
      return '动态参数'
    } else {
      return '静态属性'
    }
  }
}

vue电商后台管理系统—实现商品分类以及分类管理功能_第5张图片

10. 修改参数/属性
  1. 添加功能对话框,在date中定义editDialogVisible为false

  2. 完善内容主体区域的form表单 ,设置修改参数的表单数据对象和验证规则以及监听对话框的关闭事件,重置表单

  3. 给编辑按钮绑定点击事件showEditDialog,点击按钮,展示修改对话框

  4. 完成修改参数或属性,给确认按钮绑定点击事件editParams,当点击确认时,进行表单预验证,验证通过后发送请求

  5. 点击编辑按钮的同时,将对应参数的 id 传到处理函数中,然后根据 id 去查询参数的原始数据,并将数据绑定到表单中展示出来

    查看API接口文档 1.7.4.根据ID查询参数

    • 请求路径:categories/:id/attributes/:attrId
    • 请求方法:get
    • 请求参数
    参数名 参数说明 备注
    :id 分类 ID 不能为空携带在url中
    :attrId 属性 ID 不能为空携带在url中
    attr_sel [only,many] 不能为空
    attr_vals 如果是 many 就需要填写值的选项,以逗号分隔

问题:为什么发送请求时,只有一个参数和多个参数的写法是不一样的?

只一个参数时:

const { data: res } = await this.$http.get(
  `categories/${this.cateId}/attributes/${attrid}`,
  { params: { attr_sel: this.activeName } }
)

多个参数时:

const { data: res } = await this.$http.post(
  `categories/${this.cateId}/attributes`,
  {
    attr_name: this.addForm.attr_name,
    attr_sel: this.activeName
  }
)
  1. 点击确认按钮完成修改操作,先进行表单预校验,通过之后再发起请求修改对应的数据

    • 请求路径:categories/:id/attributes/:attrId
    • 请求方法:put
    • 请求参数
    参数名 参数说明 备注
    :id 分类 ID 不能为空携带在url中
    :attrId 属性 ID 不能为空携带在url中
    attr_name 新属性的名字 不能为空,携带在请求体
    attr_sel 属性的类型[many或only] 不能为空,携带在请求体
    attr_vals 参数的属性值 可选参数,携带在请求体
11.删除参数

根据获取的id删除参数或属性,给删除按钮绑定事件,当点击按钮时,将参数id传入函数中

查看API文档1.7.3.删除参数

  • 请求路径: categories/:id/attributes/:attrid
  • 请求方法:delete
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
:attrid 参数 ID 不能为空携带在url中
12.扩展标签
  1. attr_vals参数是字符串的形式,需要先通过 split(’ ') 方法转换成数组,书写位置是在获取参数列表成功之后,数据挂载之前,forEach() 方法对数组的每个元素执行一次给定的函数。
  2. 在展开行中通过作用域插槽接收数据
  3. 通过for循环将每一项渲染成tag标签内容

有个bug:attr_vals为空时,会出现下面的情况

解决办法:不应该直接分隔后赋值,而是先通过三元表达式进行判断是否为空

vue电商后台管理系统—实现商品分类以及分类管理功能_第6张图片

// 展开行 -->
<el-table-column type="expand">
  // 10.2 将数组内容渲染到标签上 -->
  <template slot-scope="scope">
    <el-tag
			v-for="(item, index) in scope.row.attr_vals"
			:key="index"
			closable
  	>{{ item }}</el-tag
	></template>
</el-table-column>


async getParamsData() {
  ...
  // 10.1 将attr_vals转换为数组
  res.data.forEach((item) => {
    item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
    // console.log(item)
  })
}
13.添加参数项
  1. 添加标签按钮和文本框,点击按钮显示文本框
// 展开行 -->
<el-table-column type="expand">
  //10.2 将数组内容循环渲染到标签上 -->
  <template slot-scope="scope">
    <el-tag
			v-for="(item, index) in scope.row.attr_vals"
			:key="index"
			closable
  	>{{ item }}
    </el-tag>
		// 11.添加标签的文本框
		// @keyup.enter.native 和 @blur事件 在按下回车/文本框失去焦点时触发
		<el-input
			class="input-new-tag"
			v-if="inputVisible"
			v-model="inputValue"
			ref="saveTagInput"
			size="small"
			@keyup.enter.native="handleInputConfirm"
			@blur="handleInputConfirm"
		>
 	  </el-input>
		// 12. +New Tag 标签按钮 -->
		<el-button
			v-else
			class="button-new-tag"
			size="small"
			@click="showInput"
			>+ New Tag
    </el-button>
  </template>
</el-table-column>

data(){
  return{
    ...
    // 11.1 标签文本框的显示与隐藏
    inputVisible: false,
    // 11.2 文本框中输入的内容
    inputValue: ''
  }
}

methods:{
  // 11.3 文本框失去焦点或回车都会触发此函数
  handleInputConfirm() {},
  // 12.1 点击按钮展示input文本输入框
  showInput() {
    this.inputVisible = true
  }
}

bug:参数列表中的按钮和输入框会联动,变化都一模一样

因为它们共用了inputVisible的布尔值和inputValue的值

解决办法:为每一行数据单独提供这两个值inputVisible改为scope.row.inputVisible表示每一行的布尔值

// 11.添加标签的文本框 -->
<el-input
	class="input-new-tag"
	v-if="scope.row.inputVisible"
	v-model="scope.row.inputValue"
	ref="saveTagInput"
	size="small"
	@keyup.enter.native="handleInputConfirm"
	@blur="handleInputConfirm"
>
</el-input>

methods:{
  async getParamsData() {
    ...
    // 10.1 将attr_vals转换为数组
    res.data.forEach((item) => {
      item.attr_vals = item.attr_vals ? item.attr_vals.split(' ') : []
      // 12.2 控制文本框的显示与隐藏
      item.inputVisible = false
      // 文本框中输入的值
      item.inputValue = ''
      // console.log(item)
    })
  }
  // 12.1 点击按钮展示input文本输入框
  showInput(row) {
    row.inputVisible = true
    // 12.3 让文本框自动获得焦点
    this.$nextTick((_) => {
      this.$refs.saveTagInput.$refs.input.focus()
      // console.log(this.$refs) //{saveTagInput: VueComponent}
      // console.log(this.$refs.saveTagInput.$refs)
    })
  }
}
  1. 实现按钮与文本框的切换显示,点击按钮显示文本框上一步已经实现了,我们还需要实现:当文本框失去焦点时展示按钮

业务逻辑:监听文本框失去焦点的事件,在调用了handelInputConfirm()函数时,立即将该行inputVisible改为false,因为是每行的布尔值和inputValue值,所以需要传参scope.row

methods:{
  ...
  // 11.3 文本框失去焦点或回车都会触发此函数
  handleInputConfirm(row) {
    // 12.4 文本框切换回按钮
    row.inputVisible = false
  },
  ...
}

优化:如果所输入的内容不合法,那么切换回按钮(文本框隐藏)前需要将文本框内容重置

业务逻辑:如果该行文本框内容去除空格之后的长度全等于0,那么将该行文本框内容清空,然后再切换回按钮,如果输入的内容合法,需要做后续处理(渲染成新的tag标签)

methods:{
  ...
  // 11.3 文本框失去焦点或回车都会触发此函数
  handleInputConfirm(row) {
    // 12.5 判断文本框输入的内容是否合法
    if (row.inputValue.trim().length === 0) {
      row.inputValue = ''
      // 12.4 文本框切换回按钮
      row.inputVisible = false
    }
    // 如果输入内容合法,稍后继续进行后续处理
  },
  ...
}
  1. 合法的文本框内容转换为新的tag标签

业务逻辑:紧接着上一步,如果输入的内容是合法的,那么我们需要拿到这一行的 inputValue值,通过 push()方法将其添加到attr_vals上

methods:{
  // 11.3 文本框失去焦点或回车都会触发此函数
  handleInputConfirm(row) {
    ...
    // 如果输入内容合法,稍后继续进行后续处理
    row.attr_vals.push(row.inputValue.trim())
    row.inputValue = ''
    row.inputVisible = false
  },
}

但是仍然有bug:如果什么都没输入,只是回车,会直接出现两个新的空白标签,如果输入了合法内容,按下回车,会出现新标签以及一个空白新标签,但是使文本框失去焦点的话是不会有这种情况的

目前,只是在前端的页面中,将用户输入的值保存到了数组中,但是并没有把对应的数据上传到后台数据库中,所以刷新页面后,之前的所有的操作全部没有了,那么就需要发起请求保存数据

  1. 发起请求提交参数

业务逻辑:查看API文档1.7.5.编辑提交参数,下面三个参数都可以直接使用该行的参数,但是注意,参数的属性值是数组形式,而服务器只接收字符串,需要使用join()方法,将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串

  • 请求路径:categories/:id/attributes/:attrId
  • 请求方法:put
  • 请求参数
参数名 参数说明 备注
:id 分类 ID 不能为空携带在url中
:attrId 属性 ID 不能为空携带在url中
attr_name 新属性的名字 不能为空,携带在请求体
attr_sel 属性的类型[many或only] 不能为空,携带在请求体
attr_vals 参数的属性值 可选参数,携带在请求体

最后完整代码:

// 11.3 文本框失去焦点或回车都会触发此函数
async handleInputConfirm(row) {
  // 12.5 判断文本框输入的内容是否合法
  if (row.inputValue.trim().length === 0) {
    row.inputValue = ''
    // 12.4 文本框切换回按钮
    row.inputVisible = false
  }
  // 12.6 如果输入内容合法,稍后继续进行后续处理
  row.attr_vals.push(row.inputValue.trim())
  row.inputValue = ''
  row.inputVisible = false
  // 12.7 发起请求保存上述操作的数据
  const { data: res } = await this.$http.put(
    `categories/${this.cateId}/attributes/${row.attr_id}`,
    {
      attr_name: row.attr_name,
      attr_sel: row.attr_sel,
      attr_vals: row.attr_vals.join(' ')
      // 服务器只接收字符串,需要将数组转化为字符串
    }
  )
  if (res.meta.status !== 200) {
    return this.$message.error('修改参数项失败')
  }
  this.$message.success('修改参数项成功')
},

vue电商后台管理系统—实现商品分类以及分类管理功能_第7张图片

14.删除参数项

业务逻辑:给el-tag标签绑定一个close事件handelClosed(),并将参数项的索引值传入函数中,根据索引值将其从数组中删除,我们还会用到这一行的数据,scope.row也需要传入进去,在methods中定义这个函数,通过splice()方法删除数组中的某一项,然后发起请求保存数据,这一步和上面添加参数项保存数据的请求操作一样,所以把发起请求保存数据的操作单拎出来为一个函数saveAttrvals(),分别调用即可,记得传参,定义saveAttrVals函数以及调用时,都需要传入参数 row

// 13.1 删除参数项
handelClosed(index, row) {
  row.attr_vals.splice(index, 1)
  this.saveAttrVals(row)
},

优化:选中三级分类后,删除/选中一级或二级分类时,动态属性和静态参数面板都没有清空

// 5.8 获取参数的列表数据,解决面板无法获取数据的问题
async getParamsData() {
  if (this.selectedKeys.length !== 3) {
    this.selectedKeys = []
    // 13.3 选中三级分类又删除后,动态参数和静态属性面板也需要被清空
    this.manyTableData = []
    this.onlyTableData = []
  }
  ...
}

静态参数的展开项和动态属性的展开项一样,可以直接复制展开项代码

vue电商后台管理系统—实现商品分类以及分类管理功能_第8张图片

提交代码

通过终端手动输入git命令提交代码

  1. 查看当前所在分支

    git branch
    
  2. 查看当前所在分支的文件状态

    git status
    
  3. 将所有的修改添加到暂存区

    git add . 
    
  4. 将代码提交到本地仓库中

    git commit -m '实现了分类管理功能'
    
  5. 将本地存储的goods_params分支推送到码云中

    git push
    
  6. 将码云中goods_params分支的代码合并到主分支master中

    先切换回主分支,再合并

    git checkout master
    git branch
    git merge goods_params
    
  7. 合并完成后,将master主分支的代码也推送到码云中进行保存

    git push
    

直接通过左侧操作栏提交代码

vue电商后台管理系统—实现商品分类以及分类管理功能_第9张图片

问题汇总

上面有几个小问题和疑惑,后续补充…

你可能感兴趣的:(vue,前端,vue.js,elementui)