初始化

This commit is contained in:
2026-03-26 20:18:20 +08:00
commit 120a5b4dfd
368 changed files with 35926 additions and 0 deletions

15
src/router/guard/index.ts Normal file
View File

@@ -0,0 +1,15 @@
import type { Router } from 'vue-router';
import { createRouteGuard } from './route';
import { createProgressGuard } from './progress';
import { createDocumentTitleGuard } from './title';
/**
* Router guard
*
* @param router - Router instance
*/
export function createRouterGuard(router: Router) {
createProgressGuard(router);
createRouteGuard(router);
createDocumentTitleGuard(router);
}

View File

@@ -0,0 +1,11 @@
import type { Router } from 'vue-router';
export function createProgressGuard(router: Router) {
router.beforeEach((_to, _from, next) => {
window.NProgress?.start?.();
next();
});
router.afterEach(_to => {
window.NProgress?.done?.();
});
}

194
src/router/guard/route.ts Normal file
View File

@@ -0,0 +1,194 @@
import type {
LocationQueryRaw,
NavigationGuardNext,
RouteLocationNormalized,
RouteLocationRaw,
Router
} from 'vue-router';
import type { RouteKey, RoutePath } from '@elegant-router/types';
import { useAuthStore } from '@/store/modules/auth';
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) {
handleRouteSwitch(to, from, next);
return;
}
// 需要登录但当前未登录时,跳转到登录页
if (!isLogin) {
next({ name: loginRoute, query: { redirect: to.fullPath } });
return;
}
// 已登录但没有权限时,跳转到 403 页面
if (!hasAuth) {
next({ name: noAuthorizationRoute });
return;
}
// 正常放行
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();
}
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;
}

13
src/router/guard/title.ts Normal file
View File

@@ -0,0 +1,13 @@
import type { Router } from 'vue-router';
import { useTitle } from '@vueuse/core';
import { $t } from '@/locales';
export function createDocumentTitleGuard(router: Router) {
router.afterEach(to => {
const { i18nKey, title } = to.meta;
const documentTitle = i18nKey ? $t(i18nKey) : title;
useTitle(documentTitle);
});
}