首页IT科技vue3动态菜单(Vue实现角色权限动态路由详细教程,在vue-admin-template基础上修改,附免费完整项目代码)

vue3动态菜单(Vue实现角色权限动态路由详细教程,在vue-admin-template基础上修改,附免费完整项目代码)

时间2025-08-05 15:25:12分类IT科技浏览4643
导读:前言 vue-admin-template是一个最基础的后台管理模板,只包含了一个后台需要最基础的东西,如果clone的是它的master分支,是没有权限管理的,只有完整版vue-element-admin有这个功能,但是为了小小的一个权限管理而用比较复杂的有点得不偿失。...

前言

vue-admin-template是一个最基础的后台管理模板            ,只包含了一个后台需要最基础的东西                       ,如果clone的是它的master分支        ,是没有权限管理的         ,只有完整版vue-element-admin有这个功能                      ,但是为了小小的一个权限管理而用比较复杂的有点得不偿失                。

我在网上找了一堆教程和资料            ,发现要么说的很乱      ,要么说的不全                     ,最后连个完整代码都不让我白嫖(bushi)                    。自己复制粘贴过去都实现不出来                ,仔细查看发现人家写的教程漏了一写步骤/代码   ,而且还有bug(服了这些老六)       。

在自己摸索了和看了花裤衩大佬的文章后                    ,解决了一些bug自己实现出来了                    ,代码中也有详细注释            。完整代码放文末给大家了,大家记得给我star再走(不然小拳拳锤你胸口)                     。

权限管理?动态路由?

现在开发后台管理系统项目经常有权限管理的需求                ,权限管理其实就是根据不同的角色权限显示不同的路由                        ,而其中的关键就是动态路由

router.addRoutes

实现权限验证的基本思路就是: 用户登录    ,通过token获取用户对应的 role 动态根据用户的 role 算出其对应有权限的路由 通过 router.addRoutes 动态挂载这些路由

