文章目录
- 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. 使用