@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.
- package/CHANGELOG.md +4 -0
- package/dist/configs/_common/.gitignore.template +6 -0
- package/dist/configs/_common/.prettierignore +17 -5
- package/dist/configs/_common/.vscode/settings.json +6 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/next-app/.env.template +1 -1
- package/dist/templates/next-app/README.en.md +0 -1
- package/dist/templates/next-app/README.md +0 -1
- package/dist/templates/next-app/config/Identifier/api.ts +5 -5
- package/dist/templates/next-app/config/Identifier/common/admint.table.ts +69 -0
- package/dist/templates/next-app/config/Identifier/common/common.ts +76 -0
- package/dist/templates/next-app/config/Identifier/common/index.ts +3 -0
- package/dist/templates/next-app/config/Identifier/{validator.ts → common/validators.ts} +5 -5
- package/dist/templates/next-app/config/Identifier/index.ts +2 -12
- package/dist/templates/next-app/config/Identifier/pages/index.ts +6 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.home.ts +27 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.locales.ts +266 -0
- package/dist/templates/next-app/config/Identifier/pages/page.admin.user.ts +293 -0
- package/dist/templates/{react-app/config/Identifier → next-app/config/Identifier/pages}/page.home.ts +15 -22
- package/dist/templates/next-app/config/Identifier/{page.login.ts → pages/page.login.ts} +28 -34
- package/dist/templates/next-app/config/Identifier/{page.register.ts → pages/page.register.ts} +30 -29
- package/dist/templates/next-app/config/adminNavs.ts +19 -0
- package/dist/templates/next-app/config/common.ts +22 -13
- package/dist/templates/next-app/config/i18n/HomeI18n.ts +5 -5
- package/dist/templates/next-app/config/i18n/admin18n.ts +61 -19
- package/dist/templates/next-app/config/i18n/i18nConfig.ts +2 -0
- package/dist/templates/next-app/config/i18n/i18nKeyScheam.ts +36 -0
- package/dist/templates/next-app/config/i18n/loginI18n.ts +22 -22
- package/dist/templates/next-app/config/i18n/register18n.ts +23 -24
- package/dist/templates/next-app/docs/en/index.md +0 -1
- package/dist/templates/next-app/docs/en/project-structure.md +0 -1
- package/dist/templates/next-app/docs/zh/index.md +0 -1
- package/dist/templates/next-app/docs/zh/project-structure.md +0 -1
- package/dist/templates/next-app/make/generateLocales.ts +19 -12
- package/dist/templates/next-app/migrations/schema/LocalesSchema.ts +15 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +11 -0
- package/dist/templates/next-app/package.json +7 -3
- package/dist/templates/next-app/public/locales/en.json +172 -207
- package/dist/templates/next-app/public/locales/zh.json +172 -207
- package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +153 -0
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +48 -50
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +2 -2
- package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +34 -0
- package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +40 -0
- package/dist/templates/next-app/src/app/api/admin/locales/route.ts +42 -0
- package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +32 -0
- package/dist/templates/next-app/src/app/api/locales/json/route.ts +44 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +1 -13
- package/dist/templates/next-app/src/base/cases/Datetime.ts +18 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +12 -6
- package/dist/templates/next-app/src/base/cases/ResourceState.ts +17 -0
- package/dist/templates/next-app/src/base/cases/TranslateI18nInterface.ts +25 -0
- package/dist/templates/next-app/src/base/cases/ZodColumnBuilder.ts +200 -0
- package/dist/templates/next-app/src/base/port/ZodBuilderInterface.ts +8 -0
- package/dist/templates/next-app/src/base/services/AdminLocalesService.ts +20 -0
- package/dist/templates/next-app/src/base/services/AdminPageEvent.ts +26 -0
- package/dist/templates/next-app/src/base/services/AdminPageScheduler.ts +42 -0
- package/dist/templates/next-app/src/base/services/ResourceService.ts +122 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminLocalesApi.ts +104 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +38 -5
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +1 -1
- package/dist/templates/next-app/src/i18n/request.ts +30 -1
- package/dist/templates/next-app/src/server/PageParams.ts +2 -10
- package/dist/templates/next-app/src/server/port/DBBridgeInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +2 -0
- package/dist/templates/next-app/src/server/port/LocalesRepositoryInterface.ts +43 -0
- package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +197 -0
- package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +122 -0
- package/dist/templates/next-app/src/server/sqlBridges/SupabaseBridge.ts +60 -11
- package/dist/templates/next-app/src/server/validators/ExtendedExecutorError.ts +6 -0
- package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +131 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +2 -5
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +32 -16
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +2 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +28 -29
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +2 -1
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +17 -3
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +5 -4
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +3 -2
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/EditableCell.tsx +118 -0
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +5 -6
- package/dist/templates/next-app/src/uikit/components/ThemeSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/With.tsx +2 -2
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportButton.tsx +62 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/LocalesImportEvent.ts +28 -0
- package/dist/templates/next-app/src/uikit/components/localesImportButton/import.module.css +6 -0
- package/dist/templates/next-app/src/uikit/hook/useI18nInterface.ts +8 -14
- package/dist/templates/next-app/src/uikit/hook/useWarnTranslations.ts +25 -0
- package/dist/templates/react-app/.prettierignore +17 -0
- package/dist/templates/react-app/README.en.md +71 -54
- package/dist/templates/react-app/README.md +35 -18
- package/dist/templates/react-app/__tests__/__mocks__/BootstrapTest.ts +14 -0
- package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +2 -2
- package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +1 -1
- package/dist/templates/react-app/__tests__/__mocks__/components/TestApp.tsx +45 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestBootstrapsProvider.tsx +34 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/TestRouter.tsx +46 -0
- package/dist/templates/react-app/__tests__/__mocks__/components/index.ts +12 -0
- package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +1 -2
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOC.ts +51 -0
- package/dist/templates/react-app/__tests__/__mocks__/testIOC/TestIOCRegister.ts +69 -0
- package/dist/templates/react-app/__tests__/setup/index.ts +1 -51
- package/dist/templates/react-app/__tests__/setup/setupGlobal.ts +51 -0
- package/dist/templates/react-app/__tests__/src/App.structure.test.tsx +115 -0
- package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +2 -2
- package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +3 -5
- package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +13 -2
- package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +1 -1
- package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +5 -5
- package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +1 -2
- package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +25 -15
- package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +29 -15
- package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +19 -9
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapClient.test.ts +153 -0
- package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +9 -7
- package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +4 -5
- package/dist/templates/react-app/__tests__/src/main.test.tsx +4 -4
- package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +68 -59
- package/dist/templates/react-app/config/IOCIdentifier.ts +8 -8
- package/dist/templates/react-app/config/Identifier/{common.error.ts → common/common.error.ts} +5 -5
- package/dist/templates/react-app/config/Identifier/{common.ts → common/common.ts} +9 -9
- package/dist/templates/react-app/config/Identifier/common/index.ts +2 -0
- package/dist/templates/react-app/config/Identifier/index.ts +1 -9
- package/dist/templates/react-app/config/Identifier/pages/index.ts +8 -0
- package/dist/templates/react-app/config/Identifier/{page.about.ts → pages/page.about.ts} +34 -26
- package/dist/templates/react-app/config/Identifier/{page.executor.ts → pages/page.executor.ts} +47 -39
- package/dist/templates/{next-app/config/Identifier → react-app/config/Identifier/pages}/page.home.ts +24 -23
- package/dist/templates/react-app/config/Identifier/pages/page.identifiter.ts +102 -0
- package/dist/templates/react-app/config/Identifier/{page.jsonStorage.ts → pages/page.jsonStorage.ts} +18 -11
- package/dist/templates/react-app/config/Identifier/{page.login.ts → pages/page.login.ts} +37 -27
- package/dist/templates/react-app/config/Identifier/{page.register.ts → pages/page.register.ts} +37 -25
- package/dist/templates/react-app/config/Identifier/{page.request.ts → pages/page.request.ts} +34 -44
- package/dist/templates/react-app/config/app.router.ts +66 -69
- package/dist/templates/react-app/config/i18n/PageI18nInterface.ts +51 -0
- package/dist/templates/react-app/config/i18n/aboutI18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/executorI18n.ts +51 -0
- package/dist/templates/react-app/config/i18n/homeI18n.ts +24 -0
- package/dist/templates/react-app/config/i18n/i18nConfig.ts +30 -0
- package/dist/templates/react-app/config/i18n/identifiter18n.ts +30 -0
- package/dist/templates/react-app/config/i18n/jsonStorage18n.ts +27 -0
- package/dist/templates/react-app/config/i18n/login18n.ts +42 -0
- package/dist/templates/react-app/config/i18n/notFoundI18n.ts +34 -0
- package/dist/templates/react-app/config/i18n/register18n.ts +40 -0
- package/dist/templates/react-app/config/i18n/request18n.ts +41 -0
- package/dist/templates/react-app/config/theme.ts +14 -4
- package/dist/templates/react-app/docs/en/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/en/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/en/env.md +1132 -278
- package/dist/templates/react-app/docs/en/i18n.md +858 -147
- package/dist/templates/react-app/docs/en/index.md +733 -104
- package/dist/templates/react-app/docs/en/ioc.md +1228 -287
- package/dist/templates/react-app/docs/en/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/en/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/en/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/en/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/en/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/en/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/en/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/en/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/en/store.md +1194 -184
- package/dist/templates/react-app/docs/en/why-no-globals.md +797 -0
- package/dist/templates/react-app/docs/zh/bootstrap.md +1670 -341
- package/dist/templates/react-app/docs/zh/development-guide.md +1021 -345
- package/dist/templates/react-app/docs/zh/env.md +1132 -275
- package/dist/templates/react-app/docs/zh/i18n.md +858 -147
- package/dist/templates/react-app/docs/zh/index.md +717 -104
- package/dist/templates/react-app/docs/zh/ioc.md +1229 -287
- package/dist/templates/react-app/docs/zh/playwright/e2e-tests.md +321 -0
- package/dist/templates/react-app/docs/zh/playwright/index.md +19 -0
- package/dist/templates/react-app/docs/zh/playwright/installation-summary.md +332 -0
- package/dist/templates/react-app/docs/zh/playwright/overview.md +222 -0
- package/dist/templates/react-app/docs/zh/playwright/quickstart.md +325 -0
- package/dist/templates/react-app/docs/zh/playwright/reorganization-notes.md +340 -0
- package/dist/templates/react-app/docs/zh/playwright/setup-complete.md +290 -0
- package/dist/templates/react-app/docs/zh/playwright/testing-guide.md +565 -0
- package/dist/templates/react-app/docs/zh/store.md +1192 -184
- package/dist/templates/react-app/docs/zh/why-no-globals.md +797 -0
- package/dist/templates/react-app/e2e/App.spec.ts +319 -0
- package/dist/templates/react-app/e2e/fixtures/base.fixture.ts +40 -0
- package/dist/templates/react-app/e2e/main.spec.ts +20 -0
- package/dist/templates/react-app/e2e/utils/test-helpers.ts +19 -0
- package/dist/templates/react-app/eslint.config.mjs +247 -0
- package/dist/templates/react-app/makes/eslint-utils.mjs +195 -0
- package/dist/templates/react-app/makes/generateTs2LocalesOptions.ts +26 -0
- package/dist/templates/react-app/package.json +31 -3
- package/dist/templates/react-app/playwright.config.ts +79 -0
- package/dist/templates/react-app/public/locales/en/common.json +190 -179
- package/dist/templates/react-app/public/locales/zh/common.json +190 -179
- package/dist/templates/react-app/src/App.tsx +15 -42
- package/dist/templates/react-app/src/base/apis/AiApi.ts +5 -5
- package/dist/templates/react-app/src/base/apis/feApi/FeApi.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/feApi/FeApiBootstarp.ts +8 -8
- package/dist/templates/react-app/src/base/apis/feApi/FeApiType.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApi.ts +6 -6
- package/dist/templates/react-app/src/base/apis/userApi/UserApiAdapter.ts +1 -1
- package/dist/templates/react-app/src/base/apis/userApi/UserApiBootstarp.ts +12 -14
- package/dist/templates/react-app/src/base/apis/userApi/UserApiType.ts +1 -1
- package/dist/templates/react-app/src/base/cases/DialogHandler.ts +5 -2
- package/dist/templates/react-app/src/base/cases/I18nKeyErrorPlugin.ts +3 -3
- package/dist/templates/react-app/src/base/cases/InversifyContainer.ts +3 -3
- package/dist/templates/react-app/src/base/cases/RequestLanguages.ts +2 -2
- package/dist/templates/react-app/src/base/cases/RequestLogger.ts +4 -4
- package/dist/templates/react-app/src/base/cases/RequestStatusCatcher.ts +1 -1
- package/dist/templates/react-app/src/base/cases/ResourceState.ts +23 -0
- package/dist/templates/react-app/src/base/cases/RouterLoader.ts +4 -4
- package/dist/templates/react-app/src/base/cases/TranslateI18nInterface.ts +26 -0
- package/dist/templates/react-app/src/base/port/ExecutorPageBridgeInterface.ts +2 -3
- package/dist/templates/react-app/src/base/port/I18nServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/IOCInterface.ts +36 -0
- package/dist/templates/react-app/src/base/port/JSONStoragePageBridgeInterface.ts +2 -1
- package/dist/templates/react-app/src/base/port/ProcesserExecutorInterface.ts +1 -1
- package/dist/templates/react-app/src/base/port/RequestPageBridgeInterface.ts +2 -2
- package/dist/templates/react-app/src/base/port/RouteServiceInterface.ts +9 -5
- package/dist/templates/react-app/src/base/port/UserServiceInterface.ts +1 -1
- package/dist/templates/react-app/src/base/services/I18nService.ts +29 -29
- package/dist/templates/react-app/src/base/services/IdentifierService.ts +143 -0
- package/dist/templates/react-app/src/base/services/ProcesserExecutor.ts +3 -3
- package/dist/templates/react-app/src/base/services/RouteService.ts +27 -8
- package/dist/templates/react-app/src/base/services/UserService.ts +8 -8
- package/dist/templates/react-app/src/base/types/Page.ts +14 -2
- package/dist/templates/react-app/src/base/types/global.d.ts +1 -1
- package/dist/templates/react-app/src/core/IOC.ts +5 -46
- package/dist/templates/react-app/src/core/bootstraps/{BootstrapApp.ts → BootstrapClient.ts} +44 -17
- package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +14 -7
- package/dist/templates/react-app/src/core/bootstraps/IocIdentifierTest.ts +1 -1
- package/dist/templates/react-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/react-app/src/core/clientIoc/ClientIOC.ts +40 -0
- package/dist/templates/react-app/src/core/{IocRegisterImpl.ts → clientIoc/ClientIOCRegister.ts} +35 -24
- package/dist/templates/react-app/src/core/globals.ts +9 -9
- package/dist/templates/react-app/src/main.tsx +4 -4
- package/dist/templates/react-app/src/pages/404.tsx +6 -3
- package/dist/templates/react-app/src/pages/500.tsx +5 -2
- package/dist/templates/react-app/src/pages/NoRouteFound.tsx +5 -0
- package/dist/templates/react-app/src/pages/auth/Layout.tsx +9 -6
- package/dist/templates/react-app/src/pages/auth/LoginPage.tsx +46 -56
- package/dist/templates/react-app/src/pages/auth/RegisterPage.tsx +46 -58
- package/dist/templates/react-app/src/pages/base/AboutPage.tsx +35 -40
- package/dist/templates/react-app/src/pages/base/ExecutorPage.tsx +51 -51
- package/dist/templates/react-app/src/pages/base/HomePage.tsx +14 -15
- package/dist/templates/react-app/src/pages/base/IdentifierPage.tsx +70 -11
- package/dist/templates/react-app/src/pages/base/JSONStoragePage.tsx +24 -25
- package/dist/templates/react-app/src/pages/base/Layout.tsx +2 -2
- package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -2
- package/dist/templates/react-app/src/pages/base/RequestPage.tsx +41 -59
- package/dist/templates/react-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +85 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +99 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +86 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/_default.css +34 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/dark.css +31 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/pagination/pink.css +36 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/react-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/react-app/src/styles/css/page.css +4 -3
- package/dist/templates/react-app/src/styles/css/themes/_default.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/dark.css +1 -0
- package/dist/templates/react-app/src/styles/css/themes/pink.css +1 -0
- package/dist/templates/react-app/src/styles/css/zIndex.css +1 -1
- package/dist/templates/react-app/src/uikit/bridges/ExecutorPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/bridges/JSONStoragePageBridge.ts +2 -2
- package/dist/templates/react-app/src/uikit/bridges/NavigateBridge.ts +1 -1
- package/dist/templates/react-app/src/uikit/bridges/RequestPageBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/components/AppRouterProvider.tsx +35 -0
- package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +15 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteProvider.tsx +14 -11
- package/dist/templates/react-app/src/uikit/components/BaseRouteSeo.tsx +18 -0
- package/dist/templates/react-app/src/uikit/components/BootstrapsProvider.tsx +13 -0
- package/dist/templates/react-app/src/uikit/components/ClientSeo.tsx +62 -0
- package/dist/templates/react-app/src/uikit/components/ComboProvider.tsx +38 -0
- package/dist/templates/react-app/src/uikit/components/LanguageSwitcher.tsx +48 -27
- package/dist/templates/react-app/src/uikit/components/Loading.tsx +4 -2
- package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +4 -5
- package/dist/templates/react-app/src/uikit/components/LogoutButton.tsx +34 -11
- package/dist/templates/react-app/src/uikit/components/ProcessExecutorProvider.tsx +9 -5
- package/dist/templates/react-app/src/uikit/components/RouterRenderComponent.tsx +6 -3
- package/dist/templates/react-app/src/uikit/components/ThemeSwitcher.tsx +97 -40
- package/dist/templates/react-app/src/uikit/components/UserAuthProvider.tsx +5 -5
- package/dist/templates/react-app/src/uikit/components/With.tsx +17 -0
- package/dist/templates/react-app/src/uikit/contexts/BaseRouteContext.ts +17 -11
- package/dist/templates/react-app/src/uikit/contexts/IOCContext.ts +13 -0
- package/dist/templates/react-app/src/uikit/hooks/useAppTranslation.ts +26 -0
- package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +8 -11
- package/dist/templates/react-app/src/uikit/hooks/useI18nInterface.ts +25 -0
- package/dist/templates/react-app/src/uikit/hooks/useIOC.ts +35 -0
- package/dist/templates/react-app/src/uikit/hooks/useNavigateBridge.ts +3 -3
- package/dist/templates/react-app/src/uikit/hooks/useStrictEffect.ts +0 -1
- package/dist/templates/react-app/tsconfig.e2e.json +21 -0
- package/dist/templates/react-app/tsconfig.json +8 -1
- package/dist/templates/react-app/tsconfig.node.json +1 -1
- package/dist/templates/react-app/tsconfig.test.json +3 -1
- package/dist/templates/react-app/vite.config.ts +50 -34
- package/package.json +2 -1
- package/dist/configs/react-app/eslint.config.js +0 -94
- package/dist/templates/next-app/config/Identifier/common.error.ts +0 -41
- package/dist/templates/next-app/config/Identifier/common.ts +0 -69
- package/dist/templates/next-app/config/Identifier/page.about.ts +0 -181
- package/dist/templates/next-app/config/Identifier/page.admin.ts +0 -48
- package/dist/templates/next-app/config/Identifier/page.executor.ts +0 -272
- package/dist/templates/next-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/next-app/config/Identifier/page.jsonStorage.ts +0 -72
- package/dist/templates/next-app/config/Identifier/page.request.ts +0 -182
- package/dist/templates/next-app/src/base/cases/ChatAction.ts +0 -21
- package/dist/templates/next-app/src/base/cases/FocusBarAction.ts +0 -36
- package/dist/templates/next-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +0 -85
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +0 -45
- package/dist/templates/next-app/src/uikit/components/ChatRoot.tsx +0 -17
- package/dist/templates/next-app/src/uikit/components/chat/ChatActionInterface.ts +0 -30
- package/dist/templates/next-app/src/uikit/components/chat/ChatFocusBar.tsx +0 -65
- package/dist/templates/next-app/src/uikit/components/chat/ChatMessages.tsx +0 -59
- package/dist/templates/next-app/src/uikit/components/chat/ChatWrap.tsx +0 -28
- package/dist/templates/next-app/src/uikit/components/chat/FocusBarActionInterface.ts +0 -19
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +0 -17
- package/dist/templates/next-app/src/uikit/hook/useStore.ts +0 -15
- package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +0 -13
- package/dist/templates/react-app/__tests__/src/App.test.tsx +0 -139
- package/dist/templates/react-app/config/Identifier/page.identifiter.ts +0 -39
- package/dist/templates/react-app/config/i18n.ts +0 -15
- package/dist/templates/react-app/docs/en/project-structure.md +0 -434
- package/dist/templates/react-app/docs/zh/project-structure.md +0 -434
- package/dist/templates/react-app/src/base/cases/RequestState.ts +0 -20
- package/dist/templates/react-app/src/base/port/AsyncStateInterface.ts +0 -7
- package/dist/templates/react-app/src/uikit/hooks/useDocumentTitle.ts +0 -15
- package/dist/templates/react-app/src/uikit/hooks/useStore.ts +0 -15
|
@@ -1,274 +1,431 @@
|
|
|
1
|
-
# Development
|
|
1
|
+
# Development Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **📖 This document provides a complete page development workflow and practical guide to help you quickly get started with project development.**
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
2. [Code Style Standards](#code-style-standards)
|
|
7
|
-
3. [Component Development Standards](#component-development-standards)
|
|
8
|
-
4. [State Management Standards](#state-management-standards)
|
|
9
|
-
5. [Router Development Standards](#router-development-standards)
|
|
10
|
-
6. [Internationalization Development Standards](#internationalization-development-standards)
|
|
11
|
-
7. [Theme Style Standards](#theme-style-standards)
|
|
12
|
-
8. [Testing Standards](#testing-standards)
|
|
13
|
-
9. [Documentation Standards](#documentation-standards)
|
|
5
|
+
## 📋 Table of Contents
|
|
14
6
|
|
|
15
|
-
|
|
7
|
+
- [What's Needed to Develop a Page](#-whats-needed-to-develop-a-page)
|
|
8
|
+
- [Complete Development Workflow](#-complete-development-workflow)
|
|
9
|
+
- [Practical Example: User List Page](#-practical-example-user-list-page)
|
|
10
|
+
- [Common Scenarios](#-common-scenarios)
|
|
11
|
+
- [Code Standards](#-code-standards)
|
|
12
|
+
- [Development Tools](#-development-tools)
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
---
|
|
18
15
|
|
|
19
|
-
|
|
16
|
+
## 🎯 What's Needed to Develop a Page
|
|
17
|
+
|
|
18
|
+
### Core Checklist
|
|
19
|
+
|
|
20
|
+
A complete page typically requires the following components:
|
|
20
21
|
|
|
21
22
|
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│ └── IOC.ts # IOC container
|
|
31
|
-
├── pages/ # Page components
|
|
32
|
-
│ ├── auth/ # Authentication related pages
|
|
33
|
-
│ └── base/ # Base pages
|
|
34
|
-
├── styles/ # Style files
|
|
35
|
-
│ └── css/
|
|
36
|
-
│ ├── themes/ # Theme related
|
|
37
|
-
│ └── antd-themes/ # Ant Design themes
|
|
38
|
-
├── uikit/ # UI component library
|
|
39
|
-
│ ├── components/ # Common components
|
|
40
|
-
│ ├── contexts/ # React Contexts
|
|
41
|
-
│ ├── hooks/ # Custom Hooks
|
|
42
|
-
│ └── providers/ # Provider components
|
|
43
|
-
└── App.tsx # Application entry
|
|
23
|
+
✅ 1. Interface Definition (Port) - base/port/XxxServiceInterface.ts
|
|
24
|
+
✅ 2. Service Implementation - base/services/XxxService.ts
|
|
25
|
+
✅ 3. API Adapter (Optional) - base/apis/xxxApi/XxxApi.ts
|
|
26
|
+
✅ 4. Route Configuration - config/app.router.ts
|
|
27
|
+
✅ 5. i18n Text Definition - config/Identifier/pages/page.xxx.ts
|
|
28
|
+
✅ 6. Page Component - pages/xxx/XxxPage.tsx
|
|
29
|
+
✅ 7. IOC Registration (New Services) - core/clientIoc/ClientIOCRegister.ts
|
|
30
|
+
✅ 8. Test Files - __tests__/src/pages/xxx/XxxPage.test.tsx
|
|
44
31
|
```
|
|
45
32
|
|
|
46
|
-
###
|
|
33
|
+
### Dependency Diagram
|
|
47
34
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────────────────────────────┐
|
|
37
|
+
│ Route Configuration (app.router.ts) │
|
|
38
|
+
│ Define page paths and metadata │
|
|
39
|
+
└──────────────┬──────────────────────────┘
|
|
40
|
+
↓
|
|
41
|
+
┌─────────────────────────────────────────┐
|
|
42
|
+
│ Page Component (XxxPage.tsx) │
|
|
43
|
+
│ - Use useIOC to get services │
|
|
44
|
+
│ - Use useStore to subscribe to state │
|
|
45
|
+
│ - Use useAppTranslation for i18n │
|
|
46
|
+
│ - Handle UI rendering & interactions │
|
|
47
|
+
└──────────────┬──────────────────────────┘
|
|
48
|
+
↓
|
|
49
|
+
┌─────────────────────────────────────────┐
|
|
50
|
+
│ Service Layer (XxxService.ts) │
|
|
51
|
+
│ - Implement business logic │
|
|
52
|
+
│ - Extend StoreInterface │
|
|
53
|
+
│ - Dependency injection │
|
|
54
|
+
└──────────────┬──────────────────────────┘
|
|
55
|
+
↓
|
|
56
|
+
┌─────────────────────────────────────────┐
|
|
57
|
+
│ Interface Definition (XxxServiceInterface.ts) │
|
|
58
|
+
│ - Define service contract │
|
|
59
|
+
│ - Easy to test and mock │
|
|
60
|
+
└─────────────────────────────────────────┘
|
|
61
|
+
↓
|
|
62
|
+
┌─────────────────────────────────────────┐
|
|
63
|
+
│ API Adapter (XxxApi.ts) │
|
|
64
|
+
│ - Encapsulate HTTP requests │
|
|
65
|
+
│ - Transform data formats │
|
|
66
|
+
└─────────────────────────────────────────┘
|
|
67
|
+
↓
|
|
68
|
+
┌─────────────────────────────────────────┐
|
|
69
|
+
│ i18n Text (page.xxx.ts) │
|
|
70
|
+
│ - Define all text keys for page │
|
|
71
|
+
│ - Auto-generate translation files │
|
|
72
|
+
└─────────────────────────────────────────┘
|
|
73
|
+
```
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
- All lowercase, using hyphens (e.g., `user-profile/`)
|
|
56
|
-
- Feature modules use singular form (e.g., `auth/`, not `auths/`)
|
|
75
|
+
---
|
|
57
76
|
|
|
58
|
-
##
|
|
77
|
+
## 🚀 Complete Development Workflow
|
|
59
78
|
|
|
60
|
-
|
|
79
|
+
### Workflow Diagram
|
|
61
80
|
|
|
62
|
-
|
|
81
|
+
```
|
|
82
|
+
📝 1. Requirements Analysis
|
|
83
|
+
├── Determine page features
|
|
84
|
+
├── Determine data sources (API)
|
|
85
|
+
└── Determine interaction logic
|
|
86
|
+
↓
|
|
87
|
+
🎨 2. Define i18n Keys
|
|
88
|
+
├── Page titles, button text, etc.
|
|
89
|
+
└── Error messages, success messages, etc.
|
|
90
|
+
↓
|
|
91
|
+
🔌 3. Define Interfaces (Port)
|
|
92
|
+
├── Service interfaces
|
|
93
|
+
└── Data types
|
|
94
|
+
↓
|
|
95
|
+
⚙️ 4. Implement Service
|
|
96
|
+
├── Extend StoreInterface
|
|
97
|
+
├── Implement business logic
|
|
98
|
+
└── Dependency injection
|
|
99
|
+
↓
|
|
100
|
+
🌐 5. Implement API Adapter (if needed)
|
|
101
|
+
├── Encapsulate HTTP requests
|
|
102
|
+
└── Data transformation
|
|
103
|
+
↓
|
|
104
|
+
🗺️ 6. Configure Routes
|
|
105
|
+
├── Add route configuration
|
|
106
|
+
└── Set metadata
|
|
107
|
+
↓
|
|
108
|
+
🎭 7. Implement Page Component
|
|
109
|
+
├── Use useIOC to get services
|
|
110
|
+
├── Use useStore to subscribe to state
|
|
111
|
+
└── Implement UI rendering
|
|
112
|
+
↓
|
|
113
|
+
🔗 8. Register to IOC (if new service)
|
|
114
|
+
└── Register in ClientIOCRegister
|
|
115
|
+
↓
|
|
116
|
+
🧪 9. Write Tests
|
|
117
|
+
├── Service tests (logic)
|
|
118
|
+
├── UI tests (rendering)
|
|
119
|
+
└── Integration tests (workflow)
|
|
120
|
+
↓
|
|
121
|
+
✅ 10. Self-test and Submit
|
|
122
|
+
├── Feature testing
|
|
123
|
+
├── Code review
|
|
124
|
+
└── Submit PR
|
|
125
|
+
```
|
|
63
126
|
|
|
64
|
-
|
|
65
|
-
// Use interface for object types
|
|
66
|
-
interface UserProfile {
|
|
67
|
-
id: string;
|
|
68
|
-
name: string;
|
|
69
|
-
age?: number; // Optional properties use ?
|
|
70
|
-
}
|
|
127
|
+
---
|
|
71
128
|
|
|
72
|
-
|
|
73
|
-
type Theme = 'light' | 'dark' | 'pink';
|
|
74
|
-
type Nullable<T> = T | null;
|
|
129
|
+
## 📚 Practical Example: User List Page
|
|
75
130
|
|
|
76
|
-
|
|
77
|
-
enum UserRole {
|
|
78
|
-
ADMIN = 'ADMIN',
|
|
79
|
-
USER = 'USER',
|
|
80
|
-
GUEST = 'GUEST'
|
|
81
|
-
}
|
|
131
|
+
Let's assume we want to develop a user list page with the following features:
|
|
82
132
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
133
|
+
- Display user list
|
|
134
|
+
- Search users
|
|
135
|
+
- Pagination
|
|
136
|
+
- View user details
|
|
87
137
|
|
|
88
|
-
|
|
89
|
-
interface Repository<TEntity> {
|
|
90
|
-
find(id: string): Promise<TEntity>;
|
|
91
|
-
}
|
|
92
|
-
```
|
|
138
|
+
### 1. Requirements Analysis
|
|
93
139
|
|
|
94
|
-
|
|
140
|
+
**Feature List:**
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
142
|
+
- 📄 Display user list (avatar, name, email, role)
|
|
143
|
+
- 🔍 Search users (by name)
|
|
144
|
+
- 📃 Pagination (10 items per page)
|
|
145
|
+
- 👁️ View details (click to navigate to detail page)
|
|
146
|
+
- 🔄 Refresh list
|
|
102
147
|
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<div>
|
|
106
|
-
<h3>{name}</h3>
|
|
107
|
-
<p>{age}</p>
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
};
|
|
148
|
+
**Data Source:**
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
const useUser = (userId: string) => {
|
|
114
|
-
const [user, setUser] = useState<UserProfile | null>(null);
|
|
115
|
-
const [loading, setLoading] = useState(false);
|
|
150
|
+
- API: `GET /api/users?page=1&pageSize=10&keyword=xxx`
|
|
116
151
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
152
|
+
### 2. Define i18n Keys
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// config/Identifier/pages/page.users.ts
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @description User list page title
|
|
159
|
+
* @localZh 用户列表
|
|
160
|
+
* @localEn User List
|
|
161
|
+
*/
|
|
162
|
+
export const PAGE_USERS_TITLE = 'page.users.title';
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @description Search user placeholder
|
|
166
|
+
* @localZh 搜索用户姓名
|
|
167
|
+
* @localEn Search user name
|
|
168
|
+
*/
|
|
169
|
+
export const PAGE_USERS_SEARCH_PLACEHOLDER = 'page.users.search.placeholder';
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @description View user detail button
|
|
173
|
+
* @localZh 查看详情
|
|
174
|
+
* @localEn View Detail
|
|
175
|
+
*/
|
|
176
|
+
export const PAGE_USERS_VIEW_DETAIL = 'page.users.viewDetail';
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @description Refresh button
|
|
180
|
+
* @localZh 刷新
|
|
181
|
+
* @localEn Refresh
|
|
182
|
+
*/
|
|
183
|
+
export const PAGE_USERS_REFRESH = 'page.users.refresh';
|
|
120
184
|
|
|
121
|
-
|
|
122
|
-
|
|
185
|
+
/**
|
|
186
|
+
* @description Loading message
|
|
187
|
+
* @localZh 加载中...
|
|
188
|
+
* @localEn Loading...
|
|
189
|
+
*/
|
|
190
|
+
export const PAGE_USERS_LOADING = 'page.users.loading';
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @description Empty message
|
|
194
|
+
* @localZh 暂无用户数据
|
|
195
|
+
* @localEn No users found
|
|
196
|
+
*/
|
|
197
|
+
export const PAGE_USERS_EMPTY = 'page.users.empty';
|
|
123
198
|
```
|
|
124
199
|
|
|
125
|
-
|
|
200
|
+
### 3. Define Interfaces and Types
|
|
126
201
|
|
|
127
|
-
|
|
202
|
+
```typescript
|
|
203
|
+
// base/port/UserServiceInterface.ts
|
|
128
204
|
|
|
129
|
-
|
|
205
|
+
import { StoreInterface } from '@qlover/corekit-bridge';
|
|
130
206
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
207
|
+
/**
|
|
208
|
+
* User information
|
|
209
|
+
*/
|
|
210
|
+
export interface UserInfo {
|
|
211
|
+
id: string;
|
|
212
|
+
name: string;
|
|
213
|
+
email: string;
|
|
214
|
+
avatar: string;
|
|
215
|
+
role: 'admin' | 'user';
|
|
216
|
+
}
|
|
135
217
|
|
|
136
|
-
|
|
218
|
+
/**
|
|
219
|
+
* User list query parameters
|
|
220
|
+
*/
|
|
221
|
+
export interface UserListParams {
|
|
222
|
+
page: number;
|
|
223
|
+
pageSize: number;
|
|
224
|
+
keyword?: string;
|
|
225
|
+
}
|
|
137
226
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
227
|
+
/**
|
|
228
|
+
* User list response
|
|
229
|
+
*/
|
|
230
|
+
export interface UserListResponse {
|
|
231
|
+
list: UserInfo[];
|
|
232
|
+
total: number;
|
|
233
|
+
page: number;
|
|
234
|
+
pageSize: number;
|
|
235
|
+
}
|
|
144
236
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
237
|
+
/**
|
|
238
|
+
* User service state
|
|
239
|
+
*/
|
|
240
|
+
export interface UserServiceState extends StoreStateInterface {
|
|
241
|
+
users: UserInfo[];
|
|
242
|
+
total: number;
|
|
243
|
+
page: number;
|
|
244
|
+
pageSize: number;
|
|
245
|
+
loading: boolean;
|
|
246
|
+
error: Error | null;
|
|
149
247
|
}
|
|
150
248
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
249
|
+
/**
|
|
250
|
+
* User service interface
|
|
251
|
+
*/
|
|
252
|
+
export abstract class UserServiceInterface extends StoreInterface<UserServiceState> {
|
|
253
|
+
/**
|
|
254
|
+
* Fetch user list
|
|
255
|
+
*/
|
|
256
|
+
abstract fetchUsers(params: UserListParams): Promise<void>;
|
|
156
257
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Search users
|
|
260
|
+
*/
|
|
261
|
+
abstract searchUsers(keyword: string): Promise<void>;
|
|
161
262
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
263
|
+
/**
|
|
264
|
+
* Refresh list
|
|
265
|
+
*/
|
|
266
|
+
abstract refreshUsers(): Promise<void>;
|
|
166
267
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Selectors
|
|
270
|
+
*/
|
|
271
|
+
abstract selector: {
|
|
272
|
+
users: (state: UserServiceState) => UserInfo[];
|
|
273
|
+
loading: (state: UserServiceState) => boolean;
|
|
274
|
+
total: (state: UserServiceState) => number;
|
|
170
275
|
};
|
|
171
|
-
|
|
172
|
-
// 3.5 Return JSX
|
|
173
|
-
return (
|
|
174
|
-
<div>
|
|
175
|
-
{renderHeader()}
|
|
176
|
-
<Button onClick={handleUpdate}>{t('common.update')}</Button>
|
|
177
|
-
</div>
|
|
178
|
-
);
|
|
179
|
-
};
|
|
276
|
+
}
|
|
180
277
|
```
|
|
181
278
|
|
|
182
|
-
|
|
279
|
+
### 4. Implement API Adapter
|
|
183
280
|
|
|
184
|
-
|
|
281
|
+
```typescript
|
|
282
|
+
// base/apis/userApi/UserApi.ts
|
|
185
283
|
|
|
186
|
-
|
|
284
|
+
import { injectable, inject } from 'inversify';
|
|
285
|
+
import { HttpClient } from '@/base/cases/HttpClient';
|
|
286
|
+
import type {
|
|
287
|
+
UserListParams,
|
|
288
|
+
UserListResponse
|
|
289
|
+
} from '@/base/port/UserServiceInterface';
|
|
187
290
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
291
|
+
@injectable()
|
|
292
|
+
export class UserApi {
|
|
293
|
+
constructor(@inject(HttpClient) private http: HttpClient) {}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get user list
|
|
297
|
+
*/
|
|
298
|
+
async getUserList(params: UserListParams): Promise<UserListResponse> {
|
|
299
|
+
const response = await this.http.get('/api/users', { params });
|
|
300
|
+
|
|
301
|
+
// Transform backend data format
|
|
302
|
+
return {
|
|
303
|
+
list: response.data.items.map((item: any) => ({
|
|
304
|
+
id: item.user_id,
|
|
305
|
+
name: item.user_name,
|
|
306
|
+
email: item.user_email,
|
|
307
|
+
avatar: item.avatar_url,
|
|
308
|
+
role: item.user_role
|
|
309
|
+
})),
|
|
310
|
+
total: response.data.total_count,
|
|
311
|
+
page: response.data.current_page,
|
|
312
|
+
pageSize: response.data.page_size
|
|
313
|
+
};
|
|
314
|
+
}
|
|
194
315
|
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 5. Implement Service
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// base/services/UserService.ts
|
|
322
|
+
|
|
323
|
+
import { injectable, inject } from 'inversify';
|
|
324
|
+
import {
|
|
325
|
+
UserServiceInterface,
|
|
326
|
+
UserServiceState
|
|
327
|
+
} from '@/base/port/UserServiceInterface';
|
|
328
|
+
import { UserApi } from '@/base/apis/userApi/UserApi';
|
|
329
|
+
import type { UserListParams } from '@/base/port/UserServiceInterface';
|
|
195
330
|
|
|
196
|
-
// 2. Store implementation
|
|
197
331
|
@injectable()
|
|
198
|
-
export class
|
|
199
|
-
constructor() {
|
|
332
|
+
export class UserService extends UserServiceInterface {
|
|
333
|
+
constructor(@inject(UserApi) private api: UserApi) {
|
|
334
|
+
// Initialize state
|
|
200
335
|
super(() => ({
|
|
201
|
-
|
|
336
|
+
users: [],
|
|
337
|
+
total: 0,
|
|
338
|
+
page: 1,
|
|
339
|
+
pageSize: 10,
|
|
202
340
|
loading: false,
|
|
203
341
|
error: null
|
|
204
342
|
}));
|
|
205
343
|
}
|
|
206
344
|
|
|
207
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Selectors
|
|
347
|
+
*/
|
|
208
348
|
selector = {
|
|
209
|
-
|
|
210
|
-
loading: (state:
|
|
349
|
+
users: (state: UserServiceState) => state.users,
|
|
350
|
+
loading: (state: UserServiceState) => state.loading,
|
|
351
|
+
total: (state: UserServiceState) => state.total,
|
|
352
|
+
page: (state: UserServiceState) => state.page,
|
|
353
|
+
pageSize: (state: UserServiceState) => state.pageSize
|
|
211
354
|
};
|
|
212
355
|
|
|
213
|
-
|
|
214
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Fetch user list
|
|
358
|
+
*/
|
|
359
|
+
async fetchUsers(params: UserListParams): Promise<void> {
|
|
215
360
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
361
|
+
// 1. Set loading state
|
|
362
|
+
this.emit({ ...this.state, loading: true, error: null });
|
|
363
|
+
|
|
364
|
+
// 2. Call API
|
|
365
|
+
const response = await this.api.getUserList(params);
|
|
366
|
+
|
|
367
|
+
// 3. Update state
|
|
368
|
+
this.emit({
|
|
369
|
+
users: response.list,
|
|
370
|
+
total: response.total,
|
|
371
|
+
page: response.page,
|
|
372
|
+
pageSize: response.pageSize,
|
|
373
|
+
loading: false,
|
|
374
|
+
error: null
|
|
375
|
+
});
|
|
219
376
|
} catch (error) {
|
|
377
|
+
// 4. Error handling
|
|
220
378
|
this.emit({
|
|
221
379
|
...this.state,
|
|
222
|
-
|
|
223
|
-
|
|
380
|
+
loading: false,
|
|
381
|
+
error: error as Error
|
|
224
382
|
});
|
|
225
383
|
}
|
|
226
384
|
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
385
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Search users
|
|
388
|
+
*/
|
|
389
|
+
async searchUsers(keyword: string): Promise<void> {
|
|
390
|
+
await this.fetchUsers({
|
|
391
|
+
page: 1,
|
|
392
|
+
pageSize: this.state.pageSize,
|
|
393
|
+
keyword
|
|
394
|
+
});
|
|
395
|
+
}
|
|
237
396
|
|
|
238
|
-
|
|
397
|
+
/**
|
|
398
|
+
* Refresh list
|
|
399
|
+
*/
|
|
400
|
+
async refreshUsers(): Promise<void> {
|
|
401
|
+
await this.fetchUsers({
|
|
402
|
+
page: this.state.page,
|
|
403
|
+
pageSize: this.state.pageSize
|
|
404
|
+
});
|
|
405
|
+
}
|
|
239
406
|
}
|
|
240
407
|
```
|
|
241
408
|
|
|
242
|
-
|
|
409
|
+
### 6. Configure Routes
|
|
243
410
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
### 1. Basic Standards
|
|
247
|
-
|
|
248
|
-
- Route configurations are centrally managed in `config/app.router.ts`
|
|
249
|
-
- Use declarative route configuration
|
|
250
|
-
- Route components are placed in the `pages` directory
|
|
251
|
-
- Support route-level code splitting
|
|
252
|
-
- Route configurations include metadata support
|
|
411
|
+
```typescript
|
|
412
|
+
// config/app.router.ts
|
|
253
413
|
|
|
254
|
-
|
|
414
|
+
import * as i18nKeys from './Identifier/pages/page.users';
|
|
255
415
|
|
|
256
|
-
```typescript
|
|
257
|
-
// Route configuration example
|
|
258
416
|
export const baseRoutes: RouteConfigValue[] = [
|
|
259
417
|
{
|
|
260
418
|
path: '/:lng',
|
|
261
419
|
element: 'base/Layout',
|
|
262
|
-
meta: {
|
|
263
|
-
category: 'main'
|
|
264
|
-
},
|
|
265
420
|
children: [
|
|
421
|
+
// ... other routes
|
|
266
422
|
{
|
|
267
423
|
path: 'users',
|
|
268
|
-
element: 'users/
|
|
424
|
+
element: 'users/UserListPage',
|
|
269
425
|
meta: {
|
|
270
426
|
title: i18nKeys.PAGE_USERS_TITLE,
|
|
271
|
-
|
|
427
|
+
requiresAuth: true, // Requires authentication
|
|
428
|
+
category: 'main'
|
|
272
429
|
}
|
|
273
430
|
}
|
|
274
431
|
]
|
|
@@ -276,248 +433,767 @@ export const baseRoutes: RouteConfigValue[] = [
|
|
|
276
433
|
];
|
|
277
434
|
```
|
|
278
435
|
|
|
279
|
-
|
|
436
|
+
### 7. Implement Page Component
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
// pages/users/UserListPage.tsx
|
|
440
|
+
|
|
441
|
+
import { useEffect, useState } from 'react';
|
|
442
|
+
import { Table, Input, Button, Avatar, Space } from 'antd';
|
|
443
|
+
import { ReloadOutlined, EyeOutlined } from '@ant-design/icons';
|
|
444
|
+
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
445
|
+
import { useStore } from '@brain-toolkit/react-kit/hooks/useStore';
|
|
446
|
+
import { useAppTranslation } from '@/uikit/hooks/useAppTranslation';
|
|
447
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
448
|
+
import * as i18nKeys from '@config/Identifier/pages/page.users';
|
|
449
|
+
import type { UserInfo } from '@/base/port/UserServiceInterface';
|
|
450
|
+
|
|
451
|
+
export default function UserListPage() {
|
|
452
|
+
// 1. Get services
|
|
453
|
+
const userService = useIOC(IOCIdentifier.UserServiceInterface);
|
|
454
|
+
const routeService = useIOC(IOCIdentifier.RouteServiceInterface);
|
|
455
|
+
const { t } = useAppTranslation();
|
|
456
|
+
|
|
457
|
+
// 2. Subscribe to state
|
|
458
|
+
const users = useStore(userService, userService.selector.users);
|
|
459
|
+
const loading = useStore(userService, userService.selector.loading);
|
|
460
|
+
const total = useStore(userService, userService.selector.total);
|
|
461
|
+
const page = useStore(userService, userService.selector.page);
|
|
462
|
+
const pageSize = useStore(userService, userService.selector.pageSize);
|
|
463
|
+
|
|
464
|
+
// 3. Local state
|
|
465
|
+
const [keyword, setKeyword] = useState('');
|
|
466
|
+
|
|
467
|
+
// 4. Initialize loading
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
userService.fetchUsers({ page: 1, pageSize: 10 });
|
|
470
|
+
}, []);
|
|
471
|
+
|
|
472
|
+
// 5. Event handlers
|
|
473
|
+
const handleSearch = () => {
|
|
474
|
+
userService.searchUsers(keyword);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const handleRefresh = () => {
|
|
478
|
+
userService.refreshUsers();
|
|
479
|
+
};
|
|
280
480
|
|
|
281
|
-
|
|
481
|
+
const handlePageChange = (newPage: number, newPageSize: number) => {
|
|
482
|
+
userService.fetchUsers({ page: newPage, pageSize: newPageSize, keyword });
|
|
483
|
+
};
|
|
282
484
|
|
|
283
|
-
|
|
485
|
+
const handleViewDetail = (userId: string) => {
|
|
486
|
+
routeService.push(`/users/${userId}`);
|
|
487
|
+
};
|
|
284
488
|
|
|
285
|
-
|
|
489
|
+
// 6. Table column configuration
|
|
490
|
+
const columns = [
|
|
491
|
+
{
|
|
492
|
+
title: t(i18nKeys.PAGE_USERS_COLUMN_AVATAR),
|
|
493
|
+
dataIndex: 'avatar',
|
|
494
|
+
key: 'avatar',
|
|
495
|
+
render: (avatar: string) => <Avatar src={avatar} />
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
title: t(i18nKeys.PAGE_USERS_COLUMN_NAME),
|
|
499
|
+
dataIndex: 'name',
|
|
500
|
+
key: 'name'
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
title: t(i18nKeys.PAGE_USERS_COLUMN_EMAIL),
|
|
504
|
+
dataIndex: 'email',
|
|
505
|
+
key: 'email'
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
title: t(i18nKeys.PAGE_USERS_COLUMN_ROLE),
|
|
509
|
+
dataIndex: 'role',
|
|
510
|
+
key: 'role',
|
|
511
|
+
render: (role: string) => t(`common.role.${role}`)
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
title: t(i18nKeys.PAGE_USERS_COLUMN_ACTIONS),
|
|
515
|
+
key: 'actions',
|
|
516
|
+
render: (_: any, record: UserInfo) => (
|
|
517
|
+
<Button
|
|
518
|
+
type="link"
|
|
519
|
+
icon={<EyeOutlined />}
|
|
520
|
+
onClick={() => handleViewDetail(record.id)}
|
|
521
|
+
>
|
|
522
|
+
{t(i18nKeys.PAGE_USERS_VIEW_DETAIL)}
|
|
523
|
+
</Button>
|
|
524
|
+
)
|
|
525
|
+
}
|
|
526
|
+
];
|
|
286
527
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
528
|
+
// 7. Render
|
|
529
|
+
return (
|
|
530
|
+
<div className="p-6">
|
|
531
|
+
{/* Page title */}
|
|
532
|
+
<h1 className="text-2xl font-bold mb-4">
|
|
533
|
+
{t(i18nKeys.PAGE_USERS_TITLE)}
|
|
534
|
+
</h1>
|
|
535
|
+
|
|
536
|
+
{/* Search bar */}
|
|
537
|
+
<div className="mb-4 flex gap-2">
|
|
538
|
+
<Input.Search
|
|
539
|
+
placeholder={t(i18nKeys.PAGE_USERS_SEARCH_PLACEHOLDER)}
|
|
540
|
+
value={keyword}
|
|
541
|
+
onChange={(e) => setKeyword(e.target.value)}
|
|
542
|
+
onSearch={handleSearch}
|
|
543
|
+
style={{ width: 300 }}
|
|
544
|
+
/>
|
|
545
|
+
<Button
|
|
546
|
+
icon={<ReloadOutlined />}
|
|
547
|
+
onClick={handleRefresh}
|
|
548
|
+
loading={loading}
|
|
549
|
+
>
|
|
550
|
+
{t(i18nKeys.PAGE_USERS_REFRESH)}
|
|
551
|
+
</Button>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
{/* User table */}
|
|
555
|
+
<Table
|
|
556
|
+
columns={columns}
|
|
557
|
+
dataSource={users}
|
|
558
|
+
rowKey="id"
|
|
559
|
+
loading={loading}
|
|
560
|
+
pagination={{
|
|
561
|
+
current: page,
|
|
562
|
+
pageSize: pageSize,
|
|
563
|
+
total: total,
|
|
564
|
+
onChange: handlePageChange,
|
|
565
|
+
showSizeChanger: true,
|
|
566
|
+
showTotal: (total) => `${t('common.total')} ${total} ${t('common.items')}`
|
|
567
|
+
}}
|
|
568
|
+
locale={{
|
|
569
|
+
emptyText: t(i18nKeys.PAGE_USERS_EMPTY)
|
|
570
|
+
}}
|
|
571
|
+
/>
|
|
572
|
+
</div>
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
```
|
|
291
576
|
|
|
292
|
-
###
|
|
577
|
+
### 8. Register to IOC (if new service)
|
|
293
578
|
|
|
294
579
|
```typescript
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
580
|
+
// core/clientIoc/ClientIOCRegister.ts
|
|
581
|
+
|
|
582
|
+
export class ClientIOCRegister {
|
|
583
|
+
protected registerImplement(ioc: IOCContainerInterface): void {
|
|
584
|
+
// ... other service registrations
|
|
585
|
+
|
|
586
|
+
// Register UserService
|
|
587
|
+
ioc.bind(IOCIdentifier.UserServiceInterface, ioc.get(UserService));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
301
590
|
```
|
|
302
591
|
|
|
303
|
-
|
|
592
|
+
### 9. Write Tests
|
|
304
593
|
|
|
305
|
-
|
|
594
|
+
```typescript
|
|
595
|
+
// __tests__/src/base/services/UserService.test.ts
|
|
306
596
|
|
|
307
|
-
|
|
597
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
598
|
+
import { UserService } from '@/base/services/UserService';
|
|
308
599
|
|
|
309
|
-
|
|
600
|
+
describe('UserService', () => {
|
|
601
|
+
let userService: UserService;
|
|
602
|
+
let mockApi: any;
|
|
310
603
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
604
|
+
beforeEach(() => {
|
|
605
|
+
mockApi = {
|
|
606
|
+
getUserList: vi.fn()
|
|
607
|
+
};
|
|
315
608
|
|
|
316
|
-
|
|
609
|
+
userService = new UserService(mockApi);
|
|
610
|
+
});
|
|
317
611
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
612
|
+
it('should fetch users and update state', async () => {
|
|
613
|
+
const mockResponse = {
|
|
614
|
+
list: [
|
|
615
|
+
{
|
|
616
|
+
id: '1',
|
|
617
|
+
name: 'John',
|
|
618
|
+
email: 'john@example.com',
|
|
619
|
+
avatar: '',
|
|
620
|
+
role: 'user'
|
|
621
|
+
}
|
|
622
|
+
],
|
|
623
|
+
total: 1,
|
|
624
|
+
page: 1,
|
|
625
|
+
pageSize: 10
|
|
626
|
+
};
|
|
324
627
|
|
|
325
|
-
|
|
628
|
+
mockApi.getUserList.mockResolvedValue(mockResponse);
|
|
326
629
|
|
|
327
|
-
|
|
630
|
+
const states: any[] = [];
|
|
631
|
+
userService.subscribe((state) => states.push({ ...state }));
|
|
328
632
|
|
|
329
|
-
|
|
633
|
+
await userService.fetchUsers({ page: 1, pageSize: 10 });
|
|
330
634
|
|
|
331
|
-
|
|
635
|
+
// Verify state changes
|
|
636
|
+
expect(states).toHaveLength(2);
|
|
637
|
+
expect(states[0].loading).toBe(true);
|
|
638
|
+
expect(states[1].loading).toBe(false);
|
|
639
|
+
expect(states[1].users).toEqual(mockResponse.list);
|
|
640
|
+
expect(states[1].total).toBe(1);
|
|
641
|
+
});
|
|
332
642
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
- Use Jest and Testing Library
|
|
336
|
-
- Keep tests simple and maintainable
|
|
643
|
+
it('should handle error when fetch fails', async () => {
|
|
644
|
+
mockApi.getUserList.mockRejectedValue(new Error('Network error'));
|
|
337
645
|
|
|
338
|
-
|
|
646
|
+
await userService.fetchUsers({ page: 1, pageSize: 10 });
|
|
647
|
+
|
|
648
|
+
expect(userService.getState().error).toBeTruthy();
|
|
649
|
+
expect(userService.getState().loading).toBe(false);
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
```
|
|
339
653
|
|
|
340
654
|
```typescript
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
655
|
+
// __tests__/src/pages/users/UserListPage.test.tsx
|
|
656
|
+
|
|
657
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
658
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
659
|
+
import UserListPage from '@/pages/users/UserListPage';
|
|
660
|
+
import { IOCProvider } from '@/uikit/contexts/IOCContext';
|
|
661
|
+
|
|
662
|
+
describe('UserListPage', () => {
|
|
663
|
+
it('should display user list', async () => {
|
|
664
|
+
const mockUsers = [
|
|
665
|
+
{
|
|
666
|
+
id: '1',
|
|
667
|
+
name: 'John',
|
|
668
|
+
email: 'john@example.com',
|
|
669
|
+
avatar: '',
|
|
670
|
+
role: 'user'
|
|
671
|
+
}
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
const mockUserService = {
|
|
675
|
+
fetchUsers: vi.fn(),
|
|
676
|
+
searchUsers: vi.fn(),
|
|
677
|
+
refreshUsers: vi.fn(),
|
|
678
|
+
subscribe: vi.fn(),
|
|
679
|
+
getState: () => ({ users: mockUsers, loading: false, total: 1 }),
|
|
680
|
+
selector: {
|
|
681
|
+
users: (state: any) => state.users,
|
|
682
|
+
loading: (state: any) => state.loading,
|
|
683
|
+
total: (state: any) => state.total
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
const mockIOC = (identifier: string) => {
|
|
688
|
+
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
689
|
+
if (identifier === 'RouteServiceInterface') return { push: vi.fn() };
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
render(
|
|
693
|
+
<IOCProvider value={mockIOC}>
|
|
694
|
+
<UserListPage />
|
|
695
|
+
</IOCProvider>
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
await waitFor(() => {
|
|
699
|
+
expect(screen.getByText('John')).toBeInTheDocument();
|
|
700
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it('should search users when search button clicked', async () => {
|
|
705
|
+
const mockUserService = {
|
|
706
|
+
fetchUsers: vi.fn(),
|
|
707
|
+
searchUsers: vi.fn(),
|
|
708
|
+
subscribe: vi.fn(),
|
|
709
|
+
getState: () => ({ users: [], loading: false }),
|
|
710
|
+
selector: {
|
|
711
|
+
users: () => [],
|
|
712
|
+
loading: () => false,
|
|
713
|
+
total: () => 0
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
const mockIOC = (identifier: string) => {
|
|
718
|
+
if (identifier === 'UserServiceInterface') return mockUserService;
|
|
719
|
+
if (identifier === 'RouteServiceInterface') return { push: vi.fn() };
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
render(
|
|
723
|
+
<IOCProvider value={mockIOC}>
|
|
724
|
+
<UserListPage />
|
|
725
|
+
</IOCProvider>
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
const searchInput = screen.getByPlaceholderText(/search/i);
|
|
729
|
+
fireEvent.change(searchInput, { target: { value: 'John' } });
|
|
730
|
+
|
|
731
|
+
const searchButton = screen.getByRole('button', { name: /search/i });
|
|
732
|
+
fireEvent.click(searchButton);
|
|
733
|
+
|
|
734
|
+
expect(mockUserService.searchUsers).toHaveBeenCalledWith('John');
|
|
346
735
|
});
|
|
347
736
|
});
|
|
348
737
|
```
|
|
349
738
|
|
|
350
|
-
|
|
739
|
+
---
|
|
351
740
|
|
|
352
|
-
##
|
|
741
|
+
## 🎬 Common Scenarios
|
|
353
742
|
|
|
354
|
-
|
|
743
|
+
### Scenario 1: Adding a New Button Feature
|
|
355
744
|
|
|
356
|
-
|
|
745
|
+
Suppose we want to add a "bulk delete" feature to the user list page:
|
|
357
746
|
|
|
358
747
|
```typescript
|
|
748
|
+
// 1. Add i18n Key
|
|
359
749
|
/**
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* @
|
|
363
|
-
* @example
|
|
364
|
-
* const userService = IOC(UserService);
|
|
365
|
-
* await userService.login(credentials);
|
|
750
|
+
* @description Delete selected users
|
|
751
|
+
* @localZh 删除选中用户
|
|
752
|
+
* @localEn Delete Selected
|
|
366
753
|
*/
|
|
754
|
+
export const PAGE_USERS_DELETE_SELECTED = 'page.users.deleteSelected';
|
|
755
|
+
|
|
756
|
+
// 2. Add method to service
|
|
367
757
|
@injectable()
|
|
368
|
-
export class UserService {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
// Implementation
|
|
758
|
+
export class UserService extends UserServiceInterface {
|
|
759
|
+
async deleteUsers(userIds: string[]): Promise<void> {
|
|
760
|
+
try {
|
|
761
|
+
this.emit({ ...this.state, loading: true });
|
|
762
|
+
await this.api.deleteUsers(userIds);
|
|
763
|
+
await this.refreshUsers(); // Refresh list
|
|
764
|
+
} catch (error) {
|
|
765
|
+
this.emit({ ...this.state, loading: false, error: error as Error });
|
|
766
|
+
}
|
|
378
767
|
}
|
|
379
768
|
}
|
|
769
|
+
|
|
770
|
+
// 3. Use in page
|
|
771
|
+
function UserListPage() {
|
|
772
|
+
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
|
|
773
|
+
|
|
774
|
+
const handleDelete = async () => {
|
|
775
|
+
await userService.deleteUsers(selectedRowKeys);
|
|
776
|
+
setSelectedRowKeys([]);
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
return (
|
|
780
|
+
<div>
|
|
781
|
+
<Button
|
|
782
|
+
danger
|
|
783
|
+
onClick={handleDelete}
|
|
784
|
+
disabled={selectedRowKeys.length === 0}
|
|
785
|
+
>
|
|
786
|
+
{t(i18nKeys.PAGE_USERS_DELETE_SELECTED)}
|
|
787
|
+
</Button>
|
|
788
|
+
|
|
789
|
+
<Table
|
|
790
|
+
rowSelection={{
|
|
791
|
+
selectedRowKeys,
|
|
792
|
+
onChange: setSelectedRowKeys
|
|
793
|
+
}}
|
|
794
|
+
// ...
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
797
|
+
);
|
|
798
|
+
}
|
|
380
799
|
```
|
|
381
800
|
|
|
382
|
-
### 2
|
|
801
|
+
### Scenario 2: Adding a Modal Form
|
|
383
802
|
|
|
384
|
-
|
|
385
|
-
- **docs/**:
|
|
386
|
-
- `zh/`: Chinese documentation
|
|
387
|
-
- `en/`: English documentation
|
|
388
|
-
- Organize documentation files by feature modules
|
|
803
|
+
Suppose we want to add an "edit user" modal:
|
|
389
804
|
|
|
390
|
-
|
|
805
|
+
```typescript
|
|
806
|
+
// 1. Create modal component
|
|
807
|
+
// components/UserEditModal.tsx
|
|
808
|
+
interface UserEditModalProps {
|
|
809
|
+
user: UserInfo | null;
|
|
810
|
+
visible: boolean;
|
|
811
|
+
onClose: () => void;
|
|
812
|
+
onSubmit: (user: UserInfo) => void;
|
|
813
|
+
}
|
|
391
814
|
|
|
392
|
-
|
|
393
|
-
|
|
815
|
+
export function UserEditModal({ user, visible, onClose, onSubmit }: UserEditModalProps) {
|
|
816
|
+
const [form] = Form.useForm();
|
|
817
|
+
const { t } = useAppTranslation();
|
|
394
818
|
|
|
395
|
-
|
|
819
|
+
useEffect(() => {
|
|
820
|
+
if (user) {
|
|
821
|
+
form.setFieldsValue(user);
|
|
822
|
+
}
|
|
823
|
+
}, [user]);
|
|
396
824
|
|
|
397
|
-
|
|
825
|
+
const handleSubmit = async () => {
|
|
826
|
+
const values = await form.validateFields();
|
|
827
|
+
onSubmit(values);
|
|
828
|
+
};
|
|
398
829
|
|
|
399
|
-
|
|
830
|
+
return (
|
|
831
|
+
<Modal
|
|
832
|
+
title={t(i18nKeys.PAGE_USERS_EDIT_TITLE)}
|
|
833
|
+
open={visible}
|
|
834
|
+
onCancel={onClose}
|
|
835
|
+
onOk={handleSubmit}
|
|
836
|
+
>
|
|
837
|
+
<Form form={form} layout="vertical">
|
|
838
|
+
<Form.Item
|
|
839
|
+
name="name"
|
|
840
|
+
label={t(i18nKeys.PAGE_USERS_FORM_NAME)}
|
|
841
|
+
rules={[{ required: true }]}
|
|
842
|
+
>
|
|
843
|
+
<Input />
|
|
844
|
+
</Form.Item>
|
|
845
|
+
<Form.Item
|
|
846
|
+
name="email"
|
|
847
|
+
label={t(i18nKeys.PAGE_USERS_FORM_EMAIL)}
|
|
848
|
+
rules={[{ required: true, type: 'email' }]}
|
|
849
|
+
>
|
|
850
|
+
<Input />
|
|
851
|
+
</Form.Item>
|
|
852
|
+
</Form>
|
|
853
|
+
</Modal>
|
|
854
|
+
);
|
|
855
|
+
}
|
|
400
856
|
|
|
401
|
-
|
|
857
|
+
// 2. Add update method to service
|
|
858
|
+
@injectable()
|
|
859
|
+
export class UserService extends UserServiceInterface {
|
|
860
|
+
async updateUser(userId: string, data: Partial<UserInfo>): Promise<void> {
|
|
861
|
+
this.emit({ ...this.state, loading: true });
|
|
862
|
+
await this.api.updateUser(userId, data);
|
|
863
|
+
await this.refreshUsers();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
402
866
|
|
|
403
|
-
|
|
867
|
+
// 3. Use in page
|
|
868
|
+
function UserListPage() {
|
|
869
|
+
const [editUser, setEditUser] = useState<UserInfo | null>(null);
|
|
870
|
+
const [modalVisible, setModalVisible] = useState(false);
|
|
404
871
|
|
|
405
|
-
|
|
872
|
+
const handleEdit = (user: UserInfo) => {
|
|
873
|
+
setEditUser(user);
|
|
874
|
+
setModalVisible(true);
|
|
875
|
+
};
|
|
406
876
|
|
|
407
|
-
|
|
877
|
+
const handleSubmit = async (values: UserInfo) => {
|
|
878
|
+
await userService.updateUser(editUser!.id, values);
|
|
879
|
+
setModalVisible(false);
|
|
880
|
+
setEditUser(null);
|
|
881
|
+
};
|
|
408
882
|
|
|
409
|
-
|
|
883
|
+
return (
|
|
884
|
+
<div>
|
|
885
|
+
<Table
|
|
886
|
+
columns={[
|
|
887
|
+
// ...
|
|
888
|
+
{
|
|
889
|
+
title: 'Actions',
|
|
890
|
+
render: (_, record) => (
|
|
891
|
+
<Button onClick={() => handleEdit(record)}>Edit</Button>
|
|
892
|
+
)
|
|
893
|
+
}
|
|
894
|
+
]}
|
|
895
|
+
// ...
|
|
896
|
+
/>
|
|
897
|
+
|
|
898
|
+
<UserEditModal
|
|
899
|
+
user={editUser}
|
|
900
|
+
visible={modalVisible}
|
|
901
|
+
onClose={() => setModalVisible(false)}
|
|
902
|
+
onSubmit={handleSubmit}
|
|
903
|
+
/>
|
|
904
|
+
</div>
|
|
905
|
+
);
|
|
906
|
+
}
|
|
410
907
|
```
|
|
411
908
|
|
|
412
|
-
|
|
909
|
+
### Scenario 3: Adding Real-time Search
|
|
413
910
|
|
|
414
|
-
|
|
911
|
+
Suppose we want to implement "auto-search while typing":
|
|
415
912
|
|
|
416
|
-
|
|
913
|
+
```typescript
|
|
914
|
+
function UserListPage() {
|
|
915
|
+
const [keyword, setKeyword] = useState('');
|
|
417
916
|
|
|
418
|
-
|
|
419
|
-
|
|
917
|
+
// Use debounce to optimize search
|
|
918
|
+
const debouncedKeyword = useDebounce(keyword, 500);
|
|
420
919
|
|
|
421
|
-
|
|
920
|
+
useEffect(() => {
|
|
921
|
+
if (debouncedKeyword !== undefined) {
|
|
922
|
+
userService.searchUsers(debouncedKeyword);
|
|
923
|
+
}
|
|
924
|
+
}, [debouncedKeyword]);
|
|
422
925
|
|
|
423
|
-
|
|
424
|
-
|
|
926
|
+
return (
|
|
927
|
+
<Input
|
|
928
|
+
placeholder={t(i18nKeys.PAGE_USERS_SEARCH_PLACEHOLDER)}
|
|
929
|
+
value={keyword}
|
|
930
|
+
onChange={(e) => setKeyword(e.target.value)}
|
|
931
|
+
/>
|
|
932
|
+
);
|
|
933
|
+
}
|
|
425
934
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
- `docs`: Documentation updates
|
|
430
|
-
- `style`: Code formatting (changes that don't affect code execution)
|
|
431
|
-
- `refactor`: Code refactoring
|
|
432
|
-
- `test`: Adding tests
|
|
433
|
-
- `chore`: Build process or auxiliary tool changes
|
|
935
|
+
// Custom Hook
|
|
936
|
+
function useDebounce<T>(value: T, delay: number): T {
|
|
937
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
434
938
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
939
|
+
useEffect(() => {
|
|
940
|
+
const timer = setTimeout(() => {
|
|
941
|
+
setDebouncedValue(value);
|
|
942
|
+
}, delay);
|
|
439
943
|
|
|
440
|
-
|
|
944
|
+
return () => {
|
|
945
|
+
clearTimeout(timer);
|
|
946
|
+
};
|
|
947
|
+
}, [value, delay]);
|
|
441
948
|
|
|
949
|
+
return debouncedValue;
|
|
950
|
+
}
|
|
442
951
|
```
|
|
443
|
-
feat(auth): add user role management functionality
|
|
444
952
|
|
|
445
|
-
|
|
446
|
-
- Implement role permission configuration
|
|
447
|
-
- Add role assignment functionality
|
|
953
|
+
---
|
|
448
954
|
|
|
449
|
-
|
|
450
|
-
```
|
|
955
|
+
## 📐 Code Standards
|
|
451
956
|
|
|
452
|
-
|
|
957
|
+
### 1. Naming Conventions
|
|
453
958
|
|
|
454
|
-
|
|
959
|
+
```typescript
|
|
960
|
+
// ✅ Good naming
|
|
961
|
+
const userService = useIOC('UserServiceInterface'); // Service: camelCase
|
|
962
|
+
const UserListPage = () => {
|
|
963
|
+
/* ... */
|
|
964
|
+
}; // Component: PascalCase
|
|
965
|
+
const PAGE_USERS_TITLE = 'page.users.title'; // Constant: UPPER_SNAKE_CASE
|
|
966
|
+
interface UserInfo {
|
|
967
|
+
/* ... */
|
|
968
|
+
} // Interface: PascalCase
|
|
969
|
+
type UserRole = 'admin' | 'user'; // Type: PascalCase
|
|
970
|
+
|
|
971
|
+
// ❌ Bad naming
|
|
972
|
+
const UserService = useIOC('UserServiceInterface'); // Should be camelCase
|
|
973
|
+
const userListPage = () => {
|
|
974
|
+
/* ... */
|
|
975
|
+
}; // Component should be PascalCase
|
|
976
|
+
const pageUsersTitle = 'page.users.title'; // Constant should be UPPER_CASE
|
|
977
|
+
interface userInfo {
|
|
978
|
+
/* ... */
|
|
979
|
+
} // Interface should be PascalCase
|
|
980
|
+
```
|
|
455
981
|
|
|
456
|
-
###
|
|
982
|
+
### 2. File Organization
|
|
457
983
|
|
|
458
984
|
```typescript
|
|
459
|
-
//
|
|
460
|
-
|
|
985
|
+
// ✅ Good file organization
|
|
986
|
+
import { FC, useEffect, useState } from 'react'; // React
|
|
987
|
+
import { Button, Table, Input } from 'antd'; // Third-party UI
|
|
988
|
+
import { useIOC } from '@/uikit/hooks/useIOC'; // Internal project
|
|
989
|
+
import { useAppTranslation } from '@/uikit/hooks/useAppTranslation';
|
|
990
|
+
import * as i18nKeys from '@config/Identifier/pages/page.users';
|
|
991
|
+
import './UserListPage.css'; // Styles
|
|
992
|
+
|
|
993
|
+
// Type definitions
|
|
994
|
+
interface Props {
|
|
995
|
+
/* ... */
|
|
996
|
+
}
|
|
461
997
|
|
|
462
|
-
// Component
|
|
463
|
-
|
|
998
|
+
// Component
|
|
999
|
+
export default function UserListPage() {
|
|
1000
|
+
/* ... */
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// ❌ Bad file organization
|
|
1004
|
+
import './UserListPage.css'; // Styles shouldn't be first
|
|
1005
|
+
import * as i18nKeys from '@config/Identifier/pages/page.users';
|
|
1006
|
+
import { Button } from 'antd';
|
|
1007
|
+
import { useIOC } from '@/uikit/hooks/useIOC';
|
|
1008
|
+
import { FC } from 'react';
|
|
464
1009
|
```
|
|
465
1010
|
|
|
466
|
-
###
|
|
1011
|
+
### 3. Component Structure
|
|
467
1012
|
|
|
468
1013
|
```typescript
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// Use useCallback to cache functions
|
|
475
|
-
const handleUpdate = useCallback(() => {
|
|
476
|
-
// Implementation
|
|
477
|
-
}, [dependencies]);
|
|
478
|
-
|
|
479
|
-
// Use React.memo to avoid unnecessary re-renders
|
|
480
|
-
const UserCard = React.memo(({ user }) => {
|
|
481
|
-
return <div>{user.name}</div>;
|
|
482
|
-
});
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
## Security Standards
|
|
1014
|
+
// ✅ Good component structure
|
|
1015
|
+
export default function UserListPage() {
|
|
1016
|
+
// 1. Hooks
|
|
1017
|
+
const userService = useIOC('UserServiceInterface');
|
|
1018
|
+
const { t } = useAppTranslation();
|
|
486
1019
|
|
|
487
|
-
|
|
1020
|
+
// 2. State
|
|
1021
|
+
const users = useStore(userService, userService.selector.users);
|
|
1022
|
+
const [keyword, setKeyword] = useState('');
|
|
488
1023
|
|
|
489
|
-
|
|
1024
|
+
// 3. Side effects
|
|
1025
|
+
useEffect(() => {
|
|
1026
|
+
userService.fetchUsers({ page: 1, pageSize: 10 });
|
|
1027
|
+
}, []);
|
|
490
1028
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
1029
|
+
// 4. Event handlers
|
|
1030
|
+
const handleSearch = () => {
|
|
1031
|
+
userService.searchUsers(keyword);
|
|
1032
|
+
};
|
|
494
1033
|
|
|
495
|
-
//
|
|
496
|
-
const
|
|
1034
|
+
// 5. Render functions
|
|
1035
|
+
const renderActions = (record: UserInfo) => {
|
|
1036
|
+
return <Button onClick={() => handleEdit(record)}>Edit</Button>;
|
|
1037
|
+
};
|
|
497
1038
|
|
|
498
|
-
//
|
|
499
|
-
|
|
1039
|
+
// 6. Return JSX
|
|
1040
|
+
return (
|
|
1041
|
+
<div>
|
|
1042
|
+
{/* ... */}
|
|
1043
|
+
</div>
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
500
1046
|
```
|
|
501
1047
|
|
|
502
|
-
###
|
|
1048
|
+
### 4. Comment Standards
|
|
503
1049
|
|
|
504
1050
|
```typescript
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1051
|
+
/**
|
|
1052
|
+
* User list page
|
|
1053
|
+
*
|
|
1054
|
+
* @description Display user list with search, pagination, and view details functionality
|
|
1055
|
+
*/
|
|
1056
|
+
export default function UserListPage() {
|
|
1057
|
+
/**
|
|
1058
|
+
* Handle search
|
|
1059
|
+
* Search users by keyword
|
|
1060
|
+
*/
|
|
1061
|
+
const handleSearch = () => {
|
|
1062
|
+
userService.searchUsers(keyword);
|
|
1063
|
+
};
|
|
510
1064
|
|
|
511
|
-
//
|
|
512
|
-
|
|
513
|
-
|
|
1065
|
+
// Initialize user list loading
|
|
1066
|
+
useEffect(() => {
|
|
1067
|
+
userService.fetchUsers({ page: 1, pageSize: 10 });
|
|
1068
|
+
}, []);
|
|
514
1069
|
|
|
515
1070
|
return (
|
|
516
1071
|
<div>
|
|
517
|
-
{
|
|
518
|
-
|
|
519
|
-
|
|
1072
|
+
{/* Search bar */}
|
|
1073
|
+
<Input.Search onSearch={handleSearch} />
|
|
1074
|
+
|
|
1075
|
+
{/* User table */}
|
|
1076
|
+
<Table dataSource={users} />
|
|
520
1077
|
</div>
|
|
521
1078
|
);
|
|
522
1079
|
}
|
|
523
1080
|
```
|
|
1081
|
+
|
|
1082
|
+
---
|
|
1083
|
+
|
|
1084
|
+
## 🛠️ Development Tools
|
|
1085
|
+
|
|
1086
|
+
### Recommended VSCode Extensions
|
|
1087
|
+
|
|
1088
|
+
```
|
|
1089
|
+
✅ ESLint - Code linting
|
|
1090
|
+
✅ Prettier - Code formatting
|
|
1091
|
+
✅ TypeScript Vue Plugin (Volar) - Vue/React support
|
|
1092
|
+
✅ Tailwind CSS IntelliSense - Tailwind autocomplete
|
|
1093
|
+
✅ i18n Ally - i18n management
|
|
1094
|
+
✅ GitLens - Git enhancement
|
|
1095
|
+
✅ Error Lens - Error display
|
|
1096
|
+
✅ Auto Rename Tag - Tag auto-rename
|
|
1097
|
+
```
|
|
1098
|
+
|
|
1099
|
+
### Quick Commands
|
|
1100
|
+
|
|
1101
|
+
```bash
|
|
1102
|
+
# Development
|
|
1103
|
+
npm run dev # Start dev server
|
|
1104
|
+
npm run dev:staging # Start staging environment
|
|
1105
|
+
|
|
1106
|
+
# Build
|
|
1107
|
+
npm run build # Production build
|
|
1108
|
+
npm run preview # Preview build result
|
|
1109
|
+
|
|
1110
|
+
# Code quality
|
|
1111
|
+
npm run lint # ESLint check
|
|
1112
|
+
npm run lint:fix # ESLint auto-fix
|
|
1113
|
+
npm run type-check # TypeScript type checking
|
|
1114
|
+
|
|
1115
|
+
# Testing
|
|
1116
|
+
npm run test # Run tests
|
|
1117
|
+
npm run test:watch # Watch mode testing
|
|
1118
|
+
npm run test:coverage # Test coverage
|
|
1119
|
+
|
|
1120
|
+
# i18n
|
|
1121
|
+
npm run i18n:generate # Generate translation files
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
### Debugging Tips
|
|
1125
|
+
|
|
1126
|
+
```typescript
|
|
1127
|
+
// 1. Use logger
|
|
1128
|
+
import { logger } from '@/core/globals';
|
|
1129
|
+
|
|
1130
|
+
logger.debug('User data:', user);
|
|
1131
|
+
logger.error('Failed to fetch users:', error);
|
|
1132
|
+
|
|
1133
|
+
// 2. Use React DevTools
|
|
1134
|
+
// Install React Developer Tools browser extension
|
|
1135
|
+
|
|
1136
|
+
// 3. Use Redux DevTools (if needed)
|
|
1137
|
+
// View Store state changes
|
|
1138
|
+
|
|
1139
|
+
// 4. Use VSCode breakpoint debugging
|
|
1140
|
+
// Click on the left side of a code line to set breakpoint, then F5 to start debugging
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
## 🎯 Development Checklist
|
|
1146
|
+
|
|
1147
|
+
### Feature Development
|
|
1148
|
+
|
|
1149
|
+
- [ ] Define i18n Keys
|
|
1150
|
+
- [ ] Define interfaces and types
|
|
1151
|
+
- [ ] Implement API adapter (if needed)
|
|
1152
|
+
- [ ] Implement service
|
|
1153
|
+
- [ ] Configure routes
|
|
1154
|
+
- [ ] Implement page component
|
|
1155
|
+
- [ ] Register to IOC (if new service)
|
|
1156
|
+
- [ ] Feature self-testing
|
|
1157
|
+
|
|
1158
|
+
### Code Quality
|
|
1159
|
+
|
|
1160
|
+
- [ ] Pass ESLint check
|
|
1161
|
+
- [ ] Pass TypeScript type check
|
|
1162
|
+
- [ ] Code formatting (Prettier)
|
|
1163
|
+
- [ ] Remove console.log and debug code
|
|
1164
|
+
- [ ] Remove unused imports
|
|
1165
|
+
|
|
1166
|
+
### Testing
|
|
1167
|
+
|
|
1168
|
+
- [ ] Write service tests
|
|
1169
|
+
- [ ] Write UI tests
|
|
1170
|
+
- [ ] Test coverage > 80%
|
|
1171
|
+
- [ ] All tests passing
|
|
1172
|
+
|
|
1173
|
+
### Documentation
|
|
1174
|
+
|
|
1175
|
+
- [ ] Update related documentation
|
|
1176
|
+
- [ ] Add necessary code comments
|
|
1177
|
+
- [ ] Update API documentation (if any)
|
|
1178
|
+
|
|
1179
|
+
### Submission
|
|
1180
|
+
|
|
1181
|
+
- [ ] Git commit follows conventions
|
|
1182
|
+
- [ ] Code reviewed
|
|
1183
|
+
- [ ] Merged to main branch
|
|
1184
|
+
|
|
1185
|
+
---
|
|
1186
|
+
|
|
1187
|
+
## 📚 Related Documentation
|
|
1188
|
+
|
|
1189
|
+
- **[Project Architecture Design](./index.md)** - Understand overall architecture
|
|
1190
|
+
- **[IOC Container](./ioc.md)** - Dependency injection and UI separation
|
|
1191
|
+
- **[Store State Management](./store.md)** - How application layer notifies UI layer
|
|
1192
|
+
- **[Bootstrap Initializer](./bootstrap.md)** - Application startup and initialization
|
|
1193
|
+
- **[Environment Variables](./env.md)** - Multi-environment configuration
|
|
1194
|
+
- **[Internationalization](./i18n.md)** - i18n Key and translation management
|
|
1195
|
+
|
|
1196
|
+
---
|
|
1197
|
+
|
|
1198
|
+
**Feedback:**
|
|
1199
|
+
If you encounter any problems during development, please discuss in the team channel or submit an Issue.
|