zl程序教程

您现在的位置是:首页 >  前端

当前栏目

【vue后台管理系统】基于Vue+Element-UI+ECharts开发通用管理后台(下)

2023-09-27 14:26:45 时间

面包屑导航制作

效果展示

在这里插入图片描述
这里有三个注意点:

  • 默认有一个首页
  • 在点击其他组组件的时候,header部分的面包屑区域会增加
  • 当我们选择已有的菜单的时候这个数据不会重复添加

思路分析

在这里插入图片描述
我们要在这三个组件中实现数据的共享,我们需要借助Vuex,它能够做到状态的集中管理

面包屑部分Element-UI给我们提供了支持,我们可以在官方文档中找到适合我们的面包屑:
在这里插入图片描述

<el-breadcrumb separator="/">
  <el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
  <el-breadcrumb-item><a href="/">活动管理</a></el-breadcrumb-item>
  <el-breadcrumb-item>活动列表</el-breadcrumb-item>
  <el-breadcrumb-item>活动详情</el-breadcrumb-item>
</el-breadcrumb>

el-breadcrumb中使用el-breadcrumb-item标签表示从首页开始的每一级。Element 提供了一个separator属性,在el-breadcrumb标签中设置它来决定分隔符,它只能是字符串,默认为斜杠/

这里因为原理相似以及布局的影响问题,我们就不说明面包屑下面的标签栏制作过程。

然后我们还要注意的一点就是,当我们点击面包屑进行跳转的时候,我们左边CommonAside组件中的菜单栏高亮部分也是要同步的。

在原项目中这一点被忽略,我们可以自己动手实现

代码实现过程

首先我们处理面包屑自增和跳转的问题。因为这里涉及多个组件的通信,所以我们使用Vuex,将面包屑的展示列表放入state中,我们将其命名为breadCrumbList,注意这个列表中一开始是有一个元素的:
在这里插入图片描述

我们为什么放入这个对象?
因为这个对象是我们每次点击左侧导航栏的时候可以获取到的对象,我们可以跟他保持一致,简化开发:
在这里插入图片描述

然后我们的思路就是,每点击一次左侧菜单栏,就进行一次筛选,筛选过了就加入breadCrumbList,筛选有两个要求:

  • 此项不是首页
  • 此项在breadCrumbList不存在

接下来我们来实现:

首先我们要在点击左侧菜单栏的时候进行commit,将state中的数据进行修改:
在这里插入图片描述
接下来我们要在store中的mutations中去编写这个回调函数:

mutations:{
       ···
        ADD_BREADCRUMB_ITEM(state,val){
            // 我们添加进列表的标准有两个:
            // 1)不是首页
            // 2)在原列表中不存在
            if (val.label !== '首页' && state.breadCrumbList.findIndex(item => item.label === val.label) === -1)
                state.breadCrumbList.push(val)
        },

    }

这里我们使用的是JS列表中的findIndex方法:
findIndex() 方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置。
findIndex() 方法为数组中的每个元素都调用一次函数执行:

  • 当数组中的元素在测试条件时返回 true 时, findIndex() 返回符合条件的元素的索引位置,之后的值不会再调用执行函数。
  • 如果没有符合条件的元素返回 -1
array.findIndex(function(currentValue, index, arr), thisValue)

然后我们在面包屑中将breadCrumbList中的每一项进行遍历:

<el-breadcrumb separator="/" >
    <el-breadcrumb-item v-for="item in $store.state.tab.breadCrumbList" :key="item.name" :to="{ path: item.path }" >{{item.label}}</el-breadcrumb-item>
</el-breadcrumb>

这里的to就帮我们实现了路由跳转的功能,除了to我们还可以使用replace:
在这里插入图片描述

接下来我们实现当我们点击面包屑进行跳转的时候,我们左边CommonAside组件中的菜单栏高亮部分进行同步变化:
在这里插入图片描述
那么我们怎么对高亮选项进行控制呢?我们查看一下Element-UI的官方文档,发现可以通过default-active进行控制:

我们前面使用这个default-active的时候是把他写死的,这样页面加载完成之后就会先把我们的首页栏进行高亮处理,然后当我们点击的时候,通过点击菜单栏的index变化进行高亮栏的变化

在这里插入图片描述
那么我们的思路也就可以确定下来了。因为涉及到两个组件(CommonHeader、CommonAside)的通信我们这里把要高亮的菜单栏index交给state进行保管:
在这里插入图片描述

