Files
estate/src/permission.ts
2026-09-20 08:57:53 +08:00

141 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { to as tos } from 'await-to-js';
import router from './router';
import NProgress from 'nprogress';
import 'nprogress/nprogress.css';
import { getToken } from '@/utils/auth';
import { isHttp, isPathMatch } from '@/utils/validate';
import { isRelogin } from '@/utils/request';
import { useUserStore } from '@/store/modules/user';
import { useSettingsStore } from '@/store/modules/settings';
import { usePermissionStore } from '@/store/modules/permission';
import { ElMessage } from 'element-plus/es';
NProgress.configure({ showSpinner: false });
// 白名单:不需要登录就能访问的页面
const whiteList = ['/login', '/register', '/social-callback', '/register*', '/register/*'];
// 全局标记:动态路由是否已经添加过(只加载一次!)
let isDynamicRoutesAdded = false;
// ✅ 新增:导航锁,防止多个路由导航并发执行
let isNavigating = false;
// ✅ 关键修改:记录当前已添加的动态路由名称列表
let currentDynamicRouteNames: string[] = [];
// ✅ 关键修改:清除所有已记录的动态路由
const removeDynamicRoutes = () => {
currentDynamicRouteNames.forEach((name) => {
if (name && router.hasRoute(name)) {
router.removeRoute(name);
}
});
currentDynamicRouteNames = [];
};
// 判断是否在白名单
const isWhiteList = (path: string) => {
return whiteList.some((pattern) => isPathMatch(pattern, path));
};
router.beforeEach(async (to, from) => {
// ========== 导航锁核心 ==========
// 如果正在导航,直接终止本次跳转,防止并发多个守卫
if (isNavigating) {
NProgress.done();
return false;
}
isNavigating = true;
NProgress.start();
const hasToken = getToken();
const userStore = useUserStore();
const permissionStore = usePermissionStore();
try {
if (hasToken) {
// 设置标题
to.meta.title && useSettingsStore().setTitle(to.meta.title as string);
// 已登录 且 去登录页 → 跳首页
if (to.path === '/login') {
return '/';
}
// 白名单页面直接放行
if (isWhiteList(to.path)) {
return;
}
// roles为空,需要拉取用户信息+构建动态路由(只执行一次)
if (userStore.roles.length === 0) {
isRelogin.show = true;
removeDynamicRoutes();
// 获取用户信息
const [err] = await tos(userStore.getInfo());
if (err) {
await userStore.logout();
ElMessage.error(err);
isDynamicRoutesAdded = false;
return '/';
}
isRelogin.show = false;
permissionStore.resetRoutes();
// 生成动态路由
const accessRoutes = await permissionStore.generateRoutes();
// 添加路由(避免重复添加)
accessRoutes.forEach((route) => {
if (route.name && router.hasRoute(route.name)) {
router.removeRoute(route.name);
}
if (!isHttp(route.path) && !router.hasRoute(route.name)) {
router.addRoute(route);
// 记录顶层路由名称
if (route.name) {
currentDynamicRouteNames.push(route.name as string);
}
}
});
isDynamicRoutesAdded = true;
// 登录页跳转进来,直接跳首页;其他情况重新触发导航加载动态路由
if (from.path === '/login') {
return '/community/communitylist';
} else {
// 这里replace仅在首次加载路由时执行;后续菜单切换不会走到这里
return { ...to, replace: true };
}
}
// ✅ 已经有角色、动态路由已经构建完成:直接放行菜单切换,不会再走上面的动态路由构建逻辑
return;
} else {
// 未登录
isDynamicRoutesAdded = false;
if (isWhiteList(to.path)) {
return;
} else {
return `/login`;
}
}
} catch (err) {
console.error('路由守卫异常:', err);
return false;
} finally {
// ✅ 无论成功失败,必须释放导航锁
isNavigating = false;
}
});
router.afterEach(() => {
NProgress.done();
});
// ✅ 导航发生错误的时候,也要释放锁(路由跳转报错时afterEach不一定执行)
router.onError(() => {
isNavigating = false;
NProgress.done();
});