@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.
Files changed (125) hide show
  1. package/CHANGELOG.md +184 -0
  2. package/dist/index.cjs +3 -3
  3. package/dist/index.js +3 -3
  4. package/dist/templates/next-app/.env.template +8 -4
  5. package/dist/templates/next-app/config/IOCIdentifier.ts +4 -1
  6. package/dist/templates/next-app/config/Identifier/api.ts +34 -0
  7. package/dist/templates/next-app/config/Identifier/common.ts +7 -0
  8. package/dist/templates/next-app/config/Identifier/index.ts +2 -0
  9. package/dist/templates/next-app/config/Identifier/page.login.ts +2 -2
  10. package/dist/templates/next-app/config/Identifier/page.register.ts +43 -22
  11. package/dist/templates/next-app/config/Identifier/validator.ts +34 -0
  12. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  13. package/dist/templates/next-app/config/i18n/register18n.ts +44 -0
  14. package/dist/templates/next-app/eslint.config.mjs +17 -0
  15. package/dist/templates/next-app/migrations/schema/UserSchema.ts +24 -0
  16. package/dist/templates/next-app/migrations/sql/1694244000000.sql +10 -0
  17. package/dist/templates/next-app/next.config.ts +1 -0
  18. package/dist/templates/next-app/package.json +12 -2
  19. package/dist/templates/next-app/public/locales/en.json +19 -2
  20. package/dist/templates/next-app/public/locales/zh.json +19 -2
  21. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +18 -0
  22. package/dist/templates/next-app/src/app/[locale]/admin/page.tsx +22 -0
  23. package/dist/templates/next-app/src/app/[locale]/admin/users/page.tsx +62 -0
  24. package/dist/templates/next-app/src/app/[locale]/layout.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +26 -6
  26. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +7 -5
  27. package/dist/templates/next-app/src/app/[locale]/page.tsx +5 -5
  28. package/dist/templates/next-app/src/app/[locale]/register/RegisterForm.tsx +176 -0
  29. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +79 -0
  30. package/dist/templates/next-app/src/app/api/admin/users/route.ts +39 -0
  31. package/dist/templates/next-app/src/app/api/user/login/route.ts +50 -0
  32. package/dist/templates/next-app/src/app/api/user/logout/route.ts +27 -0
  33. package/dist/templates/next-app/src/app/api/user/register/route.ts +50 -0
  34. package/dist/templates/next-app/src/base/cases/AdminPageManager.ts +40 -0
  35. package/dist/templates/next-app/src/base/cases/AppConfig.ts +19 -0
  36. package/dist/templates/next-app/src/base/cases/DialogErrorPlugin.ts +46 -0
  37. package/dist/templates/next-app/src/base/cases/PageParams.ts +1 -1
  38. package/dist/templates/next-app/src/base/cases/RequestEncryptPlugin.ts +70 -0
  39. package/dist/templates/next-app/src/base/cases/RequestState.ts +20 -0
  40. package/dist/templates/next-app/src/base/cases/RouterService.ts +4 -0
  41. package/dist/templates/next-app/src/base/cases/StringEncryptor.ts +67 -0
  42. package/dist/templates/next-app/src/base/cases/UserServiceApi.ts +48 -0
  43. package/dist/templates/next-app/src/base/port/AdminLayoutInterface.ts +26 -0
  44. package/dist/templates/next-app/src/base/port/AdminPageInterface.ts +87 -0
  45. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +14 -0
  46. package/dist/templates/next-app/src/base/port/AppUserApiInterface.ts +15 -0
  47. package/dist/templates/next-app/src/base/port/AsyncStateInterface.ts +7 -0
  48. package/dist/templates/next-app/src/base/port/DBBridgeInterface.ts +21 -0
  49. package/dist/templates/next-app/src/base/port/DBMigrationInterface.ts +92 -0
  50. package/dist/templates/next-app/src/base/port/I18nServiceInterface.ts +3 -2
  51. package/dist/templates/next-app/src/base/port/MigrationApiInterface.ts +3 -0
  52. package/dist/templates/next-app/src/base/port/PaginationInterface.ts +6 -0
  53. package/dist/templates/next-app/src/base/port/ServerApiResponseInterface.ts +6 -0
  54. package/dist/templates/next-app/src/base/port/UserServiceInterface.ts +3 -2
  55. package/dist/templates/next-app/src/base/services/AdminUserService.ts +45 -0
  56. package/dist/templates/next-app/src/base/services/I18nService.ts +9 -45
  57. package/dist/templates/next-app/src/base/services/UserService.ts +9 -8
  58. package/dist/templates/next-app/src/base/services/adminApi/AdminApiRequester.ts +21 -0
  59. package/dist/templates/next-app/src/base/services/adminApi/AdminUserApi.ts +34 -0
  60. package/dist/templates/next-app/src/base/services/appApi/AppApiPlugin.ts +63 -0
  61. package/dist/templates/next-app/src/base/services/appApi/AppApiRequester.ts +56 -0
  62. package/dist/templates/next-app/src/base/services/appApi/AppUserApi.ts +71 -0
  63. package/dist/templates/next-app/src/base/services/appApi/AppUserApiBootstrap.ts +49 -0
  64. package/dist/templates/next-app/src/base/services/migrations/MigrationsApi.ts +43 -0
  65. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +1 -1
  66. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +26 -12
  67. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +5 -4
  68. package/dist/templates/next-app/src/core/bootstraps/PrintBootstrap.ts +1 -1
  69. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +1 -1
  70. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +1 -1
  71. package/dist/templates/next-app/src/core/globals.ts +1 -1
  72. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +1 -1
  73. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
  74. package/dist/templates/next-app/src/server/AppErrorApi.ts +10 -0
  75. package/dist/templates/next-app/src/server/AppSuccessApi.ts +7 -0
  76. package/dist/templates/next-app/src/server/PasswordEncrypt.ts +12 -0
  77. package/dist/templates/next-app/src/server/ServerAuth.ts +60 -0
  78. package/dist/templates/next-app/src/server/SupabaseBridge.ts +159 -0
  79. package/dist/templates/next-app/src/server/UserCredentialToken.ts +49 -0
  80. package/dist/templates/next-app/src/server/port/CrentialTokenInterface.ts +5 -0
  81. package/dist/templates/next-app/src/server/port/DBTableInterface.ts +10 -0
  82. package/dist/templates/next-app/src/server/port/ServerAuthInterface.ts +11 -0
  83. package/dist/templates/next-app/src/server/port/ServerInterface.ts +22 -0
  84. package/dist/templates/next-app/src/server/port/UserRepositoryInterface.ts +15 -0
  85. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +8 -0
  86. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +23 -0
  87. package/dist/templates/next-app/src/server/repositorys/UserRepository.ts +94 -0
  88. package/dist/templates/next-app/src/server/services/AdminAuthPlugin.ts +19 -0
  89. package/dist/templates/next-app/src/server/services/ApiUserService.ts +26 -0
  90. package/dist/templates/next-app/src/server/services/UserService.ts +105 -0
  91. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +79 -0
  92. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +48 -0
  93. package/dist/templates/next-app/src/styles/css/antd-themes/{_default.css → _common/_default.css} +74 -1
  94. package/dist/templates/next-app/src/styles/css/antd-themes/{dark.css → _common/dark.css} +73 -0
  95. package/dist/templates/next-app/src/styles/css/antd-themes/_common/index.css +3 -0
  96. package/dist/templates/next-app/src/styles/css/antd-themes/{pink.css → _common/pink.css} +70 -0
  97. package/dist/templates/next-app/src/styles/css/antd-themes/index.css +4 -3
  98. package/dist/templates/next-app/src/styles/css/antd-themes/menu/_default.css +108 -0
  99. package/dist/templates/next-app/src/styles/css/antd-themes/menu/dark.css +67 -0
  100. package/dist/templates/next-app/src/styles/css/antd-themes/menu/index.css +3 -0
  101. package/dist/templates/next-app/src/styles/css/antd-themes/menu/pink.css +67 -0
  102. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/_default.css +33 -0
  103. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/dark.css +32 -0
  104. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/index.css +3 -0
  105. package/dist/templates/next-app/src/styles/css/antd-themes/pagination/pink.css +35 -0
  106. package/dist/templates/next-app/src/styles/css/antd-themes/table/_default.css +44 -0
  107. package/dist/templates/next-app/src/styles/css/antd-themes/table/dark.css +43 -0
  108. package/dist/templates/next-app/src/styles/css/antd-themes/table/index.css +3 -0
  109. package/dist/templates/next-app/src/styles/css/antd-themes/table/pink.css +43 -0
  110. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +106 -0
  111. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +68 -17
  112. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +6 -1
  113. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +9 -2
  114. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +11 -3
  115. package/dist/templates/next-app/src/uikit/components/LanguageSwitcher.tsx +1 -1
  116. package/dist/templates/next-app/src/uikit/components/LogoutButton.tsx +21 -11
  117. package/dist/templates/next-app/src/uikit/hook/useIOC.ts +1 -1
  118. package/dist/templates/next-app/src/uikit/hook/useMountedClient.ts +7 -1
  119. package/dist/templates/next-app/tsconfig.json +3 -1
  120. package/package.json +2 -2
  121. package/dist/templates/next-app/src/base/cases/ServerAuth.ts +0 -17
  122. package/dist/templates/next-app/src/base/cases/ServerErrorHandler.ts +0 -27
  123. package/dist/templates/next-app/src/base/port/ServerAuthInterface.ts +0 -3
  124. package/dist/templates/next-app/src/base/port/ServerInterface.ts +0 -12
  125. /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,3 @@
