/** * KRA - Dynamic Router Utility * 动态路由处理工具,参考 GVA 的 asyncRouter.js 实现 */ import React from 'react'; // 页面组件映射 - 对应 GVA 的 viewModules // UMI 使用 React.lazy 动态导入 const pageModules: Record Promise<{ default: React.ComponentType }>> = { // 仪表盘 'view/dashboard/index.vue': () => import('@/pages/dashboard'), // 超级管理员 'view/superAdmin/authority/authority.vue': () => import('@/pages/system/authority'), 'view/superAdmin/menu/menu.vue': () => import('@/pages/system/menu'), 'view/superAdmin/api/api.vue': () => import('@/pages/system/api'), 'view/superAdmin/user/user.vue': () => import('@/pages/system/user'), 'view/superAdmin/dictionary/sysDictionary.vue': () => import('@/pages/system/dictionary'), 'view/superAdmin/operation/sysOperationRecord.vue': () => import('@/pages/system/operation'), 'view/superAdmin/params/sysParams.vue': () => import('@/pages/system/params'), // 系统工具 'view/systemTools/autoCode/index.vue': () => import('@/pages/systemTools/autoCode'), 'view/systemTools/formCreate/index.vue': () => import('@/pages/systemTools/formCreate'), 'view/systemTools/system/system.vue': () => import('@/pages/systemTools/system'), 'view/systemTools/autoCodeAdmin/index.vue': () => import('@/pages/systemTools/autoCodeAdmin'), 'view/systemTools/autoPkg/autoPkg.vue': () => import('@/pages/systemTools/autoPkg'), 'view/systemTools/exportTemplate/exportTemplate.vue': () => import('@/pages/systemTools/exportTemplate'), 'view/systemTools/installPlugin/index.vue': () => import('@/pages/systemTools/installPlugin'), 'view/systemTools/pubPlug/pubPlug.vue': () => import('@/pages/systemTools/pubPlug'), 'view/systemTools/version/version.vue': () => import('@/pages/systemTools/version'), 'view/systemTools/sysError/sysError.vue': () => import('@/pages/systemTools/sysError'), // 示例 'view/example/upload/upload.vue': () => import('@/pages/example/upload'), 'view/example/breakpoint/breakpoint.vue': () => import('@/pages/example/breakpoint'), 'view/example/customer/customer.vue': () => import('@/pages/example/customer'), // 插件 'plugin/announcement/view/info.vue': () => import('@/pages/plugin/announcement'), 'plugin/email/view/index.vue': () => import('@/pages/plugin/email'), // 个人中心 'view/person/person.vue': () => import('@/pages/person'), // 关于 'view/about/index.vue': () => import('@/pages/about'), // 服务器状态 'view/system/state.vue': () => import('@/pages/system/state'), // 错误页面 'view/error/reload.vue': () => import('@/pages/error/reload'), }; // 插件组件映射 const pluginModules: Record Promise<{ default: React.ComponentType }>> = { 'plugin/announcement/view/info.vue': () => import('@/pages/plugin/announcement'), 'plugin/email/view/index.vue': () => import('@/pages/plugin/email'), }; export interface BackendMenuItem { ID?: number; parentId?: number; path: string; name: string; hidden?: boolean; component?: string; sort?: number; meta?: { title?: string; icon?: string; keepAlive?: boolean; defaultMenu?: boolean; closeTab?: boolean; }; children?: BackendMenuItem[]; btns?: Record; parameters?: Array<{ key: string; value: string }>; } export interface RouteItem { path: string; name?: string; icon?: string; component?: React.ComponentType | React.LazyExoticComponent; routes?: RouteItem[]; hideInMenu?: boolean; // UMI Pro Layout 需要的字段 access?: string; // 自定义 meta 信息 meta?: { title?: string; icon?: string; keepAlive?: boolean; defaultMenu?: boolean; closeTab?: boolean; btns?: Record; }; } /** * 动态导入组件 * 对应 GVA 的 dynamicImport 函数 */ function dynamicImport(component: string): React.LazyExoticComponent | null { // 先检查页面模块 if (pageModules[component]) { return React.lazy(pageModules[component]); } // 再检查插件模块 if (component.startsWith('plugin/') && pluginModules[component]) { return React.lazy(pluginModules[component]); } console.warn(`Component not found: ${component}`); return null; } /** * 检查是否为外部链接 */ function isExternalUrl(val: string | undefined): boolean { return typeof val === 'string' && /^(https?:)?\/\//.test(val); } /** * 处理后端返回的菜单数据,转换为 UMI 路由格式 * 对应 GVA 的 asyncRouterHandle 函数 */ export function asyncRouterHandle(asyncRouter: BackendMenuItem[]): RouteItem[] { const routes: RouteItem[] = []; asyncRouter.forEach((item) => { // 跳过外部链接 if (isExternalUrl(item.path) || isExternalUrl(item.name) || isExternalUrl(item.component)) { // 外部链接作为菜单项但不作为路由 routes.push({ path: item.path, name: item.meta?.title || item.name, icon: item.meta?.icon, hideInMenu: item.hidden, meta: { title: item.meta?.title, icon: item.meta?.icon, }, }); return; } const route: RouteItem = { path: item.path.startsWith('/') ? item.path : `/${item.path}`, name: item.meta?.title || item.name, icon: item.meta?.icon, hideInMenu: item.hidden, meta: { title: item.meta?.title, icon: item.meta?.icon, keepAlive: item.meta?.keepAlive, defaultMenu: item.meta?.defaultMenu, closeTab: item.meta?.closeTab, btns: item.btns, }, }; // 处理组件 if (item.component && typeof item.component === 'string') { const component = dynamicImport(item.component); if (component) { route.component = component; } } // 递归处理子路由 if (item.children && item.children.length > 0) { route.routes = asyncRouterHandle(item.children); } routes.push(route); }); return routes; } /** * 扁平化路由(用于路由注册) */ export function flattenRoutes(routes: RouteItem[], parentPath: string = ''): RouteItem[] { const result: RouteItem[] = []; routes.forEach((route) => { const fullPath = route.path.startsWith('/') ? route.path : `${parentPath}/${route.path}`.replace(/\/+/g, '/'); result.push({ ...route, path: fullPath, routes: undefined, }); if (route.routes && route.routes.length > 0) { result.push(...flattenRoutes(route.routes, fullPath)); } }); return result; } /** * 构建菜单数据(用于侧边栏显示) */ export function buildMenuData(routes: RouteItem[]): RouteItem[] { return routes.filter((route) => !route.hideInMenu).map((route) => ({ ...route, routes: route.routes ? buildMenuData(route.routes) : undefined, })); } /** * 将后端菜单数据转换为 ProLayout 菜单格式 * 与 app.tsx 中的函数保持一致 */ export function convertToMenuData(menus: BackendMenuItem[], parentPath: string = ''): any[] { return menus.map((menu) => { const path = menu.path.startsWith('/') || menu.path.startsWith('http') ? menu.path : `${parentPath}/${menu.path}`.replace(/\/+/g, '/'); const menuItem: any = { path, name: menu.meta?.title || menu.name, icon: menu.meta?.icon, hideInMenu: menu.hidden, }; if (menu.children && menu.children.length > 0) { menuItem.children = convertToMenuData(menu.children, path); } return menuItem; }); } export default { asyncRouterHandle, flattenRoutes, buildMenuData, isExternalUrl, convertToMenuData, };