@qlover/create-app 0.10.1 → 0.10.3

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 (214) hide show
  1. package/CHANGELOG.md +178 -0
  2. package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
  3. package/dist/configs/_common/.github/workflows/release.yml +2 -2
  4. package/dist/index.cjs +4 -4
  5. package/dist/index.js +4 -4
  6. package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
  7. package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
  8. package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
  9. package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
  10. package/dist/templates/next-app/config/common.ts +1 -1
  11. package/dist/templates/next-app/config/cookies.ts +23 -0
  12. package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
  13. package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
  14. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  15. package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
  16. package/dist/templates/next-app/config/theme.ts +4 -0
  17. package/dist/templates/next-app/eslint.config.mjs +9 -2
  18. package/dist/templates/next-app/next.config.ts +1 -0
  19. package/dist/templates/next-app/package.json +17 -6
  20. package/dist/templates/next-app/public/locales/en.json +5 -0
  21. package/dist/templates/next-app/public/locales/zh.json +5 -0
  22. package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
  23. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
  24. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
  26. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  27. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
  28. package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
  29. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
  30. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
  31. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
  32. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
  33. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
  34. package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
  35. package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
  36. package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
  37. package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
  38. package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
  39. package/dist/templates/next-app/src/app/manifest.ts +16 -0
  40. package/dist/templates/next-app/src/app/robots.txt +2 -0
  41. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +3 -3
  42. package/dist/templates/next-app/src/base/cases/AppConfig.ts +14 -13
  43. package/dist/templates/next-app/src/base/cases/Datetime.ts +3 -3
  44. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +8 -2
  45. package/dist/templates/next-app/src/base/cases/DialogHandler.ts +30 -4
  46. package/dist/templates/next-app/src/base/cases/InversifyContainer.ts +16 -4
  47. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +20 -4
  48. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +8 -2
  49. package/dist/templates/next-app/src/base/cases/ResourceState.ts +3 -3
  50. package/dist/templates/next-app/src/base/cases/RouterService.ts +28 -10
  51. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +14 -2
  52. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  53. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +20 -5
  54. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +31 -8
  55. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +4 -1
  56. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
  57. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +27 -7
  58. package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
  59. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +1 -1
  60. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +3 -3
  61. package/dist/templates/next-app/src/base/services/I18nService.ts +20 -10
  62. package/dist/templates/next-app/src/base/services/ResourceService.ts +32 -11
  63. package/dist/templates/next-app/src/base/services/UserService.ts +12 -3
  64. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +9 -6
  65. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +20 -5
  66. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +21 -4
  67. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +12 -3
  68. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +10 -2
  69. package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
  70. package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
  71. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +20 -6
  72. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +27 -9
  73. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +6 -4
  74. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +39 -9
  75. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +9 -2
  76. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +37 -10
  77. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +9 -2
  78. package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
  79. package/dist/templates/next-app/src/i18n/request.ts +3 -22
  80. package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
  81. package/dist/templates/next-app/src/pages/_app.tsx +50 -0
  82. package/dist/templates/next-app/src/pages/_document.tsx +13 -0
  83. package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
  84. package/dist/templates/next-app/src/server/AppErrorApi.ts +1 -1
  85. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +107 -0
  86. package/dist/templates/next-app/src/server/AppSuccessApi.ts +1 -1
  87. package/dist/templates/next-app/src/server/NextApiServer.ts +62 -0
  88. package/dist/templates/next-app/src/server/PagesRouteParams.ts +146 -0
  89. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +8 -2
  90. package/dist/templates/next-app/src/server/ServerAuth.ts +20 -5
  91. package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +50 -8
  92. package/dist/templates/next-app/src/server/UserCredentialToken.ts +8 -2
  93. package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +86 -0
  94. package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +42 -0
  95. package/dist/templates/next-app/src/server/controllers/LocalesController.ts +36 -0
  96. package/dist/templates/next-app/src/server/controllers/UserController.ts +86 -0
  97. package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
  98. package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
  99. package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
  100. package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
  101. package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
  102. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  103. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
  104. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
  105. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
  106. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +31 -9
  107. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +15 -6
  108. package/dist/templates/next-app/src/server/services/AIService.ts +3 -1
  109. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +5 -2
  110. package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
  111. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +29 -14
  112. package/dist/templates/next-app/src/server/services/ApiUserService.ts +6 -3
  113. package/dist/templates/next-app/src/server/services/UserService.ts +12 -3
  114. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +24 -7
  115. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +17 -5
  116. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +17 -11
  117. package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  118. package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  119. package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  120. package/dist/templates/next-app/src/styles/css/index.css +1 -1
  121. package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
  122. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
  123. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
  124. package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
  125. package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
  126. package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
  127. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +7 -1
  128. package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
  129. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
  130. package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
  131. package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
  132. package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
  133. package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
  134. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
  135. package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
  136. package/dist/templates/next-app/tsconfig.json +3 -2
  137. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +3 -1
  138. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfig.ts +19 -19
  139. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +8 -8
  140. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +9 -9
  141. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +1 -1
  142. package/dist/templates/react-app/__tests__/__mocks__/i18nextHttpBackend.ts +5 -5
  143. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +9 -5
  144. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +8 -4
  145. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +4 -4
  146. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +5 -5
  147. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +3 -3
  148. package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +1 -1
  149. package/dist/templates/react-app/docs/en/components/chat-message-component.md +35 -29
  150. package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +18 -5
  151. package/dist/templates/react-app/docs/en/components/message-base-list-component.md +11 -12
  152. package/dist/templates/react-app/docs/en/request.md +1 -3
  153. package/dist/templates/react-app/docs/zh/components/chat-message-component.md +35 -29
  154. package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +18 -5
  155. package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +11 -12
  156. package/dist/templates/react-app/docs/zh/request.md +1 -3
  157. package/dist/templates/react-app/eslint.config.mjs +10 -5
  158. package/dist/templates/react-app/src/App.tsx +1 -1
  159. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +2 -2
  160. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +14 -11
  161. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +10 -16
  162. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -10
  163. package/dist/templates/react-app/src/base/cases/AppConfig.ts +19 -19
  164. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +28 -5
  165. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +5 -2
  166. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +16 -4
  167. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +1 -1
  168. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +6 -3
  169. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +17 -8
  170. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +4 -6
  171. package/dist/templates/react-app/src/base/cases/ResourceState.ts +3 -3
  172. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +3 -3
  173. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +1 -1
  174. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -2
  175. package/dist/templates/react-app/src/base/port/IOCInterface.ts +4 -2
  176. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +5 -5
  177. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +7 -7
  178. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +8 -8
  179. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +4 -2
  180. package/dist/templates/react-app/src/base/services/BaseLayoutService.ts +3 -3
  181. package/dist/templates/react-app/src/base/services/I18nService.ts +24 -13
  182. package/dist/templates/react-app/src/base/services/IdentifierService.ts +21 -26
  183. package/dist/templates/react-app/src/base/services/RouteService.ts +8 -8
  184. package/dist/templates/react-app/src/base/services/UserBootstrap.ts +2 -2
  185. package/dist/templates/react-app/src/base/services/UserGatewayPlugin.ts +9 -5
  186. package/dist/templates/react-app/src/base/services/UserService.ts +10 -4
  187. package/dist/templates/react-app/src/core/bootstraps/BootstrapClient.ts +3 -1
  188. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -2
  189. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +11 -4
  190. package/dist/templates/react-app/src/core/clientIoc/ClientIOCRegister.ts +5 -4
  191. package/dist/templates/react-app/src/pages/base/Layout.tsx +1 -1
  192. package/dist/templates/react-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  193. package/dist/templates/react-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  194. package/dist/templates/react-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  195. package/dist/templates/react-app/src/styles/css/index.css +1 -1
  196. package/dist/templates/react-app/src/styles/css/scrollbar.css +34 -0
  197. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +2 -2
  198. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +4 -4
  199. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +8 -2
  200. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +6 -6
  201. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +1 -1
  202. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +1 -3
  203. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +4 -1
  204. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +39 -16
  205. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +4 -2
  206. package/package.json +3 -3
  207. package/dist/templates/next-app/src/server/PageParams.ts +0 -66
  208. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
  209. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
  210. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
  211. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
  212. package/dist/templates/react-app/makes/eslint-utils.mjs +0 -195
  213. /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
  214. /package/dist/templates/next-app/src/uikit/{components → components-app}/LogoutButton.tsx +0 -0
