vben 之 路由守卫初始化应用数据
不知道你在写前端项目时候,会不会总有一些困惑,就是在什么时候获取用户信息,以及在什么时候获取菜单信息。
让我们从 vben 中学习,路由守卫的初始化应用数据。
为什么使用路由守卫初始化应用数据?
因为用户信息和权限有关,而路由也和权限有关,所以这两个数据,需要在进入页面前进行初始化。
然后一般页面加载的话,都是有个进度条的动画效果,以及判断是否能进入页面这两个功能。
vue-router 的路由守卫可以注册多次,这样,进度条的动画效果,以及判断是否能进入页面这两个功能,就可以分次在路由守卫中处理。
通用守卫
通用守卫负责处理页面加载状态、进度条显示等基础功能。它会记录已加载的页面路径,避免重复执行动画效果。
权限访问守卫
权限访问守卫是核心的安全控制机制,包含以下处理逻辑:
1. 基础路由检查:如登录页等不需要权限验证
如果此时,有 token,且要进入登录页面,如果地址栏的 query 中有 redirect 参数,且 redirect 参数的值不是登录页,那么就跳转 redirect 参数的值,否则就跳转 home 页。 有 query 和 token,这大概是在登录页登录成功后,手动刷新页面会遇到这种情况。
2. 访问令牌验证
- 没有令牌:没登录,就没有菜单路由
- 如果路由不需要权限即可访问,那么就直接放行。
- 如果路由需要权限,那么就跳转登录页,且增加重定向参数,重定向参数的值为当前页面的路径。
3. 如果路由已经生成,那么就直接放行。
4. 如果路由没有生成,那么获取用户信息,角色,菜单,从而生成路由
最后,如果当前路由有重定向参数,那么就跳转重定向参数的值,否则就跳转 home 页。
function setupCommonGuard(router: Router) {
// 记录已经加载的页面
const loadedPaths = new Set<string>();
router.beforeEach((to) => {
to.meta.loaded = loadedPaths.has(to.path);
// 页面加载进度条
if (!to.meta.loaded && preferences.transition.progress) {
startProgress();
}
return true;
});
router.afterEach((to) => {
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
loadedPaths.add(to.path);
// 关闭页面加载进度条
if (preferences.transition.progress) {
stopProgress();
}
});
}
/**
* 权限访问守卫配置
* @param router
*/
function setupAccessGuard(router: Router) {
router.beforeEach(async (to, from) => {
const accessStore = useAccessStore();
const userStore = useUserStore();
const authStore = useAuthStore();
// 基本路由,这些路由不需要进入权限拦截
if (coreRouteNames.includes(to.name as string)) {
if (to.path === LOGIN_PATH && accessStore.accessToken) {
return decodeURIComponent(
(to.query?.redirect as string) ||
userStore.userInfo?.homePath ||
preferences.app.defaultHomePath,
);
}
return true;
}
// accessToken 检查
if (!accessStore.accessToken) {
// 明确声明忽略权限访问权限,则可以访问
if (to.meta.ignoreAccess) {
return true;
}
// 没有访问权限,跳转登录页面
if (to.fullPath !== LOGIN_PATH) {
return {
path: LOGIN_PATH,
// 如不需要,直接删除 query
query:
to.fullPath === preferences.app.defaultHomePath
? {}
: { redirect: encodeURIComponent(to.fullPath) },
// 携带当前跳转的页面,登录后重新跳转该页面
replace: true,
};
}
return to;
}
// 是否已经生成过动态路由
if (accessStore.isAccessChecked) {
return true;
}
// 生成路由表
// 当前登录用户拥有的角色标识列表
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
const userRoles = userInfo.roles ?? [];
// 生成菜单和路由
const { accessibleMenus, accessibleRoutes } = await generateAccess({
roles: userRoles,
router,
// 则会在菜单中显示,但是访问会被重定向到403
routes: accessRoutes,
});
// 保存菜单信息和路由信息
accessStore.setAccessMenus(accessibleMenus);
accessStore.setAccessRoutes(accessibleRoutes);
accessStore.setIsAccessChecked(true);
let redirectPath: string;
if (from.query.redirect) {
redirectPath = from.query.redirect as string;
} else if (to.path === preferences.app.defaultHomePath) {
redirectPath = preferences.app.defaultHomePath;
} else if (userInfo.homePath && to.path === userInfo.homePath) {
redirectPath = userInfo.homePath;
} else {
redirectPath = to.fullPath;
}
return {
...router.resolve(decodeURIComponent(redirectPath)),
replace: true,
};
});
}