21、springboot3 vue3开发平台-前端-自定义树形穿梭框,用于角色权限分配

1. 使用原因

elemenutplus 有穿梭框,但是不支持树状数据的操作,所以这里自定义树状穿梭框,用于菜单权限分配, 如下:
在这里插入图片描述

2. 实现

这里主要是将菜单列表树解构后添加修改组合再恢复
src\components\TransferTree\index.vue

<template>
    <el-dialog v-model="transferVisible" title="权限分配" width="800">
        <div class="transfer">
            <div class="contaner">
                <!---->
                <div class="item">
                    <div class="title">
                        已分配权限
                    </div>
                    <div class="tree">
                        <el-tree ref="treeRef" :data="transData" :props="transProps" show-checkbox />
                    </div>
                </div>
                <div class="transfercenter">
                    <!---->
                    <div class="transbutton">
                        <el-button type="primary" :icon="ArrowLeft" @click="leftButtonClick"></el-button>
                        <el-button type="primary"><el-icon class="el-icon--right" @click="rightButtonClick">
                                <ArrowRight />
                            </el-icon></el-button>
                    </div>
                </div>
                <!---->
                <div class="item">
                    <div class="title">
                        未分配权限
                    </div>
                    <div class="tree">
                        <el-tree ref="treeRef1" :data="transData1" :props="transProps" show-checkbox />
                    </div>
                </div>

            </div>
            <div>
                <div class="footer">
                    <el-button type="primary" @click="onDialogFormConfirmTransfer">确 定</el-button>
                    <el-button @click="onDialogFormCancelTransFer">取 消</el-button>
                </div>
            </div>
        </div>
    </el-dialog>
</template>

<script lang="ts" setup>
import {ref } from 'vue'
import {ArrowLeft, ArrowRight  } from '@element-plus/icons-vue'

const props = defineProps<{
    transDataAuth: Array<any>,
    transDataNoAuth:  Array<any>
}>()

// 传回的菜单ids
const emit = defineEmits(['returnIds', "close"])

const transferVisible = ref(true)
const transData = ref(props.transDataAuth)
const transData1 = ref(props.transDataNoAuth)
const treeRef = ref() 
const treeRef1= ref() 

const transProps = {
    id: "roleId",
    children: 'children',
    label: 'menuName',
}

// 提交对话框表单按钮事件
const onDialogFormConfirmTransfer = async () => {
    let authMenus = flattenMenuTree(props.transDataAuth)
    let ids = authMenus.map((item: any) => item.id)
    emit('returnIds', ids)
}


// 取消对话表单框按钮事件
const onDialogFormCancelTransFer = () => {
    emit('close')
}

// 授权按钮
const leftButtonClick = () => {
    let selctedMenus = treeRef1.value.getCheckedNodes(false, false)
    let ids = selctedMenus.map((item: any) => item.id)
    // 解构菜单树
    let authList = flattenMenuTree(transData.value)
    let notAuthList = flattenMenuTree(transData1.value)
    // 添加授权,删除未授权菜单
    authList.push(...selctedMenus)
    notAuthList = notAuthList.filter((item: any) => !ids.includes(item.id))
    // 重新构建菜单树
    transData.value = []
    transData1.value = []
    transData.value = buildMenuTree(filterRepetition(authList))
    transData1.value = buildMenuTree(filterRepetition(notAuthList))
}

// 取消授权按钮
const rightButtonClick = () => {
    let selctedMenus = treeRef.value.getCheckedNodes(false, false)
    let ids = selctedMenus.map((item: any) => item.id)
    // 解构菜单树
    let authList = flattenMenuTree(transData.value)
    let notAuthList = flattenMenuTree(transData1.value)
    // 添加授权,删除未授权菜单
    notAuthList.push(...selctedMenus)
    authList = authList.filter((item: any) => !ids.includes(item.id))
    // 重新构建菜单树
    transData.value = buildMenuTree(filterRepetition(authList))
    transData1.value = buildMenuTree(filterRepetition(notAuthList))
}

// 构建列表树
function buildMenuTree(menuList: any) {  
    // 创建一个映射,以便快速通过ID查找菜单项  
    const map = new Map(menuList.map((item: any) => [item.id, { ...item, children: [] }]));  
  
    // 遍历菜单列表,构建树结构  
    function attachChildren(parentId = 0) {  
        // 查找所有具有指定parentId的菜单项  
        return menuList.filter((item: any) => item.parentId === parentId).map((item: any) => {  
            const currentNode: any = map.get(item.id)
            // 递归地为当前节点附加子节点  
            currentNode.children = attachChildren(item.id)
            return currentNode;  
        });  
    }  
  
    // 注意:顶级菜单项(没有parentId或parentId为null/undefined)  
    const tree = attachChildren();  
    return tree;  
} 

function flattenMenuTree(tree: any) {  
    let result: any = [] 
    // 递归函数来遍历树并收集节点  
    function traverse(nodes: any) { 
        if (nodes) {
            nodes.forEach((node: any) => {  
            // 将当前节点添加到结果数组中  
            result.push({ ...node});
            if (node.children && node.children.length > 0) {  
                traverse(node.children);
            }  
            })
        }
    }    
    // 开始遍历树  
    traverse(tree);  
  
    // 如果不需要层级信息,直接返回结果数组  
    return result;  
}  

// 过滤重复的菜单
const filterRepetition = (menulist: any) => {
    // console.log("menulist: " + menulist)
    let seenIds = new Set();  
    // 过滤菜单项  
    let menus = menulist.filter((item: any) => {  
        // 如果当前项的id在Set中不存在,则添加到Set中,并返回true(保留该项)  
        // 否则,返回false(移除该项)  
        if (!seenIds.has(item.id)) {  
            seenIds.add(item.id);  
            return true;  
        }  
        return false;  
    })
    //console.log("menulist: " + menulist)
    return menus
}


</script>


<style lang="scss">
.transfer {
    display: flex;
    flex-direction: column;

    .contaner {
        display: flex;
        flex-direction: row;
        justify-content: space-between;
        height: 500px;

        .item {
            display: flex;
            flex-direction: column;
            width: 40%;
            height: 100%;

            .title {
                text-align: center;
                background-color: #F5F7FA;
                height: 6%;
                font-size: 16px;

            }

            .tree {
                overflow-y: scroll;
                height: 94%;
            }
        }

        .transfercenter {
            display: flex;

            padding-top: 30px;
            box-sizing: border-box;
            width: 20%;
            height: 100%;

            // border: 10px solid black; /* 边框宽度、样式、颜色 */
            // background-color: #FF0000;
            .transbutton {
                margin-top: 200px;
            }

        }


    }

    .footer {
        display: flex;
        flex-direction: row;
        justify-content: center;
        align-items: center;
    }
}
</style>

3. 使用

在这里插入图片描述
在这里插入图片描述

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不知所云,

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值