【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框


上一篇: 【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出

参考视频:VUE项目,VUE项目实战,vue后台管理系统,前端面试,前端面试项目

案例链接
【前端】Vue+Element UI案例:通用后台管理系统-导航栏(视频p1-16)https://blog.csdn.net/karshey/article/details/127640658
【前端】Vue+Element UI案例:通用后台管理系统-Header+导航栏折叠(p17-19)https://blog.csdn.net/karshey/article/details/127652862
【前端】Vue+Element UI案例:通用后台管理系统-Home组件:卡片、表格(p20-22)https://blog.csdn.net/karshey/article/details/127674643
【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表准备:axios封装、mock数据模拟实战(p23-25)https://blog.csdn.net/karshey/article/details/127735159
【前端】Vue+Element UI案例:通用后台管理系统-Echarts图表:折线图、柱状图、饼状图(p27-30)https://blog.csdn.net/karshey/article/details/127737979
【前端】Vue+Element UI案例:通用后台管理系统-面包屑、tag栏(p31-35)https://blog.csdn.net/karshey/article/details/127756733
【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Form表单填写、Dialog对话框弹出(p36-38)https://blog.csdn.net/karshey/article/details/127787418
【前端】Vue+Element UI案例:通用后台管理系统-用户管理:Table表格增删查改、Pagination分页、搜索框(p39-42)https://blog.csdn.net/karshey/article/details/127777962
【前端】Vue+Element UI案例:通用后台管理系统-登陆页面Login(p44)https://blog.csdn.net/karshey/article/details/127795302
【前端】Vue+Element UI案例:通用后台管理系统-登陆页面功能:登录权限跳转、路由守卫、退出(p45-46)https://blog.csdn.net/karshey/article/details/127849502
【前端】Vue+Element UI案例:通用后台管理系统-登陆不同用户显示不同菜单、动态添加路由(p47-48)https://blog.csdn.net/karshey/article/details/127865621
【前端】Vue+Element UI案例:通用后台管理系统-项目总结https://blog.csdn.net/karshey/article/details/127867638

目标

总体是这样:

在这里插入图片描述

需求:本篇主要是Table表格增删查改、Pagination分页。上篇已经完成了表单、对话框弹出。

  • 表格Table显示用户数据
  • 用户数据由Mock随机生成,提供后端接口
  • 删除:可以删除用户数据,弹出消息提示
  • 搜索框:可以搜索用户数据
  • 分页效果

代码

0.结构

上篇已经完成了提交功能,但由于现在还没有表格,不知道自己写的对不对。我们先迅速地用组件把表格写出来。

当el-table元素中注入data对象数组后,在el-table-column中用prop属性来对应对象中的键名即可填入数据,用label属性来定义表格的列名。可以使用width属性来定义列宽。

在这里插入图片描述

<div class="common-table">
  <!-- 用户数据Table -->
  <el-table :data="tableData" stripe style="width: 100%">
    <el-table-column prop="name" label="姓名">
    </el-table-column>
    <el-table-column prop="age" label="年龄">
    </el-table-column>
    <el-table-column prop="sex" label="性别">
    </el-table-column>
    <el-table-column prop="birth" label="出生日期">
    </el-table-column>
    <el-table-column prop="addr" label="地址">
    </el-table-column>
    <!-- 自定义列 -->
  </el-table>
  <!-- 分页 -->
</div>

效果:

在这里插入图片描述
我们先测试一下提交功能:

在这里插入图片描述
提交本身没问题,但细节有点问题:我们要规范一下出生日期的格式。

在这里插入图片描述
代码:添加value-format="yyyy-MM-DD"

<el-form-item label="出生日期">
  <el-form-item prop="birth">
    <el-date-picker type="date" placeholder="请选择日期" v-model="form.birth" value-format="yyyy-MM-DD">
    </el-date-picker>
  </el-form-item>
</el-form-item>

1.按钮-删除

需求:

在这里插入图片描述

  • 自定义列:列名操作
  • 按钮:编辑、删除
  • 点击后有弹窗

自定义列:

在这里插入图片描述

<el-table-column label="操作">
  <template slot-scope="scope">
    <el-button
      size="mini"
      @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
    <el-button
      size="mini"
      type="danger"
      @click="handleDelete(scope.$index, scope.row)">删除</el-button>
  </template>
</el-table-column>

点击后的弹窗功能:

在这里插入图片描述
methods如下:

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

效果:删除数据+弹窗。

在这里插入图片描述

2.按钮-编辑

