feat: 1. 添加axios全局路由拦截进行自动添加请求jwt。 2.重新整理路由表。 3. 文档列表新增版本差异对比。 4.菜单路由可访问列表通过对接接口返回,添加全局路由检测。

5. 修改统一认证登录和管理员登录是通过接口形式进行,存储返回的accessToken。    6. 修改交叉评查的部分样式
This commit is contained in:
2025-11-18 11:06:24 +08:00
parent 8a50671c39
commit bfe39e45a9
53 changed files with 9503 additions and 2796 deletions
+170 -70
View File
@@ -1,7 +1,34 @@
import { toastService } from '~/components/ui';
import { postgrestGet } from '../postgrest-client';
import { apiRequest } from '../axios-client';
// 路由数据接口
// 后端返回的路由数据接口
export interface BackendRouteInfo {
id: number;
route_path: string;
route_name: string;
component: string;
parent_id: number | null;
route_title: string;
icon: string | null;
sort_order: number;
is_hidden: boolean;
is_cache: boolean;
meta: string;
children?: BackendRouteInfo[];
}
// 后端API响应接口
export interface BackendRoutesResponse {
code: number;
msg: string;
data: {
user_id: number;
username: string;
routes: BackendRouteInfo[];
};
}
// 旧的路由数据接口(保留用于兼容)
export interface RouteInfo {
id: number;
path: string;
@@ -458,82 +485,87 @@ const FALLBACK_MENU_DATA: Record<string, MenuItem[]> = {
};
/**
* 根据角色获取用户可访问的路由
* @param roleKey 角色标识 (如: 'admin', 'common', 'deptLeader', 'groupLeader')
* 根据角色获取用户可访问的路由(调用后端统一接口)
* @param roleKey 角色标识 (如: 'admin', 'common', 'deptLeader', 'groupLeader') - 暂时不使用,后端通过JWT自动识别
* @param jwt JWT token
* @returns 用户可访问的路由列表
*/
export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promise<{ success: boolean; data?: MenuItem[]; error?: string; shouldRedirectToHome?: boolean }> {
try {
console.log(`获取角色 ${roleKey} 的路由权限`);
console.log(`🔍 [User Routes] 获取用户路由,角色: ${roleKey}`);
// 首先获取角色ID
const roleResult = await postgrestGet<Array<{id: number}>>("roles", {
filter: {
"role_key": `eq.${roleKey}`
},
token: jwt
});
if (roleResult.error || !roleResult.data || roleResult.data.length === 0) {
console.error("角色不存在:", roleKey);
toastService.error("角色不存在,请联系管理员配置权限后重新登录");
return { success: false, error: "角色不存在", shouldRedirectToHome: true };
if (!jwt) {
console.error('❌ [User Routes] JWT token 未提供');
toastService.error("认证信息缺失,请重新登录");
return { success: false, error: "JWT token 未提供", shouldRedirectToHome: true };
}
const roleId = roleResult.data[0].id;
// 调用后端统一接口获取用户路由
// 注意:Authorization 头会由 axios 拦截器自动添加(从 localStorage 读取)
// 但为了确保使用正确的 token,这里仍然显式传递
const response = await apiRequest<BackendRoutesResponse>(
'/rbac/user/routes', // endpoint (第一个参数)
{
method: 'GET',
headers: {
'Authorization': `Bearer ${jwt}`
}
} // options (第二个参数)
);
// 查询角色的路由权限
const roleRoutesResult = await postgrestGet<Array<{route_id: number}>>("role_route", {
filter: {
"role_id": `eq.${roleId}`
},
token: jwt
});
// console.log('🔍 [User Routes] 后端返回:', response);
if (roleRoutesResult.error) {
console.error("查询角色路由关联失败:", roleRoutesResult.error);
toastService.error("查询角色路由关联失败,请稍后再试");
return { success: false, error: "查询角色路由关联失败", shouldRedirectToHome: true };
// 检查响应是否成功
if (response.error) {
console.error('❌ [User Routes] API 请求失败:', response.error);
toastService.error(response.error);
return { success: false, error: response.error, shouldRedirectToHome: true };
}
const roleRoutes = roleRoutesResult.data || [];
const routeIds = roleRoutes.map(item => item.route_id);
if (routeIds.length === 0) {
console.log(`角色 ${roleKey} 没有分配任何路由权限`);
toastService.error("您的角色没有分配任何路由权限,请联系管理员配置权限");
return { success: false, error: "角色没有分配任何路由权限", shouldRedirectToHome: true };
// 检查响应数据
if (!response.data) {
console.error('❌ [User Routes] 后端未返回数据');
toastService.error("获取路由数据失败");
return { success: false, error: "后端未返回数据", shouldRedirectToHome: true };
}
// 查询具体的路由信息
const routesResult = await postgrestGet<RouteInfo[]>("sys_routes", {
filter: {
"id": `in.(${routeIds.join(',')})`,
"is_menu": "eq.1"
},
order: "parent_id,meta->>order",
token: jwt
});
const backendResponse = response.data;
if (routesResult.error) {
console.error("查询路由信息失败:", routesResult.error);
toastService.error("查询路由信息失败,请稍后再试");
return { success: false, error: "查询路由信息失败", shouldRedirectToHome: true };
// 检查业务状态码(后端使用 code: 0 表示成功)
if (backendResponse.code !== 0 && backendResponse.code !== 200) {
console.error(`❌ [User Routes] 后端返回错误: ${backendResponse.msg}`);
toastService.error(backendResponse.msg || "获取路由权限失败");
return { success: false, error: backendResponse.msg || "获取路由权限失败", shouldRedirectToHome: true };
}
const routes = routesResult.data || [];
// 构建菜单树
const menuItems = buildMenuTreeFromRoutes(routes);
console.log(`角色 ${roleKey} 可访问 ${menuItems.length} 个路由`);
// 检查数据完整性
if (!backendResponse.data || !Array.isArray(backendResponse.data.routes)) {
console.error('❌ [User Routes] 后端未返回路由数据');
toastService.error("未获取到路由权限,请联系管理员配置");
return { success: false, error: "后端未返回路由数据", shouldRedirectToHome: true };
}
const routes = backendResponse.data.routes;
if (routes.length === 0) {
console.log(`⚠️ [User Routes] 用户没有分配任何路由权限`);
toastService.error("您的角色没有分配任何路由权限,请联系管理员配置");
return { success: false, error: "用户没有分配任何路由权限", shouldRedirectToHome: true };
}
// 将后端路由格式转换为前端 MenuItem 格式
const menuItems = convertBackendRoutesToMenuItems(routes);
console.log(`✅ [User Routes] 成功获取 ${menuItems.length} 个路由`);
// console.log('📋 [User Routes] 菜单数据:', menuItems);
return { success: true, data: menuItems };
} catch (error) {
console.error("获取用户路由时发生错误:", error);
console.error("❌ [User Routes] 获取用户路由时发生错误:", error);
toastService.error("获取用户路由时发生错误,请稍后再试");
return {
success: false,
return {
success: false,
error: `获取用户路由失败: ${error instanceof Error ? error.message : String(error)}`,
shouldRedirectToHome: true
};
@@ -541,14 +573,76 @@ export async function getUserRoutesByRole(roleKey: string, jwt?: string): Promis
}
/**
* 从路由信息构建菜单树结构
* Element UI 图标到 RemixIcon 的映射
*/
const ICON_MAPPING: Record<string, string> = {
'el-icon-s-home': 'ri-home-line',
'el-icon-house': 'ri-home-4-line',
'el-icon-document': 'ri-file-text-line',
'el-icon-edit': 'ri-edit-line',
'el-icon-connection': 'ri-links-line',
'el-icon-setting': 'ri-settings-4-line',
'el-icon-user': 'ri-user-line',
'el-icon-tickets': 'ri-ticket-line',
'el-icon-chat-dot-round': 'ri-chat-smile-2-line',
'el-icon-s-order': 'ri-list-check',
'el-icon-s-grid': 'ri-grid-line',
'el-icon-s-comment': 'ri-chat-1-line',
'el-icon-files': 'ri-file-copy-line',
'el-icon-folder': 'ri-folder-line',
'el-icon-upload': 'ri-upload-cloud-line',
'el-icon-download': 'ri-download-cloud-line',
'el-icon-search': 'ri-search-line',
};
/**
* 转换 Element UI 图标为 RemixIcon
*/
function convertIcon(elementIcon: string | null): string {
if (!elementIcon) {
return 'ri-file-line'; // 默认图标
}
return ICON_MAPPING[elementIcon] || 'ri-file-line';
}
/**
* 将后端路由格式转换为前端 MenuItem 格式
* @param backendRoutes 后端返回的路由数组
* @returns MenuItem 数组
*/
function convertBackendRoutesToMenuItems(backendRoutes: BackendRouteInfo[]): MenuItem[] {
return backendRoutes
.filter(route => !route.is_hidden) // 过滤隐藏的路由
.map(route => {
const menuItem: MenuItem = {
id: route.route_name || `route-${route.id}`,
title: route.route_title,
path: route.route_path,
icon: convertIcon(route.icon),
order: route.sort_order,
hideBreadcrumb: route.is_hidden
};
// 递归处理子路由
if (route.children && route.children.length > 0) {
menuItem.children = convertBackendRoutesToMenuItems(route.children);
}
return menuItem;
})
.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,
@@ -558,25 +652,25 @@ function buildMenuTreeFromRoutes(routes: RouteInfo[]): MenuItem[] {
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);
@@ -587,7 +681,7 @@ function buildMenuTreeFromRoutes(routes: RouteInfo[]): MenuItem[] {
parent.children.push(item);
}
});
// 排序
rootItems.sort((a, b) => a.order - b.order);
rootItems.forEach(item => {
@@ -595,7 +689,7 @@ function buildMenuTreeFromRoutes(routes: RouteInfo[]): MenuItem[] {
item.children.sort((a, b) => a.order - b.order);
}
});
return rootItems;
}
@@ -609,8 +703,14 @@ export function mapUserRoleToRoleKey(userRole: string): string {
'common': 'common',
'admin': 'admin',
'deptLeader': 'deptLeader',
'groupLeader': 'groupLeader'
'groupLeader': 'groupLeader',
// 添加常见的后端角色映射
'super_admin': 'admin',
'system_admin': 'admin',
'user': 'common',
'developer': 'admin'
};
return roleMapping[userRole];
// 如果找不到映射,返回 userRole 本身(假设后端已经返回了正确的 role_key)
return roleMapping[userRole] || userRole || 'common';
}