前端动态路由

This commit is contained in:
dengqichen 2025-10-31 15:51:21 +08:00
parent 99cc4f3e7e
commit e96ecf1c2f
3 changed files with 113 additions and 261 deletions

View File

@ -1,273 +1,86 @@
import { createBrowserRouter, Navigate } from 'react-router-dom'; import { createBrowserRouter, Navigate, RouteObject } from 'react-router-dom';
import { lazy, Suspense } from 'react'; import { Suspense } from 'react';
import { Spin } from 'antd'; import { Spin } from 'antd';
import Login from '../pages/Login'; import Login from '../pages/Login';
import BasicLayout from '../layouts/BasicLayout'; import BasicLayout from '../layouts/BasicLayout';
import { useSelector } from 'react-redux'; import { getRouteComponent } from './routeMap';
import { RootState } from '../store'; import type { MenuResponse } from '@/pages/System/Menu/types';
import store from '../store';
// 加组件 // 加组件
const LoadingComponent = () => ( const LoadingComponent = () => (
<div style={{ padding: 24, textAlign: 'center' }}> <div style={{ padding: 24, textAlign: 'center' }}>
<Spin size="large" /> <Spin size="large" />
</div> </div>
); );
// 路由守卫 /**
const PrivateRoute = ({ children }: { children: React.ReactNode }) => { *
const token = useSelector((state: RootState) => state.user.token); * @param menus
* @returns
*/
const generateRoutes = (menus: MenuResponse[]): RouteObject[] => {
const routes: RouteObject[] = [];
if (!token) { menus.forEach((menu) => {
return <Navigate to="/login" />; // 跳过隐藏的菜单
if (menu.hidden) return;
// 如果有 component 且有 path创建路由
if (menu.component && menu.path) {
const Component = getRouteComponent(menu.component);
if (Component) {
// 移除开头的 /
const path = menu.path.replace(/^\//, '');
routes.push({
path,
element: (
<Suspense fallback={<LoadingComponent />}>
<Component />
</Suspense>
),
});
}
} }
return <>{children}</>; // 递归处理子菜单
if (menu.children && menu.children.length > 0) {
const childRoutes = generateRoutes(menu.children);
routes.push(...childRoutes);
}
});
return routes;
}; };
// 懒加载组件 /**
const Dashboard = lazy(() => import('../pages/Dashboard')); *
const User = lazy(() => import('../pages/System/User')); * Redux store ,
const Role = lazy(() => import('../pages/System/Role')); */
const Menu = lazy(() => import('../pages/System/Menu')); const createDynamicRouter = () => {
const Department = lazy(() => import('../pages/System/Department')); const state = store.getState();
const WorkflowDefinitionList = lazy(() => import('../pages/Workflow/Definition')); const menus = state.user.menus || [];
const WorkflowDesign = lazy(() => import('../pages/Workflow/Design'));
const WorkflowInstance = lazy(() => import('../pages/Workflow/Instance'));
const WorkflowMonitor = lazy(() => import('../pages/Workflow/Monitor'));
const LogStreamPage = lazy(() => import('../pages/LogStream'));
const ApplicationList = lazy(() => import('../pages/Deploy/Application/List'));
const EnvironmentList = lazy(() => import('../pages/Deploy/Environment/List'));
const DeploymentConfigList = lazy(() => import('../pages/Deploy/Deployment/List'));
const JenkinsManagerList = lazy(() => import('../pages/Deploy/JenkinsManager/List'));
const GitManagerList = lazy(() => import('../pages/Deploy/GitManager/List'));
const External = lazy(() => import('../pages/Deploy/External'));
const TeamList = lazy(() => import('../pages/Deploy/Team/List'));
const ScheduleJobList = lazy(() => import('../pages/Deploy/ScheduleJob/List'));
const ServerList = lazy(() => import('../pages/Deploy/Server/List'));
const FormDesigner = lazy(() => import('../pages/FormDesigner'));
const FormDefinitionList = lazy(() => import('../pages/Form/Definition'));
const FormDefinitionDesigner = lazy(() => import('../pages/Form/Definition/Designer'));
const FormDataList = lazy(() => import('../pages/Form/Data'));
const FormDataDetail = lazy(() => import('../pages/Form/Data/Detail'));
// Workflow2 相关路由已迁移到 Workflow删除旧路由 // 动态生成路由
const dynamicRoutes = generateRoutes(menus);
// 创建路由 return createBrowserRouter([
const router = createBrowserRouter([
{ {
path: '/login', path: '/login',
element: <Login /> element: <Login />
}, },
{ {
path: '/', path: '/',
element: ( element: <BasicLayout />,
<PrivateRoute>
<BasicLayout />
</PrivateRoute>
),
children: [ children: [
{ {
path: '', path: '',
element: <Navigate to="/dashboard" replace /> element: <Navigate to="/dashboard" replace />
}, },
{ // 动态生成的路由
path: 'dashboard', ...dynamicRoutes,
element: ( // 404 路由
<Suspense fallback={<LoadingComponent/>}>
<Dashboard/>
</Suspense>
)
},
{
path: 'deploy',
children: [
{
path: 'applications',
element: <Suspense fallback={<LoadingComponent/>}><ApplicationList/></Suspense>
},
{
path: 'teams',
element: <Suspense fallback={<LoadingComponent/>}><TeamList/></Suspense>
},
{
path: 'deployment',
element: <Suspense fallback={<LoadingComponent/>}><DeploymentConfigList/></Suspense>
},
{
path: 'schedule-jobs',
element: <Suspense fallback={<LoadingComponent/>}><ScheduleJobList/></Suspense>
},
{
path: 'servers',
element: <Suspense fallback={<LoadingComponent/>}><ServerList/></Suspense>
}
]
},
{
path: 'resource',
children: [
{
path: 'environments',
element: <Suspense fallback={<LoadingComponent/>}><EnvironmentList/></Suspense>
},
{
path: 'jenkins-manager',
element: <Suspense fallback={<LoadingComponent/>}><JenkinsManagerList/></Suspense>
},
{
path: 'git-manager',
element: <Suspense fallback={<LoadingComponent/>}><GitManagerList/></Suspense>
},
{
path: 'external',
element: (
<Suspense fallback={<LoadingComponent/>}>
<External/>
</Suspense>
)
}
]
},
{
path: 'system',
children: [
{
path: 'user',
element: (
<Suspense fallback={<LoadingComponent/>}>
<User/>
</Suspense>
)
},
{
path: 'role',
element: (
<Suspense fallback={<LoadingComponent/>}>
<Role/>
</Suspense>
)
},
{
path: 'menu',
element: (
<Suspense fallback={<LoadingComponent/>}>
<Menu/>
</Suspense>
)
},
{
path: 'department',
element: (
<Suspense fallback={<LoadingComponent/>}>
<Department/>
</Suspense>
)
}
]
},
{
path: 'workflow',
children: [
{
path: 'definition',
element: (
<Suspense fallback={<LoadingComponent/>}>
<WorkflowDefinitionList/>
</Suspense>
)
},
{
path: 'design',
children: [
{
path: ':id',
element: (
<Suspense fallback={<LoadingComponent/>}>
<WorkflowDesign/>
</Suspense>
)
}
]
},
{
path: 'instance',
element: (
<Suspense fallback={<LoadingComponent/>}>
<WorkflowInstance/>
</Suspense>
)
},
{
path: 'form',
children: [
{
index: true,
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDefinitionList/>
</Suspense>
)
},
{
path: 'create',
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDefinitionDesigner/>
</Suspense>
)
},
{
path: ':id/design',
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDefinitionDesigner/>
</Suspense>
)
},
{
path: 'data',
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDataList/>
</Suspense>
)
},
{
path: 'data/:id',
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDataDetail/>
</Suspense>
)
}
]
},
{
path: 'monitor',
element: (
<Suspense fallback={<LoadingComponent/>}>
<WorkflowMonitor/>
</Suspense>
)
},
{
path: 'form-designer',
element: (
<Suspense fallback={<LoadingComponent/>}>
<FormDesigner/>
</Suspense>
)
},
{
path: 'log-stream/:processInstanceId',
element: (
<Suspense fallback={<LoadingComponent/>}>
<LogStreamPage/>
</Suspense>
)
}
]
},
{ {
path: '*', path: '*',
element: <Navigate to="/dashboard"/> element: <Navigate to="/dashboard"/>
@ -275,5 +88,9 @@ const router = createBrowserRouter([
] ]
} }
]); ]);
};
// 导出路由实例
const router = createDynamicRouter();
export default router; export default router;

View File

@ -0,0 +1,33 @@
import { lazy, ComponentType } from 'react';
/**
* 使 Vite import.meta.glob
*
*/
const modules = import.meta.glob<{ default: ComponentType<any> }>('/src/pages/**/index.tsx');
/**
* component
* @param componentPath (: 'Dashboard', 'Deploy/Application/List')
* @returns null
*/
export const getRouteComponent = (componentPath: string | null | undefined): React.LazyExoticComponent<ComponentType<any>> | null => {
if (!componentPath) return null;
// 移除开头和结尾的斜杠
const cleanPath = componentPath.replace(/^\/+|\/+$/g, '');
// 构建完整的模块路径
const modulePath = `/src/pages/${cleanPath}/index.tsx`;
// 检查模块是否存在
if (!modules[modulePath]) {
console.warn(`Route component not found: ${modulePath}`);
console.warn('Available modules:', Object.keys(modules));
return null;
}
// 返回懒加载组件
return lazy(() => modules[modulePath]().then((m: { default: ComponentType<any> }) => ({ default: m.default })));
};

2
frontend/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
/// <reference types="vite/client" />