import { toastService } from '~/components/ui'; 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; name: string; meta: { title: string; icon: string; order: number; requiredRole?: string; }; parent_id: number; is_menu: number; } // 用户路由权限接口 export interface UserRoutePermission { route_id: number; role_id: number; permission: string; route: RouteInfo; } // MenuItem结构接口 export interface MenuItem { id: string; title: string; path: string; icon: string; order: number; hideBreadcrumb?: boolean; requiredRole?: string; children?: MenuItem[]; } // 静态菜单数据作为后备 (保留用于开发和紧急情况,当前不使用) // eslint-disable-next-line @typescript-eslint/no-unused-vars const FALLBACK_MENU_DATA: Record = { 'admin': [ { id: 'home', title: '系统概览', path: '/home', icon: 'ri-home-line', order: 1 }, { id: 'chat-with-llm', title: 'AI对话', path: '/chat-with-llm', icon: 'ri-chat-smile-2-line', order: 2 }, { id: 'file-management', title: '文件管理', path: '/files', icon: 'ri-folder-line', order: 3, children: [ { id: 'file-upload', title: '文件上传', path: '/files/upload', icon: 'ri-upload-cloud-line', order: 1 }, { id: 'documents', title: '文档列表', path: '/documents', icon: 'ri-file-list-3-line', order: 2 } ] }, { id: 'rule-management', title: '评查规则库', path: '/rules', icon: 'ri-book-3-line', order: 4, children: [ { id: 'rule-groups', title: '评查点分组', path: '/rule-groups', icon: 'ri-folder-open-line', order: 1 }, { id: 'rules-list', title: '评查点列表', path: '/rules', icon: 'ri-list-check-3', order: 2 }, { id: 'rules-file', title: '评查文件列表', path: '/rules-files', icon: 'ri-list-check-2', order: 3 } ] }, { id: 'contract-template', title: '合同模板', path: '/contract-template', icon: 'ri-file-search-line', order: 5, children: [ { id: 'contract-search-ai', title: '智能搜索', path: '/contract-template/search', icon: 'ri-search-line', order: 1 }, { id: 'contract-list', title: '合同列表', path: '/contract-template/list', icon: 'ri-folder-line', order: 2 } ] }, { id: 'system-settings', title: '系统设置', path: '/settings', icon: 'ri-settings-4-line', order: 6, requiredRole: 'developer', children: [ { id: 'config-lists', title: '配置列表', path: '/config-lists', icon: 'ri-list-check-3', order: 1, requiredRole: 'developer' }, { id: 'document-types', title: '文档类型', path: '/document-types', icon: 'ri-file-list-line', order: 2, requiredRole: 'developer' }, { id: 'prompt-management', title: '提示词管理', path: '/prompts', icon: 'ri-chat-1-line', order: 3, requiredRole: 'developer' } ] }, { id: 'cross-checking', title: '交叉评查', path: '/cross-checking', icon: 'ri-color-filter-line', order: 7 } ], 'common': [ { id: 'home', title: '系统概览', path: '/home', icon: 'ri-home-line', order: 1 }, { id: 'file-management', title: '文件管理', path: '/files', icon: 'ri-folder-line', order: 3, children: [ { id: 'file-upload', title: '文件上传', path: '/files/upload', icon: 'ri-upload-cloud-line', order: 1 }, { id: 'documents', title: '文档列表', path: '/documents', icon: 'ri-file-list-3-line', order: 2 } ] }, { id: 'rule-management', title: '评查规则库', path: '/rules', icon: 'ri-book-3-line', order: 4, children: [ { id: 'rule-groups', title: '评查点分组', path: '/rule-groups', icon: 'ri-folder-open-line', order: 1 }, { id: 'rules-list', title: '评查点列表', path: '/rules', icon: 'ri-list-check-3', order: 2 }, { id: 'rules-file', title: '评查文件列表', path: '/rules-files', icon: 'ri-list-check-2', order: 3 } ] }, { id: 'contract-template', title: '合同模板', path: '/contract-template', icon: 'ri-file-search-line', order: 5, children: [ { id: 'contract-search-ai', title: '智能搜索', path: '/contract-template/search', icon: 'ri-search-line', order: 1 }, { id: 'contract-list', title: '合同列表', path: '/contract-template/list', icon: 'ri-folder-line', order: 2 } ] }, { id: 'cross-checking', title: '交叉评查', path: '/cross-checking', icon: 'ri-color-filter-line', order: 7 } ], 'deptLeader': [ { id: 'home', title: '系统概览', path: '/home', icon: 'ri-home-line', order: 1 }, { id: 'chat-with-llm', title: 'AI对话', path: '/chat-with-llm', icon: 'ri-chat-smile-2-line', order: 2 }, { id: 'file-management', title: '文件管理', path: '/files', icon: 'ri-folder-line', order: 3, children: [ { id: 'file-upload', title: '文件上传', path: '/files/upload', icon: 'ri-upload-cloud-line', order: 1 }, { id: 'documents', title: '文档列表', path: '/documents', icon: 'ri-file-list-3-line', order: 2 } ] }, { id: 'rule-management', title: '评查规则库', path: '/rules', icon: 'ri-book-3-line', order: 4, children: [ { id: 'rule-groups', title: '评查点分组', path: '/rule-groups', icon: 'ri-folder-open-line', order: 1 }, { id: 'rules-list', title: '评查点列表', path: '/rules', icon: 'ri-list-check-3', order: 2 }, { id: 'rules-file', title: '评查文件列表', path: '/rules-files', icon: 'ri-list-check-2', order: 3 } ] }, { id: 'contract-template', title: '合同模板', path: '/contract-template', icon: 'ri-file-search-line', order: 5, children: [ { id: 'contract-search-ai', title: '智能搜索', path: '/contract-template/search', icon: 'ri-search-line', order: 1 }, { id: 'contract-list', title: '合同列表', path: '/contract-template/list', icon: 'ri-folder-line', order: 2 } ] }, { id: 'cross-checking', title: '交叉评查', path: '/cross-checking', icon: 'ri-color-filter-line', order: 7 } ], 'groupLeader': [ { id: 'home', title: '系统概览', path: '/home', icon: 'ri-home-line', order: 1 }, { id: 'file-management', title: '文件管理', path: '/files', icon: 'ri-folder-line', order: 3, children: [ { id: 'file-upload', title: '文件上传', path: '/files/upload', icon: 'ri-upload-cloud-line', order: 1 }, { id: 'documents', title: '文档列表', path: '/documents', icon: 'ri-file-list-3-line', order: 2 } ] }, { id: 'rule-management', title: '评查规则库', path: '/rules', icon: 'ri-book-3-line', order: 4, children: [ { id: 'rule-groups', title: '评查点分组', path: '/rule-groups', icon: 'ri-folder-open-line', order: 1 }, { id: 'rules-list', title: '评查点列表', path: '/rules', icon: 'ri-list-check-3', order: 2 }, { id: 'rules-file', title: '评查文件列表', path: '/rules-files', icon: 'ri-list-check-2', order: 3 } ] }, { id: 'contract-template', title: '合同模板', path: '/contract-template', icon: 'ri-file-search-line', order: 5, children: [ { id: 'contract-search-ai', title: '智能搜索', path: '/contract-template/search', icon: 'ri-search-line', order: 1 }, { id: 'contract-list', title: '合同列表', path: '/contract-template/list', icon: 'ri-folder-line', order: 2 } ] }, { id: 'cross-checking', title: '交叉评查', path: '/cross-checking', icon: 'ri-color-filter-line', order: 7 } ] }; /** * 根据角色获取用户可访问的路由(调用后端统一接口) * @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(`🔍 [User Routes] 获取用户路由,角色: ${roleKey}`); if (!jwt) { console.error('❌ [User Routes] JWT token 未提供'); toastService.error("认证信息缺失,请重新登录"); return { success: false, error: "JWT token 未提供", shouldRedirectToHome: true }; } // 调用后端统一接口获取用户路由 // 注意:Authorization 头会由 axios 拦截器自动添加(从 localStorage 读取) // 但为了确保使用正确的 token,这里仍然显式传递 const response = await apiRequest( '/rbac/user/routes', // endpoint (第一个参数) { method: 'GET', headers: { 'Authorization': `Bearer ${jwt}` } } // options (第二个参数) ); // console.log('🔍 [User Routes] 后端返回:', response); // 检查响应是否成功 if (response.error) { console.error('❌ [User Routes] API 请求失败:', response.error); toastService.error(response.error); return { success: false, error: response.error, shouldRedirectToHome: true }; } // 检查响应数据 if (!response.data) { console.error('❌ [User Routes] 后端未返回数据'); toastService.error("获取路由数据失败"); return { success: false, error: "后端未返回数据", shouldRedirectToHome: true }; } const backendResponse = response.data; // 检查业务状态码(后端使用 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 }; } // 检查数据完整性 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("❌ [User Routes] 获取用户路由时发生错误:", error); toastService.error("获取用户路由时发生错误,请稍后再试"); return { success: false, error: `获取用户路由失败: ${error instanceof Error ? error.message : String(error)}`, shouldRedirectToHome: true }; } } /** * Element UI 图标到 RemixIcon 的映射 */ const ICON_MAPPING: Record = { '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(); 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; } /** * 根据用户角色映射到权限系统的角色标识 * @param userRole 前端用户角色 ('common' | 'admin' | 'deptLeader' | 'groupLeader') * @returns 数据库中的角色标识 */ export function mapUserRoleToRoleKey(userRole: string): string { const roleMapping: Record = { 'common': 'common', 'admin': 'admin', 'deptLeader': 'deptLeader', 'groupLeader': 'groupLeader', // 添加常见的后端角色映射 'super_admin': 'admin', 'system_admin': 'admin', 'user': 'common', 'developer': 'admin' }; // 如果找不到映射,返回 userRole 本身(假设后端已经返回了正确的 role_key) return roleMapping[userRole] || userRole || 'common'; }