原文链接: 解决 Vue3 + Element Plus 树形表格全选多选以及子节点勾选的问题
前言
最近用到了 Element Plus
组件库的中的树形表格,但官网例子只能做到一层勾选,不能做到多层勾选,无法满足业务需求,所以研究了下,如何在子节点选满的情况下自动勾选上父节点? 勾选父节点时自动勾上全部子节点?
效果
从图中可看出,已支持父子节点联动,最后勾选的行数据保存在 multipleDevCreateList
。
代码
interface nodeItem {
Path: string //路径
Capacity: string // 空间
Parent: string // 父节点(如果空就是根节点)
Mount: string // 挂载点
Typstr: string // 类型
IsUsed: boolean // 是否使用
Children?: nodeItem[]
}
const multipleDevCreateRef = ref>()
const multipleDevCreateList = ref([])
const handleSelectionChange = (value: nodeItem[]) => {
multipleDevCreateList.value = multipleDevCreateRef.value?.getSelectionRows()
}
// 转化前数据:
/* [
{
"Capacity": "20.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdb",
"TypStr": "disk"
},
{
"Capacity": "19.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdb",
"Path": "/dev/sdb1",
"TypStr": "part"
},
{
"Capacity": "200.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdc",
"TypStr": "disk"
},
{
"Capacity": "190.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdc",
"Path": "/dev/sdc1",
"TypStr": "part"
},
{
"Capacity": "9.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdc",
"Path": "/dev/sdc2",
"TypStr": "part"
},
{
"Capacity": "20.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdd",
"TypStr": "disk"
},
{
"Capacity": "19.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdd",
"Path": "/dev/sdd1",
"TypStr": "part"
}
] */
// 转化后的数据
const tableData = ref([
{
"Capacity": "200.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdc",
"TypStr": "disk",
"Children": [
{
"Capacity": "190.GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdc",
"Path": "/dev/sdc1",
"TypStr": "part"
},
{
"Capacity": "9.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdc",
"Path": "/dev/sdc2z",
"TypStr": "part"
}
]
},
{
"Capacity": "20.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdd",
"TypStr": "disk",
"Children": [
{
"Capacity": "19.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdd",
"Path": "/dev/sdd1",
"TypStr": "part"
}
]
},
{
"Capacity": "20.0GB",
"IsUsed": false,
"Mount": "",
"Parent": "",
"Path": "/dev/sdb",
"TypStr": "disk",
"Children": [
{
"Capacity": "19.9GB",
"IsUsed": false,
"Mount": "",
"Parent": "/dev/sdb",
"Path": "/dev/sdb1",
"TypStr": "part"
}
]
}
])
const tableRowClassName = ({ row }: { row: nodeItem }) => {
// 被使用了的设备 颜色加深 原生UI 不太明显
if (row.IsUsed === true) {
return 'disabled-row'
} else {
return ''
}
}
const selectable = (row: nodeItem) => {
return row.IsUsed === false
}
const setChildren = (children: nodeItem[], type: boolean) => {
// 编辑多个子层级
children.map((j: nodeItem) => {
toggleSelection(j, type)
if (j.Children) {
setChildren(j.Children, type)
}
})
}
// 设置父级选中/取消
const setParent = (currentRow: any, type: boolean, parent: nodeItem[], selectionLists: nodeItem[]) => {
if (!parent.length) {
parent = tableData.value
}
let allSelect: any[] = []
parent.forEach((item: nodeItem) => {
if (item.Children) {
// 注:Parent 是当前选中节点的所有父节点的一个字符串形式的数据,这个很关键
if (currentRow.Parent === item.Path) {
// 选中
if (type) {
selectionLists.forEach((k: nodeItem) => {
item.Children?.forEach((j: nodeItem) => {
if (k.Path == j.Path) {
allSelect.push(j)
}
})
})
if (allSelect.length == item.Children.length) {
toggleSelection(item, type)
selectionLists.push(item)
select(selectionLists, item)
} else {
setParent(currentRow, type, item.Children, selectionLists)
}
} else {
// 取消选中
toggleSelection(item, type)
setParent(currentRow, type, item.Children, [])
}
}
}
})
}
const toggleSelection = (row: nodeItem, select: boolean) => {
// 编辑多个子层级
if (row) {
multipleDevCreateRef.value?.toggleRowSelection(row, select)
}
}
// 选中父节点时,子节点一起选中/取消
const select = (selection: nodeItem[], row: nodeItem) => {
const hasSelect = selection.some((el: nodeItem) => {
return row.Path === el.Path
})
if (hasSelect) {
if (row.Children) {
// 解决子组件没有被勾选到
setChildren(row.Children, true)
}
// 子节点被全勾选,父节点也勾上
setParent(row, true, [], selection)
} else {
if (row.Children) {
setChildren(row.Children, false)
}
// 子级取消选中, 传入当前选中节点, 所有父级取消选中
setParent(row, false, [], [])
}
}
// 选择全部
const selectAll = (selection: nodeItem[]) => {
// tabledata第一层只要有在selection里面就是全选
const isSelect = selection.some((el: nodeItem) => {
const tableDataPaths = tableData.value.map((j: nodeItem) => j.Path)
return tableDataPaths.includes(el.Path)
})
// tableDate第一层只要有不在selection里面就是全不选
const isCancel = !tableData.value.every((el: nodeItem) => {
const selectPaths = selection.map(j => j.Path)
return selectPaths.includes(el.Path)
})
if (isCancel) {
tableData.value.map((el: nodeItem) => {
if (el.Children) {
// 解决子组件没有被勾选到
setChildren(el.Children, false)
}
})
}
if (isSelect) {
selection.map(el => {
if (el.Children) {
// 解决子组件没有被勾选到
setChildren(el.Children, true)
}
})
}
}
结语
应该没什么 bug ,遇到 bug 记得留言!