el-tree(element-plus)搜索树形菜单,并且带全选多选功能

如下图所示

 采用的是elementui的元素来进行此功能的开发,下面为代码示例

<template>
    <div class="body">
        <div class="left">
            <el-input v-model="filterText" placeholder="请输入要搜索的知识点" />
            <el-button type="primary" @click="selectAll" :disabled="Fselect">{{ checkAllText }}</el-button>
            <el-tree ref="treeRef" class="filter-tree" :data="filteredData" default-expand-all
                :filter-node-method="(filterNode as any)" :render-after-expand="false" multiple show-checkbox
                @check="handleNodeClick" node-key="id" />
        </div>
        <el-divider direction="vertical" />
        <div class="right">
            <p>已选{{ selectNum }}个</p>
            <el-tag type="info" size="large" v-for="(el, i) in childrenValues" :key="i" style="margin: 5px 0 0 5px;"
                @close="handleClose(el)" closable>
                {{ el.label }}
            </el-tag>
        </div>
    </div>
</template>

<script lang='ts' setup>
import { computed, ref, watch } from 'vue'
import { ElTree } from 'element-plus'

interface Tree {
    id: number
    label: string
    children?: Tree[]
}
//是否全选
const isCheckAll = ref<boolean>(false);
const checkAllText = computed(() => isCheckAll.value ? '取消全选' : '全选');
const selectNum = ref<number>(0);
const filterText = ref<string>('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const Fselect = ref<boolean>(false)
const data: Tree[] = [
    {
        id: 1,
        label: '计算机组成原理',
        children: [
            {
                id: 2,
                label: '寄存器1',
            },
        ],
    },
    {
        id: 3,
        label: '中央处理器',
        children: [
            {
                id: 4,
                label: '寄存器2',
            },
        ],
    },
    {
        id: 5,
        label: '寄存器',
        children: [
            {
                id: 6,
                label: '寄存器3',
            },
        ],
    },
    {
        id: 7,
        label: '寄存器',
        children: [
            {
                id: 8,
                label: '寄存器4',
            },
        ],
    }
]



watch(filterText, (val) => {
    treeRef.value!.filter(val)
    seleHig(childrenValues.value)

    if (val.length == 0) {
        Fselect.value = false
    } else {
        Fselect.value = true
    }
})


//选中高亮
const seleHig = (arr: { id: number, label: string }[]) => {
    const levelOneNodes = arr.map((node: Tree) => node.id);
    const levelTwoNodes = arr.flatMap((node: Tree) =>
        node.children ? node.children.map((child: Tree) => child.id) : []
    );
    defaultCheckedKeys.value = levelOneNodes.concat(levelTwoNodes);
    (treeRef.value as any).setCheckedKeys(defaultCheckedKeys.value);
}
const filterNode = (value: string, data: Tree) => {
    if (!value) return true
    return data.label.includes(value)
}


const filteredData = computed(() => {
    const result = data.filter((node) => filterFn(filterText.value, node))
    return result
})



const filterFn = (filterText: string, node: Tree) => {
    // 如果节点匹配过滤器文本,则将其返回
    if (node.label.includes(filterText)) {
        return true
    }
    // 否则,递归遍历该节点的所有子节点,查找匹配项
    if (node.children) {
        for (let i = 0; i < node.children.length; i++) {
            if (filterNode(filterText, node.children[i])) {
                return true
            }
        }
    }

    // 如果没有找到匹配项,则返回false
    return false
}






//选中的数据操作

/**
 * @param { id: number, label: string }>获取ref内选中的children的值
 */
const childrenValues = ref<{ id: number, label: string }[]>([])
const handleNodeClick = () => {
    const checkedNodes = (treeRef.value as any).getCheckedNodes()
    childrenValues.value = checkedNodes.flatMap(({ children }: Tree) =>
        children ? children.map(({ id, label }: Tree) => ({ id, label })) : []
    );
}

//默认选中的节点
const defaultCheckedKeys = ref<number[]>([]);

const selectAll = (): void => {
    class Person {
        getChildren = async (): Promise<void> => {
            try {
                isCheckAll.value = !isCheckAll.value;
                if (isCheckAll.value) {
                    seleHig(filteredData.value);
                    handleNodeClick();
                } else {
                    defaultCheckedKeys.value = [];
                    (treeRef.value as any).setCheckedKeys([]);
                    childrenValues.value = []
                }
            } catch (error) {
                // 在这里处理错误
                console.error('Error occurred:', error);
            }
        }
    }
    new Person().getChildren();
}



//监听childrenValues与data--children的差距
watch(childrenValues, (newVal: Tree[]) => {
    selectNum.value = newVal.length
    const childrenVa = filteredData.value.flatMap(node => node.children ?? []).map(child => child);
    if (newVal.length == childrenVa.length) {
        // 全选了
        isCheckAll.value = true;
    } else {
        // 部分选中或全没选
        isCheckAll.value = false;
    }
})
const handleClose = (tag: any) => {
    const index = childrenValues.value.findIndex((item) => item.id === tag.id);
    if (index !== -1) {
        childrenValues.value.splice(index, 1);
    }
    seleHig(childrenValues.value)
    if (childrenValues.value.length == 0) {
        isCheckAll.value = false;
    }
}
defineExpose({
    childrenValues
})
</script>

<style lang="scss" scoped>
.body {
    display: flex;
    justify-content: space-around;

    .el-divider--vertical {
        height: auto;
    }

    .left {
        width: 50%;
        position: relative;

        .el-tree {
            background: #F2F2F9;
        }

        .el-input {
            width: 70%;
            margin-bottom: 50px;
        }

        .el-button {
            position: absolute;
            top: 40px;
            right: 0;
            background: #060E83;
            color: #fff;
        }
    }

    .right {
        width: 50%;
        box-sizing: border-box;
        padding: 0 0 0 30px;
    }
}

:deep(.el-tree-node__content) {
    display: flex !important;
    align-items: center !important;
    height: 40px !important;
    cursor: pointer !important;
}

:deep(.el-tree-node__content):hover {
    background: none;
}

:deep(.el-tree-node:focus>.el-tree-node__content) {
    background: none;
}

:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
    background-color: #060E83;
    border-color: #060E83;
}

