@qlover/create-app 0.7.15 → 0.8.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 (340) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/configs/_common/.gitignore.template +6 -0
  3. package/dist/configs/_common/.prettierignore +17 -5
  4. package/dist/configs/_common/.vscode/settings.json +6 -1
  5. package/dist/index.cjs +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/templates/next-app/.env.template +1 -1
  8. package/dist/templates/next-app/README.en.md +0 -1
  9. package/dist/templates/next-app/README.md +0 -1
  10. package/dist/templates/next-app/config/Identifier/api.ts +5 -5
  11. package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
  12. package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
  13. package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
  14. package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
  15. package/dist/templates/next-app/config/Identifier/index.ts +2 -12
  16. package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
  17. package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
  18. package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
  19. package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
  20. package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
  21. package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
  22. package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
  23. package/dist/templates/next-app/config/adminNavs.ts +19 -0
  24. package/dist/templates/next-app/config/common.ts +22 -13
  25. package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
  26. package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
  27. package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
  28. package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
  29. package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
  30. package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
  31. package/dist/templates/next-app/docs/en/index.md +0 -1
  32. package/dist/templates/next-app/docs/en/project-structure.md +0 -1
  33. package/dist/templates/next-app/docs/zh/index.md +0 -1
  34. package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
  35. package/dist/templates/next-app/make/generateLocales.ts +19 -12
  36. package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
  37. package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
  38. package/dist/templates/next-app/package.json +7 -3
  39. package/dist/templates/next-app/public/locales/en.json +172 -207
  40. package/dist/templates/next-app/public/locales/zh.json +172 -207
  41. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
  42. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
  43. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
  44. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
  45. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
  46. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
  47. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
  48. package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
  49. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
  50. package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
  51. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
  52. package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
  53. package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
  54. package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
  55. package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
  56. package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
  57. package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
  58. package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
  59. package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
  60. package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
  61. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
  62. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
  63. package/dist/templates/next-app/src/i18n/request.ts +30 -1
  64. package/dist/templates/next-app/src/server/PageParams.ts +2 -10
  65. package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
  66. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
  67. package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
  68. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
  69. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
  70. package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
  71. package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
  72. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
  73. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
  74. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
  75. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
  76. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
  77. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
  78. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
  79. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
  80. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
  81. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
  82. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
  83. package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
  84. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
  85. package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
  86. package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
  87. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
  88. package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
  89. package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
  90. package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
  91. package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
  92. package/dist/templates/react-app/.prettierignore +17 -0
  93. package/dist/templates/react-app/README.en.md +71 -54
  94. package/dist/templates/react-app/README.md +35 -18
  95. package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
  96. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
  97. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
  98. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
  99. package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
  100. package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
  101. package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
  102. package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
  103. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
  104. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
  105. package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
  106. package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
  107. package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
  108. package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
  109. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
  110. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
  111. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
  112. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
  113. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
  114. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
  115. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
  116. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
  117. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
  118. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
  119. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
  120. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
  121. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
  122. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
  123. package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
  124. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
  125. package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
  126. package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
  127. package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
  128. package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
  129. package/dist/templates/react-app/config/Identifier/index.ts +1 -9
  130. package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
  131. package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
  132. package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
  133. package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
  134. package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
  135. package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
  136. package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
  137. package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
  138. package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
  139. package/dist/templates/react-app/config/app.router.ts +66 -69
  140. package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
  141. package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
  142. package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
  143. package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
  144. package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
  145. package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
  146. package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
  147. package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
  148. package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
  149. package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
  150. package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
  151. package/dist/templates/react-app/config/theme.ts +14 -4
  152. package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
  153. package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
  154. package/dist/templates/react-app/docs/en/env.md +1132 -278
  155. package/dist/templates/react-app/docs/en/i18n.md +858 -147
  156. package/dist/templates/react-app/docs/en/index.md +733 -104
  157. package/dist/templates/react-app/docs/en/ioc.md +1228 -287
  158. package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
  159. package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
  160. package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
  161. package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
  162. package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
  163. package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
  164. package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
  165. package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
  166. package/dist/templates/react-app/docs/en/store.md +1194 -184
  167. package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
  168. package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
  169. package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
  170. package/dist/templates/react-app/docs/zh/env.md +1132 -275
  171. package/dist/templates/react-app/docs/zh/i18n.md +858 -147
  172. package/dist/templates/react-app/docs/zh/index.md +717 -104
  173. package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
  174. package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
  175. package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
  176. package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
  177. package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
  178. package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
  179. package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
  180. package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
  181. package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
  182. package/dist/templates/react-app/docs/zh/store.md +1192 -184
  183. package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
  184. package/dist/templates/react-app/e2e/App.spec.ts +319 -0
  185. package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
  186. package/dist/templates/react-app/e2e/main.spec.ts +20 -0
  187. package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
  188. package/dist/templates/react-app/eslint.config.mjs +247 -0
  189. package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
  190. package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
  191. package/dist/templates/react-app/package.json +31 -3
  192. package/dist/templates/react-app/playwright.config.ts +79 -0
  193. package/dist/templates/react-app/public/locales/en/common.json +190 -179
  194. package/dist/templates/react-app/public/locales/zh/common.json +190 -179
  195. package/dist/templates/react-app/src/App.tsx +15 -42
  196. package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
  197. package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
  198. package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
  199. package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
  200. package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
  201. package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
  202. package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
  203. package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
  204. package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
  205. package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
  206. package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
  207. package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
  208. package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
  209. package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
  210. package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
  211. package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
  212. package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
  213. package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
  214. package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
  215. package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
  216. package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
  217. package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
  218. package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
  219. package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
  220. package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
  221. package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
  222. package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
  223. package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
  224. package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
  225. package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
  226. package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
  227. package/dist/templates/react-app/src/base/types/Page.ts +14 -2
  228. package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
  229. package/dist/templates/react-app/src/core/IOC.ts +5 -46
  230. package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
  231. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
  232. package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
  233. package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  234. package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
  235. package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
  236. package/dist/templates/react-app/src/core/globals.ts +9 -9
  237. package/dist/templates/react-app/src/main.tsx +4 -4
  238. package/dist/templates/react-app/src/pages/404.tsx +6 -3
  239. package/dist/templates/react-app/src/pages/500.tsx +5 -2
  240. package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
  241. package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
  242. package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
  243. package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
  244. package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
  245. package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
  246. package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
  247. package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
  248. package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
  249. package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
  250. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
  251. package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
  252. package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
  253. package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
  254. package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
  255. package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
  256. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
  257. package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  258. package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  259. package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
  260. package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  261. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
  262. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
  263. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  264. package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
  265. package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
  266. package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
  267. package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
  268. package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
  269. package/dist/templates/react-app/src/styles/css/page.css +4 -3
  270. package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
  271. package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
  272. package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
  273. package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
  274. package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
  275. package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
  276. package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
  277. package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
  278. package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
  279. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
  280. package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
  281. package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
  282. package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
  283. package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
  284. package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
  285. package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
  286. package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
  287. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
  288. package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
  289. package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
  290. package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
  291. package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
  292. package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
  293. package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
  294. package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
  295. package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
  296. package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
  297. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
  298. package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
  299. package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
  300. package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
  301. package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
  302. package/dist/templates/react-app/tsconfig.e2e.json +21 -0
  303. package/dist/templates/react-app/tsconfig.json +8 -1
  304. package/dist/templates/react-app/tsconfig.node.json +1 -1
  305. package/dist/templates/react-app/tsconfig.test.json +3 -1
  306. package/dist/templates/react-app/vite.config.ts +50 -34
  307. package/package.json +2 -1
  308. package/dist/configs/react-app/eslint.config.js +0 -94
  309. package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
  310. package/dist/templates/next-app/config/Identifier/common.ts +0 -69
  311. package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
  312. package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
  313. package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
  314. package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
  315. package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
  316. package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
  317. package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
  318. package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
  319. package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
  320. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
  321. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
  322. package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
  323. package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
  324. package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
  325. package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
  326. package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
  327. package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
  328. package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
  329. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
  330. package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
  331. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
  332. package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
  333. package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
  334. package/dist/templates/react-app/config/i18n.ts +0 -15
  335. package/dist/templates/react-app/docs/en/project-structure.md +0 -434
  336. package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
  337. package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
  338. package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
  339. package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
  340. package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
