前端动态路由
This commit is contained in:
parent
99cc4f3e7e
commit
e96ecf1c2f
@ -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;
|
||||
33
frontend/src/router/routeMap.ts
Normal file
33
frontend/src/router/routeMap.ts
Normal 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
2
frontend/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user