# 实现elementUI组件库中el-table表格全选功能

​ 本文利用elementUI组件库中的el-table组件实现全选功能,经过从网上学习,总结了一套完美的方案。详细方案请您继续阅读本文。

# 背景

​ 对于后台管理系统或者一些需要导出信息的表格功能,常常就会出现批量导出功能。此篇文章产生的背景就是项目需要实现一个批量提现申请的功能,element表格表头的全选是本页全选,告知产品后并建议按照滴滴开发票中有 本页全选 及 全选所有 两个按钮同时出现的形式来实现。建议归建议,实现还是有点难度,记得N年前做过这个功能,就是当年没好好总结。这次实现了可得好好把代码保留下来。

# 开干

​ 这种功能是很常见的功能,在网上肯定一堆。时间紧,任务重,只得直接网上搜索了,网上一个链接一个链接尝试,试的怀疑人生,几乎没有一个ok的,阅读他们代码,有的是需要将表格数据完全加载出来的(不可取),找到最后,皇天不负有心人,经测试终于有一个差不多可行的,就是有些bug。

# 主要思路

  1. element组件库的table组件 设置row-key属性 行数据的 Key,用来优化 Table 的渲染;

  2. table-column设置reserve-selection为true 为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key

    TIP

    经过以上两个步骤,表格跨页就可以实现数据保留了,但是当前表头的勾选还仅仅是本页全选,且用户会误以为全选所有

  3. 新增一个checkbox,用来表示全选所有

  4. 当checkbox为true时,表示全选所有,但是此时用户会取消掉部分选中数据,因此需要将取消的数据保存起来

  5. 用户去掉部分数据后,全选所有checkbox的样式还是全选打勾的状态,因此需要引入一个indeterminate来控制checkbox的样式

  6. 当checkbox为true时,让后端排除掉取消勾选的数据,全做处理。当checkbox为false时,仅做勾选数据的处理即可

  7. 当checkbox为true时,切换分页的时候得把数据样式全部选中

  8. 将checkbox全选所有及表头的勾选 新增 文字说明,并修改样式

# 后端配合

  1. 后端字段要求

    页面中搜索所有条件字段(除了分页,查询所有)

    是否全选

    选中列表

    排除列表

  2. 数据计算方式

    2.1 先根据条件查询出来①总数

    2.2 判断是②否是全选

    2.3 是全选的话 查看 ③排除列表 ①减去③ 就是 最终选中的数据

    2.4 不是全选的话 查看 ④选中列表 ④就是 最终选中的数据

# 示例

暂无数据

代码中有模拟分页接口,对于后端无数据又需要测试分页功能时,可使用。

<template>
  <div class="el-table-select-all">
    <div class="table-box">
      <el-checkbox class="check-all" :indeterminate="indeterminate " v-model="allCheck" @change="allCheckEvent">全选所有</el-checkbox>
      <el-table ref="recordTable" :data="tableData" height="600" :loading="loading" @row-click="clickRow" :row-key="rowKey" @select-all="selectAll" @selection-change="handleSelectionChange" @select="selectOne">
        <el-table-column label="" align="center" width="60px">
          <template slot-scope="scope">
            {{ (scope.$index + 1 )+ (params.page_no - 1) * params.page_size }}
          </template>
        </el-table-column>
        <el-table-column type="selection" width="65px" :reserve-selection="true"></el-table-column>
        <el-table-column label="姓名" prop="name"> </el-table-column>
        <el-table-column label="性别" prop="gender"> </el-table-column>
        <el-table-column label="手机号" prop="tel" dateFormat="datetime"> </el-table-column>
        <el-table-column label="地址" prop="address" dateFormat="datetime"> </el-table-column>
        <el-table-column label="操作">
          <template slot-scope="scope">
            <div>
              <el-button size="mini" type="text" @click.native.stop="haddleDetail(scope.row)">详细信息</el-button>
            </div>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <div class="pagination-box">
      <div class="tips" v-if="total > 0">已选择 {{selectNum}} 条</div>
      <el-pagination class="pagination" v-if="total > 0" @size-change="handlePageSizeChange" @current-change="handlePageCurrentChange" :current-page="params.page_no" :page-sizes="[10, 20, 50, 100]" :page-size="params.page_size" layout="total, sizes, prev, pager, next, jumper" :total="total"></el-pagination>
    </div>
  </div>
</template>

