Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/router/guards/alive.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { handleAliveRoute } from "@/router/utils";
import type { GuardContext } from "./type";

/** keep-alive缓存守卫 */
export function aliveGuard({ to, from }: GuardContext): boolean {
if (to.meta?.keepAlive) {
handleAliveRoute(to as any, "add");
// 页面整体刷新和点击标签页刷新
if (from.name === undefined || from.name === "Redirect") {
handleAliveRoute(to as any);
}
}

return true;
}
68 changes: 68 additions & 0 deletions src/router/guards/dynamic.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
import { initRouter, getTopMenu, findRouteByPath } from "@/router/utils";
import { usePermissionStoreHook } from "@/store/modules/permission";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { userKey, type DataInfo } from "@/utils/auth";
import type { GuardContext } from "./type";
import Cookies from "js-cookie";
import { multipleTabsKey } from "@/utils/auth";

/** 动态路由初始化守卫 */
export async function dynamicRouteGuard({
to,
from,
router
}: GuardContext): Promise<boolean> {
const userInfo = storageLocal().getItem<DataInfo<number>>(userKey);
const hasToken = Cookies.get(multipleTabsKey) && userInfo;

// 刷新时需要重新初始化动态路由
if (!from?.name && hasToken) {
if (
usePermissionStoreHook().wholeMenus.length === 0 &&
to.path !== "/login"
) {
try {
const initializedRouter = await initRouter();

if (!useMultiTagsStoreHook().getMultiTagsCache) {
const { path } = to;
const route = findRouteByPath(
path,
initializedRouter.options.routes[0].children
);
getTopMenu(true);

if (route && route.meta?.title) {
if (isAllEmpty((route as any).parentId) && route.meta?.backstage) {
// 此处为动态顶级路由(目录)
const { path, name, meta } = (route as any).children[0];
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
} else {
const { path, name, meta } = route as any;
useMultiTagsStoreHook().handleTags("push", {
path,
name,
meta
});
}
}
}

// 确保动态路由完全加入路由列表
if (isAllEmpty(to.name)) {
router.push(to.fullPath);
return false;
}
} catch (error) {
console.error("Failed to initialize dynamic routes:", error);
}
}
}

return true;
}
17 changes: 17 additions & 0 deletions src/router/guards/external.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { isUrl, openLink } from "@pureadmin/utils";
import NProgress from "@/utils/progress";
import type { GuardContext } from "./type";

/** 外部链接守卫 */
export function externalLinkGuard({ to, next }: GuardContext): boolean {
const externalLink = isUrl(to?.name as string);

if (externalLink) {
openLink(to?.name as string);
NProgress.done();
next(false);
return false;
}

return true;
}
90 changes: 90 additions & 0 deletions src/router/guards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Router } from "vue-router";
import { progressGuard, progressDoneGuard } from "./progress.guard";
import { titleGuard } from "./title.guard";
import { aliveGuard } from "./alive.guard";
import { permissionGuard, whiteListRedirectGuard } from "./permission.guard";
import { externalLinkGuard } from "./external.guard";
import { dynamicRouteGuard } from "./dynamic.guard";
import type { GuardContext } from "./type";

export * from "./type";

/**
* 路由守卫执行管道
* 按顺序执行守卫函数,任何一个守卫返回false则终止执行
*/
async function runGuardPipeline(
context: GuardContext,
guards: Function[]
): Promise<boolean> {
for (const guard of guards) {
const result = await guard(context);
if (result === false) {
return false;
}
}
return true;
}

