@skyroc/scripts 2.5.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 (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +508 -0
  3. package/dist/cli.mjs +78 -0
  4. package/dist/config-BKkdncJS.mjs +987 -0
  5. package/dist/index.mjs +14 -0
  6. package/package.json +83 -0
  7. package/templates/admin/.env +62 -0
  8. package/templates/admin/.env.prod +7 -0
  9. package/templates/admin/.env.test +7 -0
  10. package/templates/admin/.oxlintrc.json +6 -0
  11. package/templates/admin/README.md +508 -0
  12. package/templates/admin/index.html +14 -0
  13. package/templates/admin/package.json +105 -0
  14. package/templates/admin/public/favicon.svg +3 -0
  15. package/templates/admin/src/App.tsx +71 -0
  16. package/templates/admin/src/assets/audio/wechat-style-notification.wav +0 -0
  17. package/templates/admin/src/assets/imgs/skyroc.jpg +0 -0
  18. package/templates/admin/src/assets/svg-icon/activity.svg +1 -0
  19. package/templates/admin/src/assets/svg-icon/at-sign.svg +1 -0
  20. package/templates/admin/src/assets/svg-icon/avatar.svg +1 -0
  21. package/templates/admin/src/assets/svg-icon/banner.svg +1 -0
  22. package/templates/admin/src/assets/svg-icon/cast.svg +1 -0
  23. package/templates/admin/src/assets/svg-icon/chrome.svg +1 -0
  24. package/templates/admin/src/assets/svg-icon/copy.svg +1 -0
  25. package/templates/admin/src/assets/svg-icon/custom-icon.svg +1 -0
  26. package/templates/admin/src/assets/svg-icon/empty-data.svg +1 -0
  27. package/templates/admin/src/assets/svg-icon/error.svg +1 -0
  28. package/templates/admin/src/assets/svg-icon/expectation.svg +1 -0
  29. package/templates/admin/src/assets/svg-icon/heart.svg +1 -0
  30. package/templates/admin/src/assets/svg-icon/logo.svg +15 -0
  31. package/templates/admin/src/assets/svg-icon/network-error.svg +1 -0
  32. package/templates/admin/src/assets/svg-icon/no-icon.svg +1 -0
  33. package/templates/admin/src/assets/svg-icon/no-permission.svg +1 -0
  34. package/templates/admin/src/assets/svg-icon/not-found.svg +1 -0
  35. package/templates/admin/src/assets/svg-icon/service-error.svg +1 -0
  36. package/templates/admin/src/bootstrap.tsx +60 -0
  37. package/templates/admin/src/components/SkyrocAvatar.tsx +13 -0
  38. package/templates/admin/src/components/SystemLogo.tsx +7 -0
  39. package/templates/admin/src/config.ts +125 -0
  40. package/templates/admin/src/features/antd/AntdProvider.tsx +21 -0
  41. package/templates/admin/src/features/auth/components/UserAvatar.tsx +76 -0
  42. package/templates/admin/src/features/auth/use-auth-form-rules.ts +83 -0
  43. package/templates/admin/src/features/auth/use-auth.ts +96 -0
  44. package/templates/admin/src/features/auth/use-login.ts +72 -0
  45. package/templates/admin/src/features/effects/GlobalEffect.tsx +15 -0
  46. package/templates/admin/src/features/menus/dynamic-routes.ts +10 -0
  47. package/templates/admin/src/features/menus/extras.ts +3 -0
  48. package/templates/admin/src/features/menus/menu-category.ts +14 -0
  49. package/templates/admin/src/features/menus/menu-config.ts +5 -0
  50. package/templates/admin/src/features/router/RouterProvider.tsx +18 -0
  51. package/templates/admin/src/features/router/guard.ts +97 -0
  52. package/templates/admin/src/features/router/index.ts +52 -0
  53. package/templates/admin/src/features/router/routeTree.gen.ts +237 -0
  54. package/templates/admin/src/features/router/router-ref.ts +24 -0
  55. package/templates/admin/src/locales/antd.ts +8 -0
  56. package/templates/admin/src/locales/index.ts +22 -0
  57. package/templates/admin/src/locales/sync.ts +14 -0
  58. package/templates/admin/src/main.tsx +9 -0
  59. package/templates/admin/src/pages/(admin)/home/index.tsx +46 -0
  60. package/templates/admin/src/pages/(admin)/layout.tsx +44 -0
  61. package/templates/admin/src/pages/(auth)/login/index.tsx +102 -0
  62. package/templates/admin/src/pages/(auth)/login/layout.tsx +71 -0
  63. package/templates/admin/src/pages/(auth)/login-out.tsx +27 -0
  64. package/templates/admin/src/pages/(errors)/403.tsx +16 -0
  65. package/templates/admin/src/pages/(errors)/404.tsx +16 -0
  66. package/templates/admin/src/pages/(errors)/500.tsx +16 -0
  67. package/templates/admin/src/pages/__root.tsx +54 -0
  68. package/templates/admin/src/pages/error.tsx +19 -0
  69. package/templates/admin/src/pages/index.tsx +14 -0
  70. package/templates/admin/src/pages/loading.tsx +45 -0
  71. package/templates/admin/src/pages/not-found.tsx +9 -0
  72. package/templates/admin/src/plugins/app.tsx +47 -0
  73. package/templates/admin/src/plugins/assets.ts +4 -0
  74. package/templates/admin/src/plugins/index.ts +32 -0
  75. package/templates/admin/src/service/adapter.ts +63 -0
  76. package/templates/admin/src/service/api/auth/api.ts +31 -0
  77. package/templates/admin/src/service/api/auth/hooks.ts +28 -0
  78. package/templates/admin/src/service/api/auth/index.ts +10 -0
  79. package/templates/admin/src/service/api/auth/keys.ts +7 -0
  80. package/templates/admin/src/service/api/auth/urls.ts +7 -0
  81. package/templates/admin/src/service/api/index.ts +2 -0
  82. package/templates/admin/src/service/api/route/api.ts +7 -0
  83. package/templates/admin/src/service/api/route/hooks.ts +21 -0
  84. package/templates/admin/src/service/api/route/index.ts +10 -0
  85. package/templates/admin/src/service/api/route/keys.ts +3 -0
  86. package/templates/admin/src/service/api/route/urls.ts +5 -0
  87. package/templates/admin/src/service/queryClient.ts +13 -0
  88. package/templates/admin/src/service/request/index.ts +62 -0
  89. package/templates/admin/src/styles/scss/global.scss +1 -0
  90. package/templates/admin/src/styles/scss/scrollbar.scss +28 -0
  91. package/templates/admin/src/types/auto-imports.d.ts +186 -0
  92. package/templates/admin/src/types/global.d.ts +16 -0
  93. package/templates/admin/src/types/router.d.ts +14 -0
  94. package/templates/admin/src/types/storage.d.ts +17 -0
  95. package/templates/admin/src/types/vite-env.d.ts +117 -0
  96. package/templates/admin/src/utils/common.tsx +38 -0
  97. package/templates/admin/src/utils/icon.ts +9 -0
  98. package/templates/admin/src/utils/service.ts +75 -0
  99. package/templates/admin/src/utils/storage.ts +7 -0
  100. package/templates/admin/tsconfig.json +22 -0
  101. package/templates/admin/uno.config.ts +18 -0
  102. package/templates/admin/vite.config.ts +13 -0
  103. package/templates/admin-shell/i18n/atoms/lang.ts +20 -0
  104. package/templates/admin-shell/i18n/config/default.ts +11 -0
  105. package/templates/admin-shell/i18n/features/lang/LangEffect.tsx +25 -0
  106. package/templates/admin-shell/i18n/features/lang/LangSwitch.tsx +49 -0
  107. package/templates/admin-shell/i18n/hooks/use-lang.ts +47 -0
  108. package/templates/admin-shell/i18n/i18n.ts +116 -0
  109. package/templates/admin-shell/i18n/index.ts +23 -0
  110. package/templates/admin-shell/i18n/langs/en-us/common.json +49 -0
  111. package/templates/admin-shell/i18n/langs/en-us/datatable.json +8 -0
  112. package/templates/admin-shell/i18n/langs/en-us/dropdown.json +9 -0
  113. package/templates/admin-shell/i18n/langs/en-us/form.json +27 -0
  114. package/templates/admin-shell/i18n/langs/en-us/icon.json +13 -0
  115. package/templates/admin-shell/i18n/langs/en-us/notification.json +20 -0
  116. package/templates/admin-shell/i18n/langs/en-us/page.json +442 -0
  117. package/templates/admin-shell/i18n/langs/en-us/request.json +8 -0
  118. package/templates/admin-shell/i18n/langs/en-us/route.json +84 -0
  119. package/templates/admin-shell/i18n/langs/en-us/system.json +9 -0
  120. package/templates/admin-shell/i18n/langs/en-us/theme.json +173 -0
  121. package/templates/admin-shell/i18n/langs/index.ts +63 -0
  122. package/templates/admin-shell/i18n/langs/zh-cn/common.json +49 -0
  123. package/templates/admin-shell/i18n/langs/zh-cn/datatable.json +8 -0
  124. package/templates/admin-shell/i18n/langs/zh-cn/dropdown.json +9 -0
  125. package/templates/admin-shell/i18n/langs/zh-cn/form.json +27 -0
  126. package/templates/admin-shell/i18n/langs/zh-cn/icon.json +13 -0
  127. package/templates/admin-shell/i18n/langs/zh-cn/notification.json +20 -0
  128. package/templates/admin-shell/i18n/langs/zh-cn/page.json +442 -0
  129. package/templates/admin-shell/i18n/langs/zh-cn/request.json +8 -0
  130. package/templates/admin-shell/i18n/langs/zh-cn/route.json +84 -0
  131. package/templates/admin-shell/i18n/langs/zh-cn/system.json +9 -0
  132. package/templates/admin-shell/i18n/langs/zh-cn/theme.json +173 -0
  133. package/templates/admin-shell/i18n/types/i18n.ts +29 -0
  134. package/templates/admin-shell/i18n/types.ts +53 -0
  135. package/templates/admin-shell/i18n/utils/helpers.ts +18 -0
  136. package/templates/admin-shell/layouts/AdminLayout.tsx +161 -0
  137. package/templates/admin-shell/layouts/constant.ts +56 -0
  138. package/templates/admin-shell/layouts/context.tsx +101 -0
  139. package/templates/admin-shell/layouts/features/menus/MenuBadge.tsx +76 -0
  140. package/templates/admin-shell/layouts/features/menus/dynamic-routes.ts +146 -0
  141. package/templates/admin-shell/layouts/features/menus/menu-generator.tsx +444 -0
  142. package/templates/admin-shell/layouts/features/menus/menu-renderer.tsx +149 -0
  143. package/templates/admin-shell/layouts/features/menus/permissions.ts +28 -0
  144. package/templates/admin-shell/layouts/features/menus/use-menus.ts +98 -0
  145. package/templates/admin-shell/layouts/features/use-route.ts +30 -0
  146. package/templates/admin-shell/layouts/index.ts +44 -0
  147. package/templates/admin-shell/layouts/materials/index.ts +6 -0
  148. package/templates/admin-shell/layouts/materials/libs/admin-layout/index.module.css +63 -0
  149. package/templates/admin-shell/layouts/materials/libs/admin-layout/index.module.css.d.ts +18 -0
  150. package/templates/admin-shell/layouts/materials/libs/admin-layout/index.tsx +238 -0
  151. package/templates/admin-shell/layouts/materials/libs/admin-layout/shared.ts +67 -0
  152. package/templates/admin-shell/layouts/materials/libs/page-tab/ButtonTab.tsx +44 -0
  153. package/templates/admin-shell/layouts/materials/libs/page-tab/ChromeTab.tsx +54 -0
  154. package/templates/admin-shell/layouts/materials/libs/page-tab/ChromeTabBg.tsx +30 -0
  155. package/templates/admin-shell/layouts/materials/libs/page-tab/SliderTab.tsx +44 -0
  156. package/templates/admin-shell/layouts/materials/libs/page-tab/SvgClose.tsx +51 -0
  157. package/templates/admin-shell/layouts/materials/libs/page-tab/hook.ts +34 -0
  158. package/templates/admin-shell/layouts/materials/libs/page-tab/index.module.css +120 -0
  159. package/templates/admin-shell/layouts/materials/libs/page-tab/index.module.css.d.ts +18 -0
  160. package/templates/admin-shell/layouts/materials/libs/page-tab/index.tsx +76 -0
  161. package/templates/admin-shell/layouts/materials/libs/page-tab/shared.ts +32 -0
  162. package/templates/admin-shell/layouts/materials/types/index.ts +322 -0
  163. package/templates/admin-shell/layouts/modules/AdminContent.tsx +285 -0
  164. package/templates/admin-shell/layouts/modules/AdminFooter.tsx +13 -0
  165. package/templates/admin-shell/layouts/modules/AdminLogo.tsx +28 -0
  166. package/templates/admin-shell/layouts/modules/AdminSider.tsx +50 -0
  167. package/templates/admin-shell/layouts/modules/admin-header/AdminHeader.tsx +132 -0
  168. package/templates/admin-shell/layouts/modules/admin-header/components/Breadcrumb.tsx +62 -0
  169. package/templates/admin-shell/layouts/modules/admin-header/components/ThemeButton.tsx +24 -0
  170. package/templates/admin-shell/layouts/modules/admin-menu/AdminMenu.tsx +37 -0
  171. package/templates/admin-shell/layouts/modules/admin-menu/components/FirstLevelMenu.tsx +156 -0
  172. package/templates/admin-shell/layouts/modules/admin-menu/components/HorizontalMenu.tsx +88 -0
  173. package/templates/admin-shell/layouts/modules/admin-menu/components/MenuPortal.tsx +28 -0
  174. package/templates/admin-shell/layouts/modules/admin-menu/components/VerticalMenu.tsx +123 -0
  175. package/templates/admin-shell/layouts/modules/admin-menu/enum.ts +47 -0
  176. package/templates/admin-shell/layouts/modules/admin-menu/modules/Horizontal.tsx +22 -0
  177. package/templates/admin-shell/layouts/modules/admin-menu/modules/TopHybridHeaderFirst.tsx +15 -0
  178. package/templates/admin-shell/layouts/modules/admin-menu/modules/TopHybridSidebarFirst.tsx +18 -0
  179. package/templates/admin-shell/layouts/modules/admin-menu/modules/Vertical.tsx +14 -0
  180. package/templates/admin-shell/layouts/modules/admin-menu/modules/VerticalHybridHeaderFirst.tsx +18 -0
  181. package/templates/admin-shell/layouts/modules/admin-menu/modules/VerticalMix.tsx +132 -0
  182. package/templates/admin-shell/layouts/modules/admin-search/AdminSearch.tsx +36 -0
  183. package/templates/admin-shell/layouts/modules/admin-search/components/SearchFooter.tsx +38 -0
  184. package/templates/admin-shell/layouts/modules/admin-search/components/SearchModal.tsx +171 -0
  185. package/templates/admin-shell/layouts/modules/admin-search/components/SearchResult.tsx +43 -0
  186. package/templates/admin-shell/layouts/modules/admin-tab/AdminTab.tsx +125 -0
  187. package/templates/admin-shell/layouts/modules/admin-tab/components/TabContextMenu.tsx +107 -0
  188. package/templates/admin-shell/layouts/modules/admin-tab/hooks/useTabScroll.ts +86 -0
  189. package/templates/admin-shell/layouts/modules/shared/logo.ts +42 -0
  190. package/templates/admin-shell/layouts/modules/theme-drawer/ThemeDrawer.tsx +68 -0
  191. package/templates/admin-shell/layouts/modules/theme-drawer/components/AnimatedCollapse.tsx +36 -0
  192. package/templates/admin-shell/layouts/modules/theme-drawer/components/AnimatedItem.tsx +35 -0
  193. package/templates/admin-shell/layouts/modules/theme-drawer/components/LayoutModeCard.tsx +110 -0
  194. package/templates/admin-shell/layouts/modules/theme-drawer/components/SettingItem.tsx +31 -0
  195. package/templates/admin-shell/layouts/modules/theme-drawer/modules/ConfigOperation.tsx +53 -0
  196. package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/CustomPicker.tsx +105 -0
  197. package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeColor.tsx +58 -0
  198. package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeSchema.tsx +42 -0
  199. package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/ThemeToken.tsx +44 -0
  200. package/templates/admin-shell/layouts/modules/theme-drawer/modules/appearance/index.tsx +23 -0
  201. package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/GlobalSettings.tsx +57 -0
  202. package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/ThemeGeneral.tsx +20 -0
  203. package/templates/admin-shell/layouts/modules/theme-drawer/modules/general/WatermarkSettings.tsx +103 -0
  204. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/ContentSettings.tsx +100 -0
  205. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/FooterSettings.tsx +71 -0
  206. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/HeaderSettings.tsx +63 -0
  207. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/LayoutMode.tsx +73 -0
  208. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/SiderSettings.tsx +124 -0
  209. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/TabSettings.tsx +95 -0
  210. package/templates/admin-shell/layouts/modules/theme-drawer/modules/layout/ThemeLayout.tsx +32 -0
  211. package/templates/admin-shell/layouts/modules/theme-drawer/modules/preset/ThemePreset.tsx +132 -0
  212. package/templates/admin-shell/layouts/options.tsx +79 -0
  213. package/templates/admin-shell/layouts/setup.ts +102 -0
  214. package/templates/admin-shell/layouts/state/AdminEffect.tsx +56 -0
  215. package/templates/admin-shell/layouts/state/AdminTabEffect.tsx +28 -0
  216. package/templates/admin-shell/layouts/state/index.ts +3 -0
  217. package/templates/admin-shell/layouts/state/menus/MenuToggler.tsx +53 -0
  218. package/templates/admin-shell/layouts/state/menus/use-admin-menu-badges.ts +46 -0
  219. package/templates/admin-shell/layouts/state/menus/use-admin-menus.tsx +164 -0
  220. package/templates/admin-shell/layouts/state/tabs/index.ts +1 -0
  221. package/templates/admin-shell/layouts/state/tabs/shared.tsx +148 -0
  222. package/templates/admin-shell/layouts/state/tabs/use-admin-tab.ts +377 -0
  223. package/templates/admin-shell/layouts/state/use-admin-state.ts +118 -0
  224. package/templates/admin-shell/layouts/types/router.d.ts +10 -0
  225. package/templates/admin-shell/layouts/types/storage.ts +18 -0
  226. package/templates/admin-shell/notification/NotificationButton.tsx +91 -0
  227. package/templates/admin-shell/notification/NotificationContext.ts +8 -0
  228. package/templates/admin-shell/notification/NotificationPanel.tsx +194 -0
  229. package/templates/admin-shell/notification/NotificationProvider.tsx +59 -0
  230. package/templates/admin-shell/notification/index.ts +12 -0
  231. package/templates/admin-shell/notification/mock.ts +59 -0
  232. package/templates/admin-shell/notification/notification-store.ts +438 -0
  233. package/templates/admin-shell/notification/notification.css +60 -0
  234. package/templates/admin-shell/notification/types.ts +104 -0
  235. package/templates/admin-shell/notification/use-notification.ts +51 -0
  236. package/templates/admin-shell/notification/useNotificationContext.ts +14 -0
  237. package/templates/admin-shell/runtime/app-update.ts +156 -0
  238. package/templates/admin-shell/runtime/dayjs.ts +20 -0
  239. package/templates/admin-shell/runtime/iconify.ts +17 -0
  240. package/templates/admin-shell/runtime/index.ts +10 -0
  241. package/templates/admin-shell/runtime/nprogress.ts +25 -0
  242. package/templates/admin-shell/runtime/plugins.ts +62 -0
  243. package/templates/admin-shell/styles/global.css +54 -0
  244. package/templates/admin-shell/styles/nprogress.css +85 -0
  245. package/templates/admin-shell/styles/reset.css +378 -0
  246. package/templates/admin-shell/theme/antd/AntdProvider.tsx +106 -0
  247. package/templates/admin-shell/theme/antd/index.ts +23 -0
  248. package/templates/admin-shell/theme/antd/shared.ts +80 -0
  249. package/templates/admin-shell/theme/antd/ui.ts +250 -0
  250. package/templates/admin-shell/theme/antd-algorithm/algorithm/dark/colors.ts +99 -0
  251. package/templates/admin-shell/theme/antd-algorithm/algorithm/dark/index.ts +87 -0
  252. package/templates/admin-shell/theme/antd-algorithm/algorithm/default/colors.ts +91 -0
  253. package/templates/admin-shell/theme/antd-algorithm/algorithm/default/index.ts +77 -0
  254. package/templates/admin-shell/theme/antd-algorithm/index.ts +80 -0
  255. package/templates/admin-shell/theme/antd-algorithm/seed/index.ts +41 -0
  256. package/templates/admin-shell/theme/antd-algorithm/shared/colorAlgorithm.ts +10 -0
  257. package/templates/admin-shell/theme/antd-algorithm/shared/genColorMapToken.ts +165 -0
  258. package/templates/admin-shell/theme/antd-algorithm/shared/genCommonMapToken.ts +23 -0
  259. package/templates/admin-shell/theme/antd-algorithm/shared/genControlHeight.ts +14 -0
  260. package/templates/admin-shell/theme/antd-algorithm/shared/genFontMapToken.ts +274 -0
  261. package/templates/admin-shell/theme/antd-algorithm/shared/genRadiusMapToken.ts +139 -0
  262. package/templates/admin-shell/theme/antd-algorithm/shared/genSizeMapToken.ts +114 -0
  263. package/templates/admin-shell/theme/antd-algorithm/types/index.ts +20 -0
  264. package/templates/admin-shell/theme/components/ThemeEffect.tsx +55 -0
  265. package/templates/admin-shell/theme/components/ThemeSchemaSegmented.tsx +27 -0
  266. package/templates/admin-shell/theme/components/ThemeSchemaSwitch.tsx +77 -0
  267. package/templates/admin-shell/theme/components/shared.ts +5 -0
  268. package/templates/admin-shell/theme/config/default.ts +108 -0
  269. package/templates/admin-shell/theme/config/index.ts +1 -0
  270. package/templates/admin-shell/theme/hooks/index.ts +2 -0
  271. package/templates/admin-shell/theme/hooks/use-theme.ts +244 -0
  272. package/templates/admin-shell/theme/index.ts +78 -0
  273. package/templates/admin-shell/theme/presets/azir.json +39 -0
  274. package/templates/admin-shell/theme/presets/compact.json +38 -0
  275. package/templates/admin-shell/theme/presets/dark.json +93 -0
  276. package/templates/admin-shell/theme/presets/default.json +95 -0
  277. package/templates/admin-shell/theme/presets/index.ts +29 -0
  278. package/templates/admin-shell/theme/presets/shadcn.json +99 -0
  279. package/templates/admin-shell/theme/setup.ts +115 -0
  280. package/templates/admin-shell/theme/types/index.ts +27 -0
  281. package/templates/admin-shell/theme/types/theme.d.ts +316 -0
  282. package/templates/admin-shell/theme/utils/dark-mode.ts +46 -0
  283. package/templates/admin-shell/theme/utils/filters.ts +20 -0
  284. package/templates/admin-shell/theme/utils/index.ts +3 -0
  285. package/templates/admin-shell/theme/utils/settings.ts +43 -0
  286. package/templates/admin-shell/types/api/auth.d.ts +148 -0
  287. package/templates/admin-shell/types/api/common.d.ts +54 -0
  288. package/templates/admin-shell/types/api/route.d.ts +104 -0
  289. package/templates/admin-shell/types/api/service.d.ts +51 -0
  290. package/templates/admin-shell/types/api/system-manage.d.ts +160 -0
  291. package/templates/admin-shell/types/app/common.d.ts +31 -0
  292. package/templates/admin-shell/types/app/global.d.ts +71 -0
  293. package/templates/admin-shell/types/app/menu.d.ts +57 -0
  294. package/templates/admin-shell/types/app/router.d.ts +201 -0
  295. package/templates/admin-shell/types/app/storage.d.ts +10 -0
  296. package/templates/admin-shell/types/app/union-key.d.ts +156 -0
  297. package/templates/admin-shell/types/locales/i18n.d.ts +135 -0
  298. package/templates/admin-shell/ui/antd/components/AppTree.tsx +62 -0
  299. package/templates/admin-shell/ui/antd/components/BeyondHiding.tsx +42 -0
  300. package/templates/admin-shell/ui/antd/components/ButtonIcon.tsx +123 -0
  301. package/templates/admin-shell/ui/antd/components/ButtonLink.tsx +30 -0
  302. package/templates/admin-shell/ui/antd/components/ErrorBoundary.tsx +45 -0
  303. package/templates/admin-shell/ui/antd/components/ExceptionBase.tsx +69 -0
  304. package/templates/admin-shell/ui/antd/components/FullScreen.tsx +33 -0
  305. package/templates/admin-shell/ui/antd/components/PinToggler.tsx +35 -0
  306. package/templates/admin-shell/ui/antd/components/ReloadButton.tsx +20 -0
  307. package/templates/admin-shell/ui/antd/components/index.ts +12 -0
  308. package/templates/admin-shell/ui/antd/components/styles/button-icon-animations.css +246 -0
  309. package/templates/admin-shell/ui/antd/index.ts +1 -0
  310. package/templates/admin-shell/ui/antd/styles.d.ts +1 -0
  311. package/templates/admin-shell/ui/compose/animate-feature.ts +5 -0
  312. package/templates/admin-shell/ui/compose/components/BetterScroll.tsx +51 -0
  313. package/templates/admin-shell/ui/compose/components/DarkModeContainer.tsx +22 -0
  314. package/templates/admin-shell/ui/compose/components/FlipText.tsx +41 -0
  315. package/templates/admin-shell/ui/compose/components/I18nLabel.tsx +18 -0
  316. package/templates/admin-shell/ui/compose/components/LazyAnimate.tsx +10 -0
  317. package/templates/admin-shell/ui/compose/components/LookForward.tsx +24 -0
  318. package/templates/admin-shell/ui/compose/components/NumberTicker.tsx +105 -0
  319. package/templates/admin-shell/ui/compose/components/Portal.tsx +74 -0
  320. package/templates/admin-shell/ui/compose/components/SvgIcon.tsx +39 -0
  321. package/templates/admin-shell/ui/compose/components/TypingAnimation.tsx +83 -0
  322. package/templates/admin-shell/ui/compose/components/WaveBg.tsx +56 -0
  323. package/templates/admin-shell/ui/compose/components/index.ts +11 -0
  324. package/templates/admin-shell/ui/compose/index.ts +2 -0
  325. package/templates/admin-shell/ui/compose/table/DragContent.tsx +239 -0
  326. package/templates/admin-shell/ui/compose/table/TableHeaderOperation.tsx +210 -0
  327. package/templates/admin-shell/ui/compose/table/hooks.ts +196 -0
  328. package/templates/admin-shell/ui/compose/table/index.ts +16 -0
  329. package/templates/admin-shell/ui/compose/table/types.ts +244 -0
  330. package/templates/admin-shell/ui/compose/table/use-table-operate.ts +136 -0
  331. package/templates/admin-shell/ui/compose/table/use-table-scroll.ts +49 -0
  332. package/templates/admin-shell/ui/compose/table/use-table.ts +303 -0
  333. package/templates/admin.meta.json +756 -0
@@ -0,0 +1,35 @@
1
+ import { SvgIcon } from '@shell/ui/compose';
2
+ import React from 'react';
3
+
4
+ import ButtonIcon from './ButtonIcon';
5
+
6
+ interface Props {
7
+ /** Button class */
8
+ className: string;
9
+ /** Click handler */
10
+ onClick?: React.ComponentProps<'button'>['onClick'];
11
+ /** Whether the item is pinned */
12
+ pin?: boolean;
13
+ /** Tooltip text when pinned (default: 'Unpin') */
14
+ pinnedTooltip?: string;
15
+ /** Tooltip text when unpinned (default: 'Pin') */
16
+ unpinnedTooltip?: string;
17
+ }
18
+ const PinToggler = (props: Props) => {
19
+ const { className, onClick, pin, pinnedTooltip = 'Unpin', unpinnedTooltip = 'Pin' } = props;
20
+
21
+ const icon = pin ? 'mdi-pin-off' : 'mdi-pin';
22
+ return (
23
+ <ButtonIcon
24
+ triggerParent
25
+ className={className}
26
+ tooltipContent={pin ? pinnedTooltip : unpinnedTooltip}
27
+ tooltipPlacement="bottomLeft"
28
+ onClick={onClick}
29
+ >
30
+ <SvgIcon icon={icon} />
31
+ </ButtonIcon>
32
+ );
33
+ };
34
+
35
+ export default PinToggler;
@@ -0,0 +1,20 @@
1
+ import { Icon } from '@iconify/react';
2
+
3
+ import type { ButtonIconProps } from './ButtonIcon';
4
+ import ButtonIcon from './ButtonIcon';
5
+
6
+ type Props = ButtonIconProps & {
7
+ isReload: boolean;
8
+ };
9
+
10
+ const ReloadButton = (props: Props) => {
11
+ const { isReload, ...rest } = props;
12
+
13
+ return (
14
+ <ButtonIcon {...rest}>
15
+ <Icon className={isReload ? '' : 'animate-spin animate-duration-750'} icon="ant-design:reload-outlined" />
16
+ </ButtonIcon>
17
+ );
18
+ };
19
+
20
+ export default ReloadButton;
@@ -0,0 +1,12 @@
1
+ export { default as AppTree } from './AppTree';
2
+ export type { AppTreeProps } from './AppTree';
3
+ export { default as BeyondHiding } from './BeyondHiding';
4
+ export { default as ButtonIcon } from './ButtonIcon';
5
+ export type { ButtonIconProps, IconHoverAnimation } from './ButtonIcon';
6
+ export { default as ButtonLink } from './ButtonLink';
7
+ export type { ButtonLinkComponentProps } from './ButtonLink';
8
+ export { default as ErrorBoundary } from './ErrorBoundary';
9
+ export { default as ExceptionBase } from './ExceptionBase';
10
+ export { default as FullScreen } from './FullScreen';
11
+ export { default as PinToggler } from './PinToggler';
12
+ export { default as ReloadButton } from './ReloadButton';
@@ -0,0 +1,246 @@
1
+ /* ==================== ButtonIcon Hover Animations ==================== */
2
+
3
+ /* 基础过渡效果 */
4
+ .btn-icon-hover-scale,
5
+ .btn-icon-hover-rotate,
6
+ .btn-icon-hover-shake,
7
+ .btn-icon-hover-bounce,
8
+ .btn-icon-hover-pulse,
9
+ .btn-icon-hover-spin,
10
+ .btn-icon-hover-swing,
11
+ .btn-icon-hover-flip,
12
+ .btn-icon-hover-wiggle,
13
+ .btn-icon-hover-tada {
14
+ display: inline-flex;
15
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
16
+ }
17
+
18
+ /* 1. Scale - 缩放放大效果 (经典的交互反馈) */
19
+ .btn-icon-hover-scale {
20
+ transition: transform 0.3s ease;
21
+ }
22
+
23
+ .btn-icon-hover-scale:hover {
24
+ transform: scale(1.2);
25
+ }
26
+
27
+ /* 2. Rotate - 旋转效果 (适合刷新、设置图标) */
28
+ .btn-icon-hover-rotate {
29
+ transition: transform 0.4s ease;
30
+ }
31
+
32
+ .btn-icon-hover-rotate:hover {
33
+ transform: rotate(180deg);
34
+ }
35
+
36
+ /* 3. Shake - 左右抖动效果 (吸引注意力) */
37
+ .btn-icon-hover-shake:hover {
38
+ animation: btn-icon-shake 0.5s ease-in-out;
39
+ }
40
+
41
+ @keyframes btn-icon-shake {
42
+ 0%,
43
+ 100% {
44
+ transform: translateX(0);
45
+ }
46
+ 10%,
47
+ 30%,
48
+ 50%,
49
+ 70%,
50
+ 90% {
51
+ transform: translateX(-4px);
52
+ }
53
+ 20%,
54
+ 40%,
55
+ 60%,
56
+ 80% {
57
+ transform: translateX(4px);
58
+ }
59
+ }
60
+
61
+ /* 4. Bounce - 弹跳效果 (活泼有趣) */
62
+ .btn-icon-hover-bounce:hover {
63
+ animation: btn-icon-bounce 0.6s ease;
64
+ }
65
+
66
+ @keyframes btn-icon-bounce {
67
+ 0%,
68
+ 100% {
69
+ transform: translateY(0);
70
+ }
71
+ 10%,
72
+ 30% {
73
+ transform: translateY(-8px);
74
+ }
75
+ 20% {
76
+ transform: translateY(-12px);
77
+ }
78
+ 40%,
79
+ 60% {
80
+ transform: translateY(-4px);
81
+ }
82
+ 50% {
83
+ transform: translateY(-6px);
84
+ }
85
+ 70%,
86
+ 90% {
87
+ transform: translateY(-2px);
88
+ }
89
+ 80% {
90
+ transform: translateY(-3px);
91
+ }
92
+ }
93
+
94
+ /* 5. Pulse - 脉冲缩放效果 (呼吸效果) */
95
+ .btn-icon-hover-pulse:hover {
96
+ animation: btn-icon-pulse 1s ease-in-out infinite;
97
+ }
98
+
99
+ @keyframes btn-icon-pulse {
100
+ 0%,
101
+ 100% {
102
+ transform: scale(1);
103
+ opacity: 1;
104
+ }
105
+ 50% {
106
+ transform: scale(1.15);
107
+ opacity: 0.85;
108
+ }
109
+ }
110
+
111
+ /* 6. Spin - 持续旋转效果 (适合加载、刷新) */
112
+ .btn-icon-hover-spin:hover {
113
+ animation: btn-icon-spin 0.8s linear infinite;
114
+ }
115
+
116
+ @keyframes btn-icon-spin {
117
+ from {
118
+ transform: rotate(0deg);
119
+ }
120
+ to {
121
+ transform: rotate(360deg);
122
+ }
123
+ }
124
+
125
+ /* 7. Swing - 摇摆效果 (类似钟摆) */
126
+ .btn-icon-hover-swing:hover {
127
+ animation: btn-icon-swing 0.6s ease-in-out;
128
+ transform-origin: center top;
129
+ }
130
+
131
+ @keyframes btn-icon-swing {
132
+ 0%,
133
+ 100% {
134
+ transform: rotate(0deg);
135
+ }
136
+ 15% {
137
+ transform: rotate(15deg);
138
+ }
139
+ 30% {
140
+ transform: rotate(-10deg);
141
+ }
142
+ 45% {
143
+ transform: rotate(8deg);
144
+ }
145
+ 60% {
146
+ transform: rotate(-6deg);
147
+ }
148
+ 75% {
149
+ transform: rotate(4deg);
150
+ }
151
+ 90% {
152
+ transform: rotate(-2deg);
153
+ }
154
+ }
155
+
156
+ /* 8. Flip - 3D翻转效果 (酷炫效果) */
157
+ .btn-icon-hover-flip {
158
+ transition: transform 0.6s ease;
159
+ transform-style: preserve-3d;
160
+ }
161
+
162
+ .btn-icon-hover-flip:hover {
163
+ transform: rotateY(180deg);
164
+ }
165
+
166
+ /* 9. Wiggle - 扭动效果 (俏皮可爱) */
167
+ .btn-icon-hover-wiggle:hover {
168
+ animation: btn-icon-wiggle 0.5s ease-in-out;
169
+ }
170
+
171
+ @keyframes btn-icon-wiggle {
172
+ 0%,
173
+ 100% {
174
+ transform: rotate(0deg);
175
+ }
176
+ 10%,
177
+ 30%,
178
+ 50%,
179
+ 70%,
180
+ 90% {
181
+ transform: rotate(-3deg);
182
+ }
183
+ 20%,
184
+ 40%,
185
+ 60%,
186
+ 80% {
187
+ transform: rotate(3deg);
188
+ }
189
+ }
190
+
191
+ /* 10. Tada - 惊喜效果 (多维度组合动画) */
192
+ .btn-icon-hover-tada:hover {
193
+ animation: btn-icon-tada 0.8s ease;
194
+ }
195
+
196
+ @keyframes btn-icon-tada {
197
+ 0% {
198
+ transform: scale(1) rotate(0deg);
199
+ }
200
+ 10%,
201
+ 20% {
202
+ transform: scale(0.95) rotate(-3deg);
203
+ }
204
+ 30%,
205
+ 50%,
206
+ 70%,
207
+ 90% {
208
+ transform: scale(1.1) rotate(3deg);
209
+ }
210
+ 40%,
211
+ 60%,
212
+ 80% {
213
+ transform: scale(1.1) rotate(-3deg);
214
+ }
215
+ 100% {
216
+ transform: scale(1) rotate(0deg);
217
+ }
218
+ }
219
+
220
+ /* 性能优化:使用 GPU 加速 */
221
+ .btn-icon-hover-scale,
222
+ .btn-icon-hover-rotate,
223
+ .btn-icon-hover-shake,
224
+ .btn-icon-hover-bounce,
225
+ .btn-icon-hover-pulse,
226
+ .btn-icon-hover-spin,
227
+ .btn-icon-hover-swing,
228
+ .btn-icon-hover-flip,
229
+ .btn-icon-hover-wiggle,
230
+ .btn-icon-hover-tada {
231
+ will-change: transform;
232
+ }
233
+
234
+ /* 添加平滑的光标指针 */
235
+ .btn-icon-hover-scale:hover,
236
+ .btn-icon-hover-rotate:hover,
237
+ .btn-icon-hover-shake:hover,
238
+ .btn-icon-hover-bounce:hover,
239
+ .btn-icon-hover-pulse:hover,
240
+ .btn-icon-hover-spin:hover,
241
+ .btn-icon-hover-swing:hover,
242
+ .btn-icon-hover-flip:hover,
243
+ .btn-icon-hover-wiggle:hover,
244
+ .btn-icon-hover-tada:hover {
245
+ cursor: pointer;
246
+ }
@@ -0,0 +1 @@
1
+ export * from './components';
@@ -0,0 +1 @@
1
+ declare module '*.css';
@@ -0,0 +1,5 @@
1
+ import { domMax } from 'motion/react';
2
+
3
+ const animateFeature = domMax;
4
+
5
+ export default animateFeature;
@@ -0,0 +1,51 @@
1
+ import type { Options } from '@better-scroll/core';
2
+ import BScroll from '@better-scroll/core';
3
+ import { useMount, useSize, useUpdateEffect } from 'ahooks';
4
+ import { clsx } from 'clsx';
5
+ import type { ComponentPropsWithoutRef, FC, Ref } from 'react';
6
+ import { useImperativeHandle, useRef } from 'react';
7
+
8
+ interface Props extends ComponentPropsWithoutRef<'div'> {
9
+ /**
10
+ * BetterScroll options
11
+ *
12
+ * @link https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html
13
+ */
14
+ options: Options;
15
+ ref: Ref<BScroll>;
16
+ }
17
+
18
+ const BetterScroll: FC<Props> = ({ children, className, options, ref, ...rest }) => {
19
+ const bsWrapper = useRef<HTMLDivElement>(null);
20
+ const bsContent = useRef<HTMLDivElement>(null);
21
+ const bsWrapperSize = useSize(bsWrapper);
22
+ const bsContentSize = useSize(bsContent);
23
+ const instance = useRef<BScroll | null>(null);
24
+
25
+ const isScrollY = Boolean(options.scrollY);
26
+
27
+ function initBetterScroll() {
28
+ if (!bsWrapper.current) return;
29
+ instance.current = new BScroll(bsWrapper.current, options);
30
+ }
31
+
32
+ useImperativeHandle(ref, () => instance.current as BScroll);
33
+
34
+ useUpdateEffect(() => {
35
+ instance.current?.refresh();
36
+ }, [bsWrapperSize?.width, bsContentSize?.width]);
37
+
38
+ useMount(() => {
39
+ initBetterScroll();
40
+ });
41
+
42
+ return (
43
+ <div ref={bsWrapper} {...rest} className={clsx('h-full text-left', className)}>
44
+ <div className={clsx('inline-block', { 'h-full': !isScrollY })} ref={bsContent}>
45
+ {children}
46
+ </div>
47
+ </div>
48
+ );
49
+ };
50
+
51
+ export default BetterScroll;
@@ -0,0 +1,22 @@
1
+ import { clsx } from 'clsx';
2
+
3
+ interface Props extends React.ComponentProps<'div'> {
4
+ inverted?: boolean;
5
+ }
6
+
7
+ const DarkModeContainer = ({ children, className, inverted, ...rest }: Props) => {
8
+ return (
9
+ <div
10
+ className={clsx(
11
+ 'bg-container text-base-text transition-300',
12
+ { 'bg-inverted text-#1f1f1f': inverted },
13
+ className
14
+ )}
15
+ {...rest}
16
+ >
17
+ {children}
18
+ </div>
19
+ );
20
+ };
21
+
22
+ export default DarkModeContainer;
@@ -0,0 +1,41 @@
1
+ import { clsx } from 'clsx';
2
+ import type { Variants } from 'motion/react';
3
+ import { AnimatePresence, motion } from 'motion/react';
4
+
5
+ interface FlipTextProps {
6
+ readonly className?: string;
7
+ readonly delayMultiple?: number;
8
+ readonly duration?: number;
9
+ readonly framerProps?: Variants;
10
+ readonly word: string;
11
+ }
12
+
13
+ const FlipText = ({
14
+ className,
15
+ delayMultiple = 0.08,
16
+ duration = 0.5,
17
+ framerProps = {
18
+ hidden: { opacity: 0, rotateX: -90 },
19
+ visible: { opacity: 1, rotateX: 0 }
20
+ },
21
+ word
22
+ }: FlipTextProps) => {
23
+ return (
24
+ <AnimatePresence mode="sync">
25
+ {word.split('').map((char, i) => (
26
+ <motion.span
27
+ animate="visible"
28
+ className={clsx('drop-shadow-sm', className)}
29
+ initial="hidden"
30
+ key={i}
31
+ transition={{ delay: i * delayMultiple, duration }}
32
+ variants={framerProps}
33
+ >
34
+ {char}
35
+ </motion.span>
36
+ ))}
37
+ </AnimatePresence>
38
+ );
39
+ };
40
+
41
+ export default FlipText;
@@ -0,0 +1,18 @@
1
+ import type { i18n } from 'i18next';
2
+ import type { ReactNode } from 'react';
3
+ import { Translation } from 'react-i18next';
4
+
5
+ interface I18nLabelProps {
6
+ /** Fallback content when i18nKey is not provided */
7
+ fallback?: ReactNode;
8
+ /** I18n instance (optional, uses default if not provided) */
9
+ i18n?: i18n;
10
+ /** I18n translation key */
11
+ i18nKey?: string | null;
12
+ }
13
+
14
+ const I18nLabel = ({ fallback, i18n: i18nInstance, i18nKey }: I18nLabelProps) => {
15
+ return <Translation i18n={i18nInstance}>{t => (i18nKey ? t(i18nKey) : fallback)}</Translation>;
16
+ };
17
+
18
+ export default I18nLabel;
@@ -0,0 +1,10 @@
1
+ import { LazyMotion } from 'motion/react';
2
+ import type { PropsWithChildren } from 'react';
3
+
4
+ const loadFeatures = () => import('../animate-feature').then(res => res.default);
5
+
6
+ const LazyAnimate = ({ children }: PropsWithChildren) => {
7
+ return <LazyMotion features={loadFeatures}>{children}</LazyMotion>;
8
+ };
9
+
10
+ export default LazyAnimate;
@@ -0,0 +1,24 @@
1
+ import type { PropsWithChildren } from 'react';
2
+
3
+ import SvgIcon from './SvgIcon';
4
+
5
+ interface LookForwardProps extends PropsWithChildren {
6
+ /** Fallback title text when no children provided (default: 'Coming Soon') */
7
+ title?: string;
8
+ }
9
+
10
+ const LookForward = (props: LookForwardProps) => {
11
+ const { children, title = 'Coming Soon' } = props;
12
+
13
+ return (
14
+ <div className="size-full min-h-520px flex-col-center gap-24px overflow-hidden">
15
+ <div className="flex text-400px text-primary">
16
+ <SvgIcon localIcon="expectation" />
17
+ </div>
18
+
19
+ {children || <h3 className="text-28px text-primary font-500">{title}</h3>}
20
+ </div>
21
+ );
22
+ };
23
+
24
+ export default LookForward;
@@ -0,0 +1,105 @@
1
+ import { clsx } from 'clsx';
2
+ import type { AnimationPlaybackControls } from 'motion/react';
3
+ import { animate, useInView, useMotionValue, useSpring } from 'motion/react';
4
+ import type { ComponentPropsWithoutRef } from 'react';
5
+ import { useEffect, useRef } from 'react';
6
+
7
+ interface NumberTickerProps extends ComponentPropsWithoutRef<'span'> {
8
+ /** - 小数位数 */
9
+ decimalPlaces?: number;
10
+ /** - 延时时间 单位:秒 */
11
+ delay?: number;
12
+ /** - 动画方向 */
13
+ direction?: 'down' | 'up';
14
+ /** - 动画时长 单位:秒 如果不指定 则使用spring动画 更为自然地过渡 */
15
+ duration?: number;
16
+ /** - 前缀 */
17
+ prefix?: string;
18
+ /** - 起始值 */
19
+ startValue?: number;
20
+ /** - 后缀 */
21
+ suffix?: string;
22
+ /** - 目标值 */
23
+ value: number;
24
+ }
25
+
26
+ const NumberTicker = ({
27
+ className,
28
+ decimalPlaces = 0,
29
+ delay = 0,
30
+ direction = 'up',
31
+ duration,
32
+ prefix = '',
33
+ startValue = 0,
34
+ suffix = '',
35
+ value,
36
+ ...props
37
+ }: NumberTickerProps) => {
38
+ const ref = useRef<HTMLSpanElement>(null);
39
+
40
+ const endValue = direction === 'down' ? 0 : value;
41
+
42
+ const motionValue = useMotionValue(direction === 'down' ? value : 0);
43
+
44
+ const isInView = useInView(ref, { margin: '0px', once: true });
45
+
46
+ const springValue = useSpring(motionValue, {
47
+ damping: 60,
48
+ stiffness: 100
49
+ });
50
+
51
+ function updateTextContent(latest: number) {
52
+ if (!ref.current) return;
53
+
54
+ const formattedNumber = Intl.NumberFormat('en-US', {
55
+ maximumFractionDigits: decimalPlaces,
56
+ minimumFractionDigits: decimalPlaces
57
+ }).format(Number(latest.toFixed(decimalPlaces)));
58
+ ref.current.textContent = `${prefix} ${formattedNumber} ${suffix}`;
59
+ }
60
+
61
+ useEffect(() => {
62
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
63
+ isInView &&
64
+ setTimeout(() => {
65
+ motionValue.set(endValue);
66
+ }, delay * 1000);
67
+ }, [motionValue, isInView, delay, endValue]);
68
+
69
+ useEffect(() => {
70
+ let animation: AnimationPlaybackControls;
71
+ if (duration) {
72
+ animation = animate(
73
+ motionValue.get(), // 起始值
74
+ endValue, // 目标值
75
+ {
76
+ duration, // 动画时长(秒)
77
+ onUpdate: latest => {
78
+ // 更新 motionValue(可选:如果后续还有依赖该值的逻辑)
79
+ motionValue.set(latest);
80
+
81
+ updateTextContent(latest);
82
+ }
83
+ }
84
+ );
85
+ } else {
86
+ springValue.on('change', latest => {
87
+ updateTextContent(latest);
88
+ });
89
+ }
90
+ return () => animation?.cancel();
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
92
+ }, [motionValue, springValue, endValue, duration]);
93
+
94
+ return (
95
+ <span
96
+ className={clsx('inline-block text-white tracking-wider tabular-nums dark:text-dark', className)}
97
+ ref={ref}
98
+ {...props}
99
+ >
100
+ {prefix} {startValue} {suffix}
101
+ </span>
102
+ );
103
+ };
104
+
105
+ export default NumberTicker;
@@ -0,0 +1,74 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ import { type ReactNode, useEffect, useRef, useState } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+
5
+ interface PortalProps {
6
+ /** 是否在容器不存在时自动创建 */
7
+ autoCreate?: boolean;
8
+ children: ReactNode;
9
+ /** 容器 ID 或 CSS 选择器 */
10
+ container: string | HTMLElement;
11
+
12
+ /** 容器不存在时的回退内容 */
13
+ fallback?: ReactNode;
14
+ /** 自动创建时的标签名 */
15
+ tagName?: keyof HTMLElementTagNameMap;
16
+ }
17
+
18
+ const Portal = (props: PortalProps) => {
19
+ const { autoCreate = false, children, container, fallback = null, tagName = 'div' } = props;
20
+
21
+ const [targetElement, setTargetElement] = useState<HTMLElement | null>(null);
22
+
23
+ const createdElementRef = useRef<HTMLElement | null>(null);
24
+
25
+ function findTargetElement() {
26
+ let element: HTMLElement | null = null;
27
+
28
+ // 支持直接传入 HTMLElement
29
+ if (container instanceof HTMLElement) {
30
+ element = container;
31
+ }
32
+ // 支持 ID(#id)
33
+ else if (typeof container === 'string') {
34
+ if (container.startsWith('#')) {
35
+ element = document.getElementById(container.slice(1));
36
+ }
37
+ // 支持 CSS 选择器
38
+ else {
39
+ element = document.querySelector<HTMLElement>(container);
40
+ }
41
+ }
42
+
43
+ // 自动创建容器
44
+ if (!element && autoCreate && typeof container === 'string') {
45
+ element = document.createElement(tagName);
46
+ element.id = container.startsWith('#') ? container.slice(1) : container;
47
+ document.body.appendChild(element);
48
+ createdElementRef.current = element;
49
+ }
50
+
51
+ setTargetElement(element);
52
+ }
53
+
54
+ useEffect(() => {
55
+ findTargetElement();
56
+
57
+ // 清理:移除自动创建的元素
58
+ return () => {
59
+ if (createdElementRef.current) {
60
+ document.body.removeChild(createdElementRef.current);
61
+ createdElementRef.current = null;
62
+ }
63
+ };
64
+ }, []);
65
+
66
+ // 容器不存在时渲染回退内容
67
+ if (!targetElement) {
68
+ return fallback;
69
+ }
70
+
71
+ return createPortal(children, targetElement);
72
+ };
73
+
74
+ export default Portal;