众所周知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);
}
},
},
};