router
// components/DynamicRouter.tsx
import { Suspense, useEffect, useState, lazy, Children } from "react";
import { useRoutes, Navigate } from "react-router-dom";
interface RouteItem {
path: string;
element: React.ReactNode;
meta?: {
title: string;
roles?: string[]; // 权限控制
requiresAuth?: boolean;
hideInMenu?: boolean;
};
children?: RouteItem[];
}
interface MenuItem {
path?: string;
name: string;
component?: string; // 组件路径(如:"@/pages/Dashboard")
meta?: Omit<RouteItem["meta"], "hideInMenu">;
children?: MenuItem[];
index?: boolean;
}
// 模拟从后端获取菜单数据
const fetchMenuData = async (): Promise<MenuItem[]> => {
// 实际项目中替换为 API 请求
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
index: true,
name: "home",
component: "../views/Home",
meta: { title: "控制台", requiresAuth: true,},
},
{
path: "/admin",
name: "Admin",
meta: { title: "管理页", roles: ["admin"] },
children: [
{
path: "/admin/user",
name: "User",
component: "@/pages/Admin/User",
meta: { title: "用户管理" },
},
],
},
]);
}, 500);
});
};
// 权限校验组件
const AuthGuard = ({
children,
roles,
}: {
children: any;
roles?: string[];
}) => {
const isAuthenticated = true; // 替换为实际登录状态
const userRoles = ["user"]; // 替换为实际用户角色
if (!isAuthenticated) return <Navigate to="/login" replace />;
if (roles && !roles.some((role) => userRoles.includes(role)))
return <Navigate to="/403" replace />;
return children;
};
const Layout = lazy(() => import("../views/Layout"));
const Login = lazy(() => import("../views/Login"));
// 动态路由组件
export const DynamicRouter = () => {
const [routes, setRoutes] = useState<RouteItem[]>([]);
useEffect(() => {
const initRoutes = async () => {
// 1. 获取菜单数据
const menuData = await fetchMenuData();
// 2. 转换为路由配置
const dynamicRoutes = menuData.map((menu) => convertMenuToRoute(menu));
// 3. 添加静态路由(如登录页、404)
const allRoutes = [
{ path: "/login", element: <Login></Login> },
{ path: "/", element: <Layout></Layout>, children: [...dynamicRoutes] },
{ path: "*", element: <div>404</div> },
];
setRoutes(allRoutes);
};
initRoutes();
}, []);
// 递归转换菜单项为路由
const convertMenuToRoute = (menu: any): RouteItem => {
let element: React.ReactNode = <div>Default</div>;
if (menu.component) {
// 动态导入组件(懒加载)
const Component = lazy(() => import(`${menu.component}`));
element = (
<Suspense fallback={<div>Loading...</div>}>
<Component />
</Suspense>
);
}
const route: any = {
element: <AuthGuard roles={menu.meta?.roles}>{element}</AuthGuard>,
meta: menu.meta,
};
// 3. 处理 index 路由(不能有 path)
if (menu.index) {
route.index = true;
} else {
route.path = menu.path; // 非 index 路由才设置 path
}
// 4. 递归处理子路由
if (menu.children) {
route.children = menu.children.map((child: any) =>
convertMenuToRoute(child)
);
}
return route;
};
// 渲染路由
const element = useRoutes(routes);
console.log(routes);
return <div className="router-view">{element}</div>;
};
app.tsx
// App.tsx
import { BrowserRouter } from 'react-router-dom';
import { DynamicRouter } from './router/index';
const App = () => {
return (
<BrowserRouter>
<DynamicRouter>
</DynamicRouter>
</BrowserRouter>
);
};
export default App;
main.tsx
import { createRoot } from 'react-dom/client'
import './App.css'
import App from './App'
createRoot(document.getElementById('root')!).render(
<App></App>
)