需求:

  • 点击编辑后:打开表单
  • 把当前用户信息填上去(深拷贝)

显然会调用update的接口。

表单有新建和编辑两种打开方式:

  • 新建:打开空表单,左上角为“新建”,调用接口为create
  • 编辑:打开表单,自动填进当前用户信息,左上角为“编辑”,调用接口为update

因此我们用一个变量modalType来表示是新建(0)还是编辑(1)。

代码:点击编辑按钮后标记modalType为1,打开表单,进行深拷贝

// 编辑按钮
handleEdit(index) {
 this.modalType = 1
 this.openForm()
 // 深拷贝
 this.form = JSON.parse(JSON.stringify(index))
},
// 打开表单
openForm() {
 this.dialogVisible = true
}

接下来的逻辑判断就由submit来实现:若modalType为0,则新增;否则修改:

// 表单提交
submit() {
  // 要用箭头函数,若用function会报错,不知道为什么
  this.$refs.form.validate((valid) => {
    // 符合校验
    if (valid) {
      // 提交数据
      if (this.modalType === 0) {
        // 新增
        createUser(this.form).then(() => {
          this.getList()
        })
      } else {
        // 编辑
        updateUser(this.form).then(() => {
          this.getList()
        })
      }
      // 清空,关闭
      this.closeDialog()
    }
  })
}

效果:确实显示在了表单中。显然还有一些问题,比如日期显示的是错误的,性别显示的是数字。我们在下一节修改它。

3.debug

debug:

  • 点击新建则表单左上角显示新建,点击编辑则表单左上角显示编辑
  • 性别显示“男女”而非10

新建与编辑:

<el-dialog 
:title="modalType == 0 ? '新建' : '编辑'" 
:visible.sync="dialogVisible" 
width="50%" 
:before-close="closeDialog">

性别显示:

<el-table-column prop="sex" label="性别">
 <template slot-scope="scope">
   <span>{{ scope.row.sex == 1 ? '男' : '女' }}</span>
 </template>
</el-table-column>

效果:在表格中的性别显示已经改过来了。至于为什么年龄匹配不上,且名为娟的会是男性…因为数据全是随机生成的(详见user.js中的mock.random)

在这里插入图片描述
但是出现了新的问题,点击编辑后自动填入的性别还是1和0,为什么呢?

在这里插入图片描述
原因:后端返回的数据中sex是integer(0,1)

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)
    })
  )
}

而前端的表单中的value是String:

<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>

解决方法:动态绑定value,让它可以更改:

<el-option label="" :value="1"></el-option>
<el-option label="" :value="0"></el-option>

效果:

在这里插入图片描述

4.样式

当前样式:鼠标滚轮往下滑,可以看到表格的所有数据都显示在一页。许多数据会把table的高度撑的很大。我们要限制table的宽高。

在这里插入图片描述

在html中增加Attribute:height

在这里插入图片描述

<el-table 
:data="tableData" 
stripe 
style="width: 100%" 
height="90%">

这样会受控于外部样式,所以我们要给外部div也写上样式。

最大的div的高度占浏览器100%,common-table高度为父级的90%,于是el-table表格的高度为common-table的90%。

.manage {
  height: 100%;

  .common-table {
    height: 90%;
  }
}

效果:

在这里插入图片描述

5.分页Pagination:功能

在这里插入图片描述

<el-pagination
  layout="prev, pager, next"
  :total="1000">
</el-pagination>

这只是结构,我们还要完成点击对应页码就跳转到对应Table页的效果:

在这里插入图片描述
参数为点击的页码:

// 改变页码
currentChange(val){
  console.log(val);
}

在这里插入图片描述
到这里我们可以确定:我们能通过currentChange获取到点击的页码,至于如何完成跳转呢?由于表格中显示哪些数据由getList这个函数决定,而getList又是由后端接口中的getUserList决定的: 我们可以看一下后端的接口文档。

