最近开发了一个需求,element 树状表格,表格全选时所有项(包括所有子节点)都选中,选中树状表格父节点时,这个父节点下所有子节点也都要选中,如果某个父节点下的所有子节点没有全部,则这个父节点处于半选状态

效果图:

element 树形结构的table可以设置展开行 elementui 树形 表格 selection_数据

1.HTML

<template>
  <el-table
    v-loading="loading"
    :data="orderList"
    @selection-change="handleSelectionChange"
    :row-key='rowKeyFunc'
    :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
    :row-class-name="rowClassNameFun"
    ref="table"
    @select-all="selectAllFun"
    @select="selectFun"
  >
    <el-table-column type="selection" width="55" align="center"/>
    <el-table-column label="运营商" align="center" prop="providerType"  width="120px"/>
    <el-table-column
      label="创建时间"
      align="center"
      prop="gmtCreate"
      width="180"
    >
    </el-table-column>
    <el-table-column
      label="客户编号"
      align="center"
      width="200"
      prop="customerNo"
    />
    <el-table-column
      label="系统单号"
      align="center"
      width="200"
      prop="orderNo"
    />
  </el-table>
</template>

2.JS

<script>
export default {
  name: "index",
  data() {
    return {
      // 遮罩层
      loading: true,
      // 选中数组
      ids: [],
      // 非单个禁用
      single: true,
      // 非多个禁用
      multiple: true,
      // 总条数
      total: 0,
      // 运单管理表格数据
      orderList: [],
      // 查询参数
      queryParams: {

      },
      page: {
        pageNum: 1,
        pageSize: 15,
      },
      oneProductIsSelect:[],
      isFun:false
    };

  },
  created() {
    this.getList();
  },
  methods: {
    // 查询列表
    getList() {
      this.loading = true;
      listOrder(this.queryParams, this.page).then((response) => {
        this.orderList=response.rows
        //这一步是为了将父和子之间有联系,给子定义一个taskId,将子的taskId和父的id一致
        this.orderList.forEach((item, index) => {
          if (item.children) {
            item.children.forEach((cItem, cIndex) => {
              cItem.taskId = item.id;
            });
          }
        });
        // 由于后端返回的数组id不唯一(父里的id和其中一个子的id一样),然后:row-key='id'里面的id要是唯一值,所以处理了一下,将父的id改变,将数组里面的id都唯一,当然,你可以跟后端商量一下,返回给你一个唯一值,这个处理代码就可以省略了
        this.orderList = this.orderList.map((item,index)=>{
          return{
            ...item,
            uuid:`${index}-${this.guid()}`
          }
        })
        this.total = response.total; //页数
        this.loading = false; // 遮罩层
        this.initData(this.orderList)
      });
    },
    //生成唯一ID
    guid() {
      return Number(
        Math.random().toString().substr(3, 3) + Date.now()
      ).toString(36);
    },
    //row-key唯一值
    rowKeyFunc(row){
      if(row.uuid){
        return row.uuid
      }else {
        return row.id
      }
    },
    //初始化数据,将数据都用isSelect标记一下,isSelect为false不选中、true选中、half半选
    initData(data) {
      data.forEach((item) => {
        item.isSelect = false; //默认为不选中
        if (item.children && item.children.length) {
          this.initData(item.children);
        }
      });
    },
    // 判断是不是全选
    checkIsAllSelect() {
      this.oneProductIsSelect = [];
      this.orderList.forEach((item) => {
        this.oneProductIsSelect.push(item.isSelect);
      });
      //判断一级产品是否是全选.如果一级产品全为true,则设置为取消全选,否则全选
      let isAllSelect = this.oneProductIsSelect.every((selectStatusItem) => {
        return true == selectStatusItem;
      });
      return isAllSelect;
    },
    // 全选或者全不选(这个是祖父的勾选)
    selectAllFun(selection) {
      let isAllSelect = this.checkIsAllSelect();
      this.orderList.forEach((item) => {
        item.isSelect = isAllSelect;
        this.$refs.table.toggleRowSelection(item, !isAllSelect);
        this.selectFun(selection, item);
      });
    },
    selectFun(selection, row) {
      this.setRowIsSelect(row);
    },
    setRowIsSelect(row) {
      //当点击父级点复选框时,当前的状态可能为未知状态,所以当前行状态设为false并选中,即可实现子级点全选效果
      if (row.isSelect == "half") {
        row.isSelect = false;
        this.$refs.table.toggleRowSelection(row, true);
      }
      row.isSelect = !row.isSelect;
      //判断操作的是子级点复选框还是父级点复选框,如果是父级点,则控制子级点的全选和不全选
      if (row.children && row.children.length > 0) {
        row.children.forEach((item) => {
          item.isSelect = row.isSelect;
          this.$refs.table.toggleRowSelection(item, row.isSelect);
        });
      } else {
        //操作的是子节点  1、获取父节点  2、判断子节点选中个数,如果全部选中则父节点设为选中状态,如果都不选中,则为不选中状态,如果部分选择,则设为不明确状态
        let parentId = row.taskId;
        this.orderList.forEach((item) => {
          let isAllSelect = [];
          if (item.id == parentId) {
            if(item.children){
              item.children.forEach((databaseSourceListItem) => {
                isAllSelect.push(databaseSourceListItem.isSelect);
              });
            }
            if (
              isAllSelect.every((selectItem) => {
                return true == selectItem;
              })
            ) {
              item.isSelect = true;
              this.$refs.table.toggleRowSelection(item, true);
            } else if (
              isAllSelect.every((selectItem) => {
                return false == selectItem;
              })
            ) {
              item.isSelect = false;
              this.$refs.table.toggleRowSelection(item, false);
            } else{
              item.isSelect ="half";
            }
          }
        });
      }
    },
    rowClassNameFun({row}){
      if(row.isSelect=='half'){
        return "indeterminate";
      }
    },
   handleSelectionChange(selection) {
      let ids = []
      let waybill=[]
      selection.forEach(node => {
        if(node.isSelect==true){//如果上来勾选了
        if(node.children && node.children.length > 0){//判断是否有子元素,如果有子,遍历子
            node.children.forEach(child=>{
              if(child.isSelect){
                let index = ids.findIndex(item =>
                  item.outOrderNo === child.outOrderNo
                  && item.packageNo === child.packageNo
                );
                if(index === -1){
                  ids.push({
                    outOrderNo: child.outOrderNo,
                    packageNo: child.packageNo
                  })
                }
                if (!waybill.includes(child.trackNumber)) {
                  waybill.push(child.trackNumber);
                }
              }
            })
        }else {//如果没有子,
          console.log('选中')
          let index = ids.findIndex(item =>
            item.outOrderNo === node.outOrderNo
            && item.packageNo === node.packageNo
          );
          if(index===-1){
            ids.push({
              outOrderNo: node.outOrderNo,
              packageNo: node.packageNo
            })
          }
          if (!waybill.includes(node.trackNumber)) {
            waybill.push(node.trackNumber);
          }
        }
        }else if(node.isSelect=="half"){//如果是半选,遍历子,将勾选的子进行push
          console.log('半选')
          if(node.children && node.children.length > 0){
            node.children.forEach(child=>{
              if(child.isSelect){
              }
            })
          }else {
          }

        }else {
          console.log('取消')
        }
      })
      this.waybill=waybill
      this.ids = ids
      this.single = this.ids.length !== 1;
      this.multiple = !this.ids.length
    },
  },
};
</script>