然后我们给面包屑绑定点击事件,每当我们点击面包屑,我们就对activeName进行更新:

<el-breadcrumb separator="/" >
     <el-breadcrumb-item v-for="item in $store.state.tab.breadCrumbList" :key="item.name" :to="{ path: item.path }" @click.native="changeActiveName(item.name)">{{item.label}}</el-breadcrumb-item>
</el-breadcrumb>

在这里插入图片描述

注意这里我们使用的是@click.native:
@click.native是给组件绑定原生事件
我们这里不能直接通过@click绑定事件,因为el-breadcrumb是一个组件,并不是一个原生的html标签

Tips:
在vue项目中使用Element-UI组件库时,经常会用到类似el-breadcrumb面包屑或者Dropdown下拉菜单的组件。有时我们需要在子项上添加click事件,但是官方文档中并没有给出el-breadcrumb-item / el-dropdown-item的点击事件。
若直接在el-breadcrumb-item / el-dropdown-item上添加click事件,点击后会没有任何反应
在click后添加.native修饰符,则可解决问题

然后我们在mutations中去编写对应的回调函数即可:
在这里插入图片描述

需求优化

先在我们在此基础上添加一个面包屑删除功能,效果如下:
在这里插入图片描述
当我们点击X号之后,我们的此选项会被剔除。

这个需求我们也好实现,图标我们直接使用Element-UI内置的图标:
在这里插入图片描述
然后我们绑定点击事件:

<el-breadcrumb separator="/" >
    <el-breadcrumb-item v-for="item in $store.state.tab.breadCrumbList" :key="item.name" :to="{ path: item.path }" @click.native="changeActiveName(item.name)">{{item.label}}<i class="el-icon-close" @click="deletebBeadCrumbItem(item)"></i></el-breadcrumb-item>
</el-breadcrumb>

在这里插入图片描述
最后再到我们的mutations中去编写我们的回调函数:

mutations:{
		···
        ADD_BREADCRUMB_ITEM(state,val){
            // 我们添加进列表的标准有两个:
            // 1)不是首页
            // 2)在原列表中不存在
            if (val.label !== '首页' && state.breadCrumbList.findIndex(item => item.label === val.label) === -1)
                state.breadCrumbList.push(val)
        },
        CHANGE_ACTIVENAME(state,val){
          state.activeName = val;
        },
        DELETE_BREADCRUMB_ITEM(state,val){

            state.breadCrumbList = state.breadCrumbList.filter(item => item.name !== val.name)
        }
    }

用户管理页面

效果展示

我们的用户管理页面分为三个部分:

  • 新增
  • 列表呈现部分
  • 列表查询部分

在这里插入图片描述

新增用户表单实现

我们在点击新增按钮的时候,会弹出来一个表单:
在这里插入图片描述
这里我们使用Element-UI中的验证型表单:
在这里插入图片描述
确定了表单之后,那么这种弹窗的效果怎么做出来呢?Element-UI同样给我们提供了支持,我们在官方文档中找到对话框组件
在这里插入图片描述

<el-button type="text" @click="dialogVisible = true">点击打开 Dialog</el-button>

<el-dialog
  title="提示"
  :visible.sync="dialogVisible"
  width="30%"
  :before-close="handleClose">
  <span>这是一段信息</span>
  <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
  </span>
</el-dialog>

<script>
  export default {
    data() {
      return {
        dialogVisible: false
      };
    },
    methods: {
      handleClose(done) {
        this.$confirm('确认关闭?')
          .then(_ => {
            done();
          })
          .catch(_ => {});
      }
    }
  };
</script>

在这里插入图片描述
因为我们不需要在关闭的时候进行相关的确认提示,所以我们是不需要handleClose相关的回调函数的,可以把它删除,所以我们要利用的只有dialogVisible这个属性,用它来控制我们的对话框是否显示。

接确认了基本思路之后,下来我们开始制作:

<template>
    <div>
        <el-button type="primary" @click="dialogVisible = true">新增 +</el-button>

        <el-dialog
                title="提示"
                :visible.sync="dialogVisible"
                width="30%">
            <span>这是一段信息</span>
            <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
            </span>
        </el-dialog>
    </div>

</template>

<script>
    export default {
        name: "User",
        data() {
            return {
                dialogVisible: false
            }
        }

    }
</script>

