@qlover/create-app 0.7.9 → 0.7.11
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 +184 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +3 -3
- package/dist/templates/next-app/.env.template +8 -4
- package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
- package/dist/templates/next-app/config/Identifier/api.ts +34 -0
- package/dist/templates/next-app/config/Identifier/common.ts +7 -0
- package/dist/templates/next-app/config/Identifier/index.ts +2 -0
- package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
- package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
- package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
- package/dist/templates/next-app/config/i18n/index.ts +1 -0
- package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
- package/dist/templates/next-app/eslint.config.mjs +17 -0
- package/dist/templates/next-app/migrations/schema/UserSchema.ts +24 -0
- package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
- package/dist/templates/next-app/next.config.ts +1 -0
- package/dist/templates/next-app/package.json +12 -2
- package/dist/templates/next-app/public/locales/en.json +19 -2
- package/dist/templates/next-app/public/locales/zh.json +19 -2
- package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +18 -0
- package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +22 -0
- 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 +26 -6
- package/dist/templates/next-app/src/app/[locale]/login/page.tsx +7 -5
- package/dist/templates/next-app/src/app/[locale]/page.tsx +5 -5
- package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
- package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
- package/dist/templates/next-app/src/app/api/admin/users/route.ts +39 -0
- package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
- package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
- package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
- package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +40 -0
- package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
- package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +46 -0
- package/dist/templates/next-app/src/base/cases/PageParams.ts +1 -1
- package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
- package/dist/templates/next-app/src/base/cases/RequestState.ts +20 -0
- package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
- package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
- package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
- 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 +14 -0
- package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
- package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +7 -0
- package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +21 -0
- package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
- package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
- package/dist/templates/next-app/src/base/port/PaginationInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
- package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
- package/dist/templates/next-app/src/base/services/AdminUserService.ts +45 -0
- package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
- package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
- 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 +63 -0
- package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +56 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +71 -0
- package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +49 -0
- package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
- package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -1
- package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +26 -12
- package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -4
- 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/AppErrorApi.ts +10 -0
- package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
- package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
- package/dist/templates/next-app/src/server/ServerAuth.ts +60 -0
- package/dist/templates/next-app/src/server/SupabaseBridge.ts +159 -0
- package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
- package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
- package/dist/templates/next-app/src/server/port/DBTableInterface.ts +10 -0
- package/dist/templates/next-app/src/server/port/ServerAuthInterface.ts +11 -0
- package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
- package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
- package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
- package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
- package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +94 -0
- 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 +105 -0
- package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
- 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 +9 -2
- 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 +21 -11
- 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/dist/templates/next-app/tsconfig.json +3 -1
- package/package.json +2 -2
- package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
- package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +0 -27
- package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
- package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
- /package/dist/templates/next-app/src/{app/[locale]/login → uikit/components}/FeatureItem.tsx +0 -0
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
className="
|
|
73
|
+
<div className="flex items-center">{RenderLeft}</div>
|
|
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"
|
|
29
81
|
>
|
|
30
|
-
|
|
31
|
-
</
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<div className="flex items-center gap-2">
|
|
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>
|
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import '@ant-design/v5-patch-for-react-19';
|
|
3
3
|
import { useRouter } from 'next/navigation';
|
|
4
|
-
import { useLocale } from 'next-intl';
|
|
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';
|
|
7
|
+
import type { I18nServiceLocale } from '@/base/port/I18nServiceInterface';
|
|
8
8
|
import { BootstrapClient } from '@/core/bootstraps/BootstrapClient';
|
|
9
9
|
import { clientIOC } from '@/core/clientIoc/ClientIOC';
|
|
10
|
+
import { I } from '@config/IOCIdentifier';
|
|
10
11
|
import { IOCContext } from '../context/IOCContext';
|
|
11
12
|
|
|
12
13
|
export function BootstrapsProvider(props: { children: React.ReactNode }) {
|
|
13
14
|
const IOC = clientIOC.create();
|
|
14
15
|
const locale = useLocale();
|
|
15
16
|
const router = useRouter();
|
|
17
|
+
const t = useTranslations();
|
|
16
18
|
|
|
17
19
|
useEffect(() => {
|
|
18
20
|
IOC(I.RouterServiceInterface).setLocale(locale);
|
|
19
21
|
IOC(NavigateBridge).setUIBridge(router);
|
|
20
22
|
}, [locale, router, IOC]);
|
|
21
23
|
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
IOC(I.I18nServiceInterface).changeLanguage(locale as I18nServiceLocale);
|
|
26
|
+
IOC(I.I18nServiceInterface).setTranslator(t);
|
|
27
|
+
}, [t, IOC, locale]);
|
|
28
|
+
|
|
22
29
|
useEffect(() => {
|
|
23
30
|
if (typeof window !== 'undefined') {
|
|
24
31
|
BootstrapClient.main({
|
|
@@ -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,34 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LogoutOutlined } from '@ant-design/icons';
|
|
2
|
+
import { Tooltip } from 'antd';
|
|
2
3
|
import { useCallback } from 'react';
|
|
4
|
+
import type { PageI18nInterface } from '@config/i18n';
|
|
3
5
|
import {
|
|
4
6
|
AUTH_LOGOUT_DIALOG_CONTENT,
|
|
5
7
|
AUTH_LOGOUT_DIALOG_TITLE
|
|
6
8
|
} from '@config/Identifier';
|
|
7
|
-
import {
|
|
9
|
+
import { I } from '@config/IOCIdentifier';
|
|
8
10
|
import { useI18nInterface } from '../hook/useI18nInterface';
|
|
9
11
|
import { useIOC } from '../hook/useIOC';
|
|
10
|
-
import type { PageI18nInterface } from '@config/i18n';
|
|
11
12
|
|
|
12
13
|
export function LogoutButton() {
|
|
13
|
-
const
|
|
14
|
+
const dialogHandler = useIOC(I.DialogHandler);
|
|
15
|
+
const userService = useIOC(I.UserServiceInterface);
|
|
16
|
+
const routerService = useIOC(I.RouterServiceInterface);
|
|
17
|
+
|
|
14
18
|
const tt = useI18nInterface({
|
|
15
19
|
title: AUTH_LOGOUT_DIALOG_TITLE,
|
|
16
20
|
content: AUTH_LOGOUT_DIALOG_CONTENT
|
|
17
21
|
} as PageI18nInterface);
|
|
18
22
|
|
|
19
23
|
const onClick = useCallback(() => {
|
|
20
|
-
|
|
24
|
+
dialogHandler.confirm({
|
|
21
25
|
title: tt.title,
|
|
22
26
|
content: tt.content,
|
|
23
|
-
onOk: () => {
|
|
24
|
-
|
|
27
|
+
onOk: async () => {
|
|
28
|
+
await userService.logout();
|
|
29
|
+
routerService.gotoLogin();
|
|
25
30
|
}
|
|
26
31
|
});
|
|
27
|
-
}, [tt,
|
|
32
|
+
}, [tt, dialogHandler, userService, routerService]);
|
|
28
33
|
|
|
29
34
|
return (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
<Tooltip data-testid="LogoutIcon" title={tt.title} placement="right">
|
|
36
|
+
<span
|
|
37
|
+
className="text-text hover:text-red-500 cursor-pointer text-lg transition-colors"
|
|
38
|
+
onClick={onClick}
|
|
39
|
+
>
|
|
40
|
+
<LogoutOutlined />
|
|
41
|
+
</span>
|
|
42
|
+
</Tooltip>
|
|
33
43
|
);
|
|
34
44
|
}
|
|
@@ -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;
|
|
@@ -22,13 +22,15 @@
|
|
|
22
22
|
],
|
|
23
23
|
"paths": {
|
|
24
24
|
"@/*": ["./src/*"],
|
|
25
|
-
"@config/*": ["./config/*"]
|
|
25
|
+
"@config/*": ["./config/*"],
|
|
26
|
+
"@migrations/*": ["./migrations/*"]
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
29
|
"include": [
|
|
29
30
|
"next-env.d.ts",
|
|
30
31
|
"src/**/*.ts",
|
|
31
32
|
"src/**/*.tsx",
|
|
33
|
+
"migrations/**/*.ts",
|
|
32
34
|
".next/types/**/*.ts",
|
|
33
35
|
"config/**/*.ts"
|
|
34
36
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qlover/create-app",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.11",
|
|
4
4
|
"description": "Create a new app with a single command",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"commander": "^13.1.0",
|
|
40
40
|
"inquirer": "^12.3.2",
|
|
41
|
-
"@qlover/scripts-context": "1.1.
|
|
41
|
+
"@qlover/scripts-context": "1.1.4"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup",
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { cookies } from 'next/headers';
|
|
2
|
-
import { I } from '@config/IOCIdentifier';
|
|
3
|
-
import type { ServerAuthInterface } from '../port/ServerAuthInterface';
|
|
4
|
-
import type { ServerInterface } from '../port/ServerInterface';
|
|
5
|
-
|
|
6
|
-
export class ServerAuth implements ServerAuthInterface {
|
|
7
|
-
constructor(protected server: ServerInterface) {}
|
|
8
|
-
|
|
9
|
-
async hasAuth(): Promise<boolean> {
|
|
10
|
-
const cookieStore = await cookies();
|
|
11
|
-
const appConfig = this.server.getIOC(I.AppConfig);
|
|
12
|
-
|
|
13
|
-
const token = cookieStore.get(appConfig.userTokenKey);
|
|
14
|
-
|
|
15
|
-
return !!token;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -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,12 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type IOCContainerInterface,
|
|
3
|
-
type IOCFunctionInterface
|
|
4
|
-
} from '@qlover/corekit-bridge';
|
|
5
|
-
import { type IOCIdentifierMapServer } from '@config/IOCIdentifier';
|
|
6
|
-
|
|
7
|
-
export interface ServerInterface {
|
|
8
|
-
getIOC(): IOCFunctionInterface<IOCIdentifierMapServer, IOCContainerInterface>;
|
|
9
|
-
getIOC<T extends keyof IOCIdentifierMapServer>(
|
|
10
|
-
identifier: T
|
|
11
|
-
): IOCIdentifierMapServer[T];
|
|
12
|
-
}
|
/package/dist/templates/next-app/src/{app/[locale]/login → uikit/components}/FeatureItem.tsx
RENAMED
|
File without changes
|