1
+ @import './_default.css';
2
+ @import './dark.css';
3
+ @import './pink.css';
@@ -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 Link from 'next/link';
4
- import { IOCIdentifier } from '@config/IOCIdentifier';
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 function BaseHeader(props: { showLogoutButton?: boolean }) {
11
- const { showLogoutButton } = props;
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"
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
- {appConfig.appName}
31
- </span>
32
- </Link>
33
- </div>
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 showLogoutButton={showLogoutButton} />
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 { BootstrapsProvider } from './BootstrapsProvider';
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 { Button } from 'antd';
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 { IOCIdentifier } from '@config/IOCIdentifier';
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 IOC = useIOC();
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
- IOC(IOCIdentifier.DialogHandler).confirm({
24
+ dialogHandler.confirm({
21
25
  title: tt.title,
22
26
  content: tt.content,
23
- onOk: () => {
24
- IOC(IOCIdentifier.UserServiceInterface).logout();
27
+ onOk: async () => {
28
+ await userService.logout();
29
+ routerService.gotoLogin();
25
30
  }
26
31
  });
27
- }, [tt, IOC]);
32
+ }, [tt, dialogHandler, userService, routerService]);
28
33
 
29
34
  return (
30
- <Button data-testid="LogoutButton" danger onClick={onClick}>
31
- {tt.title}
32
- </Button>
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
- setMounted(true);
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.9",
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.3"
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,3 +0,0 @@
1
- export interface ServerAuthInterface {
2
- hasAuth(): Promise<boolean>;
3
- }
@@ -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
- }