解决 Vue3 + Element Plus 树形表格全选多选以及子节点勾选的问题

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

前言

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

效果

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

代码

HTML 复制代码
<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>
Ts 复制代码
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 记得留言!

相关推荐
范文杰1 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪1 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪2 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy2 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom3 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom3 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom3 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom3 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom3 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
LaoZhangAI4 小时前
2025最全GPT-4o图像生成API指南:官方接口配置+15个实用提示词【保姆级教程】
前端