/**
* 创建路由守卫管理器
* @param router 路由实例
*/
export function createRouterGuard(router: Router) {
// 记录已经加载的页面路径
const loadedPaths = new Set<string>();

// 重置已加载页面记录
function resetLoadedPaths() {
loadedPaths.clear();
}

// 全局前置守卫
router.beforeEach(async (to, from, next) => {
// 注入loaded状态
(to.meta as any).loaded = loadedPaths.has(to.path);

const context: GuardContext = {
to: to as any,
from,
next,
router
};

// 第一阶段:基础处理守卫(总是执行)
const baseGuards = [progressGuard, aliveGuard, titleGuard];

const basePassed = await runGuardPipeline(context, baseGuards);
if (!basePassed) return;

// 第二阶段:权限验证守卫
const permissionPassed = permissionGuard(context);
if (!permissionPassed) return;

// 第三阶段:白名单重定向守卫
const whiteListPassed = whiteListRedirectGuard(context);
if (!whiteListPassed) return;

// 第四阶段:外部链接处理
const externalPassed = externalLinkGuard(context);
if (!externalPassed) return;

// 第五阶段:动态路由初始化(可能需要异步)
const dynamicPassed = await dynamicRouteGuard(context);
if (!dynamicPassed) return;

// 所有守卫通过,继续导航
next();
});

// 全局后置守卫
router.afterEach(to => {
loadedPaths.add(to.path);
progressDoneGuard(to);
});

return {
resetLoadedPaths,
loadedPaths
};
}
63 changes: 63 additions & 0 deletions src/router/guards/permission.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Cookies from "js-cookie";
import { storageLocal } from "@pureadmin/utils";
import { isOneOfArray } from "@/router/utils";
import {
removeToken,
multipleTabsKey,
userKey,
type DataInfo
} from "@/utils/auth";
import type { GuardContext } from "./type";

const { VITE_HIDE_HOME } = import.meta.env;

/** 权限验证守卫 */
export function permissionGuard({ to, next }: GuardContext): boolean {
const userInfo = storageLocal().getItem<DataInfo<number>>(userKey);
const hasToken = Cookies.get(multipleTabsKey) && userInfo;

if (!hasToken) {
if (to.path !== "/login") {
const whiteList = ["/login"];
if (whiteList.indexOf(to.path) !== -1) {
next();
} else {
removeToken();
next({ path: "/login" });
}
} else {
next();
}
return false;
}

// 无权限跳转403页面
if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles || [])) {
next({ path: "/error/403" });
return false;
}

// 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
next({ path: "/error/404" });
return false;
}

return true;
}

/** 白名单重定向守卫 */
export function whiteListRedirectGuard({
to,
from,
next
}: GuardContext): boolean {
const whiteList = ["/login"];

if (whiteList.includes(to.fullPath)) {
next(from.fullPath);
return false;
}

return true;
}
18 changes: 18 additions & 0 deletions src/router/guards/progress.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import NProgress from "@/utils/progress";
import type { GuardContext } from "./type";

/** 进度条守卫 */
export function progressGuard({ to }: GuardContext): boolean {
to.meta.loaded = (to.meta as any).loaded;

if (!to.meta.loaded) {
NProgress.start();
}

return true;
}

/** 进度条结束守卫(用于afterEach) */
export function progressDoneGuard(_to: any) {
NProgress.done();
}
23 changes: 23 additions & 0 deletions src/router/guards/title.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getConfig } from "@/config";
import { isUrl } from "@pureadmin/utils";
import type { GuardContext } from "./type";

/** 页面标题守卫 */
export function titleGuard({ to }: GuardContext): boolean {
const externalLink = isUrl(to?.name as string);

if (!externalLink) {
to.matched.some(item => {
if (!item.meta.title) return "";
const Title = getConfig().Title;
if (Title) {
document.title = `${item.meta.title} | ${Title}`;
} else {
document.title = item.meta.title as string;
}
return true;
});
}

return true;
}
27 changes: 27 additions & 0 deletions src/router/guards/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type {
Router,
NavigationGuardNext,
RouteLocationNormalized
} from "vue-router";

export type ToRouteType = RouteLocationNormalized & {
meta?: {
title?: string;
roles?: Array<string>;
keepAlive?: boolean;
loaded?: boolean;
saveSrollTop?: boolean;
[key: string]: any;
};
};

export interface GuardContext {
to: ToRouteType;
from: RouteLocationNormalized;
next: NavigationGuardNext;
router: Router;
}

export type GuardFunction = (
context: GuardContext
) => Promise<boolean | void> | boolean | void;
Loading