<script>
export default {
  name: "el-table-select-all",
  data() {
    return {
      params: {
        page_no: 1,
        page_size: 10
      },
      total: 0,
      tableData: [], //表格数据
      loading: false, //是否在加载数据
      allCheck: false,  // 是否全选
      checkList: [], //已选择的数据
      falseList: [], //存储取消的数据
      indeterminate: false, //是否全选的样式 
      clickRowObj: null, //点击的行数据
      isClickRow: false, //是否是 点击行
      rowKey: "id" //关键唯一标识
    }
  },
  watch: {
    //表格数据变化
    tableData: {
      handler(value) {
        if (this.allCheck) {
          let that = this
          let len = that.falseList.length
          let lenCheck = that.checkList.length
          if (that.checkList.length === 0) {
            value.forEach(row => {
              that.$refs.recordTable.toggleRowSelection(row, true)
            })
          } else {
            value.forEach(row => {
              for (let i = 0; i < lenCheck; i++) {
                if (row[this.rowKey] === that.checkList[i][this.rowKey]) {
                  that.$refs.recordTable.toggleRowSelection(row, false)
                  break
                } else {
                  that.$refs.recordTable.toggleRowSelection(row, true)
                }
              }
              if (len !== 0) {
                for (let j = 0; j < len; j++) {
                  if (row[this.rowKey] === that.falseList[j][this.rowKey]) {
                    that.$refs.recordTable.toggleRowSelection(row, false)
                    break
                  }
                }
              }
            })
          }
        }
      },
      deep: true
    },
    //排除的选项
    falseList: {
      handler(value) {
        if (value.length === this.total) {
          this.allCheck = false
          this.indeterminate = false
        }
        if (value.length === 0) {
          this.allCheck = true
          this.indeterminate = false
        }
      },
      deep: true
    },
    //选中的选项   allCheck 为false时 此选项才数据才是真实的
    checkList: {
      handler(value) {
        if (!this.allCheck) {
          //如果选中数量等于总数  排除项设置为 空数组   自动触发 全选设置为 true  
          if (value.length === this.total) {
            this.falseList = []
          }
          //如果选中数量为 空 
          if (value.length === 0) {
            this.indeterminate = false
          }

          //如果选中数量大于0 小于总数  设置样式为横杠
          if (value.length < this.total && value.length > 0) {
            this.indeterminate = true
          }
        }
      },
      deep: true
    }
  },
  computed: {
    //计算已选择的条数
    selectNum() {
      if (this.allCheck) {
        return this.total - this.falseList.length
      } else {
        return this.checkList.length
      }
    }
  },
  created() {
    //搜索数据
    this.handleSearch()
  },
  methods: {
    //点击全选 checkbox 按钮
    allCheckEvent() {
      if (this.allCheck) {
        //全选的时候把 排除项 设置为 空数组
        this.falseList = []
        this.tableData.forEach(row => {
          this.$refs.recordTable.toggleRowSelection(row, true)
        })
      } else {
        //非全选的时候把  checkList 置位 空数组
        this.checkList = []
        this.$refs.recordTable.clearSelection()
      }
    },
    //点击行
    clickRow(row) {
      this.isClickRow = true
      this.clickRowObj = row
      this.$refs.recordTable.toggleRowSelection(row)
    },
    //点击表格里的 本页全选
    selectAll(rows) {
      if (this.allCheck) {
        let that = this
        that.indeterminate = true
        let lenNow = that.tableData.length
        // 判断勾选全选本页是选中还是取消
        if (rows.indexOf(that.tableData[0]) !== -1) {
          // 选中
          for (let i = 0; i < lenNow; i++) {
            for (let n = 0; n < that.falseList.length; n++) {
              if (that.falseList[n][this.rowKey] === that.tableData[i][this.rowKey]) {
                that.falseList.splice(n, 1)
              }
            }
          }
        } else {
          // 取消
          for (let j = 0; j < lenNow; j++) {
            if (that.falseList.length !== 0) {
              if (that.falseList.indexOf(that.tableData[j]) === -1) {
                that.falseList.push(that.tableData[j])
              }
            } else {
              that.falseList.push(that.tableData[j])
            }
          }
        }
      }
    },
    //判断选中状态
    judgeFunc(rows, row) {
      // 多选框样式改变
      this.indeterminate = true
      // 判断勾选数据行是选中还是取消
      let selected = rows.length && rows.indexOf(row) !== -1
      let lenFalse = this.falseList.length
      if (selected) {
        // 选中
        if (lenFalse !== 0) {
          for (let i = 0; i < lenFalse; i++) {
            if (this.falseList[i][this.rowKey] === row[this.rowKey]) {
              this.falseList.splice(i, 1)
              break
            }
          }
        }
      } else {
        // 取消
        this.falseList.push(row)
      }
    },
    //当选择项发生变化时会触发该事件
    handleSelectionChange(rows) {
      if (this.isClickRow) {
        if (this.allCheck) {
          this.judgeFunc(rows, this.clickRowObj)
        }
        this.isClickRow = false
        this.clickRowObj = null
      }
      this.checkList = rows

    },
    //当用户手动勾选数据行的 Checkbox 时触发的事件	
    selectOne(rows, row) {
      if (this.allCheck) {
        this.judgeFunc(rows, row)
      }
    },
    /**
    * @description: 搜索数据
    * @param {type} 
    * @return {type} 
    * @author: syx
    */
    handleSearch() {
      //当有搜索条件时 更换搜索条件会触发  这个方法,所以这时需要清空之前的选项 并把 allCheck 置为false
      this.allCheck = false
      this.$refs.recordTable&&this.$refs.recordTable.clearSelection()

      this.params.page_no = 1
      this.fetchData()
    },
    //模拟接口
    getListApi(params) {
      return new Promise((resolve) => {
        const data = []
        const total = 22
        for (let i = 0; i < params.page_size; i++) {
          if ((params.page_no - 1) * params.page_size + i < total) {
            const id = (params.page_no - 1) * params.page_size + i
            data.push({
              id,
              name: "小" + ["明", "红", "布", "娟", "宁", "婷", "洲", "滨", "青", "黄", "华", "东", "西", "南", "北", "橙", "绿", "蓝", "紫", "涵", "罗", "沈"][id],
              tel: "131452066" + (i + "").padStart(2, "0"),
              gender: (id % 2 === 0) ? "男" : "女",
              address: "福建省" + ["漳州市", "厦门市", "泉州市", "宁德市", "福州市", "三明市", "莆田市", "龙岩市", "南平市"][Math.floor(Math.random() * 9)]
            })
          } else {
            continue
          }
        }
        resolve(
          {
            data,
            total: total,
          }
        )
      })
    },
    /**
     * @description: 触发搜索
     * @param {type}
     * @return:
     * @author: syx
     */
    fetchData() {
      //请求接口加载表格数据
      this.loading = true
      this.getListApi(this.params).then(data => {
        this.tableData = data.data
        this.total = data.total
        this.loading = false
      }).catch(err => {
        this.loading = false
      })
    },
    /** 分页大小发生改变 */
    handlePageSizeChange(size) {
      this.params.page_size = size
      this.handleSearch()
    },
    /** 分页页数发生改变 */
    handlePageCurrentChange(page) {
      this.params.page_no = page
      this.fetchData()
    },
    haddleDetail(row) {
      alert("注意方法中使用  .native.stop 修饰符  不然会触发点击行选中方法")
    }
  }
}
</script>

