253 lines
7.6 KiB
TypeScript
253 lines
7.6 KiB
TypeScript
/**
|
|
* KRA - Dynamic Router Utility
|
|
* 动态路由处理工具,参考 GVA 的 asyncRouter.js 实现
|
|
*/
|
|
import React from 'react';
|
|
|
|
// 页面组件映射 - 对应 GVA 的 viewModules
|
|
// UMI 使用 React.lazy 动态导入
|
|
const pageModules: Record<string, () => Promise<{ default: React.ComponentType<any> }>> = {
|
|
// 仪表盘
|
|
'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<string, () => Promise<{ default: React.ComponentType<any> }>> = {
|
|
'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<string, number>;
|
|
parameters?: Array<{ key: string; value: string }>;
|
|
}
|
|
|
|
export interface RouteItem {
|
|
path: string;
|
|
name?: string;
|
|
icon?: string;
|
|
component?: React.ComponentType<any> | React.LazyExoticComponent<any>;
|
|
routes?: RouteItem[];
|
|
hideInMenu?: boolean;
|
|
// UMI Pro Layout 需要的字段
|
|
access?: string;
|
|
// 自定义 meta 信息
|
|
meta?: {
|
|
title?: string;
|
|
icon?: string;
|
|
keepAlive?: boolean;
|
|
defaultMenu?: boolean;
|
|
closeTab?: boolean;
|
|
btns?: Record<string, number>;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 动态导入组件
|
|
* 对应 GVA 的 dynamicImport 函数
|
|
*/
|
|
function dynamicImport(component: string): React.LazyExoticComponent<any> | 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,
|
|
};
|