前端动态路由

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,279 +1,96 @@
import { createBrowserRouter, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { createBrowserRouter, Navigate, RouteObject } from 'react-router-dom';
import { Suspense } from 'react';
import { Spin } from 'antd';
import Login from '../pages/Login';
import BasicLayout from '../layouts/BasicLayout';
import { useSelector } from 'react-redux';
import { RootState } from '../store';
import { getRouteComponent } from './routeMap';
import type { MenuResponse } from '@/pages/System/Menu/types';
import store from '../store';
// 加组件
// 加组件
const LoadingComponent = () => (
<div style={{ padding: 24, textAlign: 'center' }}>
<Spin size="large" />
</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) {
return <Navigate to="/login" />;
}
menus.forEach((menu) => {
// 跳过隐藏的菜单
if (menu.hidden) return;
return <>{children}</>;
// 如果有 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>
),
});
}
}
// 递归处理子菜单
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'));
const Role = lazy(() => import('../pages/System/Role'));
const Menu = lazy(() => import('../pages/System/Menu'));
const Department = lazy(() => import('../pages/System/Department'));
const WorkflowDefinitionList = lazy(() => import('../pages/Workflow/Definition'));
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'));
/**
*
* Redux store ,
*/
const createDynamicRouter = () => {
const state = store.getState();
const menus = state.user.menus || [];
// 动态生成路由
const dynamicRoutes = generateRoutes(menus);
// Workflow2 相关路由已迁移到 Workflow删除旧路由
return createBrowserRouter([
{
path: '/login',
element: <Login />
},
{
path: '/',
element: <BasicLayout />,
children: [
{
path: '',
element: <Navigate to="/dashboard" replace />
},
// 动态生成的路由
...dynamicRoutes,
// 404 路由
{
path: '*',
element: <Navigate to="/dashboard"/>
}
]
}
]);
};
// 创建路由
const router = createBrowserRouter([
{
path: '/login',
element: <Login />
},
{
path: '/',
element: (
<PrivateRoute>
<BasicLayout />
</PrivateRoute>
),
children: [
{
path: '',
element: <Navigate to="/dashboard" replace />
},
{
path: 'dashboard',
element: (
<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: '*',
element: <Navigate to="/dashboard"/>
}
]
}
]);
// 导出路由实例
const router = createDynamicRouter();
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" />