<style lang="less" scoped>
.el-table-select-all {
  width: 100%;
  .table-box {
    position: relative;
    /deep/ .check-all {
      position: absolute;
      top: 12px;
      left: 6px;
      z-index: 9;
      display: flex;
      justify-content: flex-start;
      align-items: center;
      .el-checkbox__label {
        width: 34px;
        display: inline-block;
        font-size: 12px;
        line-height: 14px;
        white-space: pre-wrap;
        color: #606266;
      }
    }

    /deep/ .el-table {
      .el-table__header-wrapper {
        .el-table__header {
          .el-table-column--selection {
            .cell {
              overflow: visible;
              .el-checkbox {
                position: relative;
                &::after {
                  content: "本页全选";
                  position: absolute;
                  top: 50%;
                  left: 100%;
                  transform: translateY(-50%);
                  white-space: pre-wrap;
                  width: 34px;
                  line-height: 14px;
                  font-size: 12px;
                  padding-left: 8px;
                  color: #606266;
                }
              }
            }
          }
        }
      }
    }
  }

  .pagination-box {
    position: relative;
    .tips {
      position: absolute;
      top: 50%;
      left: 0;
      transform: translateY(-50%);
      font-size: 14px;
      color: #409eff;
    }
    .pagination {
      display: flex;
      justify-content: flex-end;
      align-items: center;
    }
  }
}
</style>
显示代码

# 总结

​ 有bug可以搜索微信公众号 【爆米花小布】进行反馈。此篇文章旨在需要用到此功能时,复制代码即可,提高工作效率。也希望干后端的同志遇到全选功能时了解需要 一个是否全选字段,一个选中集合,一个排除集合。

阅读原文实现elementUI组件库中el-table表格全选功能 (opens new window)