以上步骤实现的核心是routervuex            ,下面就详细介绍如何实现(按代码执行逻辑倒推

具体实现

创建vue实例的时候将vue-router挂载                       ,但这个时候vue-router挂载一些登录或者不用权限的公用的页面          。 当用户登录后        ,获取用role         ,将role和路由表每个页面的需要的权限作比较                      ,生成最终用户可访问的路由表        。 调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由                      。 使用vuex管理路由表            ,根据vuex中可访问的路由渲染侧边栏组件

本段转载自vue-element-admin的作者:花裤衩

1.router.js路由表

首先我们实现router.js路由表      ,

一共有两个路由表                     , 一个是constantRoutes                ,这个用来放没有权限要求的页面   ,每个角色都可以访问                    ,比如首页                    ,登录页; 一个是asyncRoutes 动态需要根据权限加载的路由表              。meta里面的roles就存放了页面需要的权限,这些页面只有roles数组里面的角色才能看到    。

注意:404一定要放最后面

                ,不然都会重定向到404

src/router/index.js代码如下: import Vue from vue import Router from vue-router Vue.use(Router) /* Layout */ import Layout from @/layout /** * constantRoutes * 没有权限要求的基本页面 *所有角色都可以访问 如首页和登录页和一些不用权限的公用页面 */ export const constantRoutes = [{ path: /login, component: () => import(@/views/login/index), hidden: true }, { path: /404, component: () => import(@/views/404), hidden: true }, { path: /, component: Layout, redirect: /dashboard, children: [{ path: dashboard, name: Dashboard, component: () => import(@/views/dashboard/index), meta: { title: Dashboard, icon: dashboard, } }] }, ] //异步挂载的路由 //动态需要根据权限加载的路由表 export const asyncRoutes = [{ path: /example, component: Layout, redirect: /example/table, name: Example, alwaysShow: true, meta: { title: Example, icon: el-icon-s-help, }, children: [{ path: table, name: Table, component: () => import(@/views/table/index), meta: { title: Table, icon: table, roles: [editor] } }, { path: tree, name: Tree, component: () => import(@/views/tree/index), meta: { title: Tree, icon: tree, roles: [admin, editor] } } ] }, { path: /form, component: Layout, children: [{ path: index, name: Form, component: () => import(@/views/form/index), meta: { title: Form, icon: form, roles: [editor] } }] }, { path: /nested, component: Layout, redirect: /nested/menu1, alwaysShow: true, name: Nested, meta: { title: Nested, icon: nested, }, children: [{ path: menu1, component: () => import(@/views/nested/menu1/index), // Parent router-view name: Menu1, meta: { title: Menu1, roles: [admin] }, children: [{ path: menu1-1, component: () => import(@/views/nested/menu1/menu1-1), name: Menu1-1, meta: { title: Menu1-1 } }, { path: menu1-2, component: () => import(@/views/nested/menu1/menu1-2), name: Menu1-2, meta: { title: Menu1-2 }, children: [{ path: menu1-2-1, component: () => import(@/views/nested/menu1/menu1-2/menu1-2-1), name: Menu1-2-1, meta: { title: Menu1-2-1 } }, { path: menu1-2-2, component: () => import(@/views/nested/menu1/menu1-2/menu1-2-2), name: Menu1-2-2, meta: { title: Menu1-2-2 } } ] }, { path: menu1-3, component: () => import(@/views/nested/menu1/menu1-3), name: Menu1-3, meta: { title: Menu1-3 } } ] }, { path: menu2, component: () => import(@/views/nested/menu2/index), name: Menu2, meta: { title: menu2, roles: [editor] } } ] }, // 如果需要配置重定向404页面的话                        ,需要配置在asyncRoutes的最后 { path: *, redirect: /404, hidden: true } ] // 实例化vue的时候只挂载constantRouter const createRouter = () => new Router({ // mode: history, // require service support scrollBehavior: () => ({ y: 0 }), routes: constantRoutes }) const router = createRouter() // Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 export function resetRouter() { const newRouter = createRouter() router.matcher = newRouter.matcher // reset router } export default router

2. src/permission.js动态添加路由

我们在登录成功后    ,router会重定向一个新页面            ,在跳转之前src/permission.js里面路由守卫router.beforeEach会先做一些拦截验证                       ,根据判断会做不同页面跳转和操作        ,比如没登录就先跳到登录页         ,根据获取到的用户信息roles筛选路由后动态添加路由                       。

src/permisson.js具体实现代码与注释: import router from ./router import store from ./store import { Message } from element-ui // 页面进度条组件 import NProgress from nprogress // progress bar import nprogress/nprogress.css // progress bar style import { getToken } from @/utils/auth // get token from cookie import getPageTitle from @/utils/get-page-title NProgress.configure({ showSpinner: false }) // NProgress 配置 const whiteList = [/login, /404] // 不重定向的白名单 router.beforeEach(async (to, from, next) => { // start progress bar NProgress.start() // 设置页面标题 document.title = getPageTitle(to.meta.title) // 确定用户是否已登录 const hasToken = getToken() // 判断是否存在token,没有就重新登陆 if (hasToken) { if (to.path === /login) { // 如果已登录                      ,则重定向到主页 next({ path: / }) NProgress.done() } else { // 确定用户是否通过getInfo获得了权限角色 const hasRoles = store.getters.roles && store.getters.roles.length > 0 //这里指的是src/store/getters.js的roles // console.log(hasRoles) //判断是否已经有roles了 if (hasRoles) { next(); //当有用户权限的时候            ,说明所有可访问路由已生成 如访问没权限的全面会自动进入404页面 } else { try { // get user info // 注意: roles 角色必须是对象数组! 例如: [admin] 或 ,[developer,editor] // 1. 获取roles const { roles } = await store.dispatch(user/getInfo) //第一步 // 2. 根据角色生成可访问路由图 // 获取通过权限验证的路由 const accessRoutes = await store.dispatch(permission/generateRoutes, roles) //第二步 // 3. 更新加载路由 router.options.routes = store.getters.permission_routes //第三步 // 动态添加可访问路由 router.addRoutes(accessRoutes) // console.log(store) // console.log(accessRoutes); // hack方法 确保addRoutes已完成      ,以确保地址是完整的 // 设置replace: true                     ,这样导航就不会留下历史记录 next({ ...to, replace: true }) } catch (error) { // 删除token并转到登录页面重新登录 await store.dispatch(user/resetToken) Message.error(出现错误~请重新登录) next(`/login?redirect=${to.path}`) NProgress.done() } } } } else { /* 没有token */ if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单                ,直接进入 next() } else { next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页 NProgress.done() } } })

3. store/modules/user.js

而获取+存储roles通过store/modules/user.js实现   ,筛选+存储路由又是通过通过store/modules/permission.js实现的

store/modules/user.js主要是获取和存储roles store/modules/user.js完整代码: import { login, logout, getInfo } from @/api/user import { getToken, setToken, removeToken } from @/utils/auth import { resetRouter } from @/router const getDefaultState = () => { return { token: getToken(), name: , avatar: , roles: [], } } const state = getDefaultState() const mutations = { RESET_STATE: (state) => { Object.assign(state, getDefaultState()) }, SET_TOKEN: (state, token) => { state.token = token }, SET_NAME: (state, name) => { state.name = name }, SET_ROLES: (state, roles) => { state.roles = roles }, SET_AVATAR: (state, avatar) => { state.avatar = avatar } } const actions = { // user login login({ commit }, userInfo) { const { username, password } = userInfo return new Promise((resolve, reject) => { login({ username: username.trim(), password: password }).then(response => { const { data } = response commit(SET_TOKEN, data.token) setToken(data.token) resolve() }).catch(error => { reject(error) }) }) }, // get user info getInfo({ commit, state }) { return new Promise((resolve, reject) => { // state.token之前没有传 出现了重复登陆问题 getInfo(state.token).then(response => { const { data } = response if (!data) { return reject(验证失败,请重新登录) } const { name, roles, avatar } = data if (!roles || roles.length <= 0) { reject(getInfo:roles must be a non-null array!) } commit(SET_NAME, name) commit(SET_ROLES, roles) commit(SET_AVATAR, avatar) resolve(data) }).catch(error => { reject(error) }) }) }, // user logout logout({ commit, state }) { return new Promise((resolve, reject) => { logout(state.token).then(() => { removeToken() // must remove token first resetRouter() commit(RESET_STATE) commit(SET_ROLES, []) resolve() }).catch(error => { reject(error) }) }) }, // remove token resetToken({ commit }) { return new Promise(resolve => { removeToken() // must remove token first commit(RESET_STATE) resolve() }) } } export default { // 加上这个会有报错,不加的话user/login这种方式用不了 namespaced: true, state, mutations, actions }

4. store/modules/permission.js筛选路由

store/modules/permission.js用于匹配权限                    ,筛选角色对应的路由并存储起来

import { asyncRoutes, constantRoutes } from @/router /** * 使用 meta.role 以确定当前用户是否具有权限 * @param roles * @param route */ // 匹配权限 function hasPermission(roles, route) { if (route.meta && route.meta.roles) { return roles.some(role => route.meta.roles.includes(role)) } else { return true } } /** * 通过递归过滤异步路由表 * @param routes asyncRoutes * @param roles */ export function filterAsyncRoutes(routes, roles) { const res = [] routes.forEach(route => { const tmp = { ...route } if (hasPermission(roles, tmp)) { if (tmp.children) { tmp.children = filterAsyncRoutes(tmp.children, roles) } res.push(tmp) } }) return res } const state = { routes: [], addRoutes: [] } const mutations = { SET_ROUTES: (state, routes) => { state.addRoutes = routes state.routes = constantRoutes.concat(routes) // 将过滤后的路由和constantRoutes存起来 } } // 筛选 const actions = { generateRoutes({ commit }, roles) { return new Promise(resolve => { let accessedRoutes // 管理员admin显示全部路由                    , // 我这里admin是想让它不显示全部的 想要admin能看见全部的话把注释去掉 // if (roles.includes(admin)) { // accessedRoutes = asyncRoutes || [] // } else { //过滤路由 accessedRoutes = filterAsyncRoutes(asyncRoutes, roles) // accessedRoutes这个就是当前角色可见的动态路由 // } commit(SET_ROUTES, accessedRoutes) resolve(accessedRoutes) }) } } export default { namespaced: true, state, mutations, actions }

这里的代码说白了就是干了一件事,通过用户的权限和之前在router.js里面asyncRoutes的每一个页面所需要的权限做匹配                ,最后返回一个该用户能够访问路由有哪些                。

5. store/getter.js和store/index.js

当然了                        ,新加的模块要记得引入进去

store/getter.js添加以下代码:

store/index.js

6. sidebar使用筛选后的路由

src\layout\components\Sidebar\index.vue

遍历之前算出来的permission_routers    ,通过vuex拿到之后动态v-for渲染 <sidebar-item v-for="route in permission_routes" :key="route.path" :item="route" :base-path="route.path" />

效果

如何看效果

?如果是用的vue-admin-template的mock做登录和获取用户数据            ,登录用户名为admin则role为admin                       ,用户名为editor则role为editor。

角色为admin看到的菜单栏

角色为editor看到的菜单栏

完整源码

记得给我一个star https://gitee.com/yyy1203/vue-admin-template-permission.git

有兴趣可以看看花裤衩大佬文章:https://juejin.cn/post/6844903478880370701#heading-5

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
三星平板几月发布(新一代平板电脑 三星Galaxy Note 10.1将于本月末发布 TECH2IPO创见)