Files
schoolNews/schoolNewsWeb/src/utils/permission.ts
2025-10-18 18:19:19 +08:00

261 lines
6.6 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.

/**
* @description 路由守卫和权限检查
* @author yslg
* @since 2025-10-07
*/
import type { Router, NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import type { Store } from 'vuex';
import { AuthState } from '@/store/modules/auth';
/**
* 白名单路由 - 无需登录即可访问
*/
const WHITE_LIST = [
'/login',
'/register',
'/forgot-password',
'/home',
'/403',
'/404', // 404页面允许访问但未登录时不会被路由到这里
'/500'
];
/**
* 设置路由守卫
* @param router Vue Router实例
* @param store Vuex Store实例
*/
export function setupRouterGuards(router: Router, store: Store<any>) {
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
// 开始页面加载进度条
startProgress();
try {
await handleRouteGuard(to, from, next, store);
} catch (error) {
console.error('路由守卫执行失败:', error);
// 发生错误时跳转到500页面
next('/500');
}
});
// 全局后置钩子
router.afterEach((to) => {
// 结束页面加载进度条
finishProgress();
// 设置页面标题
setPageTitle(to.meta?.title as string);
});
// 全局解析守卫(在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后调用)
router.beforeResolve(async (to, from, next) => {
// 这里可以处理一些最终的权限检查或数据预加载
next();
});
}
/**
* 处理路由守卫逻辑
*/
async function handleRouteGuard(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext,
store: Store<any>
) {
const authState: AuthState = store.state.auth;
const isAuthenticated = store.getters['auth/isAuthenticated'];
// 检查是否在白名单中
if (isInWhiteList(to.path)) {
return next();
}
// 检查用户是否已登录
if (!isAuthenticated) {
// 未登录,重定向到登录页
return next({
path: '/login',
query: {
redirect: to.fullPath // 记录用户想要访问的页面
}
});
}
// 用户已登录,检查是否需要生成动态路由
// 注意:通常情况下路由应该在 main.ts 初始化时就已经生成
// 这里主要处理登录后首次生成路由的情况
if (!authState.routesLoaded && authState.menus && authState.menus.length > 0) {
try {
console.log('[路由守卫] 路由未加载,开始生成动态路由');
// 生成动态路由
await store.dispatch('auth/generateRoutes');
console.log('[路由守卫] 动态路由生成成功,重新导航');
// 重新导航到目标路由
return next({ ...to, replace: true });
} catch (error) {
console.error('[路由守卫] 生成动态路由失败:', error);
// 清除认证信息并跳转到登录页
store.commit('auth/CLEAR_AUTH');
return next('/login');
}
}
// 检查页面权限
const hasPermission = await checkPagePermission(to, store);
if (!hasPermission) {
// 无权限访问跳转到403页面
return next('/403');
}
// 所有检查通过,继续导航
next();
}
/**
* 检查路径是否在白名单中
*/
function isInWhiteList(path: string): boolean {
return WHITE_LIST.some(whitePath => {
if (whitePath.endsWith('*')) {
// 支持通配符匹配
const prefix = whitePath.slice(0, -1);
return path.startsWith(prefix);
}
return path === whitePath;
});
}
/**
* 检查页面权限
*/
async function checkPagePermission(
route: RouteLocationNormalized,
store: Store<any>
): Promise<boolean> {
// 如果路由元信息中没有要求权限,则允许访问
if (route.meta?.requiresAuth === false) {
return true;
}
// 检查路由是否需要特定权限
const requiredPermissions = route.meta?.permissions as string[] | undefined;
if (!requiredPermissions || requiredPermissions.length === 0) {
// 无特定权限要求,但需要登录,已经在前面检查过登录状态
return true;
}
// 检查用户是否有所需权限
const hasPermission = store.getters['auth/hasAnyPermission'];
return hasPermission(requiredPermissions);
}
/**
* 设置页面标题
*/
function setPageTitle(title?: string) {
const appTitle = '校园新闻管理系统';
document.title = title ? `${title} - ${appTitle}` : appTitle;
}
/**
* 开始进度条(可以集成 NProgress 或其他进度条库)
*/
function startProgress() {
// TODO: 集成进度条库,如 NProgress
// NProgress.start();
}
/**
* 完成进度条
*/
function finishProgress() {
// TODO: 集成进度条库,如 NProgress
// NProgress.done();
}
/**
* Token自动刷新中间件
*/
export function setupTokenRefresh(store: Store<any>) {
// 设置定时器自动刷新Token
setInterval(async () => {
const authState: AuthState = store.state.auth;
if (!authState.token || !authState.loginDomain) {
return;
}
// 检查Token是否快要过期例如提前5分钟刷新
const tokenExpireTime = authState.loginDomain.tokenExpireTime;
if (tokenExpireTime) {
const expireTime = new Date(tokenExpireTime).getTime();
const currentTime = Date.now();
const fiveMinutes = 5 * 60 * 1000;
if (expireTime - currentTime <= fiveMinutes) {
try {
await store.dispatch('auth/refreshToken');
console.log('Token自动刷新成功');
} catch (error) {
console.error('Token自动刷新失败:', error);
}
}
}
}, 60 * 1000); // 每分钟检查一次
}
/**
* 权限检查工具函数
*/
export class PermissionChecker {
private store: Store<any>;
constructor(store: Store<any>) {
this.store = store;
}
/**
* 检查是否有指定权限
*/
hasPermission(permissionCode: string): boolean {
return this.store.getters['auth/hasPermission'](permissionCode);
}
/**
* 检查是否有任意一个权限
*/
hasAnyPermission(permissionCodes: string[]): boolean {
return this.store.getters['auth/hasAnyPermission'](permissionCodes);
}
/**
* 检查是否有所有权限
*/
hasAllPermissions(permissionCodes: string[]): boolean {
return this.store.getters['auth/hasAllPermissions'](permissionCodes);
}
/**
* 检查是否有指定角色
*/
hasRole(roleCode: string): boolean {
const userRoles = this.store.getters['auth/userRoles'];
return userRoles.some((role: any) => role.code === roleCode);
}
/**
* 检查是否有任意一个角色
*/
hasAnyRole(roleCodes: string[]): boolean {
return roleCodes.some(code => this.hasRole(code));
}
}