el-table树形数据复刻el-tree的选中联动实现

961 阅读2分钟

众所周知element-ui的树形表格使用多选框时,没有类似el-tree的【祖-父-子】的选中联动效果(element-plus目前有父到子的联动,没有从子孙到父级的联动),如下所示:

前段时间的一个项目正好有树形表格的功能,需要支持勾选数据,因此在项目开发过程中抽空余时间调试了一个实现方案,由于当时项目已经开始了一段时间,很多地方都存在树表,基于最小修改和变动的考虑,因此在实现的时候将其抽离为一个mixin文件方便已有的页面直接混入使用,当然,无法避免树表列模板的修改,效果如下:

使用的时候需要在表格的模板中添加一列el-table-column并通过插槽设置为选择框:

<el-table-column type="index" width="55">
  <template #header>
    <el-checkbox v-model="tts_isCheckedAll" :indeterminate="tts_allIndeterminate" @change="ttsCheckedAll" />
  </template>
  <template #default="{ row }">
    <el-checkbox v-model="tts_rowSelectedMap[row[tts_rowKey]]"
      :indeterminate="tts_rowIndeterminateMap[row[tts_rowKey]]"
      @change="(val) => ttsChangeRowSelect(val, row)" />
  </template>
</el-table-column>

再混入下面的文件(还是基于上面提到的最小修改的考虑,混入的文件单独提取了一些变量tts_rowKey,tts_childrenKey, tts_tableRef, tts_tableDataField,这些变量的值需要手动修改为具体的值)即可:

// 处理表格树数据选择
// 变量名统一以tts_开头 防止被组件变量名覆盖
// tts = tree-table-select

/* 以下为外部引入工具函数 */
function getDeepKeyValue(o, keys) {
  // 遍历keys数组,逐层深入对象
  for (let i = 0; i < keys.length; i++) {
    // 如果当前层级没有属性,则返回undefined
    if (!o || !Object.prototype.hasOwnProperty.call(o, keys[i])) {
      return undefined;
    }
    // 继续下一层
    o = o[keys[i]];
  }
  return o;
}

