@qlover/create-app 0.7.10 → 0.7.12
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 +88 -0
- package/dist/index.cjs +6 -6
- package/dist/index.js +6 -6
- package/dist/templates/next-app/config/Identifier/api.ts +14 -0
- package/dist/templates/next-app/config/Identifier/common.ts +7 -0
- package/dist/templates/next-app/eslint.config.mjs +17 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +21 -10
- package/dist/templates/next-app/next.config.ts +1 -0
- package/dist/templates/next-app/package.json +1 -0
- package/dist/templates/next-app/public/locales/en.json +4 -1
- package/dist/templates/next-app/public/locales/zh.json +4 -1
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +4 -7
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +16 -4
- package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +62 -0
- package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/page.tsx +3 -3
- package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +1 -1
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +1 -1
- package/dist/templates/next-app/src/app/api/admin/users/route.ts +39 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +40 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +1 -1
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +13 -2
- package/dist/templates/next-app/src/base/cases/PageParams.ts +1 -1
- package/dist/templates/next-app/src/base/cases/RequestState.ts +20 -0
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +1 -1
- package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +26 -0
- package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +87 -0
- package/dist/templates/next-app/src/base/port/AppApiInterface.ts +1 -1
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +4 -4
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +7 -0
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/PaginationInterface.ts +6 -0
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +45 -0
- package/dist/templates/next-app/src/base/services/UserService.ts +1 -1
- package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +21 -0
- package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +34 -0
- package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +2 -2
- package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +56 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +30 -31
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +5 -4
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -1
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +3 -14
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +2 -2
- package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
- package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +1 -1
- package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +1 -1
- package/dist/templates/next-app/src/core/globals.ts +1 -1
- package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +1 -1
- package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
- package/dist/templates/next-app/src/server/ServerAuth.ts +13 -3
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +37 -2
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +1 -1
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +10 -0
- package/dist/templates/next-app/src/server/port/{UserAuthInterface.ts → ServerAuthInterface.ts} +3 -1
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +1 -1
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +32 -1
- package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +19 -0
- package/dist/templates/next-app/src/server/services/ApiUserService.ts +26 -0
- package/dist/templates/next-app/src/server/services/UserService.ts +3 -3
- package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +48 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +74 -1
- package/dist/templates/next-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +73 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/_common/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +70 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/index.css +4 -3
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/_default.css +108 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/dark.css +67 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/menu/pink.css +67 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +33 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +32 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +35 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/_default.css +44 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/dark.css +43 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/index.css +3 -0
- package/dist/templates/next-app/src/styles/css/antd-themes/table/pink.css +43 -0
- package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +106 -0
- package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +68 -17
- package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +6 -1
- package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +11 -3
- package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +1 -1
- package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +1 -1
- package/dist/templates/next-app/src/uikit/hook/useIOC.ts +1 -1
- package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +7 -1
- package/package.json +1 -1
- package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +0 -27
- package/dist/templates/next-app/src/base/port/DBTableInterface.ts +0 -3
- package/dist/templates/next-app/src/base/services/appApi/AppUserType.ts +0 -51
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[data-theme='dark'],
|
|
2
|
+
[data-theme='dark'] .fe-theme {
|
|
3
|
+
/* Antd Pagination 组件变量 */
|
|
4
|
+
.ant-pagination-css-var {
|
|
5
|
+
--fe-pagination-item-bg: rgb(var(--color-bg-secondary));
|
|
6
|
+
--fe-pagination-item-size: 32px;
|
|
7
|
+
--fe-pagination-item-size-sm: 24px;
|
|
8
|
+
--fe-pagination-item-active-bg: rgb(var(--color-bg-secondary));
|
|
9
|
+
--fe-pagination-item-link-bg: rgb(var(--color-bg-secondary));
|
|
10
|
+
--fe-pagination-item-active-color-disabled: rgb(var(--text-tertiary));
|
|
11
|
+
--fe-pagination-item-active-bg-disabled: rgba(var(--text-primary), 0.15);
|
|
12
|
+
--fe-pagination-item-input-bg: rgb(var(--color-bg-secondary));
|
|
13
|
+
--fe-pagination-mini-options-size-changer-top: 0px;
|
|
14
|
+
--fe-pagination-padding-block: 4px;
|
|
15
|
+
--fe-pagination-padding-block-sm: 0px;
|
|
16
|
+
--fe-pagination-padding-block-lg: 7px;
|
|
17
|
+
--fe-pagination-padding-inline: 11px;
|
|
18
|
+
--fe-pagination-padding-inline-sm: 7px;
|
|
19
|
+
--fe-pagination-padding-inline-lg: 11px;
|
|
20
|
+
--fe-pagination-addon-bg: rgba(var(--text-primary), 0.02);
|
|
21
|
+
--fe-pagination-active-border-color: rgb(var(--color-brand));
|
|
22
|
+
--fe-pagination-hover-border-color: rgb(var(--color-brand-hover));
|
|
23
|
+
--fe-pagination-active-shadow: 0 0 0 2px rgba(var(--color-brand), 0.2);
|
|
24
|
+
--fe-pagination-error-active-shadow: 0 0 0 2px rgba(239, 68, 68, 0.2);
|
|
25
|
+
--fe-pagination-warning-active-shadow: 0 0 0 2px rgba(245, 158, 11, 0.2);
|
|
26
|
+
--fe-pagination-hover-bg: rgb(var(--color-bg-elevated));
|
|
27
|
+
--fe-pagination-active-bg: rgb(var(--color-bg-elevated));
|
|
28
|
+
--fe-pagination-input-font-size: 14px;
|
|
29
|
+
--fe-pagination-input-font-size-lg: 16px;
|
|
30
|
+
--fe-pagination-input-font-size-sm: 14px;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[data-theme='pink'],
|
|
2
|
+
[data-theme='pink'] .fe-theme {
|
|
3
|
+
/* Antd Pagination 组件变量 */
|
|
4
|
+
.ant-pagination-css-var {
|
|
5
|
+
--fe-pagination-item-bg: var(--ant-color-bg-container);
|
|
6
|
+
--fe-pagination-item-size: 32px;
|
|
7
|
+
--fe-pagination-item-size-sm: 24px;
|
|
8
|
+
--fe-pagination-item-active-bg: var(--ant-color-bg-container);
|
|
9
|
+
--fe-pagination-item-link-bg: var(--ant-color-bg-container);
|
|
10
|
+
--fe-pagination-item-active-color-disabled: var(
|
|
11
|
+
--ant-color-text-quaternary
|
|
12
|
+
);
|
|
13
|
+
--fe-pagination-item-active-bg-disabled: var(--ant-color-text-quaternary);
|
|
14
|
+
--fe-pagination-item-input-bg: var(--ant-color-bg-container);
|
|
15
|
+
--fe-pagination-mini-options-size-changer-top: 0px;
|
|
16
|
+
--fe-pagination-padding-block: 4px;
|
|
17
|
+
--fe-pagination-padding-block-sm: 0px;
|
|
18
|
+
--fe-pagination-padding-block-lg: 7px;
|
|
19
|
+
--fe-pagination-padding-inline: 11px;
|
|
20
|
+
--fe-pagination-padding-inline-sm: 7px;
|
|
21
|
+
--fe-pagination-padding-inline-lg: 11px;
|
|
22
|
+
--fe-pagination-addon-bg: var(--fe-input-addon-bg);
|
|
23
|
+
--fe-pagination-active-border-color: var(--fe-color-primary);
|
|
24
|
+
--fe-pagination-hover-border-color: var(--fe-color-primary-hover);
|
|
25
|
+
--fe-pagination-active-shadow: 0 0 0 2px var(--fe-color-primary-bg);
|
|
26
|
+
--fe-pagination-error-active-shadow: 0 0 0 2px rgba(40, 22, 24, 0.1)
|
|
27
|
+
--fe-pagination-warning-active-shadow: 0 0 0 2px
|
|
28
|
+
var(--fe-color-warning-bg);
|
|
29
|
+
--fe-pagination-hover-bg: var(--ant-color-bg-elevated);
|
|
30
|
+
--fe-pagination-active-bg: var(--ant-color-bg-elevated);
|
|
31
|
+
--fe-pagination-input-font-size: 14px;
|
|
32
|
+
--fe-pagination-input-font-size-lg: 16px;
|
|
33
|
+
--fe-pagination-input-font-size-sm: 14px;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* custom variables - for antd and custom css */
|
|
2
|
+
html,
|
|
3
|
+
.fe-theme {
|
|
4
|
+
/* Antd Table 组件变量 */
|
|
5
|
+
.ant-table-css-var {
|
|
6
|
+
--fe-table-header-bg: rgb(var(--color-bg-elevated));
|
|
7
|
+
--fe-table-header-color: rgb(var(--text-primary));
|
|
8
|
+
--fe-table-header-sort-active-bg: rgb(var(--color-bg-secondary));
|
|
9
|
+
--fe-table-header-sort-hover-bg: rgb(var(--color-bg-secondary));
|
|
10
|
+
--fe-table-body-sort-bg: rgb(var(--color-bg-elevated));
|
|
11
|
+
--fe-table-row-hover-bg: rgb(var(--color-bg-secondary));
|
|
12
|
+
--fe-table-row-selected-bg: rgba(var(--color-brand), 0.1);
|
|
13
|
+
--fe-table-row-selected-hover-bg: rgba(var(--color-brand-hover), 0.2);
|
|
14
|
+
--fe-table-row-expanded-bg: rgba(var(--text-primary), 0.02);
|
|
15
|
+
--fe-table-cell-padding-block: 16px;
|
|
16
|
+
--fe-table-cell-padding-inline: 16px;
|
|
17
|
+
--fe-table-cell-padding-block-md: 12px;
|
|
18
|
+
--fe-table-cell-padding-inline-md: 8px;
|
|
19
|
+
--fe-table-cell-padding-block-sm: 8px;
|
|
20
|
+
--fe-table-cell-padding-inline-sm: 8px;
|
|
21
|
+
--fe-table-border-color: rgb(var(--color-border));
|
|
22
|
+
--fe-table-header-border-radius: 8px;
|
|
23
|
+
--fe-table-footer-bg: rgb(var(--color-bg-elevated));
|
|
24
|
+
--fe-table-footer-color: rgb(var(--text-primary));
|
|
25
|
+
--fe-table-cell-font-size: 14px;
|
|
26
|
+
--fe-table-cell-font-size-md: 14px;
|
|
27
|
+
--fe-table-cell-font-size-sm: 14px;
|
|
28
|
+
--fe-table-header-split-color: rgb(var(--color-border));
|
|
29
|
+
--fe-table-fixed-header-sort-active-bg: rgb(var(--color-bg-secondary));
|
|
30
|
+
--fe-table-header-filter-hover-bg: rgba(var(--text-primary), 0.06);
|
|
31
|
+
--fe-table-filter-dropdown-menu-bg: rgb(var(--color-bg-base));
|
|
32
|
+
--fe-table-filter-dropdown-bg: rgb(var(--color-bg-base));
|
|
33
|
+
--fe-table-expand-icon-bg: rgb(var(--color-bg-base));
|
|
34
|
+
--fe-table-selection-column-width: 32px;
|
|
35
|
+
--fe-table-sticky-scroll-bar-bg: rgba(var(--text-primary), 0.25);
|
|
36
|
+
--fe-table-sticky-scroll-bar-border-radius: 100px;
|
|
37
|
+
--fe-table-expand-icon-margin-top: 2.5px;
|
|
38
|
+
--fe-table-header-icon-color: rgb(var(--text-tertiary));
|
|
39
|
+
--fe-table-header-icon-hover-color: rgb(var(--text-secondary));
|
|
40
|
+
--fe-table-expand-icon-half-inner: 7px;
|
|
41
|
+
--fe-table-expand-icon-size: 17px;
|
|
42
|
+
--fe-table-expand-icon-scale: 0.9411764705882353;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[data-theme='dark'],
|
|
2
|
+
[data-theme='dark'] .fe-theme {
|
|
3
|
+
/* Antd Table 组件变量 */
|
|
4
|
+
.ant-table-css-var {
|
|
5
|
+
--fe-table-header-bg: rgb(var(--color-bg-secondary));
|
|
6
|
+
--fe-table-header-color: rgb(var(--text-primary));
|
|
7
|
+
--fe-table-header-sort-active-bg: rgb(var(--color-bg-elevated));
|
|
8
|
+
--fe-table-header-sort-hover-bg: rgb(var(--color-bg-elevated));
|
|
9
|
+
--fe-table-body-sort-bg: rgb(var(--color-bg-secondary));
|
|
10
|
+
--fe-table-row-hover-bg: rgb(var(--color-bg-elevated));
|
|
11
|
+
--fe-table-row-selected-bg: rgba(var(--color-brand), 0.2);
|
|
12
|
+
--fe-table-row-selected-hover-bg: rgba(var(--color-brand-hover), 0.3);
|
|
13
|
+
--fe-table-row-expanded-bg: rgba(var(--text-primary), 0.02);
|
|
14
|
+
--fe-table-cell-padding-block: 16px;
|
|
15
|
+
--fe-table-cell-padding-inline: 16px;
|
|
16
|
+
--fe-table-cell-padding-block-md: 12px;
|
|
17
|
+
--fe-table-cell-padding-inline-md: 8px;
|
|
18
|
+
--fe-table-cell-padding-block-sm: 8px;
|
|
19
|
+
--fe-table-cell-padding-inline-sm: 8px;
|
|
20
|
+
--fe-table-border-color: rgb(var(--color-border));
|
|
21
|
+
--fe-table-header-border-radius: 8px;
|
|
22
|
+
--fe-table-footer-bg: rgb(var(--color-bg-secondary));
|
|
23
|
+
--fe-table-footer-color: rgb(var(--text-primary));
|
|
24
|
+
--fe-table-cell-font-size: 14px;
|
|
25
|
+
--fe-table-cell-font-size-md: 14px;
|
|
26
|
+
--fe-table-cell-font-size-sm: 14px;
|
|
27
|
+
--fe-table-header-split-color: rgb(var(--color-border));
|
|
28
|
+
--fe-table-fixed-header-sort-active-bg: rgb(var(--color-bg-elevated));
|
|
29
|
+
--fe-table-header-filter-hover-bg: rgba(var(--text-primary), 0.06);
|
|
30
|
+
--fe-table-filter-dropdown-menu-bg: rgb(var(--color-bg-base));
|
|
31
|
+
--fe-table-filter-dropdown-bg: rgb(var(--color-bg-base));
|
|
32
|
+
--fe-table-expand-icon-bg: rgb(var(--color-bg-base));
|
|
33
|
+
--fe-table-selection-column-width: 32px;
|
|
34
|
+
--fe-table-sticky-scroll-bar-bg: rgba(var(--text-primary), 0.25);
|
|
35
|
+
--fe-table-sticky-scroll-bar-border-radius: 100px;
|
|
36
|
+
--fe-table-expand-icon-margin-top: 2.5px;
|
|
37
|
+
--fe-table-header-icon-color: rgb(var(--text-tertiary));
|
|
38
|
+
--fe-table-header-icon-hover-color: rgb(var(--text-secondary));
|
|
39
|
+
--fe-table-expand-icon-half-inner: 7px;
|
|
40
|
+
--fe-table-expand-icon-size: 17px;
|
|
41
|
+
--fe-table-expand-icon-scale: 0.9411764705882353;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[data-theme='pink'],
|
|
2
|
+
[data-theme='pink'] .fe-theme {
|
|
3
|
+
/* Antd Table 组件变量 */
|
|
4
|
+
.ant-table-css-var {
|
|
5
|
+
--fe-table-header-bg: var(--ant-color-bg-container);
|
|
6
|
+
--fe-table-header-color: var(--ant-color-text);
|
|
7
|
+
--fe-table-header-sort-active-bg: var(--ant-color-bg-elevated);
|
|
8
|
+
--fe-table-header-sort-hover-bg: var(--ant-color-bg-elevated);
|
|
9
|
+
--fe-table-body-sort-bg: var(--ant-color-bg-container);
|
|
10
|
+
--fe-table-row-hover-bg: var(--ant-color-bg-elevated);
|
|
11
|
+
--fe-table-row-selected-bg: var(--fe-color-primary-bg);
|
|
12
|
+
--fe-table-row-selected-hover-bg: rgba(var(--fe-color-primary-hover), 0.2);
|
|
13
|
+
--fe-table-row-expanded-bg: rgba(var(--ant-color-text), 0.02);
|
|
14
|
+
--fe-table-cell-padding-block: 16px;
|
|
15
|
+
--fe-table-cell-padding-inline: 16px;
|
|
16
|
+
--fe-table-cell-padding-block-md: 12px;
|
|
17
|
+
--fe-table-cell-padding-inline-md: 8px;
|
|
18
|
+
--fe-table-cell-padding-block-sm: 8px;
|
|
19
|
+
--fe-table-cell-padding-inline-sm: 8px;
|
|
20
|
+
--fe-table-border-color: var(--ant-color-border);
|
|
21
|
+
--fe-table-header-border-radius: 8px;
|
|
22
|
+
--fe-table-footer-bg: var(--ant-color-bg-container);
|
|
23
|
+
--fe-table-footer-color: var(--ant-color-text);
|
|
24
|
+
--fe-table-cell-font-size: 14px;
|
|
25
|
+
--fe-table-cell-font-size-md: 14px;
|
|
26
|
+
--fe-table-cell-font-size-sm: 14px;
|
|
27
|
+
--fe-table-header-split-color: var(--ant-color-border);
|
|
28
|
+
--fe-table-fixed-header-sort-active-bg: var(--ant-color-bg-elevated);
|
|
29
|
+
--fe-table-header-filter-hover-bg: rgba(var(--ant-color-text), 0.06);
|
|
30
|
+
--fe-table-filter-dropdown-menu-bg: var(--ant-color-bg-container);
|
|
31
|
+
--fe-table-filter-dropdown-bg: var(--ant-color-bg-container);
|
|
32
|
+
--fe-table-expand-icon-bg: var(--ant-color-bg-container);
|
|
33
|
+
--fe-table-selection-column-width: 32px;
|
|
34
|
+
--fe-table-sticky-scroll-bar-bg: rgba(var(--ant-color-text), 0.25);
|
|
35
|
+
--fe-table-sticky-scroll-bar-border-radius: 100px;
|
|
36
|
+
--fe-table-expand-icon-margin-top: 2.5px;
|
|
37
|
+
--fe-table-header-icon-color: var(--fe-color-icon);
|
|
38
|
+
--fe-table-header-icon-hover-color: var(--fe-color-icon-hover);
|
|
39
|
+
--fe-table-expand-icon-half-inner: 7px;
|
|
40
|
+
--fe-table-expand-icon-size: 17px;
|
|
41
|
+
--fe-table-expand-icon-scale: 0.9411764705882353;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DashboardOutlined,
|
|
5
|
+
UserOutlined,
|
|
6
|
+
MenuFoldOutlined,
|
|
7
|
+
MenuUnfoldOutlined
|
|
8
|
+
} from '@ant-design/icons';
|
|
9
|
+
import { Layout, Menu } from 'antd';
|
|
10
|
+
import { clsx } from 'clsx';
|
|
11
|
+
import React, { useCallback, useMemo, type HTMLAttributes } from 'react';
|
|
12
|
+
import { AdminPageManager } from '@/base/cases/AdminPageManager';
|
|
13
|
+
import { BaseHeader } from './BaseHeader';
|
|
14
|
+
import { LocaleLink } from './LocaleLink';
|
|
15
|
+
import { useIOC } from '../hook/useIOC';
|
|
16
|
+
import { useStore } from '../hook/useStore';
|
|
17
|
+
import type { RenderLeftFunction } from './BaseHeader';
|
|
18
|
+
import type { ItemType } from 'antd/es/menu/interface';
|
|
19
|
+
|
|
20
|
+
const { Sider } = Layout;
|
|
21
|
+
|
|
22
|
+
const IconMap = {
|
|
23
|
+
dashboard: <DashboardOutlined />,
|
|
24
|
+
users: <UserOutlined />
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface AdminLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
28
|
+
mainClassName?: string;
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AdminLayout(props: AdminLayoutProps) {
|
|
33
|
+
const { children, className, mainClassName, ...rest } = props;
|
|
34
|
+
|
|
35
|
+
const page = useIOC(AdminPageManager);
|
|
36
|
+
|
|
37
|
+
const collapsedSidebar = useStore(page, page.selectors.collapsedSidebar);
|
|
38
|
+
const navItems = useStore(page, page.selectors.navItems);
|
|
39
|
+
|
|
40
|
+
const sidebarItems = useMemo(() => {
|
|
41
|
+
return navItems.map((item) => {
|
|
42
|
+
// TODO: use i18n
|
|
43
|
+
const title = item.i18nKey;
|
|
44
|
+
const icon = IconMap[item.key as keyof typeof IconMap];
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
key: item.key,
|
|
48
|
+
label: (
|
|
49
|
+
<LocaleLink
|
|
50
|
+
href={item.pathname!}
|
|
51
|
+
title={title}
|
|
52
|
+
className="flex items-center gap-2"
|
|
53
|
+
>
|
|
54
|
+
{icon}
|
|
55
|
+
<span>{title}</span>
|
|
56
|
+
</LocaleLink>
|
|
57
|
+
),
|
|
58
|
+
link: item.pathname
|
|
59
|
+
} as ItemType;
|
|
60
|
+
});
|
|
61
|
+
}, [navItems]);
|
|
62
|
+
|
|
63
|
+
const renderHeaderLeft: RenderLeftFunction = useCallback(
|
|
64
|
+
({ defaultElement }) => (
|
|
65
|
+
<div data-testid="AdminLayoutHeader" className="flex items-center">
|
|
66
|
+
<span
|
|
67
|
+
className="text-text hover:text-text-hover cursor-pointer text-md transition-colors"
|
|
68
|
+
onClick={() => page.toggleSidebar()}
|
|
69
|
+
>
|
|
70
|
+
{collapsedSidebar ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
|
71
|
+
</span>
|
|
72
|
+
|
|
73
|
+
{defaultElement}
|
|
74
|
+
</div>
|
|
75
|
+
),
|
|
76
|
+
[collapsedSidebar, page]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Layout data-testid="AdminLayout" className={className} {...rest}>
|
|
81
|
+
<div className="overflow-auto h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
|
|
82
|
+
<Sider
|
|
83
|
+
className="h-full"
|
|
84
|
+
onCollapse={() => page.toggleSidebar()}
|
|
85
|
+
collapsed={collapsedSidebar}
|
|
86
|
+
>
|
|
87
|
+
<div className="demo-logo-vertical" />
|
|
88
|
+
<Menu mode="inline" items={sidebarItems} />
|
|
89
|
+
</Sider>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<Layout>
|
|
93
|
+
<BaseHeader
|
|
94
|
+
href="/admin"
|
|
95
|
+
showLogoutButton
|
|
96
|
+
renderLeft={renderHeaderLeft}
|
|
97
|
+
/>
|
|
98
|
+
<main
|
|
99
|
+
className={clsx('p-2 bg-primary text-text flex-1', mainClassName)}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</main>
|
|
103
|
+
</Layout>
|
|
104
|
+
</Layout>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -1,16 +1,68 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import { TeamOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useLocale, useTranslations } from 'next-intl';
|
|
5
|
+
import { useMemo } from 'react';
|
|
5
6
|
import { useIOC } from '@/uikit/hook/useIOC';
|
|
7
|
+
import { PAGE_HEAD_ADMIN_TITLE } from '@config/Identifier';
|
|
8
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
6
9
|
import { LanguageSwitcher } from './LanguageSwitcher';
|
|
10
|
+
import { LocaleLink } from './LocaleLink';
|
|
7
11
|
import { LogoutButton } from './LogoutButton';
|
|
8
12
|
import { ThemeSwitcher } from './ThemeSwitcher';
|
|
9
13
|
|
|
10
|
-
export
|
|
11
|
-
|
|
14
|
+
export type RenderLeftFunction = (props: {
|
|
15
|
+
locale: string;
|
|
16
|
+
defaultElement: React.ReactNode;
|
|
17
|
+
}) => React.ReactNode;
|
|
18
|
+
|
|
19
|
+
export function BaseHeader(props: {
|
|
20
|
+
href?: string;
|
|
21
|
+
showLogoutButton?: boolean;
|
|
22
|
+
showAdminButton?: boolean;
|
|
23
|
+
renderLeft?: React.ReactNode | RenderLeftFunction;
|
|
24
|
+
}) {
|
|
25
|
+
const { href = '/', showLogoutButton, showAdminButton, renderLeft } = props;
|
|
12
26
|
const appConfig = useIOC(IOCIdentifier.AppConfig);
|
|
13
27
|
const i18nService = useIOC(IOCIdentifier.I18nServiceInterface);
|
|
28
|
+
const locale = useLocale();
|
|
29
|
+
const t = useTranslations();
|
|
30
|
+
|
|
31
|
+
const tt = {
|
|
32
|
+
title: appConfig.appName,
|
|
33
|
+
admin: t(PAGE_HEAD_ADMIN_TITLE)
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const leftDefault = useMemo(
|
|
37
|
+
() => (
|
|
38
|
+
<LocaleLink
|
|
39
|
+
data-testid="BaseHeaderLogo"
|
|
40
|
+
title={tt.title}
|
|
41
|
+
href={href}
|
|
42
|
+
locale={locale}
|
|
43
|
+
className="flex items-center hover:opacity-80 transition-opacity"
|
|
44
|
+
>
|
|
45
|
+
<span
|
|
46
|
+
data-testid="base-header-app-name"
|
|
47
|
+
className="ml-2 text-lg font-semibold text-text"
|
|
48
|
+
>
|
|
49
|
+
{tt.title}
|
|
50
|
+
</span>
|
|
51
|
+
</LocaleLink>
|
|
52
|
+
),
|
|
53
|
+
[href, locale, tt.title]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const RenderLeft = useMemo(() => {
|
|
57
|
+
if (!renderLeft) {
|
|
58
|
+
return leftDefault;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof renderLeft === 'function') {
|
|
62
|
+
return renderLeft({ locale, defaultElement: leftDefault });
|
|
63
|
+
}
|
|
64
|
+
return renderLeft;
|
|
65
|
+
}, [renderLeft, locale, leftDefault]);
|
|
14
66
|
|
|
15
67
|
return (
|
|
16
68
|
<header
|
|
@@ -18,20 +70,19 @@ export function BaseHeader(props: { showLogoutButton?: boolean }) {
|
|
|
18
70
|
className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
|
|
19
71
|
>
|
|
20
72
|
<div className="flex items-center justify-between h-full px-4 mx-auto max-w-7xl">
|
|
21
|
-
<div className="flex items-center">
|
|
22
|
-
<Link
|
|
23
|
-
href="/"
|
|
24
|
-
className="flex items-center hover:opacity-80 transition-opacity"
|
|
25
|
-
>
|
|
26
|
-
<span
|
|
27
|
-
data-testid="base-header-app-name"
|
|
28
|
-
className="ml-2 text-lg font-semibold text-text"
|
|
29
|
-
>
|
|
30
|
-
{appConfig.appName}
|
|
31
|
-
</span>
|
|
32
|
-
</Link>
|
|
33
|
-
</div>
|
|
73
|
+
<div className="flex items-center">{RenderLeft}</div>
|
|
34
74
|
<div className="flex items-center gap-2 md:gap-4">
|
|
75
|
+
{showAdminButton && (
|
|
76
|
+
<LocaleLink
|
|
77
|
+
href="/admin"
|
|
78
|
+
title={tt.admin}
|
|
79
|
+
locale={locale}
|
|
80
|
+
className="text-text hover:text-text-hover cursor-pointer text-lg transition-colors"
|
|
81
|
+
>
|
|
82
|
+
<TeamOutlined className="text-lg text-text" />
|
|
83
|
+
</LocaleLink>
|
|
84
|
+
)}
|
|
85
|
+
|
|
35
86
|
<LanguageSwitcher i18nService={i18nService} />
|
|
36
87
|
<ThemeSwitcher />
|
|
37
88
|
{showLogoutButton && <LogoutButton />}
|
|
@@ -3,12 +3,14 @@ import type { HTMLAttributes } from 'react';
|
|
|
3
3
|
|
|
4
4
|
export interface BaseLayoutProps extends HTMLAttributes<HTMLDivElement> {
|
|
5
5
|
showLogoutButton?: boolean;
|
|
6
|
+
showAdminButton?: boolean;
|
|
6
7
|
mainProps?: HTMLAttributes<HTMLElement>;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function BaseLayout({
|
|
10
11
|
children,
|
|
11
12
|
showLogoutButton,
|
|
13
|
+
showAdminButton,
|
|
12
14
|
mainProps,
|
|
13
15
|
...props
|
|
14
16
|
}: BaseLayoutProps) {
|
|
@@ -18,7 +20,10 @@ export function BaseLayout({
|
|
|
18
20
|
className="flex flex-col min-h-screen"
|
|
19
21
|
{...props}
|
|
20
22
|
>
|
|
21
|
-
<BaseHeader
|
|
23
|
+
<BaseHeader
|
|
24
|
+
showLogoutButton={showLogoutButton}
|
|
25
|
+
showAdminButton={showAdminButton}
|
|
26
|
+
/>
|
|
22
27
|
<main className="flex flex-1 flex-col bg-primary" {...mainProps}>
|
|
23
28
|
{children}
|
|
24
29
|
</main>
|
|
@@ -3,11 +3,11 @@ import '@ant-design/v5-patch-for-react-19';
|
|
|
3
3
|
import { useRouter } from 'next/navigation';
|
|
4
4
|
import { useLocale, useTranslations } from 'next-intl';
|
|
5
5
|
import { useEffect } from 'react';
|
|
6
|
-
import { I } from '@config/IOCIdentifier';
|
|
7
6
|
import { NavigateBridge } from '@/base/cases/NavigateBridge';
|
|
8
7
|
import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
|
|
9
8
|
import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
|
|
10
9
|
import { clientIOC } from '@/core/clientIoc/ClientIOC';
|
|
10
|
+
import { I } from '@config/IOCIdentifier';
|
|
11
11
|
import { IOCContext } from '../context/IOCContext';
|
|
12
12
|
|
|
13
13
|
export function BootstrapsProvider(props: { children: React.ReactNode }) {
|
|
@@ -3,10 +3,11 @@ import '@ant-design/v5-patch-for-react-19';
|
|
|
3
3
|
import { AntdRegistry } from '@ant-design/nextjs-registry';
|
|
4
4
|
import { AntdThemeProvider } from '@brain-toolkit/antd-theme-override/react';
|
|
5
5
|
import { ThemeProvider } from 'next-themes';
|
|
6
|
-
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
7
6
|
import { clientIOC } from '@/core/clientIoc/ClientIOC';
|
|
8
|
-
import {
|
|
7
|
+
import { IOCIdentifier } from '@config/IOCIdentifier';
|
|
9
8
|
import type { CommonThemeConfig } from '@config/theme';
|
|
9
|
+
import { BootstrapsProvider } from './BootstrapsProvider';
|
|
10
|
+
import { useMountedClient } from '../hook/useMountedClient';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* CommonProvider is a provider for the common components
|
|
@@ -23,6 +24,13 @@ export function ComboProvider(props: {
|
|
|
23
24
|
themeConfig: CommonThemeConfig;
|
|
24
25
|
children: React.ReactNode;
|
|
25
26
|
}) {
|
|
27
|
+
/**
|
|
28
|
+
* useMountedClient 会等待客户端完全初始化
|
|
29
|
+
* 只有在客户端准备就绪后才渲染组件内容
|
|
30
|
+
* 这样可以确保所有的客户端代码(包括 API 配置、插件等)都已经正确初始化
|
|
31
|
+
*/
|
|
32
|
+
const mounted = useMountedClient();
|
|
33
|
+
|
|
26
34
|
const { themeConfig, children } = props;
|
|
27
35
|
|
|
28
36
|
const IOC = clientIOC.create();
|
|
@@ -42,7 +50,7 @@ export function ComboProvider(props: {
|
|
|
42
50
|
storageKey={themeConfig.storageKey}
|
|
43
51
|
>
|
|
44
52
|
<BootstrapsProvider>
|
|
45
|
-
<AntdRegistry>{children}</AntdRegistry>
|
|
53
|
+
<AntdRegistry>{mounted ? children : null}</AntdRegistry>
|
|
46
54
|
</BootstrapsProvider>
|
|
47
55
|
</ThemeProvider>
|
|
48
56
|
</AntdThemeProvider>
|
|
@@ -4,12 +4,12 @@ import { TranslationOutlined } from '@ant-design/icons';
|
|
|
4
4
|
import { Dropdown } from 'antd';
|
|
5
5
|
import { useLocale } from 'next-intl';
|
|
6
6
|
import { useCallback, useMemo } from 'react';
|
|
7
|
-
import { i18nConfig } from '@config/i18n';
|
|
8
7
|
import type {
|
|
9
8
|
I18nServiceInterface,
|
|
10
9
|
I18nServiceLocale
|
|
11
10
|
} from '@/base/port/I18nServiceInterface';
|
|
12
11
|
import { usePathname, useRouter } from '@/i18n/routing';
|
|
12
|
+
import { i18nConfig } from '@config/i18n';
|
|
13
13
|
import type { LocaleType } from '@config/i18n';
|
|
14
14
|
import type { ItemType } from 'antd/es/menu/interface';
|
|
15
15
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LogoutOutlined } from '@ant-design/icons';
|
|
2
2
|
import { Tooltip } from 'antd';
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
+
import type { PageI18nInterface } from '@config/i18n';
|
|
4
5
|
import {
|
|
5
6
|
AUTH_LOGOUT_DIALOG_CONTENT,
|
|
6
7
|
AUTH_LOGOUT_DIALOG_TITLE
|
|
@@ -8,7 +9,6 @@ import {
|
|
|
8
9
|
import { I } from '@config/IOCIdentifier';
|
|
9
10
|
import { useI18nInterface } from '../hook/useI18nInterface';
|
|
10
11
|
import { useIOC } from '../hook/useIOC';
|
|
11
|
-
import type { PageI18nInterface } from '@config/i18n';
|
|
12
12
|
|
|
13
13
|
export function LogoutButton() {
|
|
14
14
|
const dialogHandler = useIOC(I.DialogHandler);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useContext } from 'react';
|
|
2
|
-
import { IOCContext } from '../context/IOCContext';
|
|
3
2
|
import type { IOCIdentifierMap } from '@config/IOCIdentifier';
|
|
3
|
+
import { IOCContext } from '../context/IOCContext';
|
|
4
4
|
import type {
|
|
5
5
|
IOCContainerInterface,
|
|
6
6
|
IOCFunctionInterface
|
|
@@ -4,7 +4,13 @@ export const useMountedClient = () => {
|
|
|
4
4
|
const [mounted, setMounted] = useState(false);
|
|
5
5
|
|
|
6
6
|
useEffect(() => {
|
|
7
|
-
|
|
7
|
+
const timer = requestAnimationFrame(() => {
|
|
8
|
+
setMounted(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
return () => {
|
|
12
|
+
cancelAnimationFrame(timer);
|
|
13
|
+
};
|
|
8
14
|
}, []);
|
|
9
15
|
|
|
10
16
|
return mounted;
|
package/package.json
CHANGED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { ExecutorError, type ExecutorContext } from '@qlover/fe-corekit';
|
|
2
|
-
import { SERVER_AUTH_ERROR } from '@config/Identifier';
|
|
3
|
-
import type { BootstrapServerContextValue } from '@/core/bootstraps/BootstrapServer';
|
|
4
|
-
import type { BootstrapExecutorPlugin } from '@qlover/corekit-bridge';
|
|
5
|
-
|
|
6
|
-
export class ServerErrorHandler implements BootstrapExecutorPlugin {
|
|
7
|
-
pluginName = 'ServerErrorHandler';
|
|
8
|
-
|
|
9
|
-
onError(
|
|
10
|
-
context: ExecutorContext<BootstrapServerContextValue>
|
|
11
|
-
): ExecutorError | void {
|
|
12
|
-
const { parameters, error } = context;
|
|
13
|
-
const { messages } = parameters;
|
|
14
|
-
|
|
15
|
-
if (error instanceof Error) {
|
|
16
|
-
const messageId = error.message;
|
|
17
|
-
|
|
18
|
-
if (messageId === SERVER_AUTH_ERROR) {
|
|
19
|
-
const message = messages[messageId];
|
|
20
|
-
|
|
21
|
-
if (message) {
|
|
22
|
-
return new ExecutorError(messageId, message);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import type { RequestEncryptPluginProps } from '@/base/cases/RequestEncryptPlugin';
|
|
2
|
-
import type { AppApiResponse } from '@/base/port/AppApiInterface';
|
|
3
|
-
import type {
|
|
4
|
-
RequestAdapterConfig,
|
|
5
|
-
RequestAdapterResponse,
|
|
6
|
-
RequestTransactionInterface
|
|
7
|
-
} from '@qlover/fe-corekit';
|
|
8
|
-
|
|
9
|
-
export interface UserApiConfig<Request = unknown>
|
|
10
|
-
extends RequestAdapterConfig<Request>,
|
|
11
|
-
RequestEncryptPluginProps<Request> {}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* UserApiResponse
|
|
15
|
-
*
|
|
16
|
-
* @description
|
|
17
|
-
* UserApiResponse is the response for the UserApi.
|
|
18
|
-
*
|
|
19
|
-
* extends:
|
|
20
|
-
* - RequestAdapterResponse<Request, Response>
|
|
21
|
-
*/
|
|
22
|
-
export type UserApiResponse<
|
|
23
|
-
Request = unknown,
|
|
24
|
-
Response = unknown
|
|
25
|
-
> = RequestAdapterResponse<Request, AppApiResponse<Response>>;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* UserApi common transaction
|
|
29
|
-
*/
|
|
30
|
-
export interface UserApiTransaction<Request = unknown, Response = unknown>
|
|
31
|
-
extends RequestTransactionInterface<
|
|
32
|
-
UserApiConfig<Request>,
|
|
33
|
-
UserApiResponse<Request, Response>
|
|
34
|
-
> {
|
|
35
|
-
data: UserApiConfig<Request>['data'];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export type UserApiLoginTransaction = UserApiTransaction<
|
|
39
|
-
{ email: string; password: string },
|
|
40
|
-
{
|
|
41
|
-
token: string;
|
|
42
|
-
}
|
|
43
|
-
>;
|
|
44
|
-
|
|
45
|
-
export type UserApiRegisterTransaction = UserApiTransaction<
|
|
46
|
-
{
|
|
47
|
-
email: string;
|
|
48
|
-
password: string;
|
|
49
|
-
},
|
|
50
|
-
UserApiTransaction['response']['data']
|
|
51
|
-
>;
|