权限管理
权限管理应该是每一个后台管理系统最重要的一部分,主要分为页面权限与按钮权限。 本文主要讲解页面权限的简单实现方式。
解决思路
首先我们要把页面分成三种类型:
- 任何人,包括游客(无需登录)就可以访问的页面(e.g. 登录页,指南页)
- 登录后人人都可以访问的页面(e.g. 系统首页)
- 登录后,且拥有该页面权限的人才可以访问的页面 (e.g. 数据列表页,权限管理页)
可以总结为下图:
代码结构
分析完后,就可以开始构思代码了
- 第一步: 先将路由分为 静态路由 staticRoutes 和 动态路由 dynamicRoutes 用上图的六个页面举例子
src/router/index.js
import { createRouter, createWebHashHistory } from 'vue-router'
/* Layout */
import Layout from '@/layout/index.vue'
/* staticRoutes */
export const staticRoutes = [
{
path: '/',
hidden: true,
redirect: '/home',
},
{
path: '/home',
name: 'Home',
component: () => import('@/views/HomePage/index.vue'),
meta: { title: '首页' },
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login/index.vue'),
meta: { title: '登录' },
},
{
path: '/handbook',
name: 'Handbook',
component: () => import('@/views/CityMap/index.vue'),
meta: { title: '用户手册' },
},
]
/**
* dynamicRoutes
* the routes that need to be dynamically loaded based on user module
*/
export const dynamicRoutes = [
{
component: Layout,
hidden: true,
children: [
{
path: '/data',
name: 'Data',
component: () => import('@/views/Data/index.vue'),
meta: { title: '数据列表' },
},
{
path: '/permission',
name: 'Permission',
component: () => import('@/views/Permission/index.vue'),
meta: { title: '权限配置' },
},
],
},
]
const router = createRouter({
// hash模式配置
history: createWebHashHistory(),
routes: staticRoutes,
})
export default router
- 第二步: 设置 全局前置守卫(beforEach)
使用全局前置守卫去作为一个拦截器这样的角色,拦截每一次的路由跳转变化,在去下一个路由之前,做出一系列的判断。
src/router/permission.js
import { useUserStore, usePermissionStore } from '@/store'
import { getToken } from '@/utils/auth';
//路由白名单列表,把路由添加到这个数组,不用登陆也可以访问
const whiteList = ['/login', '/handbook']
export function setBeforeEach(router) {
router.beforeEach(async (to, from, next) => {
// 设置标题
document.title = to.meta.title
//获取token
const hasToken = getToken()
const userStore = useUserStore()
const permissionStore = usePermissionStore()
//如果存在token,即存在已登陆的令牌
if (hasToken) {
//如果用户存在令牌的情况请求登录页面,就让用户直接跳转到首页,避免存在重复登录的情况
if (to.path === '/login') {
// 直接跳转到首页,取决于你的路由重定向到哪里
next({ path: '/' })
} else {
//如果已经有令牌的用户请求的不是登录页,是其他页面
//就从store里拿到用户的信息,这里也证明用户不是第一次登录了
const isGetUserInfo = permissionStore.isGetUserInfo
if (isGetUserInfo) {
//信息拿到后,用户请求哪就跳转哪
next()
} else {
try {
// 如果有令牌,但是没有用户信息,证明用户是第一次登录,需要先设置用户信息
let accessRoutes = []
// 获取用户拥有的权限模块数组,从接口获取
// note: module must be a array! such as: ['数据列表'] or ,['数据列表','权限管理']
const { module } = await userStore.getInfo()
accessRoutes = permissionStore.generateRoutes(module)
// 动态添加可访问的路由
accessRoutes.forEach(route => {
router.addRoute(route)
})
// 把动态路由和静态路由结合后放到store里面,方便首页或者sidebar做菜单的显示隐藏
permissionStore.M_ROUTES(accessRoutes)
//成功拿到用户信息
permissionStore.M_IS_GET_USER_INFO(true)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// 如果出错了,把令牌去掉,并让用户重新去到登录页面
console.log('error: ', error)
await userStore.resetState()
next(`/login?redirect=${to.path}`)
}
}
}
} else {
//这里是没有令牌的情况
//whiteList.indexOf(to.path) !== -1)判断用户请求的路由是否在白名单里
if (whiteList.indexOf(to.path) !== -1) {
// 不是-1就证明存在白名单里,不管你有没有令牌,都直接去到白名单路由对应的页面
next()
} else {
// 如果这个页面不在白名单里,直接跳转到登录页面
next(`/login?redirect=${to.path}`)
}
}
})
}
src/store/permission.js
import { defineStore } from 'pinia'
import { staticRoutes, dynamicRoutes } from '@/router'
/**
* Use meta.moduleName to determine if the current user has permission
* @param module
* @param route
*/
function hasPermission(module, route) {
const title = route.meta?.title
return title ? module.includes(title) : true
}
/**
* Filter asynchronous routing tables by recursion
* @param routes dynamicRoutes
* @param module
*/
export function filterDynamicRoutes(routes, module) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(module, tmp)) {
if (tmp.children) {
tmp.children = filterDynamicRoutes(tmp.children, module)
}
res.push(tmp)
}
})
return res
}
export const usePermissionStore = defineStore('permission', {
state: () => {
return {
isGetUserInfo: false, // get userInfo
routes: [], //将过滤后的动态路由和静态路由集合
dynamicRoutes: [], //过滤后的动态路由
}
},
/***
*封装处理数据的函数(业务逻辑):修改数据
*/
actions: {
M_ROUTES(routes) {
this.$patch(state => {
state.dynamicRoutes = routes
state.routes = staticRoutes.concat(routes)
})
},
M_IS_GET_USER_INFO(data) {
this.isGetUserInfo = data
},
generateRoutes(module) {
return filterDynamicRoutes(dynamicRoutes, module)
},
},
})
上文中提到的getToken()和getInfo()就不展开讲了,
getToken() 方法,用于从cookie中获取到token
getInfo()方法,用户获取当前登录的用户信息,这里主要是需要拿到当前用户的权限(module)
- 第三步: 在router/index.js文件中添加路由守卫
import { setBeforeEach } from './permission'
...
/* Permission Control */
import { setBeforeEach } from './permission'
export const staticRoutes = [
...
]
export const dynamicRoutes = [
...
]
const router = createRouter({
// hash模式配置
history: createWebHashHistory(),
routes: staticRoutes,
})
/* 设置路由前置守卫 */
setBeforeEach(router)
export default router
总结
做好了以上三个步骤,其实就完成了最基本的权限管理。因为每个项目的需求不一样,接口返回的信息也可能不同,所以很多的细节没有写出来,但是思路基本上都是这一套。如果大家有更好的方案,欢迎一起学习和探讨!