.el-tag--large {
    padding: 20px 20px;
    height: 32px;
    font-size: 16px;
}
</style>

下面示例是对接口数据以及修复的一些bug操作,可以相互对照一下有哪些不同

<template>
    <div class="body">

        <div class="left">
            <el-input v-model="filterText" placeholder="请输入要搜索的知识点" :offset="80" />
            <el-button type="primary" @click="selectAll" :disabled="Fselect" :offset="100">{{ checkAllText
            }}</el-button>
            <el-scrollbar>
                <el-tree ref="treeRef" class="filter-tree" :data="filteredData" :filter-node-method="(filterNode as any)"
                    :render-after-expand="false" multiple show-checkbox @check="handleNodeClick"
                    node-key="knowledgePointsNo" :props="{ label: 'knowledgePointsName' }" />
            </el-scrollbar>
        </div>
        <el-divider direction="vertical" />
        <div class="right">
            <p>已选{{ selectNum }}个</p>
            <el-scrollbar>
                <el-tag type="info" size="large" v-for="(el, i) in childrenValues" :key="i" style="margin: 5px 0 0 5px;"
                    @close="handleClose(el)" closable>
                    {{ el.knowledgePointsName }}
                </el-tag>
            </el-scrollbar>
        </div>
    </div>
</template>

<script lang='ts' setup>
import { computed, ref, watch, onMounted } from 'vue'
import { ElTree } from 'element-plus'
import { KnowledgeList } from "@/api/teacher/question_blank";