首先我们完成按钮和弹窗的制作:
在这里插入图片描述
然后接下来我们只需要将对话框中的内容定制成我们的添加成员的表单即可:
在这里插入图片描述
在这之前我们先熟悉一下Element-UI中的验证型表单组件,我们拿他的示例来说:

因为示例比较长具体代码可以在官网中查询

  • 在el-form中每一个表单域用el-form-item来表示

  • label属性就是显示的名称
    在这里插入图片描述

  • 插槽的位置显示的代码就是我们表达域里的基本内容
    在这里插入图片描述 在这里插入图片描述

  • 我们最终要通过表单拿到用户数据进行验证以及相关的操作,那么我们怎么去获得这些数据呢?

    • 这个表单有一个数据对象model,我们在每一个表单域中进行数据的输入的时候,就是通过双向绑定对数据对象的属性进行修改:
      在这里插入图片描述
      在这里插入图片描述

在这里插入图片描述
一般这里的表单数据我们是通过后端拿到的,这里我们直接使用mock模拟后端数据的传输:

operateFormLabel: [
                        {
                            model: 'name',
                            label: '姓名',
                            type: 'input'
                        },
                        {
                            model: 'age',
                            label: '年龄',
                            type: 'input'
                        },
                        {
                            model: 'sex',
                            label: '性别',
                            type: 'select',
                            opts: [
                                {
                                    label: '男',
                                    value: 1
                                },
                                {
                                    label: '女',
                                    value: 0
                                }
                            ]
                        },
                        {
                            model: 'birth',
                            label: '出生日期',
                            type: 'date'
                        },
                        {
                            model: 'addr',
                            label: '地址',
                            type: 'input'
                        }
                    ],

然后我们根据这些数据,把我们表单的数据对象的相关属性给补充一下:

<script>
    export default {
        name: "User",
        data() {
            return {
                dialogVisible: false,
                form: {
                    name: '',
                        age: '',
                        sex: '',
                        birth: '',
                        addr: ''
                },
            }
        }
    }
</script>

然后接下来我们再完善一下我们的html表单结构:

<template>
    <div>
        <el-button type="primary" @click="dialogVisible = true">新增 +</el-button>

        <el-dialog
                title="提示"
                :visible.sync="dialogVisible"
                width="30%">
            <!-- 用户的表单信息 -->
            <el-form ref="form" :model="form" label-width="80px">
                <el-form-item label="姓名" >
                    <el-input placeholder="请输入姓名" v-model="form.name"></el-input>
                </el-form-item>
                <el-form-item label="年龄" >
                    <el-input placeholder="请输入年龄" v-model="form.age"></el-input>
                </el-form-item>
                <el-form-item label="性别" >
                    <el-select v-model="form.sex" placeholder="请选择">
                        <el-option label="" :value="1"></el-option>
                        <el-option label="" :value="0"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="出生日期" >
                    <el-date-picker
                            v-model="form.birth"
                            type="date"
                            placeholder="选择日期"
                            value-format="yyyy-MM-DD">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="地址" >
                    <el-input placeholder="请输入地址" v-model="form.addr"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="dialogVisible = false">取 消</el-button>
                <el-button type="primary" @click="dialogVisible = false">确 定</el-button>
            </span>
        </el-dialog>
    </div>

</template>

这里我们有几个注意点:

  • 我们查看效果的时候会发现,我们的表单是竖着呈现的,这跟我们的目标不符合:
    在这里插入图片描述

    • 这里我们就使用到了行内表单:
      在这里插入图片描述
    • 我们需要给el-form添加一个inline属性
    • 然后我们还需要将我们的表单大小扩大到50%,否则不能一行展开:
      在这里插入图片描述
  • 时间控件Element-UI提供了很多种:
    在这里插入图片描述
    这里我们使用的是最简单的一种:
    在这里插入图片描述

然后我们需要给这个表单添加校验,这是为我们拿到表单的数据做准备

我们通过Element-UI的示例看看,如何为表单添加校验:

Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Itemprop 属性设置为需校验的字段名即可。

在这里插入图片描述
然后我们来看看rules中可以设置哪些内容:

  • 我们要使用字段名称作为属性,其内容为一个数组,数组里面的每一项就是一条规则:
    在这里插入图片描述
    • required:表示是否必须有值,取值为true/false,如为true,表示必须有值,如果无值,则校验不通过;如为false,则允许无值,但在有值的情况下,不影响其它规则的使用。
    • message:提示消息,在校验不通过时提示此消息。
    • trigger:触发方式,取值为blur/change,blue表示失去焦点,一般在input组件中使用;change为值改变,一般在选择框中使用。
    • min:字符串最小长度。
    • max:字符串最大长度。
    • 更多具体的用法可以参考下面的文章:rules详解