@@ -0,0 +1,797 @@
1
+ # 为什么要禁止直接使用浏览器全局变量?
2
+
3
+ ## 📋 目录
4
+
5
+ - [核心理念](#-核心理念)
6
+ - [禁用的全局变量](#-禁用的全局变量)
7
+ - [允许使用的位置](#-允许使用的位置)
8
+ - [为什么要这样做](#-为什么要这样做)
9
+ - [实际应用场景](#-实际应用场景)
10
+ - [最佳实践](#-最佳实践)
11
+ - [常见问题](#-常见问题)
12
+
13
+ ---
14
+
15
+ ## 🎯 核心理念
16
+
17
+ 在我们的项目中,禁止在业务代码中直接使用浏览器全局变量(如 `window`、`document`、`localStorage` 等),而是要求**通过应用入口或封装层传入**。
18
+
19
+ ### 简单来说:
20
+
21
+ ```typescript
22
+ // ❌ 不允许:在业务组件中直接使用
23
+ function MyComponent() {
24
+ const width = window.innerWidth; // ESLint 错误!
25
+ return <div>宽度:{width}</div>;
26
+ }
27
+
28
+ // ✅ 推荐:从封装层导入
29
+ import { localStorage } from '@/core/globals';
30
+
31
+ function MyComponent() {
32
+ const token = localStorage.getItem('token'); // 正确!
33
+ return <div>Token: {token}</div>;
34
+ }
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 🚫 禁用的全局变量
40
+
41
+ 以下全局变量在 `src/**/*.{ts,tsx,js,jsx}` 中被禁止直接使用:
42
+
43
+ - `window` - 浏览器窗口对象
44
+ - `document` - DOM 文档对象
45
+ - `localStorage` - 本地存储
46
+ - `sessionStorage` - 会话存储
47
+ - `navigator` - 浏览器信息
48
+ - `location` - URL 信息
49
+ - `history` - 浏览器历史
50
+
51
+ ---
52
+
53
+ ## ✅ 允许使用的位置
54
+
55
+ ### 1. **应用入口** (`src/main.tsx`)
56
+
57
+ 这是唯一允许直接访问浏览器环境的地方,因为它是应用的启动点:
58
+
59
+ ```typescript:1:19:src/main.tsx
60
+ // !only this file use `window`, `document` ...global variables
61
+ import 'reflect-metadata';
62
+ import { StrictMode } from 'react';
63
+ import { createRoot } from 'react-dom/client';
64
+ import App from './App.tsx';
65
+ import { BootstrapClient } from './core/bootstraps/BootstrapClient';
66
+ import { clientIOC } from './core/clientIoc/ClientIOC.ts';
67
+
68
+ BootstrapClient.main({
69
+ root: window, // ✅ 直接使用 window
70
+ bootHref: window.location.href, // ✅ 直接使用 location
71
+ ioc: clientIOC
72
+ });
73
+
74
+ createRoot(document.getElementById('root')!).render( // ✅ 直接使用 document
75
+ <StrictMode>
76
+ <App />
77
+ </StrictMode>
78
+ );
79
+ ```
80
+
81
+ **为什么?** 因为 `main.tsx` 负责将浏览器环境注入到应用中,它是"依赖注入"的起点。
82
+
83
+ ### 2. **全局变量封装层** (`src/core/globals.ts`)
84
+
85
+ 这是统一封装和管理全局变量的地方:
86
+
87
+ ```typescript:38:48:src/core/globals.ts
88
+ /**
89
+ * Override localStorage to use the global local storage
90
+ */
91
+ export const localStorage = new SyncStorage(new ObjectStorage(), [
92
+ JSON,
93
+ new Base64Serializer(),
94
+ window.localStorage as unknown as SyncStorageInterface<string> // ✅ 封装 localStorage
95
+ ]);
96
+
97
+ export const localStorageEncrypt = localStorage;
98
+
99
+ export const cookieStorage = new CookieStorage();
100
+ ```
101
+
102
+ **为什么?** 这里是封装层,负责将原始的浏览器 API 包装成统一的、类型安全的接口。
103
+
104
+ ### 3. **特殊的基础设施层**
105
+
106
+ 某些基础设施代码(如 IOC 容器初始化)可能需要访问全局变量,但应该:
107
+
108
+ #### ⚠️ 情况 A:不推荐但可接受
109
+
110
+ 在 `ClientIOC.ts` 中直接使用:
111
+
112
+ ```typescript:28:30:src/core/clientIoc/ClientIOC.ts
113
+ const register = new ClientIOCRegister({
114
+ pathname: window.location.pathname, // ⚠️ 特殊情况,可以使用
115
+ appConfig: appConfig
116
+ });
117
+ ```
118
+
119
+ **说明:** IOC 容器初始化时需要 `pathname`,这是可以接受的,但不是最佳实践。
120
+
121
+ #### ✅ 情况 B:更好的做法(推荐)
122
+
123
+ 通过 `main.tsx` 传入:
124
+
125
+ ```typescript
126
+ // main.tsx
127
+ BootstrapClient.main({
128
+ root: window,
129
+ bootHref: window.location.href, // 在入口获取
130
+ pathname: window.location.pathname, // 通过参数传入
131
+ ioc: clientIOC
132
+ });
133
+
134
+ // ClientIOC.ts
135
+ create(pathname: string) { // 接收参数而不是直接访问
136
+ const register = new ClientIOCRegister({
137
+ pathname: pathname, // ✅ 使用传入的参数
138
+ appConfig: appConfig
139
+ });
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 🤔 为什么要这样做?
146
+
147
+ ### 1. **测试友好** 🧪
148
+
149
+ 直接使用全局变量会让测试变得**极其困难甚至不可能**。
150
+
151
+ #### ❌ 问题示例:难以测试的组件
152
+
153
+ ```typescript
154
+ // UserProfile.tsx - 直接使用全局变量
155
+ function UserProfile() {
156
+ const [user, setUser] = useState(null);
157
+
158
+ useEffect(() => {
159
+ // 直接使用 fetch
160
+ fetch('/api/user')
161
+ .then(res => res.json())
162
+ .then(data => {
163
+ // 直接使用 localStorage
164
+ localStorage.setItem('lastUser', data.id);
165
+ setUser(data);
166
+ });
167
+ }, []);
168
+
169
+ return <div>{user?.name}</div>;
170
+ }
171
+
172
+ // ❌ 测试代码 - 几乎无法测试
173
+ describe('UserProfile', () => {
174
+ it('should load and display user', async () => {
175
+ // 问题 1:如何 mock fetch?需要 polyfill 或全局 mock
176
+ global.fetch = jest.fn();
177
+
178
+ // 问题 2:如何 mock localStorage?需要手动实现
179
+ const mockLocalStorage = {
180
+ setItem: jest.fn()
181
+ };
182
+ global.localStorage = mockLocalStorage as any;
183
+
184
+ // 问题 3:需要清理全局状态,否则影响其他测试
185
+ // 问题 4:多个测试之间可能互相干扰
186
+
187
+ render(<UserProfile />);
188
+ // 难以验证行为...
189
+ });
190
+ });
191
+ ```
192
+
193
+ **问题:**
194
+
195
+ - 😰 需要 mock 全局变量(fetch、localStorage)
196
+ - 😰 测试之间可能互相干扰
197
+ - 😰 难以测试错误场景
198
+ - 😰 测试代码充满技巧和 hack
199
+ - 😰 在 Node.js 环境中可能根本无法运行
200
+
201
+ #### ✅ 解决方案 1:从封装层导入
202
+
203
+ ```typescript
204
+ // UserProfile.tsx - 从封装层导入
205
+ import { localStorage } from '@/core/globals';
206
+
207
+ function getUser() {
208
+ return fetch('/api/user').then((res) => res.json());
209
+ }
210
+
211
+ // ✅ 测试代码 - 更容易测试
212
+ jest.mock('@/core/globals', () => ({
213
+ localStorage: {
214
+ setItem: jest.fn()
215
+ }
216
+ }));
217
+
218
+ describe('UserProfile', () => {
219
+ it('should save user to localStorage', () => {
220
+ // 相对容易 mock,但仍需处理 fetch
221
+ });
222
+ });
223
+ ```
224
+
225
+ #### ⭐ 解决方案 2:使用 IOC 容器(最佳)
226
+
227
+ ```typescript
228
+ // UserProfile.tsx - 使用 IOC 容器
229
+ import { useIoc } from '@/uikit/hooks/useIoc';
230
+
231
+ function UserProfile() {
232
+ const userService = useIoc('UserService');
233
+ const [user, setUser] = useState(null);
234
+
235
+ useEffect(() => {
236
+ userService.getCurrentUser().then(setUser);
237
+ }, []);
238
+
239
+ return <div>{user?.name}</div>;
240
+ }
241
+
242
+ // ✅✅ 测试代码 - 非常容易!
243
+ import { render, screen, waitFor } from '@testing-library/react';
244
+ import { IocProvider } from '@/contexts/IocContext';
245
+
246
+ describe('UserProfile', () => {
247
+ it('should load and display user', async () => {
248
+ // ✅ 只需要 mock 服务,不需要 mock 全局变量
249
+ const mockUserService = {
250
+ getCurrentUser: jest.fn().mockResolvedValue({
251
+ id: '1',
252
+ name: 'John Doe'
253
+ })
254
+ };
255
+
256
+ const mockIoc = (serviceName: string) => {
257
+ if (serviceName === 'UserService') return mockUserService;
258
+ };
259
+
260
+ render(
261
+ <IocProvider value={mockIoc}>
262
+ <UserProfile />
263
+ </IocProvider>
264
+ );
265
+
266
+ // ✅ 清晰的断言
267
+ await waitFor(() => {
268
+ expect(screen.getByText('John Doe')).toBeInTheDocument();
269
+ });
270
+
271
+ // ✅ 验证服务调用
272
+ expect(mockUserService.getCurrentUser).toHaveBeenCalledTimes(1);
273
+ });
274
+
275
+ it('should handle error', async () => {
276
+ // ✅ 轻松测试错误场景
277
+ const mockUserService = {
278
+ getCurrentUser: jest.fn().mockRejectedValue(new Error('Network error'))
279
+ };
280
+
281
+ // 测试错误处理...
282
+ });
283
+
284
+ it('should handle loading state', () => {
285
+ // ✅ 轻松测试加载状态
286
+ const mockUserService = {
287
+ getCurrentUser: jest.fn().mockReturnValue(new Promise(() => {})) // 永不 resolve
288
+ };
289
+
290
+ // 测试加载中状态...
291
+ });
292
+ });
293
+ ```
294
+
295
+ #### 对比总结
296
+
297
+ | 测试场景 | 直接使用全局变量 | 使用封装层 | 使用 IOC 容器 |
298
+ | --------------- | ---------------- | ---------- | ------------- |
299
+ | Mock 复杂度 | 😰😰😰 很难 | 😐 中等 | 😊😊😊 简单 |
300
+ | 测试隔离性 | ❌ 差 | ⚠️ 一般 | ✅ 好 |
301
+ | 测试错误场景 | ❌ 困难 | ⚠️ 可以 | ✅ 容易 |
302
+ | 测试代码可读性 | ❌ 差 | ⚠️ 一般 | ✅ 好 |
303
+ | 在 Node.js 运行 | ❌ 困难 | ✅ 可以 | ✅ 可以 |
304
+
305
+ **关键优势:**
306
+
307
+ - ✅ **Mock 简单**:只需 mock 一个服务对象,不需要 mock 全局环境
308
+ - ✅ **测试隔离**:每个测试有独立的 mock,互不干扰
309
+ - ✅ **易测错误**:轻松模拟各种错误场景(网络错误、超时、权限错误等)
310
+ - ✅ **快速运行**:不需要真实的浏览器环境,测试跑得更快
311
+ - ✅ **代码清晰**:测试代码简单直观,易于维护
312
+
313
+ ### 2. **SSR/多环境兼容** 🌐
314
+
315
+ 如果你的应用需要支持服务端渲染(如 Next.js),直接使用全局变量会导致错误:
316
+
317
+ ```typescript
318
+ // ❌ SSR 时会报错
319
+ function MyComponent() {
320
+ const width = window.innerWidth; // ReferenceError: window is not defined
321
+ return <div>{width}</div>;
322
+ }
323
+
324
+ // ✅ 安全的方式
325
+ import { getWindow } from '@/core/globals';
326
+
327
+ function MyComponent() {
328
+ const win = getWindow(); // 封装层可以处理 SSR 情况
329
+ const width = win ? win.innerWidth : 0;
330
+ return <div>{width}</div>;
331
+ }
332
+ ```
333
+
334
+ ### 3. **类型安全和错误处理** 🛡️
335
+
336
+ 封装层可以提供更好的类型和错误处理:
337
+
338
+ ```typescript
339
+ // src/core/globals.ts
340
+ export const localStorage = new SyncStorage(/* ... */); // 有完整的类型定义
341
+
342
+ // 业务代码
343
+ import { localStorage } from '@/core/globals';
344
+
345
+ // ✅ 有完整的类型提示和错误处理
346
+ localStorage.setItem('key', value); // TypeScript 会检查类型
347
+ ```
348
+
349
+ ### 4. **代码可追踪性** 🔍
350
+
351
+ 通过 ESLint 规则,我们可以:
352
+
353
+ - **一眼看出** 哪些代码依赖浏览器环境
354
+ - **轻松查找** 所有使用浏览器 API 的地方(搜索 `from '@/core/globals'`)
355
+ - **方便重构** 统一修改所有浏览器 API 调用
356
+
357
+ ```typescript
358
+ // 想知道哪里用了 localStorage?
359
+ // 搜索:import { localStorage } from '@/core/globals'
360
+ // 而不是在整个项目中搜索 "localStorage"(会有很多误报)
361
+ ```
362
+
363
+ ### 5. **统一的降级和 Polyfill** 🔄
364
+
365
+ 在封装层可以统一处理兼容性和降级:
366
+
367
+ ```typescript
368
+ // src/core/globals.ts
369
+ export const localStorage = (() => {
370
+ try {
371
+ const storage = window.localStorage;
372
+ // 测试是否可用
373
+ storage.setItem('__test__', '1');
374
+ storage.removeItem('__test__');
375
+ return storage;
376
+ } catch {
377
+ // 降级到内存存储(如隐私模式)
378
+ console.warn('localStorage 不可用,使用内存存储');
379
+ return new MemoryStorage();
380
+ }
381
+ })();
382
+ ```
383
+
384
+ ### 6. **防止意外耦合** 🚫
385
+
386
+ 强制开发者思考:
387
+
388
+ - 这段代码真的需要依赖浏览器环境吗?
389
+ - 可以写成纯函数吗?
390
+ - 是否可以通过参数传入而不是直接访问?
391
+
392
+ ```typescript
393
+ // ❌ 紧密耦合浏览器环境
394
+ function isDesktop() {
395
+ return window.innerWidth > 768;
396
+ }
397
+
398
+ // ✅ 解耦:通过参数传入
399
+ function isDesktop(width: number) {
400
+ return width > 768;
401
+ }
402
+
403
+ // 在调用处传入
404
+ const desktop = isDesktop(window.innerWidth);
405
+ ```
406
+
407
+ ---
408
+
409
+ ## 💡 实际应用场景
410
+
411
+ ### 场景 1:需要操作 localStorage
412
+
413
+ ```typescript
414
+ // ❌ 错误做法:直接使用浏览器 API
415
+ function saveToken(token: string) {
416
+ localStorage.setItem('token', token); // ESLint 错误!
417
+ }
418
+
419
+ // ✅ 正确做法 1:从 globals 导入封装的 storage
420
+ import { localStorage } from '@/core/globals';
421
+
422
+ function saveToken(token: string) {
423
+ localStorage.setItem('token', token); // 使用封装的 localStorage
424
+ }
425
+
426
+ // ✅ 正确做法 2:通过 IOC 容器获取服务(推荐)
427
+ import { useIoc } from '@/uikit/hooks/useIoc';
428
+
429
+ function useAuth() {
430
+ const authService = useIoc('AuthService'); // 从 IOC 容器获取服务
431
+
432
+ const saveToken = (token: string) => {
433
+ authService.setToken(token); // 服务内部已封装 storage 操作
434
+ };
435
+
436
+ return { saveToken };
437
+ }
438
+
439
+ // 在组件中使用
440
+ function LoginComponent() {
441
+ const { saveToken } = useAuth();
442
+
443
+ const handleLogin = async () => {
444
+ const token = await login();
445
+ saveToken(token); // 不需要关心底层是用 localStorage 还是其他存储
446
+ };
447
+ }
448
+ ```
449
+
450
+ **为什么 IOC 方式更好?**
451
+
452
+ - 服务层已经封装了所有存储逻辑
453
+ - 业务代码不需要关心存储实现细节
454
+ - 易于切换存储方式(localStorage → IndexedDB → 服务器)
455
+ - 服务可以包含更多业务逻辑(加密、验证、过期处理等)
456
+
457
+ ### 场景 2:需要获取当前路径
458
+
459
+ ```typescript
460
+ // ❌ 错误做法:在组件中直接访问
461
+ function MyComponent() {
462
+ const path = window.location.pathname; // ESLint 错误!
463
+ // ...
464
+ }
465
+
466
+ // ✅ 正确做法 1:使用 React Router
467
+ import { useLocation } from 'react-router-dom';
468
+
469
+ function MyComponent() {
470
+ const location = useLocation();
471
+ const path = location.pathname; // 通过 Router 提供的 hook
472
+ // ...
473
+ }
474
+
475
+ // ✅ 正确做法 2:通过 IOC 容器获取路由服务
476
+ import { useIoc } from '@/uikit/hooks/useIoc';
477
+
478
+ function MyComponent() {
479
+ const routerService = useIoc('RouterService'); // 从 IOC 获取路由服务
480
+ const path = routerService.getCurrentPath(); // 通过服务获取路径
481
+
482
+ // 路由服务还可以提供更多功能
483
+ const navigate = (path: string) => {
484
+ routerService.navigate(path); // 统一的路由跳转
485
+ };
486
+ }
487
+ ```
488
+
489
+ ### 场景 3:需要发起 HTTP 请求
490
+
491
+ ```typescript
492
+ // ❌ 错误做法:直接使用 fetch
493
+ async function getUserInfo(id: string) {
494
+ const response = await fetch(`/api/users/${id}`); // 直接使用全局 fetch
495
+ return response.json();
496
+ }
497
+
498
+ // ✅ 正确做法:通过 IOC 容器获取 HTTP 服务
499
+ import { useIoc } from '@/uikit/hooks/useIoc';
500
+
501
+ function useUserService() {
502
+ const httpService = useIoc('HttpService'); // 从 IOC 获取 HTTP 服务
503
+
504
+ const getUserInfo = async (id: string) => {
505
+ // HTTP 服务已经封装了:
506
+ // - 统一的错误处理
507
+ // - 请求拦截器(添加 token)
508
+ // - 响应拦截器(处理错误码)
509
+ // - 请求取消
510
+ // - 超时控制
511
+ return httpService.get(`/users/${id}`);
512
+ };
513
+
514
+ return { getUserInfo };
515
+ }
516
+
517
+ // 在组件中使用
518
+ function UserProfile({ userId }: { userId: string }) {
519
+ const { getUserInfo } = useUserService();
520
+ const [user, setUser] = useState(null);
521
+
522
+ useEffect(() => {
523
+ getUserInfo(userId).then(setUser);
524
+ }, [userId]);
525
+
526
+ return <div>{user?.name}</div>;
527
+ }
528
+ ```
529
+
530
+ ### 场景 4:需要国际化翻译
531
+
532
+ ```typescript
533
+ // ❌ 错误做法:直接依赖全局 i18n 实例
534
+ import i18n from 'i18next';
535
+
536
+ function MyComponent() {
537
+ const text = i18n.t('common.welcome'); // 直接依赖全局实例
538
+ return <div>{text}</div>;
539
+ }
540
+
541
+ // ✅ 正确做法:通过 IOC 容器获取 I18n 服务
542
+ import { useIoc } from '@/uikit/hooks/useIoc';
543
+
544
+ function MyComponent() {
545
+ const i18nService = useIoc('I18nService'); // 从 IOC 获取服务
546
+ const text = i18nService.t('common.welcome'); // 通过服务翻译
547
+
548
+ // I18n 服务还提供更多功能
549
+ const changeLanguage = (lang: string) => {
550
+ i18nService.changeLanguage(lang);
551
+ };
552
+
553
+ return (
554
+ <div>
555
+ {text}
556
+ <button onClick={() => changeLanguage('en')}>English</button>
557
+ </div>
558
+ );
559
+ }
560
+ ```
561
+
562
+ ### 场景 5:需要获取窗口宽度
563
+
564
+ ```typescript
565
+ // ❌ 错误做法
566
+ function useWindowSize() {
567
+ const [size, setSize] = useState(window.innerWidth); // ESLint 错误!
568
+ // ...
569
+ }
570
+
571
+ // ✅ 正确做法 1:从 globals 导入
572
+ import { window } from '@/core/globals';
573
+
574
+ function useWindowSize() {
575
+ const [size, setSize] = useState(window?.innerWidth || 0);
576
+ // ...
577
+ }
578
+
579
+ // ✅ 正确做法 2:通过 IOC 容器获取 Window 服务(最佳)
580
+ import { useIoc } from '@/uikit/hooks/useIoc';
581
+
582
+ function useWindowSize() {
583
+ const windowService = useIoc('WindowService');
584
+ const [size, setSize] = useState(windowService.getWidth());
585
+
586
+ useEffect(() => {
587
+ const unsubscribe = windowService.onResize((newSize) => {
588
+ setSize(newSize.width);
589
+ });
590
+
591
+ return unsubscribe; // 服务内部管理事件监听器
592
+ }, []);
593
+
594
+ return size;
595
+ }
596
+ ```
597
+
598
+ ---
599
+
600
+ ## 📖 最佳实践
601
+
602
+ ### 1. **优先使用 IOC 容器获取服务(推荐)** ⭐
603
+
604
+ ```typescript
605
+ // ✅ 最佳实践:通过 IOC 容器获取服务
606
+ import { useIoc } from '@/uikit/hooks/useIoc';
607
+
608
+ function MyComponent() {
609
+ const authService = useIoc('AuthService');
610
+ const i18nService = useIoc('I18nService');
611
+ const httpService = useIoc('HttpService');
612
+
613
+ // 业务逻辑...
614
+ }
615
+ ```
616
+
617
+ **为什么?**
618
+
619
+ - 服务已经封装了所有底层依赖(包括全局变量)
620
+ - 易于测试(可以 mock 整个服务)
621
+ - 业务代码不需要关心实现细节
622
+ - 统一的依赖管理
623
+
624
+ ### 2. **在应用入口注入依赖**
625
+
626
+ ```typescript
627
+ // main.tsx
628
+ BootstrapClient.main({
629
+ root: window,
630
+ bootHref: window.location.href,
631
+ ioc: clientIOC,
632
+ // 其他需要的浏览器信息
633
+ initialWindowSize: {
634
+ width: window.innerWidth,
635
+ height: window.innerHeight
636
+ }
637
+ });
638
+ ```
639
+
640
+ ### 3. **优先使用 React 生态的解决方案**
641
+
642
+ - 使用 `react-router-dom` 而不是直接访问 `location`
643
+ - 使用 CSS 媒体查询或 `useMediaQuery` 而不是读取 `window.innerWidth`
644
+ - 使用 React 的事件系统而不是 `document.addEventListener`
645
+
646
+ ### 4. **次选:通过封装层访问**
647
+
648
+ 如果没有相应的服务,可以从 `@/core/globals` 导入:
649
+
650
+ ```typescript
651
+ // src/core/globals.ts
652
+ export const getDocument = () => {
653
+ if (typeof document === 'undefined') {
654
+ throw new Error('document is not available in SSR');
655
+ }
656
+ return document;
657
+ };
658
+
659
+ // 业务代码
660
+ import { getDocument } from '@/core/globals';
661
+
662
+ const doc = getDocument();
663
+ const element = doc.getElementById('root');
664
+ ```
665
+
666
+ ### 5. **特殊情况要文档化**
667
+
668
+ 如果某个基础设施层必须直接访问全局变量,添加注释说明原因:
669
+
670
+ ```typescript
671
+ // ClientIOC.ts
672
+ create() {
673
+ // 注意:这里直接使用 window.location.pathname
674
+ // 原因:IOC 容器初始化时需要,且在 main.tsx 之后执行,浏览器环境确保可用
675
+ // TODO: 考虑通过 BootstrapClient 传入,避免直接访问
676
+ const pathname = window.location.pathname;
677
+ // ...
678
+ }
679
+ ```
680
+
681
+ ### 6. **推荐的解决方案优先级**
682
+
683
+ ```
684
+ 1️⃣ 使用 IOC 容器服务 (useIoc('XxxService')) ⭐ 最佳
685
+ 2️⃣ 使用 React 生态方案 (useLocation, useMediaQuery) 👍 推荐
686
+ 3️⃣ 从 globals 导入 (import { xxx } from '@/core/globals') ✅ 可以
687
+ 4️⃣ 直接访问全局变量 (window.xxx) ❌ 禁止
688
+ ```
689
+
690
+ ---
691
+
692
+ ## ❓ 常见问题
693
+
694
+ ### Q1: 我的代码很简单,为什么还要这么麻烦?
695
+
696
+ **A:** 架构规范不是为了"当前",而是为了:
697
+
698
+ - 未来可能的 SSR 需求
699
+ - 更容易编写单元测试
700
+ - 团队协作时的一致性
701
+ - 代码的可维护性和可追踪性
702
+
703
+ ### Q2: 如果我确实需要在某个文件中直接使用全局变量怎么办?
704
+
705
+ **A:** 在 `eslint.config.mjs` 中添加例外:
706
+
707
+ ```javascript
708
+ {
709
+ files: [
710
+ 'src/main.tsx',
711
+ 'src/core/globals.ts',
712
+ 'src/utils/dom-helper.ts' // 添加你的文件
713
+ ],
714
+ rules: {
715
+ 'no-restricted-globals': 'off'
716
+ }
717
+ }
718
+ ```
719
+
720
+ 但要慎重考虑,并添加注释说明原因。
721
+
722
+ ### Q3: `@/core/globals` 和直接使用 `window.xxx` 有什么区别?
723
+
724
+ **A:** 主要区别:
725
+
726
+ 1. **类型安全**:封装层提供完整的 TypeScript 类型
727
+ 2. **错误处理**:封装层可以处理 SSR、隐私模式等特殊情况
728
+ 3. **统一管理**:所有浏览器 API 访问都在一个地方,便于追踪和修改
729
+ 4. **可测试性**:可以轻松 mock 整个 `@/core/globals` 模块
730
+
731
+ ### Q4: 为什么 `ClientIOC` 可以直接使用 `window.location.pathname`?
732
+
733
+ **A:** 这是一个**权衡**:
734
+
735
+ - **可以接受**:因为 `ClientIOC` 是基础设施层,且在 `main.tsx` 之后执行,浏览器环境确保可用
736
+ - **更好的做法**:通过 `BootstrapClient.main()` 传入 `pathname` 参数
737
+ - **未来改进**:计划重构为依赖注入方式
738
+
739
+ ---
740
+
741
+ ## 🎯 总结
742
+
743
+ ### 允许使用全局变量的位置
744
+
745
+ | 位置 | 是否允许 | 说明 |
746
+ | --------------------------------- | ----------- | ---------------------------- |
747
+ | `src/main.tsx` | ✅ 允许 | 应用入口,负责注入依赖 |
748
+ | `src/core/globals.ts` | ✅ 允许 | 封装层,统一管理全局变量 |
749
+ | `src/core/clientIoc/ClientIOC.ts` | ⚠️ 特殊情况 | 基础设施层,建议改为注入方式 |
750
+ | 其他业务代码 | ❌ 禁止 | 必须通过封装层或依赖注入访问 |
751
+
752
+ ### 业务代码如何访问浏览器 API
753
+
754
+ ```typescript
755
+ // 优先级从高到低
756
+
757
+ // 🥇 方式 1:通过 IOC 容器获取服务(最推荐)
758
+ const authService = useIoc('AuthService');
759
+ authService.setToken(token); // 服务内部处理 storage
760
+
761
+ // 🥈 方式 2:使用 React 生态方案
762
+ const location = useLocation(); // react-router-dom
763
+ const path = location.pathname;
764
+
765
+ // 🥉 方式 3:从 globals 导入封装
766
+ import { localStorage } from '@/core/globals';
767
+ localStorage.setItem('key', value);
768
+
769
+ // ❌ 方式 4:直接访问(禁止!)
770
+ window.localStorage.setItem('key', value); // ESLint 错误
771
+ ```
772
+
773
+ ### 记住三个原则:
774
+
775
+ 1. **在入口注入** - `main.tsx` 是唯一直接访问浏览器环境的地方
776
+ 2. **在封装层封装** - `core/globals.ts` 或服务层提供统一接口
777
+ 3. **在业务层使用** - 优先通过 IOC 容器获取服务,次选从封装层导入
778
+
779
+ ### 为什么要这样做?
780
+
781
+ ✅ **易于测试** - 可以轻松 mock 服务或封装层
782
+ ✅ **SSR 兼容** - 封装层可以处理服务端渲染场景
783
+ ✅ **类型安全** - 完整的 TypeScript 类型支持
784
+ ✅ **易于追踪** - 统一的依赖管理,便于查找和重构
785
+ ✅ **降级处理** - 统一处理浏览器兼容性和降级策略
786
+ ✅ **解耦业务** - 业务代码不依赖具体实现
787
+
788
+ ---
789
+
790
+ **相关文档:**
791
+
792
+ - [ESLint 配置说明](../../eslint.config.mjs)
793
+ - [依赖注入模式](./dependency-injection.md)
794
+ - [项目架构设计](./index.md)
795
+
796
+ **需要帮助?**
797
+ 如果你不确定某个场景应该如何处理,请在团队频道中询问或提交 Issue。