文章目录
- 1. 使用原因
- 2. 实现
- 3. 使用
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. 使用