create-aiko 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/README.md +46 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +7 -0
  4. package/dist/create.d.ts +1 -0
  5. package/dist/create.js +174 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/dist/scaffold.d.ts +7 -0
  9. package/dist/scaffold.js +310 -0
  10. package/package.json +37 -0
  11. package/template/README.md +63 -0
  12. package/template/docs/better-sqlite3-design.md +101 -0
  13. package/template/docs/shared-vs-shared-auth.md +54 -0
  14. package/template/package.json +23 -0
  15. package/template/packages/admin/.env.dev +1 -0
  16. package/template/packages/admin/.env.prod +4 -0
  17. package/template/packages/admin/.env.stage +4 -0
  18. package/template/packages/admin/Dockerfile +37 -0
  19. package/template/packages/admin/README.MD +27 -0
  20. package/template/packages/admin/components.json +21 -0
  21. package/template/packages/admin/eslint.config.js +28 -0
  22. package/template/packages/admin/index.html +50 -0
  23. package/template/packages/admin/package.json +100 -0
  24. package/template/packages/admin/public/vite.svg +1 -0
  25. package/template/packages/admin/src/App.css +82 -0
  26. package/template/packages/admin/src/App.tsx +128 -0
  27. package/template/packages/admin/src/app.config.ts +3 -0
  28. package/template/packages/admin/src/components/admin-ui/approval-flow.tsx +205 -0
  29. package/template/packages/admin/src/components/admin-ui/data-table/data-table-pagination.tsx +148 -0
  30. package/template/packages/admin/src/components/admin-ui/data-table/data-table-sorter.tsx +50 -0
  31. package/template/packages/admin/src/components/admin-ui/data-table/index.tsx +296 -0
  32. package/template/packages/admin/src/components/admin-ui/editable-table.tsx +292 -0
  33. package/template/packages/admin/src/components/admin-ui/form/input-password.tsx +38 -0
  34. package/template/packages/admin/src/components/admin-ui/form/sign-in-form.tsx +104 -0
  35. package/template/packages/admin/src/components/admin-ui/layout/error-component.tsx +71 -0
  36. package/template/packages/admin/src/components/admin-ui/layout/header.tsx +148 -0
  37. package/template/packages/admin/src/components/admin-ui/layout/language-switcher.tsx +47 -0
  38. package/template/packages/admin/src/components/admin-ui/layout/layout.tsx +42 -0
  39. package/template/packages/admin/src/components/admin-ui/layout/loading-overlay.tsx +36 -0
  40. package/template/packages/admin/src/components/admin-ui/layout/shell-bar.tsx +232 -0
  41. package/template/packages/admin/src/components/admin-ui/layout/sidebar.tsx +193 -0
  42. package/template/packages/admin/src/components/admin-ui/layout/user-avatar.tsx +31 -0
  43. package/template/packages/admin/src/components/admin-ui/list-report.tsx +313 -0
  44. package/template/packages/admin/src/components/admin-ui/master-detail.tsx +382 -0
  45. package/template/packages/admin/src/components/admin-ui/notification/toaster.tsx +23 -0
  46. package/template/packages/admin/src/components/admin-ui/notification/undoable-notification.tsx +84 -0
  47. package/template/packages/admin/src/components/admin-ui/object-page.tsx +539 -0
  48. package/template/packages/admin/src/components/admin-ui/process-stepper.tsx +204 -0
  49. package/template/packages/admin/src/components/admin-ui/theme/theme-provider.tsx +160 -0
  50. package/template/packages/admin/src/components/admin-ui/theme/theme-select.tsx +129 -0
  51. package/template/packages/admin/src/components/admin-ui/theme/theme-toggle.tsx +90 -0
  52. package/template/packages/admin/src/components/admin-ui/timeline.tsx +137 -0
  53. package/template/packages/admin/src/components/admin-ui/tree-navigator.tsx +243 -0
  54. package/template/packages/admin/src/components/ui/accordion.tsx +64 -0
  55. package/template/packages/admin/src/components/ui/alert-dialog.tsx +157 -0
  56. package/template/packages/admin/src/components/ui/alert.tsx +66 -0
  57. package/template/packages/admin/src/components/ui/aspect-ratio.tsx +9 -0
  58. package/template/packages/admin/src/components/ui/avatar.tsx +53 -0
  59. package/template/packages/admin/src/components/ui/badge.tsx +47 -0
  60. package/template/packages/admin/src/components/ui/breadcrumb.tsx +111 -0
  61. package/template/packages/admin/src/components/ui/button.tsx +59 -0
  62. package/template/packages/admin/src/components/ui/calendar.tsx +74 -0
  63. package/template/packages/admin/src/components/ui/card.tsx +92 -0
  64. package/template/packages/admin/src/components/ui/carousel.tsx +237 -0
  65. package/template/packages/admin/src/components/ui/chart.tsx +351 -0
  66. package/template/packages/admin/src/components/ui/checkbox.tsx +32 -0
  67. package/template/packages/admin/src/components/ui/collapsible.tsx +33 -0
  68. package/template/packages/admin/src/components/ui/command.tsx +182 -0
  69. package/template/packages/admin/src/components/ui/context-menu.tsx +252 -0
  70. package/template/packages/admin/src/components/ui/dialog.tsx +141 -0
  71. package/template/packages/admin/src/components/ui/drawer.tsx +130 -0
  72. package/template/packages/admin/src/components/ui/dropdown-menu.tsx +255 -0
  73. package/template/packages/admin/src/components/ui/form.tsx +166 -0
  74. package/template/packages/admin/src/components/ui/hover-card.tsx +42 -0
  75. package/template/packages/admin/src/components/ui/input-otp.tsx +77 -0
  76. package/template/packages/admin/src/components/ui/input.tsx +21 -0
  77. package/template/packages/admin/src/components/ui/label.tsx +22 -0
  78. package/template/packages/admin/src/components/ui/menubar.tsx +274 -0
  79. package/template/packages/admin/src/components/ui/navigation-menu.tsx +168 -0
  80. package/template/packages/admin/src/components/ui/pagination.tsx +127 -0
  81. package/template/packages/admin/src/components/ui/popover.tsx +48 -0
  82. package/template/packages/admin/src/components/ui/progress.tsx +29 -0
  83. package/template/packages/admin/src/components/ui/radio-group.tsx +45 -0
  84. package/template/packages/admin/src/components/ui/resizable.tsx +54 -0
  85. package/template/packages/admin/src/components/ui/scroll-area.tsx +58 -0
  86. package/template/packages/admin/src/components/ui/select.tsx +183 -0
  87. package/template/packages/admin/src/components/ui/separator.tsx +26 -0
  88. package/template/packages/admin/src/components/ui/sheet.tsx +139 -0
  89. package/template/packages/admin/src/components/ui/sidebar.tsx +740 -0
  90. package/template/packages/admin/src/components/ui/skeleton.tsx +13 -0
  91. package/template/packages/admin/src/components/ui/slider.tsx +63 -0
  92. package/template/packages/admin/src/components/ui/sonner.tsx +23 -0
  93. package/template/packages/admin/src/components/ui/switch.tsx +31 -0
  94. package/template/packages/admin/src/components/ui/table.tsx +114 -0
  95. package/template/packages/admin/src/components/ui/tabs.tsx +66 -0
  96. package/template/packages/admin/src/components/ui/textarea.tsx +18 -0
  97. package/template/packages/admin/src/components/ui/toggle-group.tsx +73 -0
  98. package/template/packages/admin/src/components/ui/toggle.tsx +45 -0
  99. package/template/packages/admin/src/components/ui/tooltip.tsx +59 -0
  100. package/template/packages/admin/src/hooks/use-mobile.ts +21 -0
  101. package/template/packages/admin/src/i18n.ts +20 -0
  102. package/template/packages/admin/src/index.tsx +18 -0
  103. package/template/packages/admin/src/layouts/menu-layout.tsx +211 -0
  104. package/template/packages/admin/src/layouts/tile-layout.tsx +355 -0
  105. package/template/packages/admin/src/lib/utils.ts +6 -0
  106. package/template/packages/admin/src/locales/en.json +68 -0
  107. package/template/packages/admin/src/locales/zh.json +68 -0
  108. package/template/packages/admin/src/pages/dashboard.tsx +12 -0
  109. package/template/packages/admin/src/pages/goods-receipt/CreatePage.tsx +302 -0
  110. package/template/packages/admin/src/pages/goods-receipt/EditPage.tsx +221 -0
  111. package/template/packages/admin/src/pages/goods-receipt/ListPage.tsx +283 -0
  112. package/template/packages/admin/src/pages/goods-receipt/ViewPage.tsx +280 -0
  113. package/template/packages/admin/src/pages/goods-receipt/index.ts +4 -0
  114. package/template/packages/admin/src/pages/home-page.tsx +244 -0
  115. package/template/packages/admin/src/pages/login-page.tsx +91 -0
  116. package/template/packages/admin/src/pages/master-data/cost-centers/index.tsx +461 -0
  117. package/template/packages/admin/src/pages/master-data/currencies/index.tsx +255 -0
  118. package/template/packages/admin/src/pages/master-data/materials/ListPage.tsx +271 -0
  119. package/template/packages/admin/src/pages/master-data/materials/ViewPage.tsx +240 -0
  120. package/template/packages/admin/src/pages/master-data/materials/index.ts +2 -0
  121. package/template/packages/admin/src/pages/master-data/plants/ListPage.tsx +279 -0
  122. package/template/packages/admin/src/pages/master-data/plants/ViewPage.tsx +380 -0
  123. package/template/packages/admin/src/pages/master-data/plants/index.ts +2 -0
  124. package/template/packages/admin/src/pages/master-data/purchase-organizations/index.tsx +341 -0
  125. package/template/packages/admin/src/pages/master-data/units-of-measure/index.tsx +295 -0
  126. package/template/packages/admin/src/pages/master-data/vendors/ListPage.tsx +266 -0
  127. package/template/packages/admin/src/pages/master-data/vendors/ViewPage.tsx +274 -0
  128. package/template/packages/admin/src/pages/master-data/vendors/index.ts +2 -0
  129. package/template/packages/admin/src/pages/placeholder-page.tsx +13 -0
  130. package/template/packages/admin/src/pages/purchase-orders/ListPage.tsx +289 -0
  131. package/template/packages/admin/src/pages/purchase-orders/ViewPage.tsx +343 -0
  132. package/template/packages/admin/src/pages/purchase-orders/index.ts +2 -0
  133. package/template/packages/admin/src/pages/purchase-requisitions/CreatePage.tsx +398 -0
  134. package/template/packages/admin/src/pages/purchase-requisitions/EditPage.tsx +473 -0
  135. package/template/packages/admin/src/pages/purchase-requisitions/ListPage.tsx +307 -0
  136. package/template/packages/admin/src/pages/purchase-requisitions/ViewPage.tsx +304 -0
  137. package/template/packages/admin/src/pages/purchase-requisitions/constants.ts +51 -0
  138. package/template/packages/admin/src/pages/purchase-requisitions/index.ts +4 -0
  139. package/template/packages/admin/src/pages/reports/PurchaseOrderReport.tsx +312 -0
  140. package/template/packages/admin/src/pages/reports/PurchaseRequisitionReport.tsx +303 -0
  141. package/template/packages/admin/src/pages/reports/index.ts +2 -0
  142. package/template/packages/admin/src/pages/settings-page.tsx +335 -0
  143. package/template/packages/admin/src/providers/app-config.tsx +50 -0
  144. package/template/packages/admin/src/routes/auth.ts +6 -0
  145. package/template/packages/admin/src/routes/index.ts +85 -0
  146. package/template/packages/admin/src/routes/menu.ts +176 -0
  147. package/template/packages/admin/src/routes/modules/goods-receipt.ts +31 -0
  148. package/template/packages/admin/src/routes/modules/master-data.ts +41 -0
  149. package/template/packages/admin/src/routes/modules/purchase-orders.ts +27 -0
  150. package/template/packages/admin/src/routes/modules/purchase-requisitions.ts +39 -0
  151. package/template/packages/admin/src/routes/modules/reports.ts +33 -0
  152. package/template/packages/admin/src/routes/modules/settings.ts +19 -0
  153. package/template/packages/admin/src/routes/withSuspense.tsx +21 -0
  154. package/template/packages/admin/src/theme/amber.css +27 -0
  155. package/template/packages/admin/src/theme/blue.css +27 -0
  156. package/template/packages/admin/src/theme/default.css +75 -0
  157. package/template/packages/admin/src/theme/fiori.css +180 -0
  158. package/template/packages/admin/src/theme/green.css +27 -0
  159. package/template/packages/admin/src/theme/index.css +12 -0
  160. package/template/packages/admin/src/theme/rose.css +27 -0
  161. package/template/packages/admin/src/theme/violet.css +27 -0
  162. package/template/packages/admin/src/vite-env.d.ts +1 -0
  163. package/template/packages/admin/tsconfig.json +28 -0
  164. package/template/packages/admin/tsconfig.node.json +21 -0
  165. package/template/packages/admin/vite.config.ts +26 -0
  166. package/template/packages/api/.eslintrc.json +6 -0
  167. package/template/packages/api/.swcrc +17 -0
  168. package/template/packages/api/app.config.ts +160 -0
  169. package/template/packages/api/docs/api-document.md +497 -0
  170. package/template/packages/api/examples/security/README.md +664 -0
  171. package/template/packages/api/examples/security/complete/.env.example +26 -0
  172. package/template/packages/api/examples/security/complete/PROJECT_STRUCTURE.md +220 -0
  173. package/template/packages/api/examples/security/complete/README.md +847 -0
  174. package/template/packages/api/examples/security/complete/app.config.ts +69 -0
  175. package/template/packages/api/examples/security/complete/app.ts +63 -0
  176. package/template/packages/api/examples/security/complete/controller/auth.controller.ts +131 -0
  177. package/template/packages/api/examples/security/complete/controller/index.ts +4 -0
  178. package/template/packages/api/examples/security/complete/controller/permission.controller.ts +41 -0
  179. package/template/packages/api/examples/security/complete/controller/role.controller.ts +53 -0
  180. package/template/packages/api/examples/security/complete/controller/user.controller.ts +53 -0
  181. package/template/packages/api/examples/security/complete/dto/change-password.dto.ts +10 -0
  182. package/template/packages/api/examples/security/complete/dto/create-permission.dto.ts +14 -0
  183. package/template/packages/api/examples/security/complete/dto/create-role.dto.ts +11 -0
  184. package/template/packages/api/examples/security/complete/dto/create-user.dto.ts +15 -0
  185. package/template/packages/api/examples/security/complete/dto/index.ts +7 -0
  186. package/template/packages/api/examples/security/complete/dto/login.dto.ts +10 -0
  187. package/template/packages/api/examples/security/complete/dto/oauth-profile.dto.ts +7 -0
  188. package/template/packages/api/examples/security/complete/dto/register.dto.ts +17 -0
  189. package/template/packages/api/examples/security/complete/entity/index.ts +6 -0
  190. package/template/packages/api/examples/security/complete/entity/oauth-account.entity.ts +39 -0
  191. package/template/packages/api/examples/security/complete/entity/permission.entity.ts +31 -0
  192. package/template/packages/api/examples/security/complete/entity/role-permission.entity.ts +19 -0
  193. package/template/packages/api/examples/security/complete/entity/role.entity.ts +25 -0
  194. package/template/packages/api/examples/security/complete/entity/user-role.entity.ts +19 -0
  195. package/template/packages/api/examples/security/complete/entity/user.entity.ts +46 -0
  196. package/template/packages/api/examples/security/complete/init.sql +81 -0
  197. package/template/packages/api/examples/security/complete/middleware/auth.interceptor.ts +39 -0
  198. package/template/packages/api/examples/security/complete/middleware/index.ts +2 -0
  199. package/template/packages/api/examples/security/complete/middleware/permission.interceptor.ts +61 -0
  200. package/template/packages/api/examples/security/complete/package.json +54 -0
  201. package/template/packages/api/examples/security/complete/seed.sql +42 -0
  202. package/template/packages/api/examples/security/complete/service/auth.service.ts +41 -0
  203. package/template/packages/api/examples/security/complete/service/index.ts +5 -0
  204. package/template/packages/api/examples/security/complete/service/oauth.service.ts +82 -0
  205. package/template/packages/api/examples/security/complete/service/permission.service.ts +113 -0
  206. package/template/packages/api/examples/security/complete/service/role.service.ts +85 -0
  207. package/template/packages/api/examples/security/complete/service/user.service.ts +132 -0
  208. package/template/packages/api/examples/security/complete/tests/TEST_REPORT.md +318 -0
  209. package/template/packages/api/examples/security/complete/tests/generate-report.js +335 -0
  210. package/template/packages/api/examples/security/complete/tests/helpers/api-helpers.ts +116 -0
  211. package/template/packages/api/examples/security/complete/tests/helpers/index.ts +2 -0
  212. package/template/packages/api/examples/security/complete/tests/helpers/test-helpers.ts +129 -0
  213. package/template/packages/api/examples/security/complete/tests/integration/auth.api.test.ts +429 -0
  214. package/template/packages/api/examples/security/complete/tests/integration/role.api.test.ts +400 -0
  215. package/template/packages/api/examples/security/complete/tests/integration/user.api.test.ts +459 -0
  216. package/template/packages/api/examples/security/complete/tests/jest.config.js +40 -0
  217. package/template/packages/api/examples/security/complete/tests/run-all-tests.js +135 -0
  218. package/template/packages/api/examples/security/complete/tests/run-tests.js +109 -0
  219. package/template/packages/api/examples/security/complete/tests/setup.ts +19 -0
  220. package/template/packages/api/examples/security/complete/tests/unit/auth.service.test.ts +199 -0
  221. package/template/packages/api/examples/security/complete/tests/unit/permission.service.test.ts +377 -0
  222. package/template/packages/api/examples/security/complete/tests/unit/user.service.test.ts +288 -0
  223. package/template/packages/api/examples/security/complete/tsconfig.json +35 -0
  224. package/template/packages/api/examples/security/jwt/README.md +424 -0
  225. package/template/packages/api/examples/security/local/README.md +499 -0
  226. package/template/packages/api/examples/security/oauth2/README.md +637 -0
  227. package/template/packages/api/examples/security/permission/README.md +943 -0
  228. package/template/packages/api/examples/security/session/README.md +753 -0
  229. package/template/packages/api/package.json +56 -0
  230. package/template/packages/api/src/controller/auth.controller.ts +127 -0
  231. package/template/packages/api/src/controller/cache.controller.ts +106 -0
  232. package/template/packages/api/src/controller/menu.controller.ts +46 -0
  233. package/template/packages/api/src/controller/mq.controller.ts +35 -0
  234. package/template/packages/api/src/controller/role.controller.ts +51 -0
  235. package/template/packages/api/src/controller/upload.controller.ts +85 -0
  236. package/template/packages/api/src/controller/user.controller.ts +57 -0
  237. package/template/packages/api/src/dto/auth.dto.ts +30 -0
  238. package/template/packages/api/src/dto/cache.dto.ts +24 -0
  239. package/template/packages/api/src/dto/menu.dto.ts +37 -0
  240. package/template/packages/api/src/dto/mq.dto.ts +16 -0
  241. package/template/packages/api/src/dto/role.dto.ts +16 -0
  242. package/template/packages/api/src/dto/user.dto.ts +35 -0
  243. package/template/packages/api/src/entity/menu.entity.ts +34 -0
  244. package/template/packages/api/src/entity/role-menu.entity.ts +13 -0
  245. package/template/packages/api/src/entity/role.entity.ts +22 -0
  246. package/template/packages/api/src/entity/user-role.entity.ts +13 -0
  247. package/template/packages/api/src/entity/user.entity.ts +31 -0
  248. package/template/packages/api/src/mapper/menu.mapper.ts +6 -0
  249. package/template/packages/api/src/mapper/role-menu.mapper.ts +6 -0
  250. package/template/packages/api/src/mapper/role.mapper.ts +6 -0
  251. package/template/packages/api/src/mapper/user-role.mapper.ts +6 -0
  252. package/template/packages/api/src/mapper/user.mapper.ts +11 -0
  253. package/template/packages/api/src/scripts/init-db.ts +185 -0
  254. package/template/packages/api/src/server.ts +76 -0
  255. package/template/packages/api/src/service/auth.service.ts +106 -0
  256. package/template/packages/api/src/service/cache.service.ts +80 -0
  257. package/template/packages/api/src/service/log.request.service.ts +158 -0
  258. package/template/packages/api/src/service/log.service.ts +123 -0
  259. package/template/packages/api/src/service/menu.service.ts +94 -0
  260. package/template/packages/api/src/service/mq.consumer.service.ts +26 -0
  261. package/template/packages/api/src/service/role.service.ts +88 -0
  262. package/template/packages/api/src/service/user.service.ts +170 -0
  263. package/template/packages/api/src/types/sqljs.d.ts +18 -0
  264. package/template/packages/api/src/utils/auth.utils.js +0 -0
  265. package/template/packages/api/src/utils/jwt.util.ts +29 -0
  266. package/template/packages/api/tsconfig.json +17 -0
  267. package/template/packages/api/tsup.config.ts +9 -0
  268. package/template/packages/api/uploads/.gitkeep +0 -0
  269. package/template/packages/core/package.json +31 -0
  270. package/template/packages/core/src/auth/auth-client-middleware.ts +22 -0
  271. package/template/packages/core/src/auth/auth-service.ts +65 -0
  272. package/template/packages/core/src/auth/default-auth-provider.ts +37 -0
  273. package/template/packages/core/src/auth/index.ts +10 -0
  274. package/template/packages/core/src/auth/types.ts +38 -0
  275. package/template/packages/core/src/authorization/authorization-client-middleware.ts +38 -0
  276. package/template/packages/core/src/authorization/authorization-config.ts +13 -0
  277. package/template/packages/core/src/authorization/authorization-provider.tsx +116 -0
  278. package/template/packages/core/src/authorization/default-authorization-provider.ts +26 -0
  279. package/template/packages/core/src/authorization/index.ts +15 -0
  280. package/template/packages/core/src/authorization/types.ts +42 -0
  281. package/template/packages/core/src/index.ts +3 -0
  282. package/template/packages/core/src/utils/promise-result-cache.ts +18 -0
  283. package/template/packages/core/tsconfig.json +19 -0
  284. package/template/packages/mobile/README.md +55 -0
  285. package/template/packages/mobile/index.html +13 -0
  286. package/template/packages/mobile/package.json +30 -0
  287. package/template/packages/mobile/postcss.config.mjs +7 -0
  288. package/template/packages/mobile/src/App.tsx +5 -0
  289. package/template/packages/mobile/src/app/globals.css +1 -0
  290. package/template/packages/mobile/src/components/LoginForm.tsx +76 -0
  291. package/template/packages/mobile/src/hooks/index.ts +5 -0
  292. package/template/packages/mobile/src/lib/utils.ts +7 -0
  293. package/template/packages/mobile/src/main.tsx +13 -0
  294. package/template/packages/mobile/src/pages/HomePage.tsx +35 -0
  295. package/template/packages/mobile/src/pages/LoginPage.tsx +35 -0
  296. package/template/packages/mobile/src/pages/index.ts +2 -0
  297. package/template/packages/mobile/src/routes/ProtectedRoute.tsx +29 -0
  298. package/template/packages/mobile/src/routes/index.tsx +24 -0
  299. package/template/packages/mobile/src/routes/routes.ts +11 -0
  300. package/template/packages/mobile/src/types/index.ts +5 -0
  301. package/template/packages/mobile/src/vite-env.d.ts +1 -0
  302. package/template/packages/mobile/tsconfig.json +23 -0
  303. package/template/packages/mobile/tsconfig.node.json +11 -0
  304. package/template/packages/mobile/vite.config.ts +19 -0
  305. package/template/packages/shared/package.json +20 -0
  306. package/template/packages/shared/src/constants.ts +8 -0
  307. package/template/packages/shared/src/index.ts +5 -0
  308. package/template/packages/shared/tsconfig.json +13 -0
  309. package/template/packages/shared-auth/package.json +29 -0
  310. package/template/packages/shared-auth/src/AuthContext.tsx +177 -0
  311. package/template/packages/shared-auth/src/AuthProviderWrapper.tsx +29 -0
  312. package/template/packages/shared-auth/src/index.ts +10 -0
  313. package/template/packages/shared-auth/tsconfig.json +14 -0
  314. package/template/pnpm-lock.yaml +16349 -0
  315. package/template/pnpm-workspace.yaml +3 -0
  316. package/template/scripts/postinstall.cjs +42 -0
  317. package/template/scripts/rebuild-sqlite.cjs +23 -0
@@ -0,0 +1,335 @@
1
+ import { useState } from "react";
2
+ import { Settings, User, Bell, Palette, Globe, Shield, Check } from "lucide-react";
3
+ import { cn } from "@/lib/utils";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Label } from "@/components/ui/label";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from "@/components/ui/select";
14
+ import { Switch } from "@/components/ui/switch";
15
+ import { useTheme } from "@/components/admin-ui/theme/theme-provider";
16
+ import type { Appearance, Palette as ThemePalette } from "@/components/admin-ui/theme/theme-provider";
17
+
18
+ function SettingSection({
19
+ icon,
20
+ title,
21
+ description,
22
+ children,
23
+ iconBg,
24
+ }: {
25
+ icon: React.ReactNode;
26
+ title: string;
27
+ description?: string;
28
+ children: React.ReactNode;
29
+ iconBg: string;
30
+ }) {
31
+ return (
32
+ <div className="overflow-hidden rounded-xl border border-border bg-card shadow-sm">
33
+ <div className="flex items-center gap-3 border-b border-border px-5 py-4">
34
+ <div className={cn("flex size-9 items-center justify-center rounded-lg text-primary-foreground", iconBg)}>
35
+ {icon}
36
+ </div>
37
+ <div>
38
+ <h3 className="font-semibold">{title}</h3>
39
+ {description && <p className="mt-0.5 text-xs text-muted-foreground">{description}</p>}
40
+ </div>
41
+ </div>
42
+ <div className="p-5">{children}</div>
43
+ </div>
44
+ );
45
+ }
46
+
47
+ function ThemeOption({
48
+ value,
49
+ label,
50
+ active,
51
+ icon,
52
+ onClick,
53
+ }: {
54
+ value: string;
55
+ label: string;
56
+ active: boolean;
57
+ icon: React.ReactNode;
58
+ onClick: () => void;
59
+ }) {
60
+ return (
61
+ <button
62
+ type="button"
63
+ onClick={onClick}
64
+ className={cn(
65
+ "relative flex flex-1 flex-col items-center gap-2 rounded-xl border-2 px-3 py-4 transition-all",
66
+ active ? "border-primary bg-primary/10" : "border-border bg-card hover:border-muted-foreground"
67
+ )}
68
+ >
69
+ <div
70
+ className={cn(
71
+ "flex size-12 items-center justify-center rounded-xl",
72
+ value === "light" && "bg-muted",
73
+ value === "dark" && "bg-muted-foreground",
74
+ value === "system" && "bg-gradient-to-br from-muted to-muted-foreground"
75
+ )}
76
+ >
77
+ {icon}
78
+ </div>
79
+ <span className={cn("text-sm font-medium", active ? "text-primary" : "text-muted-foreground")}>
80
+ {label}
81
+ </span>
82
+ {active && (
83
+ <span className="absolute right-2 top-2 flex size-5 items-center justify-center rounded-full bg-primary text-primary-foreground">
84
+ <Check className="size-3" />
85
+ </span>
86
+ )}
87
+ </button>
88
+ );
89
+ }
90
+
91
+ export function SettingsPage() {
92
+ const { appearance, palette, setTheme: setAppTheme } = useTheme();
93
+ const [language, setLanguage] = useState("zh-CN");
94
+ const [notifications, setNotifications] = useState({
95
+ email: true,
96
+ push: true,
97
+ sms: false,
98
+ });
99
+
100
+ return (
101
+ <div className="flex flex-col overflow-hidden rounded-lg bg-muted/30 shadow-sm">
102
+ <div className="bg-primary px-6 py-5 text-primary-foreground">
103
+ <div className="flex items-center gap-3">
104
+ <div className="flex size-10 items-center justify-center rounded-xl bg-white/20">
105
+ <Settings className="size-5" />
106
+ </div>
107
+ <div>
108
+ <h1 className="text-xl font-semibold">系统设置</h1>
109
+ <p className="mt-0.5 text-sm text-primary-foreground/80">管理您的账户和系统偏好设置</p>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <div className="flex-1 overflow-y-auto p-4">
115
+ <div className="mx-auto max-w-3xl space-y-4">
116
+ <SettingSection
117
+ icon={<User className="size-5" />}
118
+ title="个人信息"
119
+ description="管理您的账户基本信息"
120
+ iconBg="bg-primary"
121
+ >
122
+ <div className="mb-5 flex items-center gap-4">
123
+ <div className="flex size-16 items-center justify-center rounded-2xl bg-primary text-xl font-bold text-primary-foreground">
124
+ A
125
+ </div>
126
+ <div className="flex-1">
127
+ <h4 className="text-lg font-semibold">Admin</h4>
128
+ <p className="text-sm text-muted-foreground">系统管理员</p>
129
+ </div>
130
+ <Button variant="secondary" size="sm">
131
+ 编辑资料
132
+ </Button>
133
+ </div>
134
+ <div className="grid grid-cols-2 gap-4">
135
+ <div className="space-y-2">
136
+ <Label className="text-xs text-muted-foreground">用户名</Label>
137
+ <Input value="admin" disabled className="bg-muted" />
138
+ </div>
139
+ <div className="space-y-2">
140
+ <Label className="text-xs text-muted-foreground">邮箱地址</Label>
141
+ <Input value="admin@example.com" disabled className="bg-muted" />
142
+ </div>
143
+ <div className="space-y-2">
144
+ <Label className="text-xs text-muted-foreground">手机号码</Label>
145
+ <Input value="+86 138****8888" disabled className="bg-muted" />
146
+ </div>
147
+ <div className="space-y-2">
148
+ <Label className="text-xs text-muted-foreground">部门</Label>
149
+ <Input value="信息技术部" disabled className="bg-muted" />
150
+ </div>
151
+ </div>
152
+ </SettingSection>
153
+
154
+ <SettingSection
155
+ icon={<Bell className="size-5" />}
156
+ title="通知设置"
157
+ description="配置消息通知方式"
158
+ iconBg="bg-amber-500"
159
+ >
160
+ <div className="space-y-4">
161
+ <div className="flex items-center justify-between py-2">
162
+ <div>
163
+ <Label className="font-medium">邮件通知</Label>
164
+ <p className="text-xs text-muted-foreground">接收重要事项的邮件提醒</p>
165
+ </div>
166
+ <Switch
167
+ checked={notifications.email}
168
+ onCheckedChange={(v) => setNotifications({ ...notifications, email: v })}
169
+ />
170
+ </div>
171
+ <div className="flex items-center justify-between border-t border-border py-2">
172
+ <div>
173
+ <Label className="font-medium">浏览器推送</Label>
174
+ <p className="text-xs text-muted-foreground">在浏览器中接收实时通知</p>
175
+ </div>
176
+ <Switch
177
+ checked={notifications.push}
178
+ onCheckedChange={(v) => setNotifications({ ...notifications, push: v })}
179
+ />
180
+ </div>
181
+ <div className="flex items-center justify-between border-t border-border py-2">
182
+ <div>
183
+ <Label className="font-medium">短信通知</Label>
184
+ <p className="text-xs text-muted-foreground">接收紧急事项的短信提醒</p>
185
+ </div>
186
+ <Switch
187
+ checked={notifications.sms}
188
+ onCheckedChange={(v) => setNotifications({ ...notifications, sms: v })}
189
+ />
190
+ </div>
191
+ </div>
192
+ </SettingSection>
193
+
194
+ <SettingSection
195
+ icon={<Palette className="size-5" />}
196
+ title="外观设置"
197
+ description="自定义界面外观主题"
198
+ iconBg="bg-purple-500"
199
+ >
200
+ <div className="flex gap-3">
201
+ <ThemeOption
202
+ value="light"
203
+ label="浅色模式"
204
+ active={appearance === "light"}
205
+ onClick={() => setAppTheme({ appearance: "light" as Appearance })}
206
+ icon={<div className="size-6 rounded-full border-2 border-muted-foreground" />}
207
+ />
208
+ <ThemeOption
209
+ value="dark"
210
+ label="深色模式"
211
+ active={appearance === "dark"}
212
+ onClick={() => setAppTheme({ appearance: "dark" as Appearance })}
213
+ icon={<div className="size-6 rounded-full bg-muted-foreground" />}
214
+ />
215
+ <ThemeOption
216
+ value="system"
217
+ label="跟随系统"
218
+ active={appearance === "system"}
219
+ onClick={() => setAppTheme({ appearance: "system" as Appearance })}
220
+ icon={<div className="size-6 rounded-full bg-gradient-to-br from-muted to-muted-foreground" />}
221
+ />
222
+ </div>
223
+
224
+ <div className="mt-5 grid grid-cols-2 gap-4">
225
+ <div className="space-y-2">
226
+ <Label className="text-xs text-muted-foreground">配色</Label>
227
+ <Select
228
+ value={palette}
229
+ onValueChange={(v) => setAppTheme({ palette: v as ThemePalette })}
230
+ >
231
+ <SelectTrigger><SelectValue /></SelectTrigger>
232
+ <SelectContent>
233
+ <SelectItem value="default">默认</SelectItem>
234
+ <SelectItem value="blue">蓝色</SelectItem>
235
+ <SelectItem value="green">绿色</SelectItem>
236
+ <SelectItem value="violet">紫色</SelectItem>
237
+ <SelectItem value="rose">玫红</SelectItem>
238
+ <SelectItem value="amber">琥珀</SelectItem>
239
+ <SelectItem value="fiori">Fiori</SelectItem>
240
+ </SelectContent>
241
+ </Select>
242
+ </div>
243
+ </div>
244
+ </SettingSection>
245
+
246
+ <SettingSection
247
+ icon={<Globe className="size-5" />}
248
+ title="语言和地区"
249
+ description="设置界面语言和区域格式"
250
+ iconBg="bg-emerald-500"
251
+ >
252
+ <div className="grid grid-cols-2 gap-4">
253
+ <div className="space-y-2">
254
+ <Label className="text-xs text-muted-foreground">界面语言</Label>
255
+ <Select value={language} onValueChange={setLanguage}>
256
+ <SelectTrigger><SelectValue /></SelectTrigger>
257
+ <SelectContent>
258
+ <SelectItem value="zh-CN">简体中文</SelectItem>
259
+ <SelectItem value="zh-TW">繁體中文</SelectItem>
260
+ <SelectItem value="en-US">English (US)</SelectItem>
261
+ <SelectItem value="ja-JP">日本語</SelectItem>
262
+ </SelectContent>
263
+ </Select>
264
+ </div>
265
+ <div className="space-y-2">
266
+ <Label className="text-xs text-muted-foreground">时区</Label>
267
+ <Select value="Asia/Shanghai" onValueChange={() => {}}>
268
+ <SelectTrigger><SelectValue /></SelectTrigger>
269
+ <SelectContent>
270
+ <SelectItem value="Asia/Shanghai">(UTC+8) 中国标准时间</SelectItem>
271
+ <SelectItem value="Asia/Tokyo">(UTC+9) 日本标准时间</SelectItem>
272
+ <SelectItem value="America/New_York">(UTC-5) 美国东部时间</SelectItem>
273
+ </SelectContent>
274
+ </Select>
275
+ </div>
276
+ <div className="space-y-2">
277
+ <Label className="text-xs text-muted-foreground">日期格式</Label>
278
+ <Select value="YYYY-MM-DD" onValueChange={() => {}}>
279
+ <SelectTrigger><SelectValue /></SelectTrigger>
280
+ <SelectContent>
281
+ <SelectItem value="YYYY-MM-DD">2024-12-31</SelectItem>
282
+ <SelectItem value="DD/MM/YYYY">31/12/2024</SelectItem>
283
+ <SelectItem value="MM/DD/YYYY">12/31/2024</SelectItem>
284
+ </SelectContent>
285
+ </Select>
286
+ </div>
287
+ <div className="space-y-2">
288
+ <Label className="text-xs text-muted-foreground">数字格式</Label>
289
+ <Select value="1,234.56" onValueChange={() => {}}>
290
+ <SelectTrigger><SelectValue /></SelectTrigger>
291
+ <SelectContent>
292
+ <SelectItem value="1,234.56">1,234.56</SelectItem>
293
+ <SelectItem value="1.234,56">1.234,56</SelectItem>
294
+ <SelectItem value="1 234.56">1 234.56</SelectItem>
295
+ </SelectContent>
296
+ </Select>
297
+ </div>
298
+ </div>
299
+ </SettingSection>
300
+
301
+ <SettingSection
302
+ icon={<Shield className="size-5" />}
303
+ title="安全设置"
304
+ description="管理账户安全选项"
305
+ iconBg="bg-destructive"
306
+ >
307
+ <div className="space-y-4">
308
+ <div className="flex items-center justify-between py-2">
309
+ <div>
310
+ <span className="text-sm font-medium">修改密码</span>
311
+ <p className="text-xs text-muted-foreground">上次修改:30 天前</p>
312
+ </div>
313
+ <Button variant="outline" size="sm">修改密码</Button>
314
+ </div>
315
+ <div className="flex items-center justify-between border-t border-border py-2">
316
+ <div>
317
+ <span className="text-sm font-medium">双因素认证</span>
318
+ <p className="text-xs text-muted-foreground">增强账户安全性</p>
319
+ </div>
320
+ <Button size="sm" variant="secondary" className="text-emerald-600">已启用</Button>
321
+ </div>
322
+ <div className="flex items-center justify-between border-t border-border py-2">
323
+ <div>
324
+ <span className="text-sm font-medium">登录记录</span>
325
+ <p className="text-xs text-muted-foreground">查看最近的登录活动</p>
326
+ </div>
327
+ <Button variant="outline" size="sm">查看记录</Button>
328
+ </div>
329
+ </div>
330
+ </SettingSection>
331
+ </div>
332
+ </div>
333
+ </div>
334
+ );
335
+ }
@@ -0,0 +1,50 @@
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ "use client"
3
+
4
+ import { createContext, useContext, type ReactNode } from "react"
5
+
6
+ export type AppTitleConfig = {
7
+ icon: ReactNode
8
+ text: string
9
+ }
10
+
11
+ const defaultTitle: AppTitleConfig = {
12
+ icon: (
13
+ <svg
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ width="24"
16
+ height="24"
17
+ viewBox="0 0 24 24"
18
+ fill="none"
19
+ stroke="currentColor"
20
+ strokeWidth="2"
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ >
24
+ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
25
+ </svg>
26
+ ),
27
+ text: "Admin",
28
+ }
29
+
30
+ const AppConfigContext = createContext<{ title: AppTitleConfig }>({
31
+ title: defaultTitle,
32
+ })
33
+
34
+ export function AppConfigProvider({
35
+ children,
36
+ title = defaultTitle,
37
+ }: {
38
+ children: ReactNode
39
+ title?: AppTitleConfig
40
+ }) {
41
+ return (
42
+ <AppConfigContext.Provider value={{ title }}>
43
+ {children}
44
+ </AppConfigContext.Provider>
45
+ )
46
+ }
47
+
48
+ export function useAppConfig() {
49
+ return useContext(AppConfigContext)
50
+ }
@@ -0,0 +1,6 @@
1
+ import { USE_GUARD } from "@/app.config"
2
+ import { createAuthClientMiddleware, createAuthorizationClientMiddleware } from "@scaffold/core"
3
+
4
+ export const authorizationClientMiddleware = USE_GUARD ? createAuthorizationClientMiddleware() : async (_args: unknown, next: () => Promise<unknown>) => { await next() }
5
+
6
+ export const middleware = [createAuthClientMiddleware(), authorizationClientMiddleware]
@@ -0,0 +1,85 @@
1
+ import { USE_GUARD } from "@/app.config"
2
+ import { createAuthClientMiddleware, createAuthorizationClientMiddleware } from "@scaffold/core"
3
+ import { ReactNode } from "react"
4
+ import { RouteObject } from "react-router"
5
+
6
+ type ModuleExport = {
7
+ routes?: RouteConfig[]
8
+ }
9
+
10
+ /** 中间件函数类型(与 react-router 约定一致) */
11
+ type MiddlewareFn = (
12
+ args: { request: Request; context: unknown },
13
+ next: () => Promise<unknown>
14
+ ) => Promise<unknown>
15
+
16
+ /**
17
+ * 路由配置,支持树状结构:子路由定义在 children 中;与菜单对齐可配 icon / label / group。
18
+ * - path 为相对父级路径;index 为 true 时 path 可省略
19
+ * - 有 children 时,父级可不写 element,渲染时用 <Outlet /> 占位
20
+ * - anonymous / useGuard 用于在 getModulesContext 中自动注入 middleware,无需在模块里配置
21
+ * - group 从路由读取,用于菜单分区;groupOrder 指定该分区在菜单中的顺序(越小越靠前)
22
+ */
23
+ export type RouteConfig = Omit<RouteObject, "children"> & {
24
+ label?: string
25
+ icon?: ReactNode
26
+ /** 是否允许匿名(不注入认证中间件) */
27
+ anonymous?: boolean
28
+ /** 是否注入授权中间件 */
29
+ useGuard?: boolean
30
+ /** 菜单分区(如 business / analytics / system),与 menu-layout 的 MenuItem.group 对应 */
31
+ group?: string
32
+ /** 同 groupName 的多条路由会合并为一个父菜单(如 MM-采购) */
33
+ groupName?: string
34
+ /** 分区在菜单中的顺序,数值越小越靠前;同分区取该分区内最小 groupOrder */
35
+ groupOrder?: number
36
+ /** 同级菜单顺序(同一 groupName 下多项时),数值越小越靠前 */
37
+ order?: number
38
+ children?: RouteConfig[]
39
+ }
40
+
41
+ const modules = import.meta.glob<ModuleExport>("./modules/*.ts", { eager: true })
42
+
43
+ /** 按 RouteConfig 的 anonymous / useGuard 递归注入 middleware */
44
+ function applyMiddlewareToRoutes(routes: RouteConfig[], middlewares: MiddlewareFn[]): RouteConfig[] {
45
+ return routes.map((route) => {
46
+ const { children, anonymous, useGuard, ...rest } = route
47
+ const middleware: MiddlewareFn[] = []
48
+
49
+ const requiresAuth = anonymous !== true
50
+
51
+ if (requiresAuth) {
52
+ middleware.push(middlewares[0] as MiddlewareFn)
53
+ if (useGuard !== false) {
54
+ middleware.push(middlewares[1] as MiddlewareFn)
55
+ }
56
+ }
57
+ const out: RouteConfig = { ...rest, anonymous, useGuard, middleware, children: undefined }
58
+ if (children?.length) {
59
+ out.children = applyMiddlewareToRoutes(children, middlewares)
60
+ }
61
+ return out
62
+ })
63
+ }
64
+
65
+ let _routes: RouteConfig[] = []
66
+
67
+ export function getModulesContext(): {
68
+ routes: RouteConfig[]
69
+ middlewares: MiddlewareFn[]
70
+ } {
71
+ const authClientMiddleware = createAuthClientMiddleware()
72
+ const authorizationClientMiddleware = USE_GUARD ? createAuthorizationClientMiddleware() : (_args: unknown, next: () => Promise<unknown>) => next()
73
+ if (_routes.length === 0) {
74
+ const routes: RouteConfig[] = []
75
+ for (const key of Object.keys(modules)) {
76
+ const mod = modules[key] as ModuleExport
77
+ if (mod?.routes?.length) routes.push(...mod.routes)
78
+ }
79
+ _routes = applyMiddlewareToRoutes(routes, [authClientMiddleware, authorizationClientMiddleware])
80
+ }
81
+ return {
82
+ routes: _routes,
83
+ middlewares: [authClientMiddleware, authorizationClientMiddleware]
84
+ }
85
+ }
@@ -0,0 +1,176 @@
1
+ import { useMemo } from "react"
2
+ import type { RouteConfig } from "./index"
3
+ import { getModulesContext } from "./index"
4
+
5
+ export interface MenuItem {
6
+ id: string
7
+ label: string
8
+ icon?: React.ReactNode
9
+ path?: string
10
+ children?: MenuItem[]
11
+ badge?: string | number
12
+ useGuard?: boolean
13
+ /** 菜单分区,从 route.group 读取,用于 menu-layout 分区展示与筛选 */
14
+ group?: string
15
+ }
16
+
17
+ const DEFAULT_GROUP_ORDER = 999
18
+
19
+ /**
20
+ * 从路由读取 group:优先 group,兼容旧字段 groupKey(如 master-data 映射为 business)
21
+ */
22
+ function getMenuGroup(r: RouteConfig): string {
23
+ const g = r.group
24
+ return g ?? "other"
25
+ }
26
+
27
+ /** 遍历路由树填充 path → RouteConfig,path 为完整路径(如 "/goods-receipt/create")。 */
28
+ function fillPathToRouteConfig(
29
+ routeList: RouteConfig[],
30
+ pathToRouteConfig: Map<string, RouteConfig>,
31
+ prefix = ""
32
+ ): void {
33
+ for (const r of routeList) {
34
+ const segment = r.path ?? ""
35
+ const fullPath = segment ? (prefix ? `${prefix}/${segment}` : `/${segment}`) : prefix || "/"
36
+ if (r.index) {
37
+ pathToRouteConfig.set(prefix || "/", r)
38
+ } else if (segment || fullPath === "/") {
39
+ pathToRouteConfig.set(fullPath, r)
40
+ }
41
+ if (r.children?.length) {
42
+ const nextPrefix = segment ? (prefix ? `${prefix}/${segment}` : `/${segment}`) : prefix
43
+ fillPathToRouteConfig(r.children, pathToRouteConfig, nextPrefix)
44
+ }
45
+ }
46
+ }
47
+
48
+ export type RoutesToMenuResult = {
49
+ menuItems: MenuItem[]
50
+ pathToRouteConfig: Map<string, RouteConfig>
51
+ }
52
+
53
+ /**
54
+ * 将路由配置转为菜单项,并生成 path→RouteConfig 映射(一次遍历出两个结果)。
55
+ * - group 从 route.group 读取;分区顺序由 route.groupOrder 决定
56
+ * - 同 groupName 的多个顶层路由合并为一个父菜单(如 MM-采购)
57
+ * @param routes 聚合后的 RouteConfig[]
58
+ * @param options.mainItem 首页项(path: "/", group: "main")
59
+ */
60
+ export function routesToMenu(
61
+ routeList: RouteConfig[],
62
+ options?: { mainItem?: MenuItem }
63
+ ): RoutesToMenuResult {
64
+ const pathToRouteConfig = new Map<string, RouteConfig>()
65
+ fillPathToRouteConfig(routeList, pathToRouteConfig)
66
+
67
+ const topLevel = routeList.filter((r) => r.path !== undefined && !r.index)
68
+ const byGroupKey = new Map<string, RouteConfig[]>()
69
+ for (const r of topLevel) {
70
+ const menuGroup = getMenuGroup(r)
71
+ const gn = r.groupName ?? r.path ?? ""
72
+ const key = `${menuGroup}|${gn}`
73
+ if (!byGroupKey.has(key)) byGroupKey.set(key, [])
74
+ byGroupKey.get(key)!.push(r)
75
+ }
76
+
77
+ const menuItems: MenuItem[] = []
78
+ if (options?.mainItem) menuItems.push(options.mainItem)
79
+
80
+ /** 每个分区内:(itemOrder, menuItem)[],分区内按 itemOrder 排序 */
81
+ const groupToItems = new Map<string, { order: number; item: MenuItem }[]>()
82
+ const groupOrderMin = new Map<string, number>()
83
+ for (const [key, configs] of byGroupKey) {
84
+ const [menuGroup, groupName] = key.split("|")
85
+ const itemOrder = Math.min(
86
+ ...configs.map((r) => r.groupOrder ?? DEFAULT_GROUP_ORDER)
87
+ )
88
+ if (!groupToItems.has(menuGroup)) {
89
+ groupToItems.set(menuGroup, [])
90
+ groupOrderMin.set(menuGroup, itemOrder)
91
+ }
92
+ groupOrderMin.set(
93
+ menuGroup,
94
+ Math.min(groupOrderMin.get(menuGroup)!, itemOrder)
95
+ )
96
+
97
+ if (configs.length > 1) {
98
+ const sorted = [...configs].sort(
99
+ (a, b) => (a.order ?? DEFAULT_GROUP_ORDER) - (b.order ?? DEFAULT_GROUP_ORDER)
100
+ )
101
+ const first = sorted[0]
102
+ const parent: MenuItem = {
103
+ id: slugify(groupName),
104
+ label: groupName,
105
+ icon: first.icon,
106
+ group: menuGroup,
107
+ children: sorted.map((r) => ({
108
+ id: r.path!.replace(/\//g, "-"),
109
+ label: r.label ?? r.path ?? "",
110
+ path: "/" + r.path,
111
+ })),
112
+ }
113
+ groupToItems.get(menuGroup)!.push({ order: itemOrder, item: parent })
114
+ } else {
115
+ const r = configs[0]
116
+ if (r.children?.length) {
117
+ const listChildren = r.children.filter(
118
+ (c) => c.path && !c.path.includes(":")
119
+ )
120
+ const parent: MenuItem = {
121
+ id: r.path ?? slugify(groupName),
122
+ label: r.groupName ?? r.label ?? r.path ?? "",
123
+ icon: r.icon,
124
+ group: menuGroup,
125
+ children: listChildren.map((c) => ({
126
+ id: (c.path ?? "").replace(/\//g, "-"),
127
+ label: c.label ?? c.path ?? "",
128
+ path: "/" + (r.path ? r.path + "/" : "") + c.path,
129
+ })),
130
+ }
131
+ groupToItems.get(menuGroup)!.push({ order: itemOrder, item: parent })
132
+ } else {
133
+ groupToItems.get(menuGroup)!.push({
134
+ order: itemOrder,
135
+ item: {
136
+ id: (r.path ?? "").replace(/\//g, "-"),
137
+ label: r.label ?? r.path ?? "",
138
+ icon: r.icon,
139
+ path: "/" + (r.path ?? ""),
140
+ group: menuGroup,
141
+ },
142
+ })
143
+ }
144
+ }
145
+ }
146
+
147
+ const sortedGroupKeys = [...groupToItems.keys()].sort(
148
+ (a, b) =>
149
+ (groupOrderMin.get(a) ?? DEFAULT_GROUP_ORDER) -
150
+ (groupOrderMin.get(b) ?? DEFAULT_GROUP_ORDER)
151
+ )
152
+ for (const g of sortedGroupKeys) {
153
+ const entries = groupToItems.get(g) ?? []
154
+ const items = entries
155
+ .sort((a, b) => a.order - b.order)
156
+ .map((e) => e.item)
157
+ menuItems.push(...items)
158
+ }
159
+
160
+ return { menuItems, pathToRouteConfig }
161
+ }
162
+
163
+ export type UseMenuItemsOptions = { mainItem?: MenuItem }
164
+
165
+ /**
166
+ * 根据当前路由配置生成菜单项与 path→RouteConfig 映射(声明式 hook)。
167
+ * 若 options 含动态值(如 i18n),请用 useMemo 包住 options 以保持引用稳定。
168
+ */
169
+ export function useMenuItems(options?: UseMenuItemsOptions): RoutesToMenuResult {
170
+ const { routes } = getModulesContext()
171
+ return useMemo(() => routesToMenu(routes, options), [routes, options])
172
+ }
173
+
174
+ function slugify(s: string): string {
175
+ return s.replace(/\s+/g, "-").replace(/[^\w\u4e00-\u9fa5-]/g, "") || "item"
176
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 收货管理(菜单:MM-采购 → 收货管理)
3
+ * 方式一:路由组件使用 React.lazy 动态导入,实现按路由代码分割。
4
+ */
5
+ import { createElement, lazy } from "react"
6
+ import { ShoppingCart } from "lucide-react"
7
+ import type { RouteConfig } from "../index"
8
+ import { withSuspense } from "../withSuspense"
9
+
10
+ const GoodsReceiptListPage = lazy(() => import("@/pages/goods-receipt/ListPage"))
11
+ const GoodsReceiptCreatePage = lazy(() => import("@/pages/goods-receipt/CreatePage"))
12
+ const GoodsReceiptEditPage = lazy(() => import("@/pages/goods-receipt/EditPage"))
13
+ const GoodsReceiptViewPage = lazy(() => import("@/pages/goods-receipt/ViewPage"))
14
+
15
+ export const routes: RouteConfig[] = [
16
+ {
17
+ path: "goods-receipt",
18
+ label: "收货管理",
19
+ icon: createElement(ShoppingCart, { className: "size-[18px]" }),
20
+ group: "business",
21
+ groupName: "MM-采购",
22
+ groupOrder: 10,
23
+ order: 3,
24
+ children: [
25
+ { index: true, element: withSuspense(GoodsReceiptListPage) },
26
+ { path: "create", element: withSuspense(GoodsReceiptCreatePage) },
27
+ { path: ":id/edit", element: withSuspense(GoodsReceiptEditPage) },
28
+ { path: ":id", element: withSuspense(GoodsReceiptViewPage) },
29
+ ],
30
+ },
31
+ ]