我们这里就使用最简单的校验逻辑:

			rules: {
                name: [
                    { required: true, message: '请输入姓名' }
                ],
                age: [
                    { required: true, message: '请输入年龄' }
                ],
                sex: [
                    { required: true, message: '请选择性别' }
                ],
                birth: [
                    { required: true, message: '请选择出生日期' }
                ],
                addr: [
                    { required: true, message: '请输入地址' }
                ]
            },

然后我们需要完成在点击确认的时候拿到表单的数据,也就是表单提交。在提交之前我们先要确认用户是否通过了表单校验,那么我们怎么判断呢?

Element-UI给我们提供了一个回调方法:
在这里插入图片描述

注意这是个Form的回调方法,我们要通过ref拿到Form对象再使用

在这里插入图片描述

我们将submit回调事件给确定按钮:
在这里插入图片描述

在这里插入图片描述
再提交了数据之后,我们还要把弹窗给关了:
在这里插入图片描述

然后我们会发现一个问题:当我们再次把弹窗打开后会发现我们的数据还遗留在里面。所以说我们在关闭弹窗之前还要对表单里的数据进行一个清空。

Elemen-UI中有这样一种方法可以帮助我们首先这一种效果:
在这里插入图片描述
也就是说我们只要在弹窗关闭之前调用这个resetFields方法即可

弹窗关闭之前这个地方我们之前提到过一个before-close方法:

before-close 仅当用户通过点击关闭图标或遮罩关闭 Dialog 时起效。
如果你在 footer 具名 slot 里添加了用于关闭 Dialog 的按钮(也就是说你点击的取消按钮关闭弹窗),那么可以在按钮的点击回调函数里加入 before-close 的相关逻辑

完整代码如下:

<el-dialog
            title="提示"
            :visible.sync="dialogVisible"
            width="50%"
            :before-close="handleClose">
            <!-- 用户的表单信息 -->
            <el-form ref="form" :rules="rules" :inline="true" :model="form" label-width="80px">
                <el-form-item label="姓名" prop="name">
                    <el-input placeholder="请输入姓名" v-model="form.name"></el-input>
                </el-form-item>
                <el-form-item label="年龄" prop="age">
                    <el-input placeholder="请输入年龄" v-model="form.age"></el-input>
                </el-form-item>
                <el-form-item label="性别" prop="sex">
                    <el-select v-model="form.sex" placeholder="请选择">
                        <el-option label="" :value="1"></el-option>
                        <el-option label="" :value="0"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="出生日期" prop="birth">
                    <el-date-picker
                        v-model="form.birth"
                        type="date"
                        placeholder="选择日期"
                        value-format="yyyy-MM-DD">
                    </el-date-picker>
                </el-form-item>
                <el-form-item label="地址" prop="addr">
                    <el-input placeholder="请输入地址" v-model="form.addr"></el-input>
                </el-form-item>
            </el-form>

            <span slot="footer" class="dialog-footer">
                <el-button @click="cancel">取 消</el-button>
                <el-button type="primary" @click="submit">确 定</el-button>
            </span>
        </el-dialog>