这个里面需要有个注意的地方,当父里面有两个子,勾选父,@selection-change="handleSelectionChange"的handleSelectionChange会跑三遍,一遍父、一边父与子、一边子

// 多选框选中数据
    handleSelectionChange(selection) {
      console.log(selection,'selection')
      }

element 树形结构的table可以设置展开行 elementui 树形 表格 selection_javascript_02

这个里面还是比较混乱的,打印出来三次,这个我是可以理解的,因为勾选了三个,但是打印的这个顺序,我不太懂,由于有个勾选之后,操作批量下载和批量取消,不勾选的时候,是禁止点击批量下载和批量取消的

效果图

element 树形结构的table可以设置展开行 elementui 树形 表格 selection_elementui_03

原来代码逻辑

// 多选框选中数据
    handleSelectionChange(selection) {
      console.log(selection,'selection')
      this.single = selection.length !== 1;
      this.multiple = !selection.length
      this.ids = selection.map((item) => item.trackNumber);
    },

 按理说,这个就能实现以上的功能,但是,没有实现,我进行取消勾选,还是打印了三次,最后一次有值,所以以上代码不能实现此功能

element 树形结构的table可以设置展开行 elementui 树形 表格 selection_vue.js_04

 于是我就对上面的代码进行了改进

// 多选框选中数据
     handleSelectionChange: _.debounce(function(selection) {
        let ids = []
        let waybill=[]
        selection.forEach(node => {
          console.log(node.isSelect,node)
          if(node.isSelect==true){//如果上来勾选了
          if(node.children && node.children.length > 0){//判断是否有子元素,如果有子,遍历子
              node.children.forEach(child=>{
                if(child.isSelect){
                  let index = ids.findIndex(item =>
                    item.outOrderNo === child.outOrderNo
                    && item.packageNo === child.packageNo
                  );
                  if(index === -1){
                    ids.push({
                      outOrderNo: child.outOrderNo,
                      packageNo: child.packageNo
                    })
                  }
                  if (!waybill.includes(child.trackNumber)) {
                    waybill.push(child.trackNumber);
                  }
                }
              })
          }else {//如果没有子,
            let index = ids.findIndex(item =>
              item.outOrderNo === node.outOrderNo
              && item.packageNo === node.packageNo
            );
            if(index===-1){
              ids.push({
                outOrderNo: node.outOrderNo,
                packageNo: node.packageNo
              })
            }
            if (!waybill.includes(node.trackNumber)) {
              waybill.push(node.trackNumber);
            }
          }
          }else if(node.isSelect=="half"){//如果是半选,遍历子,将勾选的子进行push
            if(node.children && node.children.length > 0){
              node.children.forEach(child=>{
                if(child.isSelect){
                }
              })
            }else {
            }

          }else {
          }
        })
        this.waybill=waybill
        this.ids = ids
        this.single = this.ids.length !== 1;
        this.multiple = !this.ids.length
    }, 300),