/**
 * 获取列表
 * 要带参数 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
  }
}

所以page页码数可以当作参数传给getUser。由注释可知:要带参数 name, page, limt; name可以不填, page,limit有默认值。显然传入的是一个对象。于是我们在data中定义这个对象:

  • page:要展示第page页
  • limit:每页limit条数据,这里是默认值
pageData:{
  page:1,
  limit:20
}

每次点击分页时就修改pageData.page,然后把它传给getUser。

// 获取列表数据
getList() {
  // 由接口文档知传入一个对象
  getUser({params:{...this.pageData}}).then((data) => {
    this.tableData = data.data.list
  })
}

效果:数据竟然消失了?

在这里插入图片描述
看一下控制台,原来错误出在::8081/api/user/get?page=1&limit=20:1 Failed to load resource: the server responded with a status of 404 (Not Found)

由于我们之前定义的getUser是get方法,它的参数是会在url里的,而我们在mock里定义的:

Mock.mock('/api/user/get',user.getUserList)

是写死的,/api/user/get?page=1&limit=20/api/user/get是匹配不上的!

解决方法:正则表达式

Mock.mock(/\/api\/user\/get/,user.getUserList)

getList:

getList() {
 // 由接口文档知传入一个对象
 getUser({ params: { ...this.pageData } }).then((data) => {
   this.tableData = data.data.list
   this.total = data.data.count || 0
 })
}

改变页码:

// 改变页码
currentChange(val) {
  this.pageData.page = val
  this.getList()
}

效果:

第一页:
在这里插入图片描述
第三页:

在这里插入图片描述

6.分页Pagination:样式

右下角。子绝父相。

.common-table {
  height: 90%;
  position: relative;

  .pager {
    position: absolute;
    right:20px;
    bottom: 0;
  }
}

效果:(忽略搜索框!分页的样式是我最后写的…)

在这里插入图片描述

7.搜索框:功能

搜索框,但是用的是表单组件:
在这里插入图片描述
html:

<el-form>
  <el-form-item>
    <el-input v-model="searchForm.name" placeholder="请输入名称"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="search">查询</el-button>
  </el-form-item>
</el-form>

由于table显示的数据是本页数据+搜索到的数据的交集,我们要修改getList:

getList() {
 // 由接口文档知传入一个对象:要返回的是当前页面数据和搜索到的数据的交集
 getUser({ params: { ...this.pageData,...this.searchForm } }).then((data) => {
   this.tableData = data.data.list
   this.total = data.data.count || 0
 })
}

但凡触发了搜索事件:重新获取table数据即可。

search(){
  this.getList()
}

效果:

在这里插入图片描述

8.搜索框:样式

让新建按钮和搜索框并排:flex。
让搜索框的输入框和按钮并排:inline。

html:

<el-form :inline="true">

css:

.manage {
  height: 100%;

  .manage-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .common-table {
    height: 90%;
  }
}

效果:

在这里插入图片描述

总效果

在这里插入图片描述

总代码

修改和创建的文件

在这里插入图片描述

User.vue

<template>
  <div class="manage">
    <div class="manage-header">
      <!-- 新增按钮 -->
      <el-button type="primary" @click="handlecreate">+ 新增</el-button>

      <!-- 对话框:点击新增或编辑才会弹出表单 -->
      <!-- :before-close="closeDialog" 点击关闭的x之前要做的事情 -->
      <el-dialog :title="modalType == 0 ? '新建' : '编辑'" :visible.sync="dialogVisible" width="50%"
        :before-close="closeDialog">
        <!-- 表单Form -->
        <!-- ref=form:为了通过this.$refs调用组件的方法 -->
        <el-form :inline="true" :model="form" :rules="rules" ref="form" label-width="80px">
          <!-- 每一项表单域:el-form-item -->
          <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="出生日期">
            <el-form-item prop="birth">
              <el-date-picker type="date" placeholder="请选择日期" v-model="form.birth" value-format="yyyy-MM-DD">
              </el-date-picker>
            </el-form-item>
          </el-form-item>

          <el-form-item label="地址" prop="addr">
            <el-input placeholder="请输入地址" v-model="form.addr"></el-input>
          </el-form-item>
        </el-form>
        <div slot="footer" class="dialog-footer">
          <el-button @click="closeDialog">取 消</el-button>
          <el-button type="primary" @click="submit">确 定</el-button>
        </div>
      </el-dialog>

      <!-- 搜索框 -->
      <el-form :inline="true">
        <el-form-item>
          <el-input v-model="searchForm.name" placeholder="请输入名称"></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="search">查询</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="common-table">
      <!-- 用户数据Table -->
      <el-table :data="tableData" stripe style="width: 100%" height="90%">
        <el-table-column prop="name" label="姓名">
        </el-table-column>
        <el-table-column prop="age" label="年龄">
        </el-table-column>
        <el-table-column prop="sex" label="性别">
          <template slot-scope="scope">
            <span>{{ scope.row.sex == 1 ? '男' : '女' }}</span>
          </template>
        </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="操作">
          <template slot-scope="scope">
            <el-button @click="handleEdit(scope.row)">编辑</el-button>
            <el-button type="danger" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- 分页 -->
      <div class="pager">
        <el-pagination layout="prev, pager, next" :total="total" @current-change="currentChange">
        </el-pagination>
      </div>
    </div>
  </div>
</template>
  
<script>
import { getUser, createUser, deleteUser, updateUser } from '../api/index'
export default {
  data() {
    return {
      // 表单绑定的数据
      form: {
        name: '',
        age: '',
        sex: '',
        birth: '',
        addr: ''
      },
      // 表单验证规则
      rules: {
        name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
        age: [{ required: true, message: '请输入年龄', trigger: 'blur' }],
        sex: [{ required: true, message: '请输入性别', trigger: 'blur' }],
        birth: [{ required: true, message: '请输入日期', trigger: 'blur' }],
        addr: [{ required: true, message: '请输入地址', trigger: 'blur' }],
      },
      // 表单是否打开
      dialogVisible: false,
      // 列表数据
      tableData: [],
      // 打开表单:新建0,编辑1
      modalType: 0,
      // 分页的对象
      pageData: {
        page: 1,
        limit: 20
      },
      // 分页页数
      total: 0,
      // 搜索框表单
      searchForm: {
        name: ''
      }
    }
  },
  methods: {
    // 获取列表数据
    getList() {
      // 由接口文档知传入一个对象:要返回的是当前页面数据和搜索到的数据的交集
      getUser({ params: { ...this.pageData, ...this.searchForm } }).then((data) => {
        this.tableData = data.data.list
        this.total = data.data.count || 0
      })
    },
    // 表单提交
    submit() {
      // 要用箭头函数,若用function会报错,不知道为什么
      this.$refs.form.validate((valid) => {
        // 符合校验
        if (valid) {
          // 提交数据
          if (this.modalType === 0) {
            // 新增
            createUser(this.form).then(() => {
              this.getList()
            })
          } else {
            // 编辑
            updateUser(this.form).then(() => {
              this.getList()
            })
          }
          // 清空,关闭
          this.closeDialog()
        }
      })
    },
    // 关闭对话框
    closeDialog() {
      // 先重置
      this.$refs.form.resetFields()
      // 后关闭
      this.dialogVisible = false
    },
    // 编辑按钮
    handleEdit(index) {
      this.modalType = 1
      this.openForm()
      // 深拷贝
      this.form = JSON.parse(JSON.stringify(index))
    },
    // 删除按钮
    handleDelete(index) {
      this.$confirm('此操作将永久删除该用户, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        // 删除操作:根据后端接口,参数是对象,id是唯一标识符
        deleteUser({ id: index.id }).then(() => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          this.getList()
        });
      }).catch(() => {
        // 点击取消:不删除了
        this.$message({
          type: 'info',
          message: '已取消删除'
        });
      });
    },
    // 新建按钮
    handlecreate() {
      this.modalType = 0
      this.openForm()
    },
    // 打开表单
    openForm() {
      this.dialogVisible = true
    },
    // 改变页码
    currentChange(val) {
      this.pageData.page = val
      this.getList()
    },
    // 搜索
    search() {
      this.getList()
    }
  },
  mounted() {
    this.getList()
  }
}
</script>
  
<style lang="less" scoped>
.manage {
  height: 100%;

  .manage-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .common-table {
    height: 90%;
    position: relative;

    .pager {
      position: absolute;
      right:20px;
      bottom: 0;
    }
  }
}
</style>

api下的index.js

import http from '../utils/request'

// 请求首页数据,直接把这个对象导出
export const getData = () => {
    // 返回一个promise
    return http.get('/home/getData')
}

// 下面四个:用户管理-后端-网络请求接口
export const getUser = (params) => {
    return http.get('/user/get/', params)
}

export const createUser = (data) => {
    return http.post('/user/create', data)
}

export const deleteUser = (data) => {
    return http.post('/user/del', data)
}

export const updateUser = (data) => {
    return http.post('/user/update', data)
}

api下的mock.js

import Mock from 'mockjs'
import homeMock from '../api/mockServe/home'
import user from './user'

// 定义mock拦截
Mock.mock('/api/home/getData',homeMock)

// 用户管理:增删查改
Mock.mock(/\/api\/user\/get/,user.getUserList)
Mock.mock('/api/user/create','post',user.createUser)
Mock.mock('/api/user/update','post',user.updateUser)
Mock.mock('/api/user/del','post',user.deleteUser)

api下的user.js

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: '编辑成功'
      }
    }
  }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

karshey

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值