export default {
    data() {
        return {
            dialogVisible: false,
            form: {
                name: '',
                age: '',
                sex: '',
                birth: '',
                addr: ''
            },
            rules: {
                name: [
                    { required: true, message: '请输入姓名' }
                ],
                age: [
                    { required: true, message: '请输入年龄' }
                ],
                sex: [
                    { required: true, message: '请选择性别' }
                ],
                birth: [
                    { required: true, message: '请选择出生日期' }
                ],
                addr: [
                    { required: true, message: '请输入地址' }
                ]
            },
    methods: {
        // 提交用户表单
        submit() {
            this.$refs.form.validate((valid) => {
                // console.log(valid, 'valid')
                if (valid) {
                    // 后续对表单数据的处理
                    console.log(this.form)
                    // 清空表单的数据
                    this.$refs.form.resetFields()
                    // 关闭弹窗
                    this.dialogVisible = false
                }
            })
        },
        // 弹窗关闭的时候
        handleClose() {
            this.$refs.form.resetFields()
            this.dialogVisible = false
        },
        cancel() {
            this.handleClose()
        },
   }       
</script>

这个地方有个小问题:我们在把form数据打印出来的时候会发现里面的每一项都是空的,这个地方我们后面会处理。

table组件编写

在编写之前我们先提供table列表中的数据,这里我们直接使用mock模拟后端数据;

import Mock from 'mockjs'

// get请求从config.url获取参数,post从config.body中获取参数
function param2Obj (url) {
  const search = url.split('?')[1]
  if (!search) {
    return {}
  }
  return JSON.parse(
    '{"' +
    decodeURIComponent(search)
      .replace(/"/g, '\\"')
      .replace(/&/g, '","')
      .replace(/=/g, '":"') +
    '"}'
  )
}

let List = []
const count = 200

for (let i = 0; i < count; i++) {
  List.push(
    Mock.mock({
      id: Mock.Random.guid(),
      name: Mock.Random.cname(),
      addr: Mock.mock('@county(true)'),
      'age|18-60': 1,
      birth: Mock.Random.date(),
      sex: Mock.Random.integer(0, 1)
    })
  )
}

export default {
  /**
   * 获取列表
   * 要带参数 name, page, limt; name可以不填, page,limit有默认值。
   * @param name, page, limit
   * @return {{code: number, count: number, data: *[]}}
   */
  getUserList: config => {
    const { name, page = 1, limit = 20 } = param2Obj(config.url)
    console.log('name:' + name, 'page:' + page, '分页大小limit:' + limit)
    const mockList = List.filter(user => {
      if (name && user.name.indexOf(name) === -1 && user.addr.indexOf(name) === -1) return false
      return true
    })
    const pageList = mockList.filter((item, index) => index < limit * page && index >= limit * (page - 1))
    return {
      code: 20000,
      count: mockList.length,
      list: pageList
    }
  },
  /**
   * 增加用户
   * @param name, addr, age, birth, sex
   * @return {{code: number, data: {message: string}}}
   */
  createUser: config => {
    const { name, addr, age, birth, sex } = JSON.parse(config.body)
    console.log(JSON.parse(config.body))
    List.unshift({
      id: Mock.Random.guid(),
      name: name,
      addr: addr,
      age: age,
      birth: birth,
      sex: sex
    })
    return {
      code: 20000,
      data: {
        message: '添加成功'
      }
    }
  },
  /**
   * 删除用户
   * @param id
   * @return {*}
   */
  deleteUser: config => {
    const { id } = JSON.parse(config.body)
    if (!id) {
      return {
        code: -999,
        message: '参数不正确'
      }
    } else {
      List = List.filter(u => u.id !== id)
      return {
        code: 20000,
        message: '删除成功'
      }
    }
  },
  /**
   * 批量删除
   * @param config
   * @return {{code: number, data: {message: string}}}
   */
  batchremove: config => {
    let { ids } = param2Obj(config.url)
    ids = ids.split(',')
    List = List.filter(u => !ids.includes(u.id))
    return {
      code: 20000,
      data: {
        message: '批量删除成功'
      }
    }
  },
  /**
   * 修改用户
   * @param id, name, addr, age, birth, sex
   * @return {{code: number, data: {message: string}}}
   */
  updateUser: config => {
    const { id, name, addr, age, birth, sex } = JSON.parse(config.body)
    const sex_num = parseInt(sex)
    List.some(u => {
      if (u.id === id) {
        u.name = name
        u.addr = addr
        u.age = age
        u.birth = birth
        u.sex = sex_num
        return true
      }
    })
    return {
      code: 20000,
      data: {
        message: '编辑成功'
      }
    }
  }
}

然后我们编写一下我们的mock文件;
在这里插入图片描述
然后我们再把发送请求的文件补充一下:
在这里插入图片描述
然后我们引入我们的table,再去请求我们的数据:

		<el-table
                :data="tableData"
                style="width: 100%">
            <el-table-column
                    prop="date"
                    label="日期"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="name"
                    label="姓名"
                    width="180">
            </el-table-column>
            <el-table-column
                    prop="address"
                    label="地址">
            </el-table-column>
        </el-table>

我们在页面初始化的时候就应该能拿到我们的数据,所以我们在mounted中就去请求我们的数据:

<script>
    import {getUser} from '../api/index'
    export default {
        ·····
    mounted() {
            getUser().then(
                ({data}) => console.log(data)
            )
    }

    }
</script>

在这里插入图片描述
然后我们开始编写table列表

		<el-table
                :data="tableData"
                style="width: 100%">
            <el-table-column
                    prop="name"
                    label="姓名"
                    >
            </el-table-column>
            <el-table-column
                    prop="sex"
                    label="性别"
                    >
            </el-table-column>
            <el-table-column
                    prop="age"
                    label="年龄">
            </el-table-column>
            <el-table-column
                    prop="birth"
                    label="出生日期">
            </el-table-column>
            <el-table-column
                    prop="addr"
                    label="地址">
            </el-table-column>
            <el-table-column

                    label="操作">
            </el-table-column>
        </el-table>

看看效果:
在这里插入图片描述
我们发现有两个问题:

  • 性别要转化成男、女
  • 操作列的两个按钮没有制作

而这两个问题我们都可以通过自定义列模板来解决:
在这里插入图片描述
其核心就是使用Vue中的作用域插槽和template,如果我们不熟悉的话可以把scope.row打出来看看:

在这里插入图片描述

关于slot插槽的详细讲解可以看我的另一篇文章:
slot插槽详解

接下来我们编写性别显示逻辑:
在这里插入图片描述

再来编写我们的按钮:
在这里插入图片描述
效果:
在这里插入图片描述
接下来我们就要去处理编辑和删除按钮的回调逻辑。

编辑和新增的弹窗是可以复用的,所以这里我们应该通过一种状态来区分我们当前点击的是新增按钮还是编辑按钮。对于不同的状态我们显示的内容是不一样的:

  • 对于新增用户而言,他是一个空白的表单:
    在这里插入图片描述

  • 对于编辑用户而言,它会把当前行的数据回显在对话框的内部:
    在这里插入图片描述

  • 并且在点击了确认按钮之后他们的处理逻辑也是不一样的

我们在data里面定义一个modelType来界定这一个状态,其中0代表新增的弹窗,1代表编辑的弹窗:
在这里插入图片描述

至于状态的切换我们只需要在点击按钮的时候对modelType进行一个重新赋值即可,这里就不多赘述。我们直接关注到点击确认按钮之后的处理逻辑(也就是submit回调函数)。

在我们的代码中点击新增用户的回调函数就是handleAdd,而点击编辑用户调用的是handleEdit。而弹窗中的确认按钮的回调函数都是submit

代码如下:

import { getUser, addUser, editUser, delUser } from '../api'

···

		// 提交用户表单
        submit() {
            this.$refs.form.validate((valid) => {
                // console.log(valid, 'valid')
                if (valid) {
                    // 后续对表单数据的处理
                    if (this.modalType === 0) {
                        addUser(this.form).then(() => {
                            // 重新获取列表的接口
                            this.getList()
                        })
                    } else {
                        editUser(this.form).then(() => {
                            // 重新获取列表的接口
                            this.getList()
                        })
                    }

                    // 清空表单的数据
                    this.$refs.form.resetFields()
                    // 关闭弹窗
                    this.dialogVisible = false
                }
            })
        },

注意点:

  • 这里的getUser, addUser, editUser, delUser都是模拟的后端接口
  • 考虑到不管是新增还是编辑在我们点击确认之后,都要重新获得列表。所以我们把重新获取列表的方法抽象出来进行封装,也就是代码中的getList()
    • 代码如下:在这里插入图片描述

然后我们尝试着添加一个用户看看效果:
在这里插入图片描述
我们发现这个时间的格式不太符合我们的要求,我们可以借助时间控件里的value-format属性来重新定义我们的时间格式:
在这里插入图片描述

接下来我们来完善编辑按钮的回调handleEdit方法。这里的handleEdit方法要处理三个事情:

  • 将modelType的属性变化为1
  • 将弹窗的显示属性调整为true
  • 将此行用户的信息回显到弹窗之中

在这里插入图片描述

如上的代码符合三个要求,但是有很大的缺陷。我们不能把row直接赋值给form。因为如果这样的话,我们后面再对form表单进行操作的时候会直接修改row这个数据,而row这个数据是从el-table-column组件中来的:
在这里插入图片描述

所以这个地方我们要进行一个深拷贝:
在这里插入图片描述

然后我们来完善我们删除按钮的逻辑:

当我们点击删除按钮之后会显示如下弹窗:
在这里插入图片描述
这个弹窗我们使用的是Element-UI中的MessageBox 弹框:
在这里插入图片描述
我们使用其中的确认信息型:
在这里插入图片描述

<template>
  <el-button type="text" @click="open">点击打开 Message Box</el-button>
</template>

<script>
  export default {
    methods: {
      open() {
        this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          });
        }).catch(() => {
          this.$message({
            type: 'info',
            message: '已取消删除'
          });          
        });
      }
    }
  }
