1. 前因
公司的项目是一个toB项目,每个客户看到的页面都取决于权限配置情况。 用户不允许通过链接进入无权限页面。
2. 方案
- 在全局路由钩子 beforeEach中,调用权限接口,获取用户权限列表,并判断用户是否有权限进入即将进入的页面。
- 改写常用的路由方法:router.push router.resolve router.resolve,router.beforeResolve方法 调用方法后,检查将要进入的页面是否已经存在路由表中,如果存在则正常进入,否则通过router.addRoute将路由加入路由表后,再进入
3. 上代码
// router/hook.ts
import type { RouteLocationNormalized } from "vue-router";
import { storeToRefs } from "pinia";
import { isUndef } from "@/lib/utils";
import { ElMessage } from "element-plus";
// 你自己的权限store文件
import useXxxStore from "@/stores/xxx";
// 检查用户是否有访问权限
export async function checkAbility(to: RouteLocationNormalized, from: RouteLocationNormalized) {
const xxxStore = useXxxStore()
const xxxStoreRefs = storeToRefs(xxxStore)
const rdata = xxxStore.getAbilities();
if (rdata.status === 0) {
const abilityKey = to.meta?.ability_key;
if (abilityKey && isUndef(xxxStoreRefs.userAbilityMap.value[abilityKey])) {
ElMessage.warning('您没有访问权限,可向管理员进行申请')
return {
path: '/',
replace: true,
}
}
} else {
ElMessage.error(rdata1.message || rdata2.message || '获取系统配置失败')
}
}
// router/index.ts
import { createRouter, createWebHistory, type RouteLocationRaw } from 'vue-router'
// 不需要权限控制,所有用户都能访问的页面
import aaa from './aaa';
import { addRoute } from './routeList';
import { checkAbility } from './hook'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
...aaa,
]
})
router.beforeResolve((to, from) => {
const newRoute = addRoute(to)
if (newRoute) {
return {
...to,
replace: true
}
}
})
const originalResolve = router.resolve;
router.resolve = function resolve(to: RouteLocationRaw) {
addRoute(to)
return originalResolve.call(this, to)
}
const originalPush = router.push;
router.push = function push(to: RouteLocationRaw) {
addRoute(to)
return originalPush.call(this, to)
}
const originalReplace = router.replace;
router.replace = function replace(to: RouteLocationRaw) {
addRoute(to)
return originalReplace.call(this, to)
}
router.beforeEach(checkAbility)
export default router
// router/routeList.ts
import { isUndef } from '@/lib/utils';
// 需要权限才能访问页面们
import xxx from './xxx';
import { storeToRefs } from "pinia";
import router from '.';
import { LocationAsRelativeRaw, MatcherLocationAsPath, RouteLocationRaw, RouteRecordRaw } from 'vue-router';
// 实际数据应该取自权限接口
const userAbilityMap = {}
// 所有需要动态加入的路由列表
export const ROUTE_LIST = [
...xxx,
]
/**
* 从路由表中查找路由
* @param to
*/
export function findRoute(to: RouteLocationRaw) {
let temp: null | RouteRecordRaw = null;
if (typeof to === 'string') {
const path = to;
temp = ROUTE_LIST.find(item => {
const itemArr = item.path.split('/');
// 计算路径中的变量
const paramsLen = itemArr.filter(item => item.startsWith(':')).length;
const pathStartIndex = path.indexOf(itemArr[0]);
const pathArr = path.slice(pathStartIndex).split('/');
return itemArr.slice(0, itemArr.length - paramsLen).every((item, index) => item === pathArr[index]);
}) || null;
} else if ((to as LocationAsRelativeRaw).name) {
temp = ROUTE_LIST.find(item => item.name === (to as LocationAsRelativeRaw).name) || null;
} else if ((to as MatcherLocationAsPath).path) {
const path = (to as MatcherLocationAsPath).path;
temp = ROUTE_LIST.find(item => {
const itemArr = item.path.split('/');
const paramsLen = itemArr.filter(item => item.startsWith(':')).length;
const pathArr = path.split('/');
return itemArr.slice(0, itemArr.length - paramsLen).every((item, index) => item === pathArr[index]);
}) || null;
}
if (temp && router.hasRoute(temp.name as string)) {
return;
}
const abilityKey = temp?.meta?.ability_key || null;
return (abilityKey && !isUndef(userAbilityMap.value[abilityKey])) ? temp : null;
}
/**
* 如果路由表中不存在需跳转路由,则添加,否则直接返回
* @param to
*/
export function addRoute(to: RouteLocationRaw) {
const newRoute = findRoute(to)
newRoute && router.addRoute(newRoute)
return newRoute
}
// router/xxx.ts 路由示例
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/xxx/xxx',
name: 'xxx',
meta: {
title: 'xxx',
// 权限控制字段,根据实际情况设置
ability_key: '',
},
component: () => import('../pages/xxx/xxxx.vue'),
},
];
export default routes;
4. 结论
功能实现:
-
根据后台权限配置,动态加载路由。
-
刷新页面,路由也能被正确加载,页面显示正常。
-
通过链接直接进入某个页面,页面通过权限检测后,可被正确加载和显示。
5. 备注
目前需要特殊处理的情况:
1. 不支持:在页面中使用 router.resolve,处理没有权限的页面,生成链接后,通过window.open 进行跳转。
// 如果A用户没有 XXX页面的权限,window.open时会报错。
// 推荐的解决方案是:先检测权限情况,如果没有权限不进行后续操作
const route = router.resolve({
name: 'XXX'
})
window.open(route.href, '_blank');
2. 使用beforeEnter钩子,进行路由中转
const routes: RouteRecordRaw[] = [
{
...
beforeEnter(to, from) {
// 如果希望在这里,中转到BBB页面,需要手动操作addRoute,然后再做跳转操作
addRoute({
name: 'BBB',
})
return {
replace: true,
name: 'BBB',
}
}
},
];