2026-03-26 20:18:20 +08:00
|
|
|
|
import type {
|
|
|
|
|
|
LocationQueryRaw,
|
|
|
|
|
|
NavigationGuardNext,
|
|
|
|
|
|
RouteLocationNormalized,
|
|
|
|
|
|
RouteLocationRaw,
|
|
|
|
|
|
Router
|
|
|
|
|
|
} from 'vue-router';
|
|
|
|
|
|
import type { RouteKey, RoutePath } from '@elegant-router/types';
|
|
|
|
|
|
import { useAuthStore } from '@/store/modules/auth';
|
2026-04-23 09:05:55 +08:00
|
|
|
|
import { useObjectContextStore } from '@/store/modules/object-context';
|
2026-03-26 20:18:20 +08:00
|
|
|
|
import { useRouteStore } from '@/store/modules/route';
|
|
|
|
|
|
import { localStg } from '@/utils/storage';
|
|
|
|
|
|
import { getRouteName } from '@/router/elegant/transform';
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 创建全局路由守卫
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param router 路由实例
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function createRouteGuard(router: Router) {
|
|
|
|
|
|
router.beforeEach(async (to, from, next) => {
|
|
|
|
|
|
const location = await initRoute(to);
|
|
|
|
|
|
|
|
|
|
|
|
if (location) {
|
|
|
|
|
|
next(location);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const authStore = useAuthStore();
|
|
|
|
|
|
|
|
|
|
|
|
const rootRoute: RouteKey = 'root';
|
|
|
|
|
|
const loginRoute: RouteKey = 'login';
|
|
|
|
|
|
const noAuthorizationRoute: RouteKey = '403';
|
|
|
|
|
|
|
|
|
|
|
|
const isLogin = Boolean(localStg.get('token'));
|
|
|
|
|
|
const needLogin = !to.meta.constant;
|
|
|
|
|
|
const routeRoles = to.meta.roles || [];
|
|
|
|
|
|
|
|
|
|
|
|
const hasRole = authStore.userInfo.roles.some(role => routeRoles.includes(role));
|
|
|
|
|
|
const hasAuth = authStore.isStaticSuper || !routeRoles.length || hasRole;
|
|
|
|
|
|
|
|
|
|
|
|
// 已登录状态下访问登录页时,直接跳到首页
|
|
|
|
|
|
if (to.name === loginRoute && isLogin) {
|
|
|
|
|
|
const redirect = to.query?.redirect as string;
|
|
|
|
|
|
if (redirect) {
|
|
|
|
|
|
next({ path: redirect });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
next({ name: rootRoute });
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 不需要登录的路由允许直接访问
|
|
|
|
|
|
if (!needLogin) {
|
2026-04-23 09:05:55 +08:00
|
|
|
|
const objectContextLocation = await handleObjectContextSwitch(to);
|
|
|
|
|
|
|
|
|
|
|
|
if (objectContextLocation) {
|
|
|
|
|
|
next(objectContextLocation);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 20:18:20 +08:00
|
|
|
|
handleRouteSwitch(to, from, next);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 需要登录但当前未登录时,跳转到登录页
|
|
|
|
|
|
if (!isLogin) {
|
|
|
|
|
|
next({ name: loginRoute, query: { redirect: to.fullPath } });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 已登录但没有权限时,跳转到 403 页面
|
|
|
|
|
|
if (!hasAuth) {
|
|
|
|
|
|
next({ name: noAuthorizationRoute });
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正常放行
|
2026-04-23 09:05:55 +08:00
|
|
|
|
const objectContextLocation = await handleObjectContextSwitch(to);
|
|
|
|
|
|
|
|
|
|
|
|
if (objectContextLocation) {
|
|
|
|
|
|
next(objectContextLocation);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 20:18:20 +08:00
|
|
|
|
handleRouteSwitch(to, from, next);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化路由
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param to 目标路由
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function initRoute(to: RouteLocationNormalized): Promise<RouteLocationRaw | null> {
|
|
|
|
|
|
const routeStore = useRouteStore();
|
|
|
|
|
|
|
|
|
|
|
|
const notFoundRoute: RouteKey = 'not-found';
|
|
|
|
|
|
const isNotFoundRoute = to.name === notFoundRoute;
|
|
|
|
|
|
|
|
|
|
|
|
// 常量路由还没初始化时,先初始化常量路由
|
|
|
|
|
|
if (!routeStore.isInitConstantRoute) {
|
|
|
|
|
|
await routeStore.initConstantRoute();
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化前可能先被 not-found 捕获,初始化完成后重新回到原目标路由
|
|
|
|
|
|
const path = to.fullPath;
|
|
|
|
|
|
const location: RouteLocationRaw = {
|
|
|
|
|
|
path,
|
|
|
|
|
|
replace: true,
|
|
|
|
|
|
query: to.query,
|
|
|
|
|
|
hash: to.hash
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const isLogin = Boolean(localStg.get('token'));
|
|
|
|
|
|
|
|
|
|
|
|
if (!isLogin) {
|
|
|
|
|
|
// 未登录时,如果访问的是常量路由且不是 not-found,则允许访问
|
|
|
|
|
|
if (to.meta.constant && !isNotFoundRoute) {
|
|
|
|
|
|
routeStore.onRouteSwitchWhenNotLoggedIn();
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 未登录时跳到登录页
|
|
|
|
|
|
const loginRoute: RouteKey = 'login';
|
|
|
|
|
|
const query = getRouteQueryOfLoginRoute(to, routeStore.routeHome);
|
|
|
|
|
|
|
|
|
|
|
|
const location: RouteLocationRaw = {
|
|
|
|
|
|
name: loginRoute,
|
|
|
|
|
|
query
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!routeStore.isInitAuthRoute) {
|
|
|
|
|
|
// 初始化权限路由
|
|
|
|
|
|
await routeStore.initAuthRoute();
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化前可能先被 not-found 捕获,初始化完成后重新回到原目标路由
|
|
|
|
|
|
if (isNotFoundRoute) {
|
|
|
|
|
|
const rootRoute: RouteKey = 'root';
|
|
|
|
|
|
const path = to.redirectedFrom?.name === rootRoute ? '/' : to.fullPath;
|
|
|
|
|
|
|
|
|
|
|
|
const location: RouteLocationRaw = {
|
|
|
|
|
|
path,
|
|
|
|
|
|
replace: true,
|
|
|
|
|
|
query: to.query,
|
|
|
|
|
|
hash: to.hash
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
routeStore.onRouteSwitchWhenLoggedIn();
|
|
|
|
|
|
|
|
|
|
|
|
// 权限路由已初始化,且当前不是 not-found,直接放行
|
|
|
|
|
|
if (!isNotFoundRoute) {
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果被 not-found 捕获,再判断是否其实是“存在但无权限”的路由
|
|
|
|
|
|
const exist = await routeStore.getIsAuthRouteExist(to.path as RoutePath);
|
|
|
|
|
|
const noPermissionRoute: RouteKey = '403';
|
|
|
|
|
|
|
|
|
|
|
|
if (exist) {
|
|
|
|
|
|
const location: RouteLocationRaw = {
|
|
|
|
|
|
name: noPermissionRoute
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return location;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleRouteSwitch(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
|
|
|
|
|
|
// 带 href 的路由直接新开窗口
|
|
|
|
|
|
if (to.meta.href) {
|
|
|
|
|
|
window.open(to.meta.href, '_blank');
|
|
|
|
|
|
|
|
|
|
|
|
next({ path: from.fullPath, replace: true, query: from.query, hash: from.hash });
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
next();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-23 09:05:55 +08:00
|
|
|
|
async function handleObjectContextSwitch(to: RouteLocationNormalized) {
|
|
|
|
|
|
const objectContextStore = useObjectContextStore();
|
|
|
|
|
|
|
|
|
|
|
|
return objectContextStore.ensureContextByRoute(to);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 20:18:20 +08:00
|
|
|
|
function getRouteQueryOfLoginRoute(to: RouteLocationNormalized, routeHome: RouteKey) {
|
|
|
|
|
|
const loginRoute: RouteKey = 'login';
|
|
|
|
|
|
const redirect = to.fullPath;
|
|
|
|
|
|
const [redirectPath, redirectQuery] = redirect.split('?');
|
|
|
|
|
|
const redirectName = getRouteName(redirectPath as RoutePath);
|
|
|
|
|
|
|
|
|
|
|
|
const isRedirectHome = routeHome === redirectName;
|
|
|
|
|
|
|
|
|
|
|
|
const query: LocationQueryRaw = to.name !== loginRoute && !isRedirectHome ? { redirect } : {};
|
|
|
|
|
|
|
|
|
|
|
|
if (isRedirectHome && redirectQuery) {
|
|
|
|
|
|
query.redirect = `/?${redirectQuery}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return query;
|
|
|
|
|
|
}
|