@@ -2,5 +2,5 @@
2
2
  @import './zIndex.css';
3
3
 
4
4
  @import './antd-themes/index.css';
5
-
5
+ @import './scrollbar.css';
6
6
  @import './page.css';
@@ -0,0 +1,34 @@
1
+ html,
2
+ .fe-theme {
3
+ --scrollbar-track-color: var(--color-secondary);
4
+ --scrollbar-thumb-color: var(--color-text-secondary);
5
+ --scrollbar-thumb-hover-color: var(--color-brand-hover);
6
+
7
+ /* Webkit browsers (Chrome, Safari, Edge) - 全局样式,使用最高优先级 */
8
+ body *::-webkit-scrollbar,
9
+ *::-webkit-scrollbar {
10
+ width: 10px;
11
+ height: 10px;
12
+ }
13
+
14
+ body *::-webkit-scrollbar-track,
15
+ *::-webkit-scrollbar-track {
16
+ background: var(--scrollbar-track-color);
17
+ }
18
+
19
+ body *::-webkit-scrollbar-thumb,
20
+ *::-webkit-scrollbar-thumb {
21
+ background-color: var(--scrollbar-thumb-color);
22
+ border-radius: 6px;
23
+ border: 1px solid var(--scrollbar-track-color);
24
+ transition: background-color 0.2s ease;
25
+ }
26
+
27
+ body *::-webkit-scrollbar-thumb:hover {
28
+ background-color: var(--scrollbar-thumb-hover-color) !important;
29
+ }
30
+
31
+ *::-webkit-scrollbar-thumb:hover {
32
+ background-color: var(--scrollbar-thumb-hover-color) !important;
33
+ }
34
+ }
@@ -13,12 +13,13 @@ import { clsx } from 'clsx';
13
13
  import { usePathname } from 'next/navigation';
