Element ui tree table selects all parent nodes and child nodes, not all child nodes selects parent node half-selection

Recently developed a requirement, the element tree table, when the table is fully selected, all items (including all child nodes) are selected, when the parent node of the tree table is selected, all child nodes under the parent node must also be selected, if a parent If there are not all child nodes under the node, the parent node is half-selected

Renderings:

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="operator" align="center" prop="providerType" width="120px"/>
    <el-table-column
      label="Creation time"
      align="center"
      prop="gmtCreate"
      width="180"
    >
    </el-table-column>
    <el-table-column
      label="Customer ID"
      align="center"
      width="200"
      prop="customerNo"
    />
    <el-table-column
      label="system tracking number"
      align="center"
      width="200"
      prop="orderNo"
    />
  </el-table>
</template>

2. JS

<script>
export default {
  name: "index",
  data() {
    return {
      // mask layer
      loading: true,
      // select the array
      ids: [],
      // Non-single disabled
      single: true,
      // non-multiple disabled
      multiple: true,
      // total number
      total: 0,
      // waybill management form data
      orderList: [],
      // query parameters
      queryParams: {

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

  },
  created() {
    this. getList();
  },
  methods: {
    // query list
    getList() {
      this.loading = true;
      listOrder(this.queryParams, this.page).then((response) => {
        this.orderList=response.rows
        //This step is to connect the parent and the child, define a taskId for the child, and match the taskId of the child with the id of the parent
        this.orderList.forEach((item, index) => {
          if (item. children) {
            item.children.forEach((cItem, cIndex) => {
              cItem.taskId = item.id;
            });
          }
        });
        // Since the array id returned by the backend is not unique (the id in the parent is the same as the id of one of the children), then if the id in: row-key='id' is a unique value, it has been processed and the parent The id of the array is changed, and the id in the array is unique. Of course, you can discuss with the backend and return a unique value to you. This processing code can be omitted.
        this.orderList = this.orderList.map((item,index)=>{
          return {
            ...item,
            uuid:`${index}-${this.guid()}`
          }
        })
        this.total = response.total; //number of pages
        this.loading = false; // overlay
        this.initData(this.orderList)
      });
    },
    //Generate a unique ID
    guid() {
      return Number(
        Math.random().toString().substr(3, 3) + Date.now()
      ).toString(36);
    },
    //row-key unique value
    rowKeyFunc(row){
      if(row.uuid){
        return row.uuid
      } else {
        return row.id
      }
    },
    //Initialize the data, mark the data with isSelect, if isSelect is false, unselected, true selected, half half selected
    initData(data) {
      data.forEach((item) => {
        item.isSelect = false; //The default is not selected
        if (item. children & amp; & amp; item. children. length) {
          this.initData(item.children);
        }
      });
    },
    // Determine whether to select all
    checkIsAllSelect() {
      this.oneProductIsSelect = [];
      this.orderList.forEach((item) => {
        this.oneProductIsSelect.push(item.isSelect);
      });
      //Judge whether the first-level product is all selected. If all the first-level products are true, set to cancel all selection, otherwise select all
      let isAllSelect = this.oneProductIsSelect.every((selectStatusItem) => {
        return true == selectStatusItem;
      });
      return isAllSelect;
    },
    // Select all or none (this is grandpa's check)
    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) {
      //When clicking the checkbox of the parent point, the current state may be unknown, so the current row state is set to false and selected to achieve the effect of selecting all child points
      if (row.isSelect == "half") {
        row.isSelect = false;
        this.$refs.table.toggleRowSelection(row, true);
      }
      row.isSelect = !row.isSelect;
      //Judging whether the operation is a child point check box or a parent point check box, if it is a parent point, then control the selection or non-selection of all child points
      if (row. children & amp; & amp; row. children. length > 0) {
        row.children.forEach((item) => {
          item.isSelect = row.isSelect;
          this.$refs.table.toggleRowSelection(item, row.isSelect);
        });
      } else {
        //The operation is child node 1, get parent node 2, judge the number of selected child nodes, if all are selected, the parent node will be selected, if none are selected, it will be unselected, if part of the selection, then set Unclear state
        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";
      }
    },
  // Select the data in the multi-select box, the logic in this is mentioned below
    handleSelectionChange(selection) {
      console.log(selection,'selection')
      if(this. isFun) {
        this.isFun = false
        return
      }
      // Determine whether the currently selected node has child nodes
      if(selection. length === 0) {
        selection = []
        this.ids = [];
        this.isFun = true
        this.multiple = !selection.length
        return
      }
      this.single = selection.length !== 1;
      this.multiple = !selection.length
      this.ids = selection.map((item) => item.trackNumber);
    },
  },
};
</script>

There is a point to pay attention to here. When there are two children in the parent, check the parent, and the handleSelectionChange of @selection-change=”handleSelectionChange” will run three times, one time for the parent, one for the parent and one for the child, and one for the child

 // Multi-selection box selects data
    handleSelectionChange(selection) {
      console.log(selection,'selection')
      }

It is still quite confusing here, and it is printed out three times, which I can understand, because three are checked, but the order of printing, I don’t quite understand, because after one is checked, the operation batch download and batch cancel, When not checked, it is forbidden to click batch download and batch cancel

renderings

original code logic

 // Multi-selection box selects data
    handleSelectionChange(selection) {
      console.log(selection,'selection')
      this.single = selection.length !== 1;
      this.multiple = !selection.length
      this.ids = selection.map((item) => item.trackNumber);
    },

It stands to reason that this can realize the above functions, but if it is not realized, I uncheck it and print it three times, and the last time has a value, so the above code cannot realize this function

So I improved the above code

//Multiple selection box selects data
    handleSelectionChange(selection) {
      if(this. isFun) {
        this.isFun = false
        return
      }
      // Determine whether the currently selected node has child nodes
      if(selection. length === 0) {
        selection = []
        this.ids = [];
        this.isFun = true
        this.multiple = !selection.length
        return
      }
      this.single = selection.length !== 1;
      this.multiple = !selection.length
      this.ids = selection.map((item) => item.trackNumber);
    },

Of course, because handleSelectionChange will run multiple times, the ids passed to the backend will have duplicate values, so it needs to be deduplicated.

 //Array deduplication
    unlink(arr) {
      return arr. filter(function (item, index, arr) {
        //The current element, the first index in the original array == the current index value, otherwise return the current element
        return arr. indexOf(item, 0) === index;
      });
    },
handleCancal(row) {
      //Cancel inside the operation
      let trackNumber
      //batch cancel
      let trackNumbers
      if (row. trackNumber) {
        trackNumber = row.trackNumber.split(',')
      } else {
        trackNumbers = this.unlink(this.ids.toString().split(','))//Sort the data and de-duplicate
      }
      let parmas = {
        trackNumbers:trackNumber || trackNumbers
      }
      this.$confirm("Are you sure to cancel the waybill whose waybill number is " + (trackNumber || trackNumbers) + "? (Note: After confirming the application, it will be accepted after 15 days)", {
        confirmButtonText: "OK",
        cancelButtonText: "Cancel",
        type: "warning",
      })
        .then(function () {
          return cancalOrder(parmas);
        })
        .then(() => {
          this. getList();
          this.msgSuccess("Apply successfully");
        })
        .catch(function () {});
    },

3. CSS

half-selected style

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

The above can realize the selection of the parent node and child nodes in the tree form, and the child nodes are not fully selected. The parent node is half-selected, but for handleSelectionChange, the data processing inside is still very confusing. Although it is solved, it feels not the best way. If there is Good solutions, you can share, welcome to share and correct.