export default {
  data() {
    return {
      // 表头全选框
      tts_isCheckedAll: false,
      tts_allIndeterminate: false,
      // 行数据选择框信息
      tts_rowSelectedMap: {},
      tts_rowIndeterminateMap: {},
      // 行数据:对应父级信息
      tts_nodeParentMap: {},
      // 其他
      tts_rowKey: "id",
      tts_childrenKey: "children",
      tts_tableRef: "multipleTable",
      // 此变量适用于table数据为多层时的情况 递归取值
      tts_tableDataField: "data.typeList",
      tts_tableData: [],
    };
  },
  watch: {
    "data.typeList": {
      handler() {
        this.ttsClearCacheData();
        this.ttsGenerateTableData();
        this.ttsGenerateChildParent();
      },
    },
  },
  beforeDestroy() {
    this.ttsClearCacheData();
  },
  methods: {
    // 生成数据
    ttsGenerateTableData() {
      this.tts_tableData = getDeepKeyValue(
        this,
        this.tts_tableDataField.split(".")
      );
    },
    // 生成节点与父节点的map
    ttsGenerateChildParent() {
      const setParents = (node, parent) => {
        this.tts_nodeParentMap[node[this.tts_rowKey]] = parent;
        if (Array.isArray(node.children)) {
          for (let child of node.children) {
            setParents(child, node);
          }
        }
      };

      for (let t of this.tts_tableData) {
        setParents(t, {
          [this.tts_rowKey]: "root",
          [this.tts_childrenKey]: this.tts_tableData,
        });
      }
    },
    // 全选
    ttsCheckedAll(val) {
      const tableData = this.tts_tableData;
      if (val) {
        if (Array.isArray(tableData)) {
          this.ttsSetRowChecked(tableData, true);
        }
        this.tts_allIndeterminate = false;
      } else {
        this.$refs[this.tts_tableRef].clearSelection();
        if (Array.isArray(tableData)) {
          this.ttsSetRowChecked(tableData, false);
        }
        this.tts_allIndeterminate = false;
      }
    },
    // 行选择
    ttsChangeRowSelect(flag, row) {
      this.$refs[this.tts_tableRef].toggleRowSelection(row, flag);
      this.$set(this.tts_rowSelectedMap, `${row[this.tts_rowKey]}`, flag);
      if (Array.isArray(row.children)) {
        this.ttsSetRowChecked(row.children, flag);
      }
      // 计算选中状态更新
      this.ttsCalcRowCheckedIndeterminate(row);
    },
    // 递归操作选择
    ttsSetRowChecked(children, flag) {
      children.forEach((tr) => {
        this.$set(this.tts_rowSelectedMap, `${tr[this.tts_rowKey]}`, flag);
        this.$refs[this.tts_tableRef].toggleRowSelection(tr, flag);
        if (Array.isArray(tr.children)) {
          this.ttsSetRowChecked(tr.children, flag);
        }
      });
    },
    // 处理checkbox的状态
    ttsCalcRowCheckedIndeterminate(row) {
      if (row.children.length) {
        const { all, none, half } = this.ttsGetChildState(row.children);
        // console.log(row.typeName, all, none, half);
        if (all) {
          if (row[this.tts_rowKey] === "root") {
            this.tts_isCheckedAll = true;
            this.tts_allIndeterminate = false;
          } else {
            this.tts_rowSelectedMap[row[this.tts_rowKey]] = true;
            this.tts_rowIndeterminateMap[row[this.tts_rowKey]] = false;
            this.$refs[this.tts_tableRef].toggleRowSelection(row, true);
          }
        } else if (half) {
          if (row[this.tts_rowKey] === "root") {
            this.tts_isCheckedAll = false;
            this.tts_allIndeterminate = true;
          } else {
            this.tts_rowSelectedMap[row[this.tts_rowKey]] = false;
            this.tts_rowIndeterminateMap[row[this.tts_rowKey]] = true;
            this.$refs[this.tts_tableRef].toggleRowSelection(row, false);
          }
        } else if (none) {
          if (row[this.tts_rowKey] === "root") {
            this.tts_isCheckedAll = false;
            this.tts_allIndeterminate = false;
          } else {
            this.tts_rowSelectedMap[row[this.tts_rowKey]] = false;
            this.tts_rowIndeterminateMap[row[this.tts_rowKey]] = false;
            this.$refs[this.tts_tableRef].toggleRowSelection(row, false);
          }
        }
      }

      const parent = this.tts_nodeParentMap[row[this.tts_rowKey]];
      if (!parent) return;
      this.ttsCalcRowCheckedIndeterminate(parent);
    },
    // 获取子元素状态
    ttsGetChildState(childs) {
      let checked = 0;
      let halfCount = 0;
      for (let i = 0, j = childs.length; i < j; i++) {
        const n = childs[i];
        if (this.tts_rowSelectedMap[n[this.tts_rowKey]] === true) {
          checked++;
        } else {
          if (this.tts_rowIndeterminateMap[n[this.tts_rowKey]]) {
            halfCount++;
          }
        }
      }
      let all = checked === childs.length;
      // all 为ture时 none一定false; checked不等于0时,none一定为false;
      // checked 等于0时 还要判断halfCount是否为0
      let none = all
        ? false
        : checked === 0
        ? halfCount
          ? false
          : true
        : false;
      return {
        all,
        none,
        half: (checked > 0 && checked < childs.length) || halfCount,
      };
    },
    // 当列表数据更新时 清除数据
    ttsClearCacheData() {
      this.tts_isCheckedAll = false;
      this.tts_allIndeterminate = false;
      this.ttsManualClearData();
      this.tts_nodeParentMap = {};
      this.tts_tableData = [];
    },
    // 当列表数据未更新 需要手动清除选中时
    ttsManualClearData() {
      this.tts_isCheckedAll = false;
      this.tts_allIndeterminate = false;
      for (let k in this.tts_rowSelectedMap) {
        this.$set(this.tts_rowSelectedMap, k, false);
      }
      for (let k in this.tts_rowIndeterminateMap) {
        this.$set(this.tts_rowIndeterminateMap, k, false);
      }
    },
  },
};