14
14
  import React, { useMemo, type HTMLAttributes } from 'react';
15
15
  import { AdminPageManager } from '@/base/cases/AdminPageManager';
16
- import { BaseHeader } from './BaseHeader';
17
- import { LanguageSwitcher } from './LanguageSwitcher';
16
+ import { COMMON_ADMIN_TITLE } from '@config/Identifier';
18
17
  import { LocaleLink } from './LocaleLink';
19
- import { LogoutButton } from './LogoutButton';
20
- import { ThemeSwitcher } from './ThemeSwitcher';
18
+ import { LanguageSwitcher } from '../components-app/LanguageSwitcher';
19
+ import { LogoutButton } from '../components-app/LogoutButton';
20
+ import { ThemeSwitcher } from '../components-app/ThemeSwitcher';
21
21
  import { useIOC } from '../hook/useIOC';
22
+ import { useWarnTranslations } from '../hook/useWarnTranslations';
22
23
  import type { ItemType } from 'antd/es/menu/interface';
23
24
 
24
25
  const { Sider } = Layout;
@@ -41,10 +42,13 @@ export function AdminLayout(props: AdminLayoutProps) {
41
42
  const page = useIOC(AdminPageManager);
42
43
  const collapsedSidebar = useStore(page, page.selectors.collapsedSidebar);
43
44
  const navItems = useStore(page, page.selectors.navItems);
45
+ const t = useWarnTranslations();
46
+
47
+ const title = useMemo(() => t(COMMON_ADMIN_TITLE), [t]);
44
48
 
45
49
  const selectedKey = useMemo(() => {
46
50
  // 移除语言前缀,例如 /en/admin/users -> /admin/users
47
- const normalizedPath = pathname.replace(/^\/[^/]+/, '');
51
+ const normalizedPath = pathname?.replace(/^\/[^/]+/, '') ?? '';
48
52
  return navItems.find((item) => item.pathname === normalizedPath)?.key || '';
49
53
  }, [pathname, navItems]);
50
54
 
@@ -81,7 +85,7 @@ export function AdminLayout(props: AdminLayoutProps) {
81
85
 
82
86
  return (
83
87
  <Layout data-testid="AdminLayout" className={className} {...rest}>
84
- <div className="overflow-y-auto overflow-x-hidden h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
88
+ <div className="overflow-y-auto overflow-x-hidden h-screen sticky top-0 bottom-0">
85
89
  <Sider
86
90
  className="h-full relative"
87
91
  onCollapse={() => page.toggleSidebar()}
@@ -111,11 +115,30 @@ export function AdminLayout(props: AdminLayoutProps) {
111
115
  </div>
112
116
 
113
117
  <Layout>
114
- <BaseHeader
115
- href="/admin"
116
- className="max-w-full pl-0"
117
- rightActions={rightActions}
118
- />
118
+ <header
119
+ data-testid="AdminLayoutHeader"
120
+ className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
121
+ >
122
+ <div
123
+ className={clsx(
124
+ 'flex items-center justify-between h-full px-4 mx-auto max-w-7xl',
125
+ className
126
+ )}
127
+ >
128
+ <div className="flex items-center">
129
+ <LocaleLink
130
+ href="/admin"
131
+ title={title}
132
+ className="flex items-center hover:opacity-80 transition-opacity"
133
+ >
134
+ <span className="text-lg font-semibold text-text">{title}</span>
135
+ </LocaleLink>
136
+ </div>
137
+ <div className="flex items-center gap-2 md:gap-4">
138
+ {rightActions}
139
+ </div>
140
+ </div>
141
+ </header>
119
142
  <main
120
143
  className={clsx('p-2 bg-primary text-text flex-1', mainClassName)}
121
144
  >
@@ -1,45 +1,75 @@
1
1
  'use client';
2
2
  import '@ant-design/v5-patch-for-react-19';
3
- import { useRouter } from 'next/navigation';
4
- import { useLocale } from 'next-intl';
5
- import { useEffect } from 'react';
6
- import { NavigateBridge } from '@/base/cases/NavigateBridge';
7
- import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
3
+
4
+ import { useState } from 'react';
8
5
  import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
9
- import { clientIOC } from '@/core/clientIoc/ClientIOC';
10
- import { I } from '@config/IOCIdentifier';
11
- import { IOCContext } from '../context/IOCContext';
12
- import { useWarnTranslations } from '../hook/useWarnTranslations';
6
+ import { useIOC } from '../hook/useIOC';
7
+ import { useStrictEffect } from '../hook/useStrictEffect';
13
8
 
14
9
  export function BootstrapsProvider(props: { children: React.ReactNode }) {
15
- const IOC = clientIOC.create();
16
- const locale = useLocale();
17
- const router = useRouter();
18
- const t = useWarnTranslations();
19
-
20
- useEffect(() => {
21
- IOC(I.RouterServiceInterface).setLocale(locale);
22
- IOC(NavigateBridge).setUIBridge(router);
23
- }, [locale, router, IOC]);
24
-
25
- useEffect(() => {
26
- IOC(I.I18nServiceInterface).changeLanguage(locale as I18nServiceLocale);
27
- IOC(I.I18nServiceInterface).setTranslator(t);
28
- }, [t, IOC, locale]);
29
-
30
- useEffect(() => {
31
- if (typeof window !== 'undefined') {
32
- BootstrapClient.main({
33
- root: window,
34
- pathname: window.location.pathname,
35
- IOC: IOC
36
- });
37
- }
38
- }, [IOC]);
39
-
40
- return (
41
- <IOCContext.Provider data-testid="BootstrapsProvider" value={IOC}>
42
- {props.children}
43
- </IOCContext.Provider>
44
- );
10
+ const IOC = useIOC();
11
+
12
+ const [, setIocMounted] = useState(false);
13
+
14
+ useStrictEffect(() => {
15
+ // clientIOC.register({
16
+ // appConfig: appConfig
17
+ // });
18
+ BootstrapClient.main({
19
+ root: window,
20
+ pathname: window.location.pathname,
21
+ IOC: IOC
22
+ }).then(() => {
23
+ setIocMounted(true);
24
+ });
25
+ }, []);
26
+
27
+ // useEffect(() => {
28
+ // const register = new ClientIOCRegister({
29
+ // appConfig: appConfig
30
+ // });
31
+
32
+ // // 启动前注册所有依赖
33
+ // // clientIOC.register(register);
34
+
35
+ // BootstrapClient.main({
36
+ // root: window,
37
+ // pathname: window.location.pathname,
38
+ // IOC: IOC,
39
+ // register: register
40
+ // }).then(() => {
41
+ // setIocMounted(true);
42
+ // });
43
+ // }, []);
44
+
45
+ // if (!register) {
46
+ // return <div data-testid="BootstrapsProviderLoading">Loading...</div>;
47
+ // }
48
+
49
+ // const IOC = clientIOC.create();
50
+ // const locale = useLocale();
51
+ // const router = useRouter();
52
+ // const t = useWarnTranslations();
53
+
54
+ // useEffect(() => {
55
+ // IOC(I.RouterServiceInterface).setLocale(locale);
56
+ // IOC(NavigateBridge).setUIBridge(router);
57
+ // }, [locale, router, IOC]);
58
+
59
+ // useEffect(() => {
60
+ // IOC(I.I18nServiceInterface).changeLanguage(locale as I18nServiceLocale);
61
+ // IOC(I.I18nServiceInterface).setTranslator(t);
62
+ // }, [t, IOC, locale]);
63
+
64
+ // useEffect(() => {
65
+ // if (typeof window !== 'undefined') {
66
+ // BootstrapClient.main({
67
+ // root: window,
68
+ // pathname: window.location.pathname,
69
+ // IOC: IOC
70
+ // });
71
+ // }
72
+ // }, [IOC]);
73
+
74
+ return props.children;
45
75
  }
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+ import '@ant-design/v5-patch-for-react-19';
3
+ import { AntdRegistry } from '@ant-design/nextjs-registry';
4
+ import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
5
+ import { ThemeProvider } from 'next-themes';
6
+ import { I } from '@config/IOCIdentifier';
7
+ import type { CommonThemeConfig } from '@config/theme';
8
+ import { useIOC } from '../hook/useIOC';
9
+
10
+ /**
11
+ * ClientRootProvider is a provider for the client components
12
+ *
13
+ * - ThemeProvider
14
+ * - AntdProvider
15
+ *
16
+ * TODO: 存在问题:
17
+ *
18
+ * 1. antd 样式存在闪烁问题, 目前没有解决, 可能是因为 cssinjs 的技术性问题
19
+ *
20
+ * 目前能将完美解决的就是完全使用客户端渲染,也就是引入 useMountedClient 当客户端渲染时才渲染, 这样就不会出现闪烁问题
21
+ * 但是他会导致国际化切换闪烁问题
22
+ *
23
+ * 可能需要等待 antd 官方解决这个问题
24
+ *
25
+ * @example
26
+ *
27
+ * ```tsx
28
+ * const mounted = useMountedClient();
29
+ *
30
+ * return mounted && children;
31
+ * ```
32
+ *
33
+ *
34
+ * @param themeConfig - The theme config
35
+ * @param children - The children components
36
+ * @returns
37
+ */
38
+ export function ClientRootProvider(props: {
39
+ themeConfig: CommonThemeConfig;
40
+ children: React.ReactNode;
41
+ }) {
42
+ const { themeConfig, children } = props;
43
+
44
+ const IOC = useIOC();
45
+
46
+ return (
47
+ <AntdThemeProvider
48
+ data-testid="ComboProvider"
49
+ theme={themeConfig.antdTheme}
50
+ staticApi={IOC(I.DialogHandler)}
51
+ >
52
+ <ThemeProvider
53
+ themes={themeConfig.supportedThemes as unknown as string[]}
54
+ attribute={themeConfig.domAttribute}
55
+ defaultTheme={themeConfig.defaultTheme}
56
+ enableSystem={themeConfig.enableSystem}
57
+ enableColorScheme={false}
58
+ storageKey={themeConfig.storageKey}
59
+ >
60
+ <AntdRegistry>{children}</AntdRegistry>
61
+ </ThemeProvider>
62
+ </AntdThemeProvider>
63
+ );
64
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import { useMountedClient } from '@brain-toolkit/react-kit';
4
+
5
+ export interface ClinetRenderProviderProps {
6
+ children: React.ReactNode;
7
+ }
8
+
9
+ /**
10
+ * ClinetRenderProvider is a provider for the client components
11
+ *
12
+ *
13
+ * 当前组件仅用于需要客户端渲染的组件, 比如 adminLayout 等完全客户端渲染的组件
14
+ *
15
+ *
16
+ * @param children - The children components
17
+ * @returns
18
+ */
19
+ export function ClinetRenderProvider(props: ClinetRenderProviderProps) {
20
+ const { children } = props;
21
+
22
+ const mounted = useMountedClient();
23
+
24
+ return (
25
+ <>
26
+ {children}
27
+
28
+ {/* 为了防止语言切换时页面闪烁, 使用一个固定定位的div, 当客户端渲染时才渲染 */}
29
+ {!mounted && (
30
+ <div
31
+ role="status"
32
+ aria-label="Loading..."
33
+ aria-busy="true"
34
+ style={{
35
+ zIndex: '99999 !important'
36
+ }}
37
+ className="fixed inset-0 overflow-hidden cursor-wait no-scrollbar bg-primary pointer-events-none"
38
+ ></div>
39
+ )}
40
+ </>
41
+ );
42
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { useMemo } from 'react';
4
+ import { appConfig } from '@/core/globals';
5
+ import { clientIOC, IOCContext, IOCInstance } from '../context/IOCContext';
6
+
7
+ export function IOCProvider(props: { children: React.ReactNode }) {
8
+ /**
9
+ * 加载组件就立即注册
10
+ *
11
+ * 这样在渲染子组件时保证 IOC.get 正常工作
12
+ *
13
+ * 但是这样会导致注册时无法传递浏览器端的依赖, 比如 window.location.pathname
14
+ *
15
+ * - 如果有需要,可以将注册放在下面 useStrictEffect 中, 然后 IocMounted=true 时在渲染子节点
16
+ *
17
+ * **但是这样会有一个问题, 组件会重新挂载渲染,当切换语言时会闪烁**
18
+ *
19
+ * 因为页面初始化时有些组件可能已经使用了容器注入,这样就会丢失注册的依赖
20
+ *
21
+ * TODO: 这是一个需要解决的问题
22
+ */
23
+ useMemo(() => {
24
+ clientIOC.register({
25
+ appConfig: appConfig
26
+ });
27
+ }, []);
28
+
29
+ return (
30
+ <IOCContext.Provider value={IOCInstance}>
31
+ {props.children}
32
+ </IOCContext.Provider>
33
+ );
34
+ }
@@ -10,13 +10,19 @@ export class LocalesImportEvent extends StoreInterface<LocalesImportEventState>
10
10
  super(() => new LocalesImportEventState());
11
11
  }
12
12
 
13
+ /**
14
+ * @override
15
+ */
13
16
  protected validate(file: File): void {
14
17
  if (file.type !== 'application/json') {
15
18
  throw new Error('File must be a JSON file');
16
19
  }
17
20
  }
18
21
 
19
- async onImport(type: LocaleType, file: File): Promise<void> {
22
+ /**
23
+ * @override
24
+ */
25
+ public async onImport(type: LocaleType, file: File): Promise<void> {
20
26
  try {
21
27
  this.validate(file);
22
28
 
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+ import { NavigateBridge } from '@/base/cases/NavigateBridge';
5
+ import { useRouter } from '@/i18n/routing';
6
+ import { useIOC } from '../hook/useIOC';
7
+
8
+ export function AppBridge() {
9
+ const router = useRouter();
10
+ const navigateBridge = useIOC(NavigateBridge);
11
+
12
+ useEffect(() => {
13
+ navigateBridge.setUIBridge(router);
14
+ }, [router, navigateBridge]);
15
+
16
+ return null;
17
+ }
@@ -0,0 +1,112 @@
1
+ import { TeamOutlined } from '@ant-design/icons';
2
+ import { clsx } from 'clsx';
3
+ import { useLocale } from 'next-intl';
4
+ import { useMemo, type HTMLAttributes } from 'react';
5
+ import { AppBridge } from './AppBridge';
6
+ import { LanguageSwitcher } from './LanguageSwitcher';
7
+ import { LogoutButton } from './LogoutButton';
8
+ import { ThemeSwitcher } from './ThemeSwitcher';
9
+ import { LocaleLink } from '../components/LocaleLink';
10
+
11
+ export interface AppRoutePageTT {
12
+ title: string;
13
+ adminTitle: string;
14
+ }
15
+
16
+ export interface AppRoutePageProps extends HTMLAttributes<HTMLDivElement> {
17
+ showLogoutButton?: boolean;
18
+ showAdminButton?: boolean;
19
+ mainProps?: HTMLAttributes<HTMLElement>;
20
+ headerClassName?: string;
21
+ headerHref?: string;
22
+ tt: AppRoutePageTT;
23
+ }
24
+
25
+ /**
26
+ * App Route Page
27
+ *
28
+ * 主要用于 /src/app 目录下页面的基础布局,包含头部、主体内容等
29
+ *
30
+ * @description
31
+ * - /src/app/[locale]/page.tsx
32
+ * - /src/app/[locale]/login/page.tsx
33
+ *
34
+ */
35
+ export function AppRoutePage({
36
+ children,
37
+ showLogoutButton,
38
+ showAdminButton,
39
+ mainProps,
40
+ headerClassName,
41
+ tt,
42
+ headerHref = '/',
43
+ ...props
44
+ }: AppRoutePageProps) {
45
+ const locale = useLocale();
46
+ const adminTitle = tt.adminTitle;
47
+
48
+ const actions = useMemo(() => {
49
+ return [
50
+ showAdminButton && (
51
+ <LocaleLink
52
+ key="admin-button"
53
+ href="/admin"
54
+ title={adminTitle}
55
+ locale={locale}
56
+ className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
57
+ >
58
+ <TeamOutlined className="text-lg text-text" />
59
+ </LocaleLink>
60
+ ),
61
+
62
+ <LanguageSwitcher key="language-switcher" />,
63
+
64
+ <ThemeSwitcher key="theme-switcher" />,
65
+
66
+ showLogoutButton && <LogoutButton key="logout-button" />
67
+ ].filter(Boolean);
68
+ }, [adminTitle, showAdminButton, showLogoutButton, locale]);
69
+
70
+ return (
71
+ <div
72
+ data-testid="AppRoutePage"
73
+ className="flex flex-col min-h-screen"
74
+ {...props}
75
+ >
76
+ <AppBridge />
77
+ <header
78
+ data-testid="BaseHeader"
79
+ className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
80
+ >
81
+ <div
82
+ className={clsx(
83
+ 'flex items-center justify-between h-full px-4 mx-auto max-w-7xl',
84
+ headerClassName
85
+ )}
86
+ >
87
+ <div className="flex items-center">
88
+ <LocaleLink
89
+ data-testid="BaseHeaderLogo"
90
+ title={tt.title}
91
+ href={headerHref}
92
+ locale={locale}
93
+ className="flex items-center hover:opacity-80 transition-opacity"
94
+ >
95
+ <span
96
+ data-testid="base-header-app-name"
97
+ className="text-lg font-semibold text-text"
98
+ >
99
+ {tt.title}
100
+ </span>
101
+ </LocaleLink>
102
+ </div>
103
+ <div className="flex items-center gap-2 md:gap-4">{actions}</div>
104
+ </div>
105
+ </header>
106
+
107
+ <main className="flex flex-1 flex-col bg-primary" {...mainProps}>
108
+ {children}
109
+ </main>
110
+ </div>
111
+ );
112
+ }
@@ -2,22 +2,20 @@
2
2
 
3
3
  import { TranslationOutlined } from '@ant-design/icons';
4
4
  import { Dropdown } from 'antd';
5
+ import { useParams } from 'next/navigation';
5
6
  import { useLocale } from 'next-intl';
6
- import { useCallback, useMemo } from 'react';
7
- import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
7
+ import { useCallback, useMemo, useTransition } from 'react';
8
8
  import { usePathname, useRouter } from '@/i18n/routing';
9
9
  import { i18nConfig } from '@config/i18n';
10
10
  import type { LocaleType } from '@config/i18n';
11
- import { I } from '@config/IOCIdentifier';
12
- import { useIOC } from '../hook/useIOC';
13
11
  import type { ItemType } from 'antd/es/menu/interface';
14
12
 
15
13
  export function LanguageSwitcher() {
16
- const i18nService = useIOC(I.I18nServiceInterface);
17
- const pathname = usePathname(); // current pathname, aware of i18n
18
-
19
- const router = useRouter(); // i18n-aware router instance
20
- const currentLocale = useLocale() as LocaleType; // currently active locale
14
+ const pathname = usePathname();
15
+ const router = useRouter();
16
+ const currentLocale = useLocale() as LocaleType;
17
+ const [isPending, startTransition] = useTransition();
18
+ const params = useParams();
21
19
 
22
20
  const options: ItemType[] = useMemo(() => {
23
21
  return i18nConfig.supportedLngs.map(
@@ -34,18 +32,16 @@ export function LanguageSwitcher() {
34
32
 
35
33
  const handleLanguageChange = useCallback(
36
34
  async (value: string) => {
37
- // Set a persistent cookie with the user's preferred locale (valid for 1 year)
38
- document.cookie = `NEXT_LOCALE=${value}; path=/; max-age=31536000; SameSite=Lax`;
39
- // Route to the same page in the selected locale
40
- router.replace(pathname, { locale: value });
35
+ if (isPending) return;
41
36
 
42
- try {
43
- await i18nService.changeLanguage(value as I18nServiceLocale);
44
- } catch (error) {
45
- console.error('Failed to change language:', error);
46
- }
37
+ startTransition(() => {
38
+ // @ts-expect-error -- TypeScript will validate that only known `params`
39
+ // are used in combination with a given `pathname`. Since the two will
40
+ // always match for the current route, we can skip runtime checks.
41
+ router.replace({ pathname, params }, { locale: value });
42
+ });
47
43
  },
48
- [i18nService, pathname, router]
44
+ [pathname, router, isPending, params]
49
45
  );
50
46
 
51
47
  const nextLocale = useMemo(() => {