前言
一个系统拥有许多用户,每个用户的身份不同,能访问到的系统资源、页面也不同,而菜单管理正是控制用户访问页面的好办法,本文提供菜单管理中菜单权限和动态路由的实现思路
页面
思路
-
正常情况登录成功后跳转至系统首页,但在跳转前需做判断是否初始化路由列表
-
路由已初始化则放行跳转
-
路由未初始化则先获取用户权限,根据用户权限返回路由列表,处理返回的路由列表,过滤不显示的路由,将处理好的路由添加到路由中并且修改路由初始化状态。
-
将菜单转换成路由格式
- 获取到的菜单为对象数组,每个元素记录了该路由的元信息(路由名称、路由路径、图标、排序、父级路由ID、路由ID等)
主要代码
//src/utils/menu.ts
//假设从数据库获取到的菜单
const menuData = [
{ id: "1", parentId: "0", name: "仪表盘", path: "/plan", identification: "plan", sort: 1, icon: "sun",title:'仪表盘',component:'/plan' },
{ id: "2", parentId: "0", name: "日志管理", path: "/file", identification: "file", sort: 2, icon: "sun" },
{ id: "3", parentId: "0", name: "权限管理", path: "/permission", identification: "file", sort: 3, icon: "sun" },
{ id: "3-1", parentId: "3", name: "角色管理", path: "/role", identification: "role", sort: 0, icon: "sun" },
{ id: "3-2", parentId: "3", name: "菜单管理", path: "/menu", identification: "menu", sort: 1, icon: "sun" },
{ id: "3-3", parentId: "3", name: "用户管理", path: "/user", identification: "user", sort: 1, icon: "sun" },
{ id: "1-1-1", parentId: "1-1", name: "1-1-1" },
{ id: "2-1", parentId: "2", name: "2-1" },
{ id: "1-1", parentId: "1", name: "1-1" }
];
//需要转换成标准的路由格式结构
/**{
name: "权限管理",
path: "/permission",
meta: {title: "权限管理",icon: "permission",sort: "1"},
component: () => import("@/pages/permission"),
childent:[
{name: "角色管理",path: "/role",meta: {title: "角色管理",icon: "role",sort: "1",component: () => import("@/pages/permission"),childent:[...]},
{name: "菜单管理",path: "/menu",meta: {title: "菜单管理",icon: "menu",sort: "2"},
{name: "用户管理",path: "/user",meta: {title: "用户管理",icon: "user",sort: "3"}]}
*/
//获取属于同一父亲的子路由
function childrenData(parentId) {
return menuData.filter(item => item.parentId === parentId);
}
//递归构建子路由,childent[]
function buildTree(arr) {
arr.forEach(item => {
const { title, identification, sort, icon, component } = item;
item.meta = { title, identification, sort, icon };
item.component = () => import("@/pages/" + component);
const children = childrenData(item.id);
if (children.length > 0) {
item.children = children;
buildTree(children);
}
});
}
//生成树形路由结构
function getMenuTree(arr) {
//获取根路由,一级菜单
const rootData = arr.filter(item => item.parentId === "0");
buildTree(rootData);
return rootData
}
const menuList = getMenuTree(menuData);
//构建完路由后,利用router.addRoute()方法添加路由
meuList.forEach(menu=>router.addRouter(menu))
export default menuList
//动态路由-也就是系统中的左侧菜单栏显示的可访问页面
//sideBar.vue
<template>
<div>
<el-menu
router
collapse-transition="true"
mode="vertical"
>
<EpMenu :routeList ="menuList" />
</el-menu>
</div>
</template>
<script lang="ts" setup>
import EpMenu from './components/EpMenu.vue'
import menuList from '@/utils/menu.ts'
</script>
//EpMenu.vue
//存在子项children则渲染el-sub-menu,并递归调用自身EpMenu组件,否则渲染el-menu-item
<script setup lang="ts">
const props = defineProps(["routeList"]);
</script>
<template>
<template v-for="item in props.routeList" :key="item.path">
<el-sub-menu
v-if="item.children && item.children.length > 0"
:index="item.path"
>
<template #title>
<i><el-icon :name="item.meta.icon"></el-icon></i>
<span>{{ item.meta.title }}</span>
</template>
<EpMenu :routeList="item.children" />
</el-sub-menu>
<el-menu-item v-else :index="item.path" >
<el-icon :name="item.meta.icon"></el-icon>
<template #title>
<span>{{ item.meta.title }}</span>
</template>
</el-menu-item>
</template>
</template>
//在用户登录成功后,利用路由跳转前生命钩子-beforeEach,判断是否获取过路由
router.beforeEach( async (to,from,next)=>{
...
//是否获取过路由
if(!isInitRouter){
await getMenu()//获取菜单,构建路由
next({...to,replace:true})
}
})