index.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. /**
  2. * 动态路由配置
  3. */
  4. import { lazy, Suspense } from "react";
  5. import { createBrowserRouter, Navigate, type RouteObject } from "react-router-dom";
  6. import MainLayout from "../layouts/MainLayout/index";
  7. import type { JeecgMenu } from "../types/menu";
  8. // 默认加载组件
  9. function LoadingPage() {
  10. return <div style={{ padding: "20px" }}>加载中...</div>;
  11. }
  12. // 默认页面(当组件路径不存在时)
  13. function DefaultPage({ title }: { title: string }) {
  14. return (
  15. <div style={{ padding: "20px" }}>
  16. <h2>{title}</h2>
  17. <p>页面开发中...</p>
  18. </div>
  19. );
  20. }
  21. // 使用 Vite 的 import.meta.glob 预加载所有页面组件
  22. // 这会在构建时扫描 src/pages 目录下的所有 .tsx 文件
  23. const pageModules = import.meta.glob("../pages/**/*.tsx");
  24. console.log("[Router] Available page modules:", Object.keys(pageModules));
  25. // 动态加载组件
  26. // 根据 component 配置动态导入 src/pages 下的组件
  27. // 例如: "content-model/index" -> src/pages/content-model/index.tsx
  28. function loadComponent(componentPath: string, title: string) {
  29. if (!componentPath) {
  30. return <DefaultPage title={title} />;
  31. }
  32. // 清理路径:移除开头的 /,移除文件后缀
  33. const cleanPath = componentPath
  34. .replace(/^\//, "")
  35. .replace(/\.(tsx|vue|jsx|js)$/, "");
  36. // 构建完整的模块路径
  37. const modulePath = `../pages/${cleanPath}.tsx`;
  38. console.log(`[Router] Loading: "${cleanPath}" (${title})`);
  39. console.log(`[Router] Module path: ${modulePath}`);
  40. // 检查模块是否存在
  41. const moduleLoader = pageModules[modulePath];
  42. if (!moduleLoader) {
  43. console.warn(`[Router] ✗ Module not found: ${modulePath}`);
  44. console.warn(`[Router] Available modules:`, Object.keys(pageModules));
  45. return <DefaultPage title={title} />;
  46. }
  47. // 使用 lazy 加载组件
  48. const Component = lazy(() =>
  49. moduleLoader()
  50. .then((module: any) => {
  51. console.log(`[Router] ✓ Loaded: ${cleanPath}`);
  52. return module;
  53. })
  54. .catch((error) => {
  55. console.error(`[Router] ✗ Failed to load: ${cleanPath}`, error);
  56. return {
  57. default: () => <DefaultPage title={title} />,
  58. };
  59. })
  60. );
  61. return (
  62. <Suspense fallback={<LoadingPage />}>
  63. <Component />
  64. </Suspense>
  65. );
  66. }
  67. // 将菜单转换为路由配置
  68. function menuToRoute(menu: JeecgMenu, isChild = false): RouteObject | null {
  69. // 跳过隐藏的菜单
  70. if (menu.hidden || menu.meta?.hideMenu) {
  71. return null;
  72. }
  73. // 处理路径
  74. let routePath = menu.path || "";
  75. // 如果是子路由且路径是绝对路径,只取最后一段作为相对路径
  76. if (isChild && routePath.startsWith("/")) {
  77. const segments = routePath.split("/").filter(Boolean);
  78. routePath = segments[segments.length - 1] || "";
  79. }
  80. const route: RouteObject = {
  81. path: routePath,
  82. };
  83. // 如果有子菜单
  84. if (menu.children && menu.children.length > 0) {
  85. const childRoutes = menu.children
  86. .map((child) => menuToRoute(child, true))
  87. .filter((r): r is RouteObject => r !== null);
  88. if (childRoutes.length > 0) {
  89. // 有子菜单,父路由作为容器,使用 Outlet
  90. // 获取第一个可见子菜单
  91. const firstChild = menu.children.find((child) => !child.hidden && !child.meta?.hideMenu);
  92. if (firstChild) {
  93. // 获取第一个子路由的相对路径
  94. const firstChildPath = firstChild.path || "";
  95. const segments = firstChildPath.split("/").filter(Boolean);
  96. const redirectPath = segments[segments.length - 1] || "";
  97. route.children = [
  98. // 默认重定向到第一个子菜单
  99. {
  100. index: true,
  101. element: <Navigate to={redirectPath} replace />,
  102. },
  103. ...childRoutes,
  104. ];
  105. } else {
  106. route.children = childRoutes;
  107. }
  108. } else {
  109. // 有 children 但都被隐藏了,当作叶子节点处理
  110. const title = menu.meta?.title || menu.name;
  111. const component = menu.component || "";
  112. // 跳过 layouts 组件
  113. if (component && !component.includes("layouts/")) {
  114. route.element = loadComponent(component, title);
  115. } else {
  116. route.element = <DefaultPage title={title} />;
  117. }
  118. }
  119. } else {
  120. // 没有子菜单,加载组件
  121. const title = menu.meta?.title || menu.name;
  122. const component = menu.component || "";
  123. // 跳过 layouts 组件
  124. if (component && !component.includes("layouts/")) {
  125. route.element = loadComponent(component, title);
  126. } else {
  127. route.element = <DefaultPage title={title} />;
  128. }
  129. }
  130. return route;
  131. }
  132. // 根据菜单数据生成路由
  133. export function generateRoutes(menus: JeecgMenu[]): RouteObject[] {
  134. const routes = menus
  135. .map((menu) => menuToRoute(menu))
  136. .filter((r): r is RouteObject => r !== null);
  137. return [
  138. {
  139. path: "/",
  140. element: <MainLayout />,
  141. children: [
  142. {
  143. index: true,
  144. element: <Navigate to={menus[0]?.path || "/content-model"} replace />,
  145. },
  146. ...routes,
  147. ],
  148. },
  149. ];
  150. }
  151. // 创建路由器(初始为空路由)
  152. export function createAppRouter(menus: JeecgMenu[]) {
  153. const routes = generateRoutes(menus);
  154. // 调试:打印路由路径结构
  155. console.log("=== Generated Routes ===");
  156. routes.forEach((route) => {
  157. console.log(`Route: ${route.path}`);
  158. if (route.children) {
  159. route.children.forEach((child) => {
  160. console.log(` - Child: ${child.path || "index"}`);
  161. if (child.children) {
  162. child.children.forEach((grandChild) => {
  163. console.log(` - GrandChild: ${grandChild.path || "index"}`);
  164. });
  165. }
  166. });
  167. }
  168. });
  169. return createBrowserRouter(routes);
  170. }