</script>

调用$confirm方法即可打开消息提示,它模拟了系统的 confirmMessage Box 组件也拥有极高的定制性,我们可以传入options作为第三个参数,它是一个字面量对象。type字段表明消息类型,可以为successerrorinfowarning,无效的设置将会被忽略。注意,第二个参数title必须定义为String类型,如果是Object,会被理解为options。在这里我们用了 Promise 来处理后续响应。

我们上述代码进行修改:

		handleDelete(row) {
            this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', {
                confirmButtonText: '确定',
                cancelButtonText: '取消',
                type: 'warning'
                }).then(() => {
                    delUser({ id: row.id }).then(() => {
                        this.$message({
                            type: 'success',
                            message: '删除成功!'
                        });
                        // 重新获取列表的接口
                        this.getList()
                    })
                    
                }).catch(() => {
                    this.$message({
                        type: 'info',
                        message: '已取消删除'
                    });          
            });
        },

代码的注意点:

  • handleDelete是我们点击删除按钮的回调函数
  • 出现弹窗之后,当我们点击确定的时候确定后,会进入then的逻辑,当我们点击取消之后会进入catch的逻辑
  • $confirm三个参数
    • 第一个参数是弹窗显示的内容
    • 第二个参数是弹窗的标题
    • 第三个参数是option
  • 如果我们选择删除也要通过 getList重新获取列表
  • 这里的this.getList()如果我们使用的不是箭头函数的话时会报错的。因为箭头函数的特性就是获取到上一层作用域的this。这里就相当于handleDelete作用域中的this,也就是指向我们的vue实例vm
  • delUser后端接口中要求传递的是一个对象

