头图
原文链接: 解决 Vue3 + Element Plus 树形表格全选多选以及子节点勾选的问题

前言

最近用到了 Element Plus 组件库的中的树形表格,但官网例子只能做到一层勾选,不能做到多层勾选,无法满足业务需求,所以研究了下,如何在子节点选满的情况下自动勾选上父节点? 勾选父节点时自动勾上全部子节点?

效果

从图中可看出,已支持父子节点联动,最后勾选的行数据保存在 multipleDevCreateList

代码

<el-table ref="multipleDevCreateRef" v-model:selected-row-keys="multipleDevCreateList" :data="tableData"
    style="width: 100%" row-key="Path" default-expand-all @select="select" @select-all="selectAll"
    @selection-change="handleSelectionChange" :tree-props="{ children: 'Children' }"
    :row-class-name="tableRowClassName">
    <el-table-column type="selection" width="55" :selectable="selectable" />
    <el-table-column property="Path" label="设备名" width="240" />
    <el-table-column property="TypStr" label="类型" />
    <el-table-column property="Mount" label="挂载点" />
    <el-table-column property="Capacity" label="容量" />
</el-table>
interface nodeItem {
    Path: string //路径
    Capacity: string // 空间
    Parent: string // 父节点(如果空就是根节点)
    Mount: string  // 挂载点
    Typstr: string // 类型
    IsUsed: boolean // 是否使用
    Children?: nodeItem[]
}

const multipleDevCreateRef = ref<InstanceType<typeof ElTable>>()
const multipleDevCreateList = ref<nodeItem[]>([])
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<any[]>([
    {
        "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 记得留言!

6289ec15a6149.jpg


唐志远
25 声望5 粉丝