Vue3后台管理系统之菜单管理-菜单权限-动态路由

3,041 阅读2分钟

前言

一个系统拥有许多用户,每个用户的身份不同,能访问到的系统资源、页面也不同,而菜单管理正是控制用户访问页面的好办法,本文提供菜单管理中菜单权限和动态路由的实现思路

页面

image.png

思路

  • 正常情况登录成功后跳转至系统首页,但在跳转前需做判断是否初始化路由列表

  • 路由已初始化则放行跳转

  • 路由未初始化则先获取用户权限,根据用户权限返回路由列表,处理返回的路由列表,过滤不显示的路由,将处理好的路由添加到路由中并且修改路由初始化状态。

  • 将菜单转换成路由格式

    • 获取到的菜单为对象数组,每个元素记录了该路由的元信息(路由名称、路由路径、图标、排序、父级路由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})
  }  
})