interface Tree {
    id: number
    label: string
    children?: Tree[]
}
//是否全选
const isCheckAll = ref<boolean>(false);
const checkAllText = computed(() => isCheckAll.value ? '取消全选' : '全选');
const selectNum = ref<number>(0);
const filterText = ref<string>('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const Fselect = ref<boolean>(false)
const data: Tree[] = ref([])



watch(filterText, (val) => {
    treeRef.value!.filter(val)
    seleHig(childrenValues.value)

    if (val.length == 0) {
        Fselect.value = false
    } else {
        Fselect.value = true
    }
})


//选中高亮
const seleHig = (arr: { knowledgePointsNo: string, knowledgePointsName: string }[]) => {
    const levelOneNodes = arr.map((node: Tree) => node.knowledgePointsNo);
    const levelTwoNodes = arr.flatMap((node: Tree) =>
        node.children ? node.children.map((child: Tree) => child.knowledgePointsNo) : []
    );
    defaultCheckedKeys.value = levelOneNodes.concat(levelTwoNodes);
    (treeRef.value as any).setCheckedKeys(defaultCheckedKeys.value);
}
const filterNode = (value: string, data: Tree) => {
    if (!value) return true
    return data.knowledgePointsName.includes(value)
}


const filteredData = computed(() => {
    const result = data.value.filter((node) => filterFn(filterText.value, node))
    return result
})



const filterFn = (filterText: string, node: Tree) => {

    // 如果节点匹配过滤器文本,则将其返回
    if (node.knowledgePointsName.includes(filterText)) {
        return true
    }
    // 否则,递归遍历该节点的所有子节点,查找匹配项
    if (node.children) {
        for (let i = 0; i < node.children.length; i++) {
            if (filterNode(filterText, node.children[i])) {
                return true
            }
        }
    }
    // 如果没有找到匹配项,则返回false
    return false
}






//选中的数据操作

/**
 * @param { id: number, label: string }>获取ref内选中的children的值
 */
const childrenValues = ref<{ knowledgePointsNo: string, knowledgePointsName: string }[]>([])
const handleNodeClick = () => {
    const checkedNodes = (treeRef.value as any).getCheckedNodes({ leafOnly: false });
    childrenValues.value = checkedNodes.map(({ knowledgePointsNo, knowledgePointsName, children }: Tree) => {
        if (!children || children.length === 0) {
            return { knowledgePointsNo, knowledgePointsName };
        }
        return { knowledgePointsNo, knowledgePointsName, children };
    });
}

//默认选中的节点
const defaultCheckedKeys = ref<number[]>([]);

const selectAll = (): void => {
    class Person {
        getChildren = async (): Promise<void> => {
            try {
                isCheckAll.value = !isCheckAll.value;
                if (isCheckAll.value) {
                    seleHig(filteredData.value);
                    handleNodeClick();
                } else {
                    defaultCheckedKeys.value = [];
                    (treeRef.value as any).setCheckedKeys([]);
                    childrenValues.value = []
                }
            } catch (error) {
                // 在这里处理错误
                console.error('Error occurred:', error);
            }
        }
    }
    new Person().getChildren();
}



//监听childrenValues与data--children的差距
watch(childrenValues, (newVal: Tree[]) => {
    selectNum.value = newVal.length
    const childrenVa = filteredData.value.flatMap(node => {
        if (node.children && node.children.length > 0) {
            return node.children;
        } else {
            return [node]; // 将当前父级节点作为选中项
        }
    });
    if (newVal.length == childrenVa.length) {
        // 全选了
        isCheckAll.value = true;
    } else {
        // 部分选中或全没选
        isCheckAll.value = false;
    }
})
const handleClose = (tag: any) => {
    const index = childrenValues.value.findIndex((item) => item.knowledgePointsNo === tag.knowledgePointsNo);
    if (index !== -1) {
        childrenValues.value.splice(index, 1);
    }
    selectNum.value = childrenValues.value.length
    seleHig(childrenValues.value);

    isCheckAll.value = false;
}
onMounted(() => {
    KnowledgeList({
        "knowledgePointsName": "",
        "pageNum": "",
        "pageSize": ""
    }).then(({ data: { data: { list } } }) => {
        data.value = list
    })
})
defineExpose({
    childrenValues
})
</script>

<style lang="scss" scoped>
.body {
    display: flex;
    justify-content: space-around;
    height: 50vh;



    .el-divider--vertical {
        height: auto;
    }

    .left {
        .el-scrollbar {
            width: 100%;
            height: 43.5vh;
        }

        width: 50%;
        position: relative;

        .el-tree {
            background: #F2F2F9;
        }

        .el-input {
            width: 70%;
            margin-bottom: 50px;
        }

        .el-button {
            position: absolute;
            top: 40px;
            right: 0;
            background: #060E83;
            color: #fff;
        }
    }

    .right {
        width: 50%;
        box-sizing: border-box;
        padding: 0 0 0 30px;
    }
}

:deep(.el-tree-node__content) {
    display: flex !important;
    align-items: center !important;
    height: 40px !important;
    cursor: pointer !important;
}

:deep(.el-tree-node__content):hover {
    background: none;
}

:deep(.el-tree-node:focus>.el-tree-node__content) {
    background: none;
}

:deep(.el-checkbox__input.is-checked .el-checkbox__inner) {
    background-color: #060E83;
    border-color: #060E83;
}

:deep(.el-checkbox__input.is-indeterminate .el-checkbox__inner) {
    background-color: #060E83;
    border-color: #060E83;
}

.el-tag--large {
    padding: 20px 20px;
    height: 32px;
    font-size: 16px;
}
</style>

Element Plus 的 `el-tree` 组件提供了一个高度可定制的数据驱动树形控件,其中包含丰富的功能,包括全选、清空和反选操作。以下是关于这些功能的基本解释: 1. **全选** (All Select): 可以通过点击顶部节点的复选框,或者设置相应的事件处理器,实现树中所有子节点的选中状态同步更新。这通常需要配合 `check` 或 `check-strictly` 属性以及 `default-checked-keys` 或 `default-checked` 数据属性来配置。 2. **清空选择** (Clear Selection): 当用户希望清除当前的所有选中项时,可以触发 `clearSelection` 方法或者监听 `@selection-change` 事件,然后手动将 `checkedKeys` 或者全局的 `selectedKeys` 设置为空数组。 3. **反选** (Invert Selection): 如果希望所有非选中的项变成选中,而选中的项变为未选中,同样可以使用 `clearSelection` 然后添加所有其他非选中的节点到 `checkedKeys` 中,或者在事件处理器中对当前选中的键进行取反操作。 要实现上述操作,你需要在组件的实例上绑定这些方法,并根据实际需求调用它们。例如: ```html <template> <el-tree :data="treeData" @selection-change="handleSelectionChange"> <!-- ... --> </el-tree> </template> <script> export default { methods: { handleSelectionChange(selectedKeys) { // 全选、清空、反选处理 if (this.toggleAll) { selectedKeys.length ? this.clearSelection() : this.selectAll() } else { // 反选逻辑 const allKeys = this.treeData.map(item => item.key); selectedKeys.length && this.checkKey(notifyKeys(allKeys, selectedKeys)) } }, selectAll() { // 设置所有节点选中 this.$refs.tree.setCheckedKeys([...allKeys]) }, clearSelection() { this.$refs.tree.setCheckedKeys([]) }, checkKey(keys) { keys.forEach(key => this.$refs.tree.setChecked(key, true)) } } } </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值