feat: 1. 修改完善全局路由检测。 2. 完善统一的token认证管理,token失效自动跳转到登录页。

This commit is contained in:
2025-11-18 20:32:43 +08:00
parent e7b1c2e294
commit adfb84a31d
17 changed files with 270 additions and 294 deletions
+69 -69
View File
@@ -488,9 +488,10 @@ const FALLBACK_MENU_DATA: Record<string, MenuItem[]> = {
* 根据角色获取用户可访问的路由(调用后端统一接口)
* @param roleKey 角色标识 (如: 'admin', 'common', 'deptLeader', 'groupLeader') - 暂时不使用,后端通过JWT自动识别
* @param jwt JWT token
* @param includeHidden 是否包含隐藏路由(默认 false)。true: 用于权限校验,false: 用于菜单渲染
* @returns 用户可访问的路由列表
*/
export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
export async function getUserRoutesByRole(roleKey: string, jwt?: string, includeHidden: boolean = false): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
try {
console.log(`🔍 [User Routes] 获取用户路由,角色: ${roleKey}`);
@@ -553,10 +554,12 @@ export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promis
return { success: false, error: "用户没有分配任何路由权限", shouldRedirectToHome: true };
}
// 将后端路由格式转换为前端 MenuItem 格式
const menuItems = convertBackendRoutesToMenuItems(routes);
// console.log('📋 [User Routes] 菜单数据:', routes);
console.log(`✅ [User Routes] 成功获取 ${menuItems.length} 个路由`);
// 将后端路由格式转换为前端 MenuItem 格式
const menuItems = convertBackendRoutesToMenuItems(routes, includeHidden);
// console.log(`✅ [User Routes] 成功获取 ${menuItems.length} 个路由 (includeHidden: ${includeHidden})`);
// console.log('📋 [User Routes] 菜单数据:', menuItems);
return { success: true, data: menuItems };
@@ -605,14 +608,70 @@ function convertIcon(elementIcon: string | null): string {
return ICON_MAPPING[elementIcon] || 'ri-file-line';
}
/**
* 将平铺的路由数组构建为树形结构
* @param routes 平铺的路由数组
* @returns 树形结构的路由数组
*/
function buildRouteTree(routes: BackendRouteInfo[]): BackendRouteInfo[] {
// 创建路由映射
const routeMap = new Map<number, BackendRouteInfo>();
const rootRoutes: BackendRouteInfo[] = [];
// 第一遍:创建所有路由的映射,并初始化 children 数组
routes.forEach(route => {
routeMap.set(route.id, { ...route, children: [] });
});
// 第二遍:构建父子关系
routes.forEach(route => {
const currentRoute = routeMap.get(route.id);
if (!currentRoute) return;
if (route.parent_id === null || route.parent_id === 0) {
// 顶级路由
rootRoutes.push(currentRoute);
} else {
// 子路由,添加到父路由的 children 中
const parentRoute = routeMap.get(route.parent_id);
if (parentRoute) {
if (!parentRoute.children) {
parentRoute.children = [];
}
parentRoute.children.push(currentRoute);
} else {
// 如果找不到父路由,当作顶级路由处理
// console.warn(`⚠️ [User Routes] 找不到父路由 (parent_id: ${route.parent_id}) for route: ${route.route_name}`);
rootRoutes.push(currentRoute);
}
}
});
return rootRoutes;
}
/**
* 将后端路由格式转换为前端 MenuItem 格式
* @param backendRoutes 后端返回的路由数组
* @param backendRoutes 后端返回的路由数组(可能是树形或平铺)
* @param includeHidden 是否包含隐藏路由(默认 false)。true: 用于权限校验,false: 用于菜单渲染
* @returns MenuItem 数组
*/
function convertBackendRoutesToMenuItems(backendRoutes: BackendRouteInfo[]): MenuItem[] {
return backendRoutes
.filter(route => !route.is_hidden) // 过滤隐藏的路由
function convertBackendRoutesToMenuItems(backendRoutes: BackendRouteInfo[], includeHidden: boolean = false): MenuItem[] {
// 检查是否需要构建树形结构
// 如果存在 parent_id 不为 null/0 但没有对应的父路由在 children 中,说明是平铺数组
const needsBuildTree = backendRoutes.some(route =>
route.parent_id !== null &&
route.parent_id !== 0 &&
!backendRoutes.some(r => r.children?.some(c => c.id === route.id))
);
// 如果是平铺数组,先构建树形结构
const treeRoutes = needsBuildTree ? buildRouteTree(backendRoutes) : backendRoutes;
return treeRoutes
.filter(route => includeHidden || !route.is_hidden) // 根据 includeHidden 决定是否过滤隐藏路由
.map(route => {
const menuItem: MenuItem = {
id: route.route_name || `route-${route.id}`,
@@ -623,9 +682,9 @@ function convertBackendRoutesToMenuItems(backendRoutes: BackendRouteInfo[]): Men
hideBreadcrumb: route.is_hidden
};
// 递归处理子路由
// 递归处理子路由,传递 includeHidden 参数
if (route.children && route.children.length > 0) {
menuItem.children = convertBackendRoutesToMenuItems(route.children);
menuItem.children = convertBackendRoutesToMenuItems(route.children, includeHidden);
}
return menuItem;
@@ -633,65 +692,6 @@ function convertBackendRoutesToMenuItems(backendRoutes: BackendRouteInfo[]): Men
.sort((a, b) => a.order - b.order); // 按 sort_order 排序
}
/**
* 从路由信息构建菜单树结构(旧版本,已废弃)
* @param routes 路由信息数组
* @returns 菜单树结构
* @deprecated 使用 convertBackendRoutesToMenuItems 替代
*/
function buildMenuTreeFromRoutes(routes: RouteInfo[]): MenuItem[] {
// 转换为MenuItem格式
const menuMap = new Map<number, MenuItem>();
routes.forEach(route => {
const menuItem: MenuItem = {
id: route.name,
title: route.meta.title,
path: route.path,
icon: route.meta.icon,
order: route.meta.order || 0,
requiredRole: route.meta.requiredRole
};
menuMap.set(route.id, menuItem);
});
// 构建父子关系
const rootItems: MenuItem[] = [];
const itemsWithParent: Array<{ item: MenuItem; parentId: number }> = [];
routes.forEach(route => {
const menuItem = menuMap.get(route.id);
if (!menuItem) return;
if (route.parent_id === 0) {
rootItems.push(menuItem);
} else {
itemsWithParent.push({ item: menuItem, parentId: route.parent_id });
}
});
// 添加子菜单
itemsWithParent.forEach(({ item, parentId }) => {
const parent = menuMap.get(parentId);
if (parent) {
if (!parent.children) {
parent.children = [];
}
parent.children.push(item);
}
});
// 排序
rootItems.sort((a, b) => a.order - b.order);
rootItems.forEach(item => {
if (item.children) {
item.children.sort((a, b) => a.order - b.order);
}
});
return rootItems;
}
/**
* 根据用户角色映射到权限系统的角色标识