Vue后台的角色权限控制 — 动态加载路由

246 阅读2分钟

场景:多角色管理系统中,会根据不同的角色划分不同的路由

  • 正常的路由

    const routes = [
        {
            path: '/',
            component: () => import('~/layouts/Admin.vue'),
            children: [
                { 
                    path: '/',
                    alias: '/home',
                    meta: {
                        title: '后台首页'
                    },
                    component: () => import('~/pages/Index.vue')
                },
                { 
                    path: '/category/list',
                    meta: {
                        title: '分类列表'
                    },
                    component: () => import('~/pages/category/List.vue')
                },
                { 
                    path: '/goods/list',
                    meta: {
                        title: '商品管理'
                    },
                    component: () => import('~/pages/goods/List.vue')
                },
            ]
        },
        {
            path: '/login',
            meta: {
                title: '登录页'
            },
            component: () => import('~/pages/Login.vue')
        },
        {
            path: '/:pathMatch(.*)*',
            component: () => import('~/pages/404.vue')
        }
    ]
    
  • 假如要动态渲染菜单

    • 后端返回的菜单的数据列表如下(可自行和后端沟通)
[
        {
            name: "后台面板",
            icon: "help",
            frontpath: null,
            child: [
                {
                    name: "主控台",
                    icon: "home-filled",
                    frontpath: "/",
                }
            ]
        },
        {
            name: "商品管理",
            icon: "shopping-bag",
            frontpath: null,
            child: [
                {
                    name: "商品管理",
                    icon: "shopping-cart-full",
                    frontpath: "/goods/list",
                },
                {
                    name: "分类管理",
                    icon: "menu",
                    frontpath: "/category/list",
                }
            ]
        }
    ]
  • 动态路由开始

router/index.js

// 默认路由,所有用户共享
// 先写出所有角色都通用的路由
const routes = [
    {
        path: '/',
        // 加上name
        // router.addRoute('admin', { path: 'settings', component: AdminSettings })
        name: 'admin',
        component: () => import('~/layouts/Admin.vue'),
    },
    {
        path: '/login',
        meta: {
            title: '登录页'
        },
        component: () => import('~/pages/Login.vue')
    },
    {
        path: '/:pathMatch(.*)*',
        component: () => import('~/pages/404.vue')
    }
]


// 动态路由,用于匹配菜单动态添加路由
// 把所有路由都定义出来,然后根据角色返回的路由列表匹配再动态添加
const asyncRoutes = [
    { 
        path: '/',
        name: '/',
        meta: {
            title: '后台首页'
        },
        component: () => import('~/pages/Index.vue')
    },
    { 
        path: '/category/list',
        name: '/category/list',
        meta: {
            title: '分类列表'
        },
        component: () => import('~/pages/category/List.vue')
    },
    { 
        path: '/goods/list',
        name: '/goods/list',
        meta: {
            title: '商品管理'
        },
        component: () => import('~/pages/goods/List.vue')
    },
]

// 动态添加路由的方法

export function addRoutes(menus) {
	// 是否匹配了新的路由
    let hasNewRouters = false
    const addRoutesByMenus = (arr) => {
        arr.forEach(menuItem => {
        	// 匹配菜单数据和动态路由的路径是否有相同
            let item = asyncRoutes.find(obj => obj.path == menuItem.frontpath)
            // 如果匹配到了,并且当前路由里没有相同的
            // router.hasRoute(name)
            // 它的参数是name
            if (item && !router.hasRoute(item.path)) {
            	// 添加路由
                router.addRoute("admin", item)
                hasNewRouters = true
            }
            // 如果有子路由,递归
            if (menuItem.child && menuItem.child.length > 0) {
                addRoutesByMenus(menuItem.child)
            }
        });
    }

    addRoutesByMenus(menus)
    
    console.log(router.getRoutes())
    
    return hasNewRouters
}


// 全局路由守卫的时候动态去加载它
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
    
    // 是否已经匹配了新的路由
    let hasNewRouters = false
    // 如果已登录,自动获取用户信息,并存储到vuex中
    if (token) {
    	// 获取到菜单参数
        const { menus } = await store.dispatch('GetInfo')
        hasNewRouters = addRoutes(menus)
    }

    next()
})

此时动态路由已经添加上了,点击菜单的跳转(导航栏输入)已经是可以跳转了

但是,刷新浏览器的时候会发现,找不到当前路由

原因是:router.addRoute()只支持 router.push()router.replace()手动导航,才能显示该新路由

问题: 1. 刷新找不到路由 2. 每次切换路由都会请求一次菜单并且重新渲染路由

  • 解决方法

    • 这时hasNnewRouters参数就起作用了
    • 定义一个变量防止重复请求
// 是否已经请求了菜单
let hasGetInfo = false

router.beforeEach(async (to, from, next) => {
    
    // 是否已经匹配了新的路由
    let hasNewRouters = false
    // 如果已登录,自动获取用户信息,并存储到vuex中
    if (token && !hasGetInfo) {
    	// 获取到菜单参数
        const { menus } = await store.dispatch('GetInfo')
        hasGetInfo = true
        hasNewRouters = addRoutes(menus)
    }

    hasNewRouters ? next(to.fullPath) : next()
})