根据上面取到的this.ids、this.waybill,来进行批量取消操作,可根据自己的代码逻辑进行修改

//取消(取消里面改动点:父packageNo为空,子packageNo有值,后端根据packageNo来进行父与子取消区分)
    handleCancal(row) {
      //操作里面的取消
      let trackNumber
      //批量取消
      let trackNumbers
      let outOrderNo
      let outOrderNos
      if (row.trackNumber) {
        trackNumber = row.trackNumber.split(',')
        outOrderNo = [{
          outOrderNo:row.outOrderNo,
          packageNo:row.packageNo,
        }]
      } else {
        trackNumbers = this.waybill
        outOrderNos=this.ids
      }
      //处理单独取消的弹框换行
      let arry
      let newData = []
      for (let index in trackNumber) {
        newData.push(trackNumber[index] + "<br/>")
      }
      arry = newData.join('')
      //处理批量取消的弹框换行
      let arrys
      let newDatas = []
      for (let index in trackNumbers) {
        newDatas.push(trackNumbers[index] + "<br/>")
      }
      arrys = newDatas.join('')
      let parmas = outOrderNo || outOrderNos
      this.$confirm(
        this.$t('order.surecan') + "<br/>" + (arry || arrys) + this.$t('order.waybill') + "<br/>" + this.$t('order.notee'),
        {
          confirmButtonText: this.$t('operation.confirm'),
          cancelButtonText: this.$t('operation.cancel'),
          type: "warning",
          dangerouslyUseHTMLString: true,
        })
        .then(function () {
          return cancalOrder(parmas);
        })
        .then(() => {
          this.getList();
          this.msgSuccess(this.$t('msg.application'));
        })
        .catch(function () {
        });
    },

3.CSS

半选的样式

element 树形结构的table可以设置展开行 elementui 树形 表格 selection_父节点_05

  

<style lang="scss" scoped >
/deep/.indeterminate {
  .el-table-column--selection .cell .el-checkbox {
    display: block !important;
  }
  .el-checkbox__input .el-checkbox__inner {
    background-color: #4a97eb !important;
    border-color: #4a97eb !important;
    color: #fff !important;
  }
}
/deep/.indeterminate .el-checkbox__input.is-checked .el-checkbox__inner::after {
  transform: scale(0.5);
}
/deep/.indeterminate .el-checkbox__input .el-checkbox__inner::after {
  border-color: #c0c4cc !important;
  background-color: #c0c4cc;
}

/deep/.indeterminate .el-checkbox__input .el-checkbox__inner::after {
  content: "";
  position: absolute;
  display: block;
  background-color: #fff;
  height: 2px;
  transform: scale(0.5);
  left: 0;
  right: 0;
  top: 5px;
  width: auto !important;
}
</style>

 以上就可以实现树状表格选择父节点子节点全选,子节点不全选父节点半选,但是对于handleSelectionChange,里面处理数据还是很懵的,虽然解决了,但是感觉不是最优的方法,如果有好的解决方法,可以分享下,欢迎分享和指正。