Element UI组件的树形结构结合多选框使用,目前是没有的,现在遇到了个需求,需要点击全选按钮,选中全部;点击父节点需选中其所有子节点,点击子节点需反选其父节点。
对上述需求,我封装了一个较为完整的组件来实现:
效果:
实现思路
1.表格增加一列选择项,并使用el-checkbox来勾选,自定义标题(也添上复选框,用来做全选,取消全选功能)
2.需要后端配合返回每列数据含有每列数据的勾选状态的字段,如有需要禁用(那也得返回禁用状态的字段),可配置selectionConfig来自定义勾选状态和禁用状态的字段名
3.实现全选功能:声明一个全选状态的字段,然后遍历table数据,并将每条数据赋值选状态字段的值,如有子节点,递归遍历其子节点并赋值
4.单选其中一列数据:设置其子节点的状态,寻找其父节点,如有父节点,则遍历其父节点所有的子节点,判断是否都为选中状态。接着递归往上遍历其父节点。最后遍历到顶级节点,没有父节点,判断顶级节点是否都为选中,是则将全选状态的字段设为true
全选功能部分代码
// 全选
setCheckAll() {
this.tableData.forEach(item => {
if (!item[this.selectionConfig.disabled]) {
item[this.selectionConfig.checked] = this.selectAll
}
this.setChildren(item, this.selectAll)
})
}
// 设置子节点
setChildren(row, checked) {
if (row[this.treeProps.children]) {
row[this.treeProps.children].forEach(item => {
if (!item[this.selectionConfig.disabled]) {
item[this.selectionConfig.checked] = checked
}
if (row[this.treeProps.children]) {
this.setChildren(item, checked)
}
})
}
},
单选功能部分代码
// 单选
setCheck(row) {
// 设置该单元格所有子节点状态
this.setChildren(row, row[this.selectionConfig.checked])
let result = this.setParentCheck(row)
// 遍历到顶级,顶级无父节点,判断顶级节点是否都为选中
if (!result) {
let isAll = this.tableData.every(item => {
return item[this.selectionConfig.checked]
})
this.selectAll = isAll
}
},
// 设置父节点
setParentCheck(row) {
// 寻找该行数据的父节点
let parent = null
for (let i = 0; i < this.tableData.length; i++) {
parent = this.findItem(this.tableData[i], row.parentId)
if (parent != null) {
break
}
}
// 遍历该行的父节点下,所有的子节点
if (parent != null) {
if (parent[this.treeProps.children]) {
// 子节点是否全都选中,如果是则则勾选该节点
let isAll = parent[this.treeProps.children].every(item => {
return item[this.selectionConfig.checked]
})
parent[this.selectionConfig.checked] = isAll
// 递归查找该行父级的父级节点
this.setParentCheck(parent, parent[this.selectionConfig.checked])
}
} else { // 遍历到顶级,停止
return null
}
},
// 查递归找该行的父节点
findItem(row, id) {
if (row.id == id) {
return row
}
if (row[this.treeProps.children]) {
let parent = null
for (let i = 0; i < row[this.treeProps.children].length; i++) {
parent = this.findItem(row[this.treeProps.children][i], id)
if (parent) {
break
}
}
return parent
} else {
return null
}
}
完整代码:
<template>
<div>
<el-table
:data="tableData"
:row-key="rowKey"
:default-expand-all="defaultExpandAll"
:tree-props="treeProps"
>
<!-- 开启树形多选 -->
<el-table-column v-if="showSelection" width="120" align="left">
<template #header>
<el-checkbox
v-model="selectAll"
:checked="selectAll"
@change="setCheckAll"
/>
{{ selectionName }}
</template>
<template slot-scope="scope">
<el-checkbox
v-model="scope.row[selectionConfig.checked]"
:checked="scope.row[selectionConfig.checked]"
:disabled="scope.row[selectionConfig.disabled]"
@change="setCheck(scope.row)"
/>
</template>
</el-table-column>
<el-table-column
v-for="item in columnConfig"
:key="item.prop"
:prop="item.prop"
:label="item.label"
:width="item.width ? item.width : ''"
:align="item.align"
>
<template slot-scope="scope">
{{ item.ownDefinedFn ? item.ownDefinedFn(scope) : scope.row[item.prop] }}
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
props: {
// 表格树形数据
tableData: {
type: Array,
default: () => ([
{ id: '1', name: '目录1', parentId: '0', checked: false, children: [
{ id: '1-1', name: '目录1-1', parentId: '1', checked: false, children: [
{ id: '1-1-1', name: '目录1-1-1', parentId: '1-1', checked: false, children: [
{ id: '1-1-1-1', name: '目录1-1-1-1', parentId: '1-1-1', checked: false, children: [] },
{ id: '1-1-1-2', name: '目录1-1-1-2', parentId: '1-1-1', checked: false, children: [] },
{ id: '1-1-1-3', name: '目录1-1-1-3', parentId: '1-1-1', checked: false, children: [] },
] }
] }
] },
{ id: '2', name: '目录2', parentId: '0', checked: false, children: [] },
{ id: '3', name: '目录3', parentId: '0', checked: false, children: [] },
{ id: '4', name: '目录4', parentId: '0', checked: false, children: [] },
])
},
// 列配置项
columnConfig: {
type: Array,
default: () => ([
{
prop: 'default',
label: '默认',
width: '200',
align: 'center',
ownDefinedFn: () => {
return '默认' // 可返回函数
}
}
])
},
// 数据唯一标识
rowKey: {
type: String,
default: 'id'
},
// 默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: true
},
// 渲染嵌套数据的配置选项
treeProps: {
type: Object,
default: () => ({
children: 'children'
})
},
// 默认开启树形多选框
showSelection: {
type: Boolean,
default: true
},
// 树形多选框标题
selectionName: {
type: String,
default: ''
},
// 多选框配置
selectionConfig: {
type: Object,
default: () => ({
checked: 'checked',
disabled: 'disabled'
})
}
},
data() {
return {
selectAll: false, // 全选
}
},
methods: {
// 全选
setCheckAll() {
this.tableData.forEach(item => {
if (!item[this.selectionConfig.disabled]) {
item[this.selectionConfig.checked] = this.selectAll
}
this.setChildren(item, this.selectAll)
})
},
// 单选
setCheck(row) {
// 设置该单元格所有子节点状态
this.setChildren(row, row[this.selectionConfig.checked])
let result = this.setParentCheck(row)
// 遍历到顶级,顶级无父节点,判断顶级节点是否都为选中
if (!result) {
let isAll = this.tableData.every(item => {
return item[this.selectionConfig.checked]
})
this.selectAll = isAll
}
},
// 设置子节点
setChildren(row, checked) {
if (row[this.treeProps.children]) {
row[this.treeProps.children].forEach(item => {
if (!item[this.selectionConfig.disabled]) {
item[this.selectionConfig.checked] = checked
}
if (row[this.treeProps.children]) {
this.setChildren(item, checked)
}
})
}
},
// 设置父节点
setParentCheck(row) {
// 寻找该行数据的父节点
let parent = null
for (let i = 0; i < this.tableData.length; i++) {
parent = this.findItem(this.tableData[i], row.parentId)
if (parent != null) {
break
}
}
// 遍历该行的父节点下,所有的子节点
if (parent != null) {
if (parent[this.treeProps.children]) {
// 子节点是否全都选中,如果是则则勾选该节点
let isAll = parent[this.treeProps.children].every(item => {
return item[this.selectionConfig.checked]
})
parent[this.selectionConfig.checked] = isAll
// 递归查找该行父级的父级节点
this.setParentCheck(parent, parent[this.selectionConfig.checked])
}
} else { // 遍历到顶级,停止
return null
}
},
// 查递归找该行的父节点
findItem(row, id) {
if (row.id == id) {
return row
}
if (row[this.treeProps.children]) {
let parent = null
for (let i = 0; i < row[this.treeProps.children].length; i++) {
parent = this.findItem(row[this.treeProps.children][i], id)
if (parent) {
break
}
}
return parent
} else {
return null
}
}
}
}
</script>
<style lang="less" scoped>
</style>