一开始打算做两种模式的路由权限,最后还是分成了3种,分别是:
- 前端固定路由,所有路由是固定的,通过权限过滤菜单和显示
- 前端动态路由,通过权限过滤路由表和菜单
- 后端动态路由,获取接口返回数据,挂载路由表和菜单
VITE_PERMISSION_MODE = 'CONSTANT'
# VITE_PERMISSION_MODE = 'FRONT'
# VITE_PERMISSION_MODE = 'BACK'
因为权限配置灵活,实际工作中一般采用第3种。在个人demo或者权限简单的项目中,第一种也够用。
简要流程
下图是一个简单流程说明,主要在路由的全局前置守卫router.beforeEach
体现。
各个模块的路由对应views,分文件放在router/modules
,方便后面统一使用
路由入口文件 router/index.ts
- 存放系统固定的几个路由(登录、注册、403、404…)
const constantRoutes: RouteRecordRaw[] = [{path: '/',name: 'Home',redirect: '/dashboard',hidden: true,meta: {title: 'home'}},{path: '/login',name: 'Login',hidden: true,meta: {title: 'signIn'},component: () => import(/* webpackChunkName: "login" */ '../views/login/login.vue')},{path: '/403',name: '403',hidden: true,meta: {title: '没有权限'},component: () => import(/* webpackChunkName: "400" */ '../views/403.vue')}
]
const lastRoutes = [{path: '/:pathMatch(.*)*',name: '404',hidden: true,meta: {title: '404'},component: () => import(/* webpackChunkName: "400" */ '../views/404.vue')}
]
- 获取router/modules文件夹中注册个模块路由
const modules = import.meta.glob('./modules/**/*.ts', { eager: true })let routeModuleList: RouteRecordRaw[] = []// 获取路由并排序
Object.values(modules).forEach((key: any) => {const mod = key.default || []const modList = Array.isArray(mod) ? [...mod] : [mod]routeModuleList.push(...modList)
})
- 导出挂载在app上的router。
前端固定路由模式会在此把所有路由挂载上。动态路由只需要挂载constanctRoutes,动态路由在前置守卫中挂载。
let routes = constantRoutes// 前端固定路由模式
if (import.meta.env.VITE_PERMISSION_MODE === 'CONSTANT') {routes = [...routeModuleList, ...constantRoutes]
}const router = createRouter({history: createWebHashHistory(),routes
})export default router
export { constantRoutes, routeModuleList, lastRoutes }
除了router文件夹中的文件,我们还需要:
- useUserStore 管理用户状态,判断路由权限时会用到它,侧边栏菜单的路由表也暂时放在这里。
- 两个API:获取用户信息、获取路由信息
通用判断
当一个导航触发时,我们通过前置守卫来处理各种情况下的导航——判断是否放行,或是跳转其他页面。
代码放在router/permission.ts,如果你习惯放在src下也没问题。
-
如果用户未登录,
当前路由在白名单则直接放行,否则跳转到登录页面/login
-
如果用户已登录,导航到/login,则直接进入系统首页
/
// 用户已登录if (useUser.userid) {if (to.path === '/login') {return '/'}...} else {// 白名单,直接放行if (whiteList.indexOf(to.path) > -1) return true// 非白名单,去登录else return '/login'}
继续往下走,需要用到开始说的3种路由模式分别处理。
1. 前端固定路由
我们扩展了_RouteRecordBase
接口,增加roles用于保存可访问路由的角色
declare module 'vue-router' {interface _RouteRecordBase {hidden?: boolean}interface RouteMeta {order?: numberroles?: array}
}
router/modules/system.ts
...{path: 'account',name: 'account',meta: {title: 'accountManagement',roles: [RoleEnum.USER] // 用于判断角色权限},component: () => import('~/views/system/account/index.vue')},...
在登录状态下,只需要判断用户角色是否能够访问这个路由即可,如果没有权限则跳转去403页面
// 前端固定路由模式,如果没有权限,进入403页面if (import.meta.env.VITE_PERMISSIOIN_MODE === 'CONSTANT' &&to.meta.roles &&!to.meta.roles.includes(role)) {return '/403'}
前端动态路由模式
无论是前端还是后端动态路由模式,都需要动态挂载。
// 前端固定路由模式,如果没有权限,进入403页面if (...}// 前端动态路由和后端动态路由,动态挂载路由else {if (!to.redirectedFrom) {await addAsyncRoutes(router, useUser)return { ...to, replace: true }} else return true}
前端模式中,通过路由角色权限和当前用户角色比对,筛选出有权限的路由,挂载到router上
后端模式中,通过接口返回数据,组装成前端可用的路由数据,挂载到router上
动态挂载路由: router.addRoute(route)
在此,因为addRoute不会改变router.optioins.routes(创建路由器时的原始routes),所以挂载后的路由数据,我们需要保存起来,以便侧边栏菜单使用。
async function addAsyncRoutes(router, userStore) {const permissionMode = import.meta.env.VITE_PERMISSIOIN_MODElet filteredRoutes = <RouteRecordRaw[]>[]// 前端动态路由模式if (permissionMode === 'FRONT') {filteredRoutes = filterFunc(routeModuleList)}// 后端动态路由模式if (permissionMode === 'BACK') {let routes = await getBackAsyncRoutes(userStore.userid)filteredRoutes = formatAsyncRoutes(routes)}console.log(filteredRoutes)filteredRoutes.forEach((val) => router.addRoute(val))userStore.setAsyncRoutes(filteredRoutes) // 保存最新的路由信息
}
所以前端动态路由,只需过滤出如何角色权限的路由即可,在router/index导出的模块路由数据中比对role即可。
后端动态路由
它和前端模式的区别就是,路由数据通过接口获取,拿到数据后,最重要的操作就是重新拼装成前端可用的形式。
async function getBackAsyncRoutes(userid: number) {return await systemApi.getRoutes({ userid })
}const modules = import.meta.glob('~/views/**/**.vue')// 把接口路由组装成前端可用结构
function formatAsyncRoutes(routes: any[]) {routes.forEach((r) => {if (r.component === 'layout' || !r.component) {r.component = Layout} else {r.component = modules[`/src/views${r.component}`]}if (r.children && r.children.length > 0) {r.children = formatAsyncRoutes(r.children)}})return routes
}
产品需求千千万,万变不离其宗 😄
写在最后
路由权限这块,这也是项目最初立下的Flag,在我✍️写到系统第18篇文章的时候终于写了,刚开始的时候还是碰到一些问题,但是当我细细梳理,画出思维导图,写完文章后,觉得一切都清晰起来。
如果一味前行不作停留,不去回顾和总结,很多东西带着一知半解就过去了忘记了。
在忙碌的项目中,我觉得自己变成了一个螺丝钉,没有丝毫成长。停下来做一些输出和总结,整装待发反而更好。
项目地址
本项目GIT地址:https://github.com/lucidity99/mocha-vue3-system
如果有帮助,给个star ✨ 点个赞👍