分页功能编写

在实现分页功能之前我们先要对table的样式进行修改,我们现在table的样式如下:
在这里插入图片描述

超出了边界这样肯定不好看,我们的理想效果是能放在一面中不会超出来。这一功能我们可以借助table的height属性:
在这里插入图片描述

我们这里直接使用百分比,让它占父盒子高度的90%(父盒子的高这里设置的是600px):

占90%是为了给分页条预留空间

在这里插入图片描述

此时的效果如下:

在这里插入图片描述

接下来我们来制作分页条。分页条组件在我们的Element-UI中也提供了相应的支持,叫做Pagination 分页:
在这里插入图片描述

<div class="block">
  <span class="demonstration">页数较少时的效果</span>
  <el-pagination
    layout="prev, pager, next"
    :total="50">
  </el-pagination>
</div>
<div class="block">
  <span class="demonstration">大于 7 页时的效果</span>
  <el-pagination
    layout="prev, pager, next"
    :total="1000">
  </el-pagination>
</div>

设置layout,表示需要显示的内容,用逗号分隔,布局元素会依次显示。prev表示上一页,next为下一页,pager表示页码列表,除此以外还提供了jumpertotalsize和特殊的布局符号->->后的元素会靠右显示,jumper表示跳页元素,total表示总条目数,size用于设置每页显示的页码数量。

这个total属性我们将其与tableData的列表长度进行同步,这个同步时机我们选在呈现列表的时候,所以我们可以把这段逻辑放在getList函数中:
在这里插入图片描述

当然有的时候后端传过来的可能不是一个列表,那么我们这么写会报错,我们可以稍微改进一下:
在这里插入图片描述

到后面这个地方还需要改进,因为我们的后端接口getList带有分页参数,我们以前请求的都是一页的20条数据,而总条数是由data.count决定的,所以应该修改成如下:
在这里插入图片描述

在这里插入图片描述

我们的page-size默认是10,我们有20条数据,所以这里就会显示2页:
在这里插入图片描述
接下来我们就来实现点击分页条实现切换的逻辑,我们使用current-change来完成:
在这里插入图片描述

size-change是在改变每页显示多少条数的时候调用的:
在这里插入图片描述

			<div class="pager">
                <el-pagination
                    layout="prev, pager, next"
                    :total="total"
                    @current-change="handlePage">
                </el-pagination>
            </div>

这里的handlePage可以拿到当前点击的是哪一面

我们接下来要做的就是根据当前的页码和每一页的条数去请求我们的数据,我们查看一下我们的后端接口getUserList:
在这里插入图片描述

这个函数中接受一个config对象,这个对象中有两个属性limit和page:

  • page是当前页码
  • limit是一页显示的条数

所以接下来我们在data中定义一个分页对象:

			pageData: {
                page: 1,
                limit: 10
            },

然后在我们请求列表的时候把他当做参数传递过去:
在这里插入图片描述

在这里插入图片描述
注意这里传的是params:this.pageData,如果你直接传this.pageData是无法生效的,因为这不符合axios中params参数的格式

还有一个注意点,在我们的mockjs中,我们请求的getUserList的路径不能写死吗,我们这里使用的是正则匹配,如果写死的话会因为路径的不匹配导致404:
在这里插入图片描述

搜索功能编写

这里我们使用的是Element-UI中的表单控件:

		<div class="manage-header">
            <el-button @click="handleAdd" type="primary">
                + 新增
            </el-button>
            <!-- form搜索区域 -->
            <el-form :inline="true" :model="userForm">
                <el-form-item>
                    <el-input placeholder="请输入名称" v-model="userForm.name"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button type="primary" @click="onSubmit">查询</el-button>
                </el-form-item>
            </el-form>
        </div>

如上的一段代码对应着页面中的红框部分:
在这里插入图片描述
在红框内部我们使用的是弹性布局
注意我们使用弹性布局之后我们左侧的新增按钮可能会发生变化:
在这里插入图片描述
我们只需要在父盒子的样式上添加align-items: center即可。
或者你可以在按钮上再包一层盒子也可以解决这个问题:
在这里插入图片描述

userForm是表达的数据对象,然后我们在data中添加对应的数据:

在这里插入图片描述

现在我们调用getUser接口的时候要传入两个对象的合并:

  • pageData分页对象
  • userForm表单数据对象

这里我们使用ES6的拓展运算符:
在这里插入图片描述
然后我们点击查询之后的回调函数onSubmit逻辑如下:

		// 列表的查询
        onSubmit() {
            this.getList()
        }

查询功能我们就完成了

附:ES6的解构赋值

在本项目中我们几乎随处可见ES6中的解构赋值语法,如果掌握不好可能会在项目制作的过程中产生许多问题,所以下面是一些解构赋值的简单用法,可以帮助你更好地理解解构赋值!

  • 解构赋值是对赋值运算符的扩展。

  • 他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。

  • 在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。

在解构中,有下面两部分参与:

  • 解构的源,解构赋值表达式的右边部分。
  • 解构的目标,解构赋值表达式的左边部分。

数组模型的解构(Array)

基本

let [a, b, c] = [1, 2, 3];
// a = 1
// b = 2
// c = 3

可嵌套

let [a, [[b], c]] = [1, [[2], 3]];
// a = 1
// b = 2
// c = 3

可忽略

let [a, , b] = [1, 2, 3];
// a = 1
// b = 3

不完全解构

let [a = 1, b] = []; // a = 1, b = undefined

剩余运算符

let [a, ...b] = [1, 2, 3];
//a = 1
//b = [2, 3]

字符串等

在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。

let [a, b, c, d, e] = 'hello';
// a = 'h'
// b = 'e'
// c = 'l'
// d = 'l'
// e = 'o'

解构默认值

let [a = 2] = [undefined]; // a = 2

当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。

let [a = 3, b = a] = [];     // a = 3, b = 3
let [a = 3, b = a] = [1];    // a = 1, b = 1
let [a = 3, b = a] = [1, 2]; // a = 1, b = 2
  • a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3
  • a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1
  • a 与 b 正常解构赋值,匹配结果:a = 1,b = 2

对象模型的解构(Object)

基本

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
// foo = 'aaa'
// bar = 'bbb'
 
let { baz : foo } = { baz : 'ddd' };
// foo = 'ddd'

可嵌套可忽略

let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, { y }] } = obj;
// x = 'hello'
// y = 'world'
let obj = {p: ['hello', {y: 'world'}] };
let {p: [x, {  }] } = obj;
// x = 'hello'

不完全解构

let obj = {p: [{y: 'world'}] };
let {p: [{ y }, x ] } = obj;
// x = undefined
// y = 'world'

剩余运算符

let {a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40};
// a = 10
// b = 20
// rest = {c: 30, d: 40}

解构默认值

let {a = 10, b = 5} = {a: 3};
// a = 3; b = 5;
let {a: aa = 10, b: bb = 5} = {a: 3};
// aa = 3; bb = 5;