@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,438 @@
1
+ import { PriorityQueue, nanoid } from '@skyroc/utils';
2
+
3
+ import type {
4
+ AddNotificationInput,
5
+ NotificationConfig,
6
+ NotificationItem,
7
+ NotificationPriority,
8
+ NotificationShortcutOptions,
9
+ NotificationSnapshot,
10
+ NotificationStoreOptions
11
+ } from './types';
12
+
13
+ /** 默认的通知运行时配置。 */
14
+ export const DEFAULT_NOTIFICATION_CONFIG: NotificationConfig = {
15
+ browserNotificationEnabled: true,
16
+ doNotDisturb: false,
17
+ maxNotifications: 99,
18
+ soundEnabled: true
19
+ };
20
+
21
+ /** 排序权重,越小越靠前。没标优先级的按 normal 算。 */
22
+ const PRIORITY_WEIGHT: Record<NotificationPriority, number> = {
23
+ high: 1,
24
+ low: 3,
25
+ normal: 2,
26
+ urgent: 0
27
+ };
28
+
29
+ /**
30
+ * 队列里额外挂的入队序号。
31
+ *
32
+ * Timestamp 是毫秒,一轮循环推进来的多条会撞成同一个值,只按它排的话顺序由 sort 的实现决定。 序号单调递增,做最后一道 tie-breaker。
33
+ */
34
+ type QueuedNotification = NotificationItem & { seq: number };
35
+
36
+ /** 把优先级换算成排序权重。未标优先级的按 normal 算,让 compareNotifications 不用到处判空。 */
37
+ function getWeight(priority: NotificationPriority | undefined): number {
38
+ return PRIORITY_WEIGHT[priority ?? 'normal'];
39
+ }
40
+
41
+ /** 先按优先级,再按时间倒序(新的在前),最后按入队序号倒序。已读与否不参与排序:否则点一下已读整个列表会跳动。 */
42
+ function compareNotifications(a: QueuedNotification, b: QueuedNotification): number {
43
+ return getWeight(a.priority) - getWeight(b.priority) || b.timestamp - a.timestamp || b.seq - a.seq;
44
+ }
45
+
46
+ /**
47
+ * 取当前本地时间的 HH:mm 文本。
48
+ *
49
+ * 免打扰配置里的 start / end 就是这个格式,统一成字符串后可以直接比大小,不用先解析成分钟数。
50
+ */
51
+ function getCurrentTimeText(): string {
52
+ const now = new Date();
53
+ const hour = now.getHours().toString().padStart(2, '0');
54
+ const minute = now.getMinutes().toString().padStart(2, '0');
55
+
56
+ return `${hour}:${minute}`;
57
+ }
58
+
59
+ /** 判断时间是否处于指定范围内,同时支持跨午夜的时间段。 */
60
+ function isWithinTimeRange(currentTime: string, start: string, end: string): boolean {
61
+ if (start <= end) {
62
+ return currentTime >= start && currentTime <= end;
63
+ }
64
+
65
+ return currentTime >= start || currentTime <= end;
66
+ }
67
+
68
+ /**
69
+ * 通知中心的状态与副作用,不依赖 React。
70
+ *
71
+ * 只管三件事:维护去重排序后的通知队列、维护运行时配置、按配置播音效和弹浏览器通知。 消息从哪来(WebSocket、SSE、HTTP 轮询、手动调用)一概不知道 —— 数据源留在宿主应用里, 否则这个包会被绑死在某一套后端协议上。
72
+ *
73
+ * React 侧用 subscribe / getSnapshot 交给 useSyncExternalStore 订阅,见 use-notification.ts。
74
+ */
75
+ export class NotificationStore {
76
+ /** 当前运行时配置,唯一真相。 */
77
+ private config: NotificationConfig;
78
+
79
+ /** 变更监听器集合,快照重建后统一回调。 */
80
+ private readonly listeners = new Set<() => void>();
81
+
82
+ /** 宿主注入的音效地址和各类失败回调。随 Provider 每次渲染整体替换,见 setOptions。 */
83
+ private options: NotificationStoreOptions;
84
+
85
+ /** 浏览器原生通知的授权状态。构造时不读,见 syncPermission。 */
86
+ private permission: NotificationPermission = 'default';
87
+
88
+ /** Id 去重 + 优先级排序 + 容量上限都在这里,store 自己不再维护第二份列表。 */
89
+ private readonly queue: PriorityQueue<QueuedNotification>;
90
+
91
+ /** 入队序号,只增不减。 */
92
+ private seq = 0;
93
+
94
+ /** 快照缓存。useSyncExternalStore 按引用比较,每次现算会一直重渲染。 */
95
+ private snapshot: NotificationSnapshot;
96
+
97
+ /** 缓存的音频实例,全部通知共用一个。每条通知新建一个的话,连着来几条会同时响。 */
98
+ private sound: HTMLAudioElement | null = null;
99
+
100
+ /** 上次创建音频用的地址,用来判断 soundUrl 换了没有。 */
101
+ private soundUrl: string | undefined = undefined;
102
+
103
+ /** 解绑「队列变化 → 重建快照」这条链的函数。只在构造时接一次,destroy 时调它断开。 */
104
+ private readonly unsubscribeQueue: () => void;
105
+
106
+ /**
107
+ * 装配队列、合并默认配置、接上队列订阅。
108
+ *
109
+ * 这里一个浏览器 API 都不碰:读权限推到 syncPermission,建 Audio 推到第一次播放。 服务端渲染时 new 出来是安全的。
110
+ */
111
+ constructor(options: NotificationStoreOptions = {}) {
112
+ this.options = options;
113
+ this.config = { ...DEFAULT_NOTIFICATION_CONFIG, ...options.defaultConfig };
114
+
115
+ this.queue = new PriorityQueue<QueuedNotification>({
116
+ capacity: this.config.maxNotifications,
117
+ compare: compareNotifications,
118
+ getId: item => item.id
119
+ });
120
+
121
+ this.snapshot = this.buildSnapshot();
122
+ this.unsubscribeQueue = this.queue.subscribe(this.refresh);
123
+ }
124
+
125
+ // ==================== 订阅 ====================
126
+
127
+ /**
128
+ * 读当前快照。
129
+ *
130
+ * 写成箭头属性是因为 useSyncExternalStore 要求函数身份稳定,写成普通方法每次渲染取到的 都是同一个引用,但 this 会丢。
131
+ */
132
+ getSnapshot = (): NotificationSnapshot => this.snapshot;
133
+
134
+ /** 订阅快照变化,配合 getSnapshot 交给 useSyncExternalStore。 */
135
+ subscribe = (listener: () => void): (() => void) => {
136
+ this.listeners.add(listener);
137
+
138
+ return () => {
139
+ this.listeners.delete(listener);
140
+ };
141
+ };
142
+
143
+ /** 断开与队列的联系并清空订阅方。实例不再复用时调。 */
144
+ destroy = (): void => {
145
+ this.unsubscribeQueue();
146
+ this.queue.clear();
147
+ this.listeners.clear();
148
+ };
149
+
150
+ // ==================== 写入 ====================
151
+
152
+ /**
153
+ * 添加一条通知,返回它的 id。
154
+ *
155
+ * 没带 id 的会生成一个随机 id —— 那等于声明这条不参与去重,重复投递会重复展示。 要去重就把稳定 id 带进来(推送场景用服务端信封里的 msg_id)。
156
+ */
157
+ add = (input: AddNotificationInput): string => {
158
+ this.seq += 1;
159
+
160
+ const item: QueuedNotification = {
161
+ ...input,
162
+ id: input.id ?? nanoid(),
163
+ read: input.read ?? false,
164
+ seq: this.seq,
165
+ timestamp: input.timestamp ?? Date.now()
166
+ };
167
+
168
+ // 同 id 已经在队列里说明是重复投递(WebSocket 和 SSE 同时连着会把同一条推两遍),
169
+ // 静默丢掉:再播一次音、再弹一次浏览器通知就穿帮了
170
+ if (!this.queue.enqueue(item)) {
171
+ return item.id;
172
+ }
173
+
174
+ if (!item.silent) {
175
+ this.playSound();
176
+ }
177
+
178
+ if (item.showBrowserNotification !== false) {
179
+ this.showBrowserNotification(item);
180
+ }
181
+
182
+ return item.id;
183
+ };
184
+
185
+ // 下面五个只是把 type 填好后转给 add,行为和 add 完全一致。选哪个看这条通知要给用户什么
186
+ // 语义,type 决定的是面板里的图标和颜色。
187
+
188
+ /** 中性告知,用户不需要做任何处理(版本更新、任务已开始)。 */
189
+ addInfo = (title: string, content: string, options: NotificationShortcutOptions = {}): string =>
190
+ this.add({ ...options, content, title, type: 'info' });
191
+
192
+ /** 操作成功的回执(导出完成、审批通过)。 */
193
+ addSuccess = (title: string, content: string, options: NotificationShortcutOptions = {}): string =>
194
+ this.add({ ...options, content, title, type: 'success' });
195
+
196
+ /** 需要用户留意但没有中断流程(配额快用完、证书快到期)。 */
197
+ addWarning = (title: string, content: string, options: NotificationShortcutOptions = {}): string =>
198
+ this.add({ ...options, content, title, type: 'warning' });
199
+
200
+ /** 出错了,通常要用户重试或介入(同步失败、任务中断)。 */
201
+ addError = (title: string, content: string, options: NotificationShortcutOptions = {}): string =>
202
+ this.add({ ...options, content, title, type: 'error' });
203
+
204
+ /** 来自其他人的消息(站内信、@ 提醒),区别于上面四种由系统发出的状态通知。 */
205
+ addMessage = (title: string, content: string, options: NotificationShortcutOptions = {}): string =>
206
+ this.add({ ...options, content, title, type: 'message' });
207
+
208
+ /** 用户点开某条通知时调。已经是已读的原样返回,避免白重建一次快照、让整棵订阅树重渲染。 */
209
+ markAsRead = (id: string): void => {
210
+ this.queue.update(id, prev => (prev.read ? prev : { ...prev, read: true }));
211
+ };
212
+
213
+ /** 面板上「全部已读」按钮调。通知本身留着,只是 unreadCount 归零。 */
214
+ markAllAsRead = (): void => {
215
+ this.queue.updateBy(
216
+ item => !item.read,
217
+ item => ({ ...item, read: true })
218
+ );
219
+ };
220
+
221
+ /**
222
+ * 删掉某条通知,用户在面板上单条删除时调。
223
+ *
224
+ * 删掉之后这个 id 就不再参与去重,同 id 的通知再推进来会重新展示一次。
225
+ */
226
+ remove = (id: string): void => {
227
+ this.queue.remove(id);
228
+ };
229
+
230
+ /** 清空面板,已读未读一起删。用户点「清空」时调。 */
231
+ clearAll = (): void => {
232
+ this.queue.clear();
233
+ };
234
+
235
+ /** 只删已读、保留未读。用户想收拾面板又不想漏掉没看过的东西时用。 */
236
+ clearRead = (): void => {
237
+ this.queue.removeBy(item => item.read);
238
+ };
239
+
240
+ // ==================== 配置与权限 ====================
241
+
242
+ /**
243
+ * 更新运行时配置,未传入的字段保持不变。设置面板上改开关走这里。
244
+ *
245
+ * 队列容量跟着 maxNotifications 一起改,调小会立刻挤掉超出的那部分。
246
+ */
247
+ updateConfig = (updates: Partial<NotificationConfig>): void => {
248
+ this.config = { ...this.config, ...updates };
249
+ this.queue.setCapacity(this.config.maxNotifications);
250
+ this.refresh();
251
+ };
252
+
253
+ /**
254
+ * 替换宿主注入的选项。
255
+ *
256
+ * Provider 每次渲染拿到的回调都是新函数,这里整体换掉即可;只有用户操作才会触发这些回调, 那时最近一次渲染的选项早已同步进来。
257
+ */
258
+ setOptions = (options: NotificationStoreOptions): void => {
259
+ this.options = options;
260
+ };
261
+
262
+ /**
263
+ * 从浏览器读一次授权状态。
264
+ *
265
+ * 不放构造函数里:服务端渲染时读不到,客户端 hydrate 时读得到,两边对不上会报 mismatch。 交给 React 在 effect 里调。
266
+ */
267
+ syncPermission = (): void => {
268
+ if (typeof window === 'undefined' || !('Notification' in window)) {
269
+ return;
270
+ }
271
+
272
+ this.setPermission(window.Notification.permission);
273
+ };
274
+
275
+ /**
276
+ * 弹浏览器的授权框,返回用户最终是不是给了权限。
277
+ *
278
+ * 必须由用户手势触发(点按钮),页面一加载就调会被浏览器直接拒掉,而且拒过一次之后 再调也不会重新弹框。
279
+ */
280
+ requestPermission = async (): Promise<boolean> => {
281
+ if (typeof window === 'undefined' || !('Notification' in window)) {
282
+ this.options.onBrowserNotificationUnsupported?.();
283
+ return false;
284
+ }
285
+
286
+ try {
287
+ const permission = await window.Notification.requestPermission();
288
+ this.setPermission(permission);
289
+
290
+ return permission === 'granted';
291
+ } catch (error) {
292
+ this.options.onRequestPermissionError?.(error);
293
+ return false;
294
+ }
295
+ };
296
+
297
+ // ==================== 内部 ====================
298
+
299
+ /**
300
+ * 现算一个全新的快照对象,供 getSnapshot 缓存起来返回。
301
+ *
302
+ * 每次都返回新引用是故意的,useSyncExternalStore 靠引用变化判断要不要重渲染。 unreadCount 也在这里算好,省得每个消费组件各遍历一遍列表。
303
+ */
304
+ private buildSnapshot(): NotificationSnapshot {
305
+ const notifications = [...this.queue.toArray()];
306
+
307
+ return {
308
+ config: this.config,
309
+ notifications,
310
+ permission: this.permission,
311
+ unreadCount: notifications.reduce((count, item) => (item.read ? count : count + 1), 0)
312
+ };
313
+ }
314
+
315
+ /**
316
+ * 当前是不是在免打扰时段里。音效和浏览器通知都要先过这一关。
317
+ *
318
+ * 免打扰只静音,不拦截:通知照样进队列、照样算未读数,用户回来能在面板里看到全部。
319
+ */
320
+ private isDoNotDisturbTime(): boolean {
321
+ if (!this.config.doNotDisturb || !this.config.doNotDisturbTime) {
322
+ return false;
323
+ }
324
+
325
+ const { end, start } = this.config.doNotDisturbTime;
326
+
327
+ return isWithinTimeRange(getCurrentTimeText(), start, end);
328
+ }
329
+
330
+ /**
331
+ * 拿到可用的音频实例,没配 soundUrl 或环境里没有 Audio 就返回 null。
332
+ *
333
+ * 惰性创建:构造时不碰 Audio,服务端渲染才不会炸。宿主换了 soundUrl 会重新建一个。
334
+ */
335
+ private getSound(): HTMLAudioElement | null {
336
+ const { soundUrl } = this.options;
337
+
338
+ if (!soundUrl || typeof Audio === 'undefined') {
339
+ return null;
340
+ }
341
+
342
+ if (!this.sound || this.soundUrl !== soundUrl) {
343
+ this.sound = new Audio(soundUrl);
344
+ this.soundUrl = soundUrl;
345
+ }
346
+
347
+ return this.sound;
348
+ }
349
+
350
+ /**
351
+ * 播一次提示音。新通知进队列时由 add 调。
352
+ *
353
+ * 播放前把 currentTime 归零,上一条还没播完时也能重新触发。 用户还没和页面交互过时浏览器会拒绝 play,所以失败是常态而不是异常,交给宿主决定要不要提示。
354
+ */
355
+ private playSound(): void {
356
+ if (!this.config.soundEnabled || this.isDoNotDisturbTime()) {
357
+ return;
358
+ }
359
+
360
+ const sound = this.getSound();
361
+
362
+ if (!sound) {
363
+ return;
364
+ }
365
+
366
+ try {
367
+ sound.currentTime = 0;
368
+ sound.play().catch(error => {
369
+ this.options.onPlaySoundError?.(error);
370
+ });
371
+ } catch (error) {
372
+ this.options.onPlaySoundError?.(error);
373
+ }
374
+ }
375
+
376
+ /** 重建快照并通知所有订阅方。队列变化会自动走到这里,改配置和权限要手动调。 */
377
+ private refresh = (): void => {
378
+ this.snapshot = this.buildSnapshot();
379
+
380
+ for (const listener of this.listeners) {
381
+ listener();
382
+ }
383
+ };
384
+
385
+ /** 写入授权状态。值没变就早退,否则每次 syncPermission 都会白刷一遍快照。 */
386
+ private setPermission(permission: NotificationPermission): void {
387
+ if (this.permission === permission) return;
388
+
389
+ this.permission = permission;
390
+ this.refresh();
391
+ }
392
+
393
+ /**
394
+ * 弹一条浏览器原生通知(页面切到后台也能看到)。新通知进队列时由 add 调。
395
+ *
396
+ * 开头四道闸门任意一道不过就安静跳过,面板里那条通知不受影响。 点击后聚焦回本页;带 link 的优先交给宿主的 onNavigate 走前端路由,没注入才硬跳。
397
+ */
398
+ private showBrowserNotification(notification: NotificationItem): void {
399
+ if (!this.config.browserNotificationEnabled || this.isDoNotDisturbTime()) {
400
+ return;
401
+ }
402
+
403
+ if (this.permission !== 'granted') {
404
+ return;
405
+ }
406
+
407
+ if (notification.silent || notification.showBrowserNotification === false) {
408
+ return;
409
+ }
410
+
411
+ if (typeof window === 'undefined' || !('Notification' in window)) {
412
+ return;
413
+ }
414
+
415
+ try {
416
+ const browserNotification = new window.Notification(notification.title, {
417
+ body: notification.content,
418
+ icon: notification.icon
419
+ });
420
+
421
+ browserNotification.addEventListener('click', () => {
422
+ window.focus();
423
+
424
+ if (notification.link) {
425
+ if (this.options.onNavigate) {
426
+ this.options.onNavigate(notification.link, notification);
427
+ } else {
428
+ window.location.href = notification.link;
429
+ }
430
+ }
431
+
432
+ browserNotification.close();
433
+ });
434
+ } catch (error) {
435
+ this.options.onBrowserNotificationError?.(error);
436
+ }
437
+ }
438
+ }
@@ -0,0 +1,60 @@
1
+ .admin-notification-button-unread {
2
+ animation: admin-notification-swing 2s ease-in-out infinite;
3
+ animation-delay: 0.5s;
4
+ }
5
+
6
+ @keyframes admin-notification-swing {
7
+ 0%,
8
+ 100% {
9
+ transform: rotate(0deg);
10
+ }
11
+
12
+ 5%,
13
+ 15% {
14
+ transform: rotate(15deg);
15
+ }
16
+
17
+ 10%,
18
+ 20% {
19
+ transform: rotate(-10deg);
20
+ }
21
+
22
+ 25%,
23
+ 35% {
24
+ transform: rotate(8deg);
25
+ }
26
+
27
+ 30%,
28
+ 40% {
29
+ transform: rotate(-6deg);
30
+ }
31
+
32
+ 45% {
33
+ transform: rotate(4deg);
34
+ }
35
+
36
+ 50% {
37
+ transform: rotate(-2deg);
38
+ }
39
+
40
+ 55% {
41
+ transform: rotate(0deg);
42
+ }
43
+ }
44
+
45
+ .admin-notification-panel-scroll::-webkit-scrollbar {
46
+ width: 6px;
47
+ }
48
+
49
+ .admin-notification-panel-scroll::-webkit-scrollbar-track {
50
+ background: transparent;
51
+ }
52
+
53
+ .admin-notification-panel-scroll::-webkit-scrollbar-thumb {
54
+ background: var(--ant-color-border);
55
+ border-radius: 3px;
56
+ }
57
+
58
+ .admin-notification-panel-scroll::-webkit-scrollbar-thumb:hover {
59
+ background: var(--ant-color-border-secondary);
60
+ }
@@ -0,0 +1,104 @@
1
+ import type { NotificationStore } from './notification-store';
2
+
3
+ /** 通知的视觉分类。 */
4
+ export type NotificationType = 'error' | 'info' | 'message' | 'success' | 'warning';
5
+
6
+ /** 通知在面板中展示的优先级。 */
7
+ export type NotificationPriority = 'high' | 'low' | 'normal' | 'urgent';
8
+
9
+ /** 由通知 Provider 管理的通知项。 */
10
+ export interface NotificationItem {
11
+ /** 在通知面板中展示的主要内容。 */
12
+ content: string;
13
+ /** 浏览器原生通知使用的图标地址。 */
14
+ icon?: string;
15
+ /** 通知的稳定唯一标识。 */
16
+ id: string;
17
+ /** 点击浏览器通知后跳转的目标地址。 */
18
+ link?: string;
19
+ /** 由调用方自定义的扩展元数据。 */
20
+ meta?: Record<string, unknown>;
21
+ /** 在面板标签中展示的优先级提示。 */
22
+ priority?: NotificationPriority;
23
+ /** 当前通知是否已读。 */
24
+ read: boolean;
25
+ /** 是否同时创建浏览器原生通知。 */
26
+ showBrowserNotification?: boolean;
27
+ /** 是否跳过通知音效。 */
28
+ silent?: boolean;
29
+ /** 通知创建时间,使用毫秒时间戳。 */
30
+ timestamp: number;
31
+ /** 通知标题。 */
32
+ title: string;
33
+ /** 用于确定图标和颜色的视觉分类。 */
34
+ type: NotificationType;
35
+ }
36
+
37
+ /** 通知运行时配置。 */
38
+ export interface NotificationConfig {
39
+ /** 是否启用浏览器原生通知。 */
40
+ browserNotificationEnabled: boolean;
41
+ /** 是否启用免打扰模式。 */
42
+ doNotDisturb: boolean;
43
+ /** 免打扰模式使用的本地时间范围。 */
44
+ doNotDisturbTime?: {
45
+ /** 结束时间,格式为 HH:mm。 */
46
+ end: string;
47
+ /** 开始时间,格式为 HH:mm。 */
48
+ start: string;
49
+ };
50
+ /** 最多保留的通知数量。 */
51
+ maxNotifications: number;
52
+ /** 是否启用通知音效。 */
53
+ soundEnabled: boolean;
54
+ }
55
+
56
+ /** 创建新通知时使用的输入类型。 */
57
+ export type AddNotificationInput = Omit<NotificationItem, 'id' | 'read' | 'timestamp'> &
58
+ Partial<Pick<NotificationItem, 'id' | 'read' | 'timestamp'>>;
59
+
60
+ /** 各类快捷通知创建函数共用的可选参数。 */
61
+ export type NotificationShortcutOptions = Partial<
62
+ Omit<NotificationItem, 'content' | 'id' | 'read' | 'timestamp' | 'title' | 'type'>
63
+ > &
64
+ Partial<Pick<NotificationItem, 'id' | 'read' | 'timestamp'>>;
65
+
66
+ /** 通知仓库的构造选项,全是宿主应用要注入的东西:音效地址和各种失败回调。 */
67
+ export interface NotificationStoreOptions {
68
+ /** 初始配置,会与包内默认配置合并。仅构造时生效,之后改配置走 updateConfig。 */
69
+ defaultConfig?: Partial<NotificationConfig>;
70
+ /** 浏览器通知无法显示时触发的回调。 */
71
+ onBrowserNotificationError?: (error: unknown) => void;
72
+ /** 当前浏览器不支持 Notification API 时触发的回调。 */
73
+ onBrowserNotificationUnsupported?: () => void;
74
+ /** 点击浏览器通知并需要打开链接时触发的回调。 */
75
+ onNavigate?: (link: string, notification: NotificationItem) => void;
76
+ /** 通知音效无法播放时触发的回调。 */
77
+ onPlaySoundError?: (error: unknown) => void;
78
+ /** 请求浏览器通知权限失败时触发的回调。 */
79
+ onRequestPermissionError?: (error: unknown) => void;
80
+ /** 宿主应用提供的可选通知音效地址。 */
81
+ soundUrl?: string;
82
+ }
83
+
84
+ /** 通知状态 Hook 使用的配置项。 */
85
+ export interface UseNotificationOptions extends NotificationStoreOptions {
86
+ /**
87
+ * 复用外部已有的通知仓库,不传则 Hook 自己建一个。
88
+ *
89
+ * 需要在 React 树之外投递通知(WebSocket、SSE 这类连接回调)时传:宿主自己持有单例,推送 那一侧直接调 store.add,不用再绕 Context 拿 addNotification。
90
+ */
91
+ store?: NotificationStore;
92
+ }
93
+
94
+ /** UseSyncExternalStore 读到的快照,写操作时整体重建一次,读的时候直接返回缓存。 */
95
+ export interface NotificationSnapshot {
96
+ /** 当前生效的运行时配置。 */
97
+ config: NotificationConfig;
98
+ /** 按优先级排好序的通知列表。 */
99
+ notifications: NotificationItem[];
100
+ /** 浏览器原生通知的授权状态。 */
101
+ permission: NotificationPermission;
102
+ /** 未读通知数量。 */
103
+ unreadCount: number;
104
+ }
@@ -0,0 +1,51 @@
1
+ import { useEffect, useRef, useSyncExternalStore } from 'react';
2
+
3
+ import { NotificationStore } from './notification-store';
4
+ import type { UseNotificationOptions } from './types';
5
+
6
+ export { DEFAULT_NOTIFICATION_CONFIG } from './notification-store';
7
+
8
+ /**
9
+ * 订阅通知中心。
10
+ *
11
+ * 状态和副作用全在 NotificationStore 里,这里只做三件事:拿到实例、订阅快照、把方法转出去。 方法都是实例上的箭头属性,引用天然稳定,不用再包 useCallback。
12
+ */
13
+ export function useNotification(options: UseNotificationOptions = {}) {
14
+ const storeRef = useRef<NotificationStore | null>(null);
15
+
16
+ // 传了 store 就用外面那个:推送侧要在 React 树之外投递通知,得和这里读的是同一份
17
+ const store = options.store ?? (storeRef.current ??= new NotificationStore(options));
18
+
19
+ const snapshot = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
20
+
21
+ useEffect(() => {
22
+ store.syncPermission();
23
+ }, [store]);
24
+
25
+ // 不给依赖数组:Provider 每次渲染的回调都是新函数,每次都同步过去最省心。
26
+ // 只赋值不通知,多跑几次没有代价。
27
+ useEffect(() => {
28
+ store.setOptions(options);
29
+ });
30
+
31
+ return {
32
+ addErrorNotification: store.addError,
33
+ addInfoNotification: store.addInfo,
34
+ addMessageNotification: store.addMessage,
35
+ addNotification: store.add,
36
+ addSuccessNotification: store.addSuccess,
37
+ addWarningNotification: store.addWarning,
38
+ clearAllNotifications: store.clearAll,
39
+ clearReadNotifications: store.clearRead,
40
+ config: snapshot.config,
41
+ markAllAsRead: store.markAllAsRead,
42
+ markAsRead: store.markAsRead,
43
+ notificationPermission: snapshot.permission,
44
+ notifications: snapshot.notifications,
45
+ removeNotification: store.remove,
46
+ requestNotificationPermission: store.requestPermission,
47
+ store,
48
+ unreadCount: snapshot.unreadCount,
49
+ updateConfig: store.updateConfig
50
+ };
51
+ }
@@ -0,0 +1,14 @@
1
+ import { useContext } from 'react';
2
+
3
+ import { NotificationContext } from './NotificationContext';
4
+
5
+ /** Read the current notification context. */
6
+ export function useNotificationContext() {
7
+ const context = useContext(NotificationContext);
8
+
9
+ if (!context) {
10
+ throw new Error('useNotificationContext must be used within NotificationProvider');
11
+ }
12
+
13
+ return context;
14
+ }