@qlover/create-app 0.10.1 → 0.10.2

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 (108) hide show
  1. package/CHANGELOG.md +141 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/next-app/config/IOCIdentifier.ts +2 -2
  5. package/dist/templates/next-app/config/Identifier/common/common.ts +14 -0
  6. package/dist/templates/next-app/config/Identifier/pages/index.ts +1 -0
  7. package/dist/templates/next-app/config/Identifier/pages/page.about.ts +20 -0
  8. package/dist/templates/next-app/config/common.ts +1 -1
  9. package/dist/templates/next-app/config/cookies.ts +23 -0
  10. package/dist/templates/next-app/config/i18n/AboutI18n.ts +14 -0
  11. package/dist/templates/next-app/config/i18n/i18nConfig.ts +3 -1
  12. package/dist/templates/next-app/config/i18n/index.ts +1 -0
  13. package/dist/templates/next-app/config/i18n/loginI18n.ts +8 -0
  14. package/dist/templates/next-app/config/theme.ts +4 -0
  15. package/dist/templates/next-app/eslint.config.mjs +4 -1
  16. package/dist/templates/next-app/next.config.ts +1 -0
  17. package/dist/templates/next-app/package.json +13 -4
  18. package/dist/templates/next-app/public/locales/en.json +5 -0
  19. package/dist/templates/next-app/public/locales/zh.json +5 -0
  20. package/dist/templates/next-app/src/app/[locale]/admin/AdminI18nProvider.tsx +37 -0
  21. package/dist/templates/next-app/src/app/[locale]/admin/layout.tsx +30 -6
  22. package/dist/templates/next-app/src/app/[locale]/admin/locales/page.tsx +1 -1
  23. package/dist/templates/next-app/src/app/[locale]/layout.tsx +47 -10
  24. package/dist/templates/next-app/src/app/[locale]/login/LoginForm.tsx +1 -1
  25. package/dist/templates/next-app/src/app/[locale]/login/page.tsx +22 -10
  26. package/dist/templates/next-app/src/app/[locale]/page.tsx +23 -8
  27. package/dist/templates/next-app/src/app/[locale]/register/page.tsx +21 -9
  28. package/dist/templates/next-app/src/app/api/admin/locales/create/route.ts +7 -28
  29. package/dist/templates/next-app/src/app/api/admin/locales/import/route.ts +7 -34
  30. package/dist/templates/next-app/src/app/api/admin/locales/route.ts +12 -34
  31. package/dist/templates/next-app/src/app/api/admin/locales/update/route.ts +7 -26
  32. package/dist/templates/next-app/src/app/api/admin/users/route.ts +14 -33
  33. package/dist/templates/next-app/src/app/api/locales/json/route.ts +13 -25
  34. package/dist/templates/next-app/src/app/api/user/login/route.ts +6 -46
  35. package/dist/templates/next-app/src/app/api/user/logout/route.ts +5 -24
  36. package/dist/templates/next-app/src/app/api/user/register/route.ts +6 -45
  37. package/dist/templates/next-app/src/app/manifest.ts +16 -0
  38. package/dist/templates/next-app/src/app/robots.txt +2 -0
  39. package/dist/templates/next-app/src/base/cases/NavigateBridge.ts +12 -2
  40. package/dist/templates/next-app/src/base/cases/RouterService.ts +5 -5
  41. package/dist/templates/next-app/src/base/port/AppApiInterface.ts +22 -0
  42. package/dist/templates/next-app/src/base/port/IOCInterface.ts +9 -0
  43. package/dist/templates/next-app/src/base/types/{PageProps.ts → AppPageRouter.ts} +4 -1
  44. package/dist/templates/next-app/src/base/types/PagesRouter.ts +9 -0
  45. package/dist/templates/next-app/src/core/bootstraps/BootstrapClient.ts +19 -5
  46. package/dist/templates/next-app/src/core/bootstraps/BootstrapServer.ts +2 -2
  47. package/dist/templates/next-app/src/core/bootstraps/BootstrapsRegistry.ts +0 -1
  48. package/dist/templates/next-app/src/core/clientIoc/ClientIOC.ts +29 -8
  49. package/dist/templates/next-app/src/core/clientIoc/ClientIOCRegister.ts +4 -2
  50. package/dist/templates/next-app/src/core/serverIoc/ServerIOC.ts +25 -7
  51. package/dist/templates/next-app/src/core/serverIoc/ServerIOCRegister.ts +1 -1
  52. package/dist/templates/next-app/src/i18n/loadMessages.ts +103 -0
  53. package/dist/templates/next-app/src/i18n/request.ts +3 -22
  54. package/dist/templates/next-app/src/pages/[locale]/about.tsx +61 -0
  55. package/dist/templates/next-app/src/pages/_app.tsx +50 -0
  56. package/dist/templates/next-app/src/pages/_document.tsx +13 -0
  57. package/dist/templates/next-app/src/{middleware.ts → proxy.ts} +2 -1
  58. package/dist/templates/next-app/src/server/AppPageRouteParams.ts +94 -0
  59. package/dist/templates/next-app/src/server/NextApiServer.ts +53 -0
  60. package/dist/templates/next-app/src/server/PagesRouteParams.ts +136 -0
  61. package/dist/templates/next-app/src/server/{sqlBridges/SupabaseBridge.ts → SupabaseBridge.ts} +2 -0
  62. package/dist/templates/next-app/src/server/controllers/AdminLocalesController.ts +74 -0
  63. package/dist/templates/next-app/src/server/controllers/AdminUserController.ts +39 -0
  64. package/dist/templates/next-app/src/server/controllers/LocalesController.ts +33 -0
  65. package/dist/templates/next-app/src/server/controllers/UserController.ts +77 -0
  66. package/dist/templates/next-app/src/server/port/AIControllerInterface.ts +8 -0
  67. package/dist/templates/next-app/src/server/port/AdminLocalesControllerInterface.ts +21 -0
  68. package/dist/templates/next-app/src/server/port/AdminUserControllerInterface.ts +11 -0
  69. package/dist/templates/next-app/src/server/port/LocalesControllerInterface.ts +10 -0
  70. package/dist/templates/next-app/src/server/port/{ParamsHandlerInterface.ts → RouteParamsnHandlerInterface.ts} +9 -2
  71. package/dist/templates/next-app/src/server/port/ServerInterface.ts +2 -2
  72. package/dist/templates/next-app/src/server/port/UserControllerInerface.ts +8 -0
  73. package/dist/templates/next-app/src/server/port/UserServiceInterface.ts +1 -1
  74. package/dist/templates/next-app/src/server/port/ValidatorInterface.ts +2 -2
  75. package/dist/templates/next-app/src/server/repositorys/LocalesRepository.ts +8 -2
  76. package/dist/templates/next-app/src/{base → server}/services/AdminLocalesService.ts +2 -2
  77. package/dist/templates/next-app/src/server/services/ApiLocaleService.ts +25 -10
  78. package/dist/templates/next-app/src/server/services/ApiUserService.ts +5 -2
  79. package/dist/templates/next-app/src/server/validators/LocalesValidator.ts +4 -2
  80. package/dist/templates/next-app/src/server/validators/LoginValidator.ts +1 -1
  81. package/dist/templates/next-app/src/server/validators/PaginationValidator.ts +10 -10
  82. package/dist/templates/next-app/src/styles/css/antd-themes/_common/_default.css +0 -44
  83. package/dist/templates/next-app/src/styles/css/antd-themes/_common/dark.css +0 -44
  84. package/dist/templates/next-app/src/styles/css/antd-themes/_common/pink.css +0 -44
  85. package/dist/templates/next-app/src/styles/css/index.css +1 -1
  86. package/dist/templates/next-app/src/styles/css/scrollbar.css +34 -0
  87. package/dist/templates/next-app/src/uikit/components/AdminLayout.tsx +34 -11
  88. package/dist/templates/next-app/src/uikit/components/BootstrapsProvider.tsx +69 -39
  89. package/dist/templates/next-app/src/uikit/components/ClientRootProvider.tsx +64 -0
  90. package/dist/templates/next-app/src/uikit/components/ClinetRenderProvider.tsx +42 -0
  91. package/dist/templates/next-app/src/uikit/components/IOCProvider.tsx +34 -0
  92. package/dist/templates/next-app/src/uikit/components-app/AppBridge.tsx +17 -0
  93. package/dist/templates/next-app/src/uikit/components-app/AppRoutePage.tsx +112 -0
  94. package/dist/templates/next-app/src/uikit/{components → components-app}/LanguageSwitcher.tsx +15 -19
  95. package/dist/templates/next-app/src/uikit/{components → components-app}/ThemeSwitcher.tsx +53 -52
  96. package/dist/templates/next-app/src/uikit/components-pages/LanguageSwitcher.tsx +98 -0
  97. package/dist/templates/next-app/src/uikit/components-pages/PagesRoutePage.tsx +93 -0
  98. package/dist/templates/next-app/src/uikit/context/IOCContext.ts +16 -4
  99. package/dist/templates/next-app/src/uikit/hook/useStrictEffect.ts +32 -0
  100. package/dist/templates/next-app/tsconfig.json +3 -2
  101. package/package.json +1 -1
  102. package/dist/templates/next-app/src/server/PageParams.ts +0 -66
  103. package/dist/templates/next-app/src/uikit/components/BaseHeader.tsx +0 -80
  104. package/dist/templates/next-app/src/uikit/components/BaseLayout.tsx +0 -65
  105. package/dist/templates/next-app/src/uikit/components/ComboProvider.tsx +0 -58
  106. package/dist/templates/next-app/src/uikit/components/NextIntlProvider.tsx +0 -21
  107. /package/dist/templates/next-app/{src/app/[locale] → public}/favicon.ico +0 -0
  108. /package/dist/templates/next-app/src/uikit/{components → components-app}/LogoutButton.tsx +0 -0
@@ -0,0 +1,21 @@
1
+ import type { LocalesSchema } from '@migrations/schema/LocalesSchema';
2
+ import type { BridgeOrderBy } from './DBBridgeInterface';
3
+ import type { UpsertResult } from './LocalesRepositoryInterface';
4
+ import type { PaginationInterface } from './PaginationInterface';
5
+ import type { ImportLocalesData } from '../services/ApiLocaleService';
6
+
7
+ export interface AdminLocalesControllerInterface {
8
+ getLocales(query: {
9
+ page: number;
10
+ pageSize: number;
11
+ orderBy?: BridgeOrderBy;
12
+ }): Promise<PaginationInterface<LocalesSchema>>;
13
+
14
+ createLocale(
15
+ body: Omit<LocalesSchema, 'id' | 'created_at' | 'updated_at'>
16
+ ): Promise<{ success: boolean }>;
17
+
18
+ updateLocale(body: Partial<LocalesSchema>): Promise<void>;
19
+
20
+ importLocales(formData: ImportLocalesData): Promise<UpsertResult>;
21
+ }
@@ -0,0 +1,11 @@
1
+ import type { UserSchema } from '@migrations/schema/UserSchema';
2
+ import type { BridgeOrderBy } from './DBBridgeInterface';
3
+ import type { PaginationInterface } from './PaginationInterface';
4
+
5
+ export interface AdminUserControllerInterface {
6
+ getUsers(query: {
7
+ page: number;
8
+ pageSize: number;
9
+ orderBy?: BridgeOrderBy;
10
+ }): Promise<PaginationInterface<UserSchema>>;
11
+ }
@@ -0,0 +1,10 @@
1
+ import type { BridgeOrderBy } from './DBBridgeInterface';
2
+
3
+ export interface LocalesControllerJsonQuery {
4
+ locale: string;
5
+ orderBy?: BridgeOrderBy;
6
+ }
7
+
8
+ export interface LocalesControllerInterface {
9
+ json(query: LocalesControllerJsonQuery): Promise<Record<string, string>>;
10
+ }
@@ -1,11 +1,18 @@
1
1
  import type { PageI18nInterface } from '@config/i18n';
2
2
 
3
- export interface ParamsHandlerInterface {
3
+ /**
4
+ * 用于处理页面渲染的路由参数处理接口
5
+ */
6
+ export interface RouteParamsnHandlerInterface {
4
7
  getLocale(defaultLocale?: string): string;
5
8
  getI18nWithNotFound(): string;
6
- getI18nMessages(): Promise<Record<string, string>>;
9
+ getI18nMessages(
10
+ namespace?: string | string[]
11
+ ): Promise<Record<string, string>>;
7
12
  getI18nInterface<T extends PageI18nInterface>(
8
13
  i18nInterface: T,
9
14
  namespace?: string
10
15
  ): Promise<T>;
16
+
17
+ getTheme(): string | Promise<string>;
11
18
  }
@@ -3,9 +3,9 @@ import { type IOCIdentifierMapServer } from '@config/IOCIdentifier';
3
3
  import type {
4
4
  ServiceIdentifier,
5
5
  IOCContainerInterface,
6
- IOCFunctionInterface,
7
- LoggerInterface
6
+ IOCFunctionInterface
8
7
  } from '@qlover/corekit-bridge';
8
+ import type { LoggerInterface } from '@qlover/logger';
9
9
 
10
10
  export interface ServerInterface {
11
11
  readonly logger: LoggerInterface;
@@ -0,0 +1,8 @@
1
+ import type { UserSchema } from '@migrations/schema/UserSchema';
2
+ import type { LoginValidatorData } from '../validators/LoginValidator';
3
+
4
+ export interface UserControllerInerface {
5
+ login(body: LoginValidatorData): Promise<UserSchema>;
6
+ register(body: LoginValidatorData): Promise<UserSchema>;
7
+ logout(): Promise<void>;
8
+ }
@@ -2,7 +2,7 @@ import type { UserSchema } from '@migrations/schema/UserSchema';
2
2
 
3
3
  export interface UserServiceInterface {
4
4
  register(params: { email: string; password: string }): Promise<UserSchema>;
5
- login(params: { email: string; password: string }): Promise<unknown>;
5
+ login(params: { email: string; password: string }): Promise<UserSchema>;
6
6
 
7
7
  logout(): Promise<void>;
8
8
  }
@@ -3,7 +3,7 @@ export interface ValidationFaildResult {
3
3
  message: string;
4
4
  }
5
5
 
6
- export interface ValidatorInterface {
6
+ export interface ValidatorInterface<T> {
7
7
  /**
8
8
  * Validate the data and return validation result
9
9
  * @param data - The data to validate
@@ -19,5 +19,5 @@ export interface ValidatorInterface {
19
19
  * @returns The data if it is valid
20
20
  * @throws {import('@qlover/fe-corekit').ExecutorError} if the data is invalid, with validation errors
21
21
  */
22
- getThrow(data: unknown): unknown;
22
+ getThrow(data: unknown): Promise<T> | T;
23
23
  }
@@ -10,7 +10,7 @@ import {
10
10
  localesSchema,
11
11
  type LocalesSchema
12
12
  } from '@migrations/schema/LocalesSchema';
13
- import { I } from '@config/IOCIdentifier';
13
+ import { SupabaseBridge } from '../SupabaseBridge';
14
14
  import type {
15
15
  LocalesRepositoryInterface,
16
16
  UpsertResult
@@ -23,7 +23,7 @@ export class LocalesRepository implements LocalesRepositoryInterface {
23
23
  protected safeFields = Object.keys(localesSchema.shape);
24
24
 
25
25
  constructor(
26
- @inject(I.DBBridgeInterface) protected dbBridge: DBBridgeInterface,
26
+ @inject(SupabaseBridge) protected dbBridge: DBBridgeInterface,
27
27
  @inject(Datetime) protected datetime: Datetime
28
28
  ) {}
29
29
 
@@ -70,6 +70,12 @@ export class LocalesRepository implements LocalesRepositoryInterface {
70
70
  id: number,
71
71
  params: Partial<Omit<LocalesSchema, 'id' | 'created_at'>>
72
72
  ): Promise<void> {
73
+ if (!id || typeof id !== 'number') {
74
+ throw new Error(
75
+ 'ID is required and must be a number for updateById operation'
76
+ );
77
+ }
78
+
73
79
  const data = {
74
80
  ...params,
75
81
  updated_at: this.datetime.timestampz()
@@ -1,9 +1,9 @@
1
1
  import { ResourceStore } from '@qlover/corekit-bridge';
2
2
  import { inject, injectable } from 'inversify';
3
+ import { ResourceState } from '@/base/cases/ResourceState';
4
+ import { AdminLocalesApi } from '@/base/services/adminApi/AdminLocalesApi';
3
5
  import { ResourceService } from '@/base/services/ResourceService';
4
6
  import type { LocalesSchema } from '@migrations/schema/LocalesSchema';
5
- import { AdminLocalesApi } from './adminApi/AdminLocalesApi';
6
- import { ResourceState } from '../cases/ResourceState';
7
7
 
8
8
  @injectable()
9
9
  export class AdminLocalesService extends ResourceService<
@@ -1,4 +1,5 @@
1
1
  import { inject, injectable } from 'inversify';
2
+ import { omit } from 'lodash';
2
3
  import { revalidateTag } from 'next/cache';
3
4
  import type { LocalesSchema } from '@migrations/schema/LocalesSchema';
4
5
  import type { LocaleType } from '@config/i18n';
@@ -20,6 +21,12 @@ export type ImportLocalesData = {
20
21
  };
21
22
  };
22
23
 
24
+ export type GetLocalesParams = {
25
+ page: number;
26
+ pageSize: number;
27
+ orderBy?: BridgeOrderBy;
28
+ };
29
+
23
30
  @injectable()
24
31
  export class ApiLocaleService {
25
32
  constructor(
@@ -45,11 +52,9 @@ export class ApiLocaleService {
45
52
  );
46
53
  }
47
54
 
48
- async getLocales(params: {
49
- page: number;
50
- pageSize: number;
51
- orderBy?: BridgeOrderBy;
52
- }): Promise<PaginationInterface<LocalesSchema>> {
55
+ async getLocales(
56
+ params: GetLocalesParams
57
+ ): Promise<PaginationInterface<LocalesSchema>> {
53
58
  return this.localesRepository.pagination({
54
59
  page: params.page,
55
60
  pageSize: params.pageSize,
@@ -58,14 +63,20 @@ export class ApiLocaleService {
58
63
  }
59
64
 
60
65
  async update(data: Partial<LocalesSchema>): Promise<void> {
66
+ if (!data.id || typeof data.id !== 'number') {
67
+ throw new Error(
68
+ 'ID is required and must be a number for update operation'
69
+ );
70
+ }
71
+
61
72
  await this.localesRepository.updateById(
62
- data.id!,
63
- data as Omit<LocalesSchema, 'id' | 'created_at'>
73
+ data.id,
74
+ omit(data, ['id', 'created_at'])
64
75
  );
65
76
 
66
77
  // 清除所有支持的语言的缓存
67
78
  const revalidatePromises = i18nConfig.supportedLngs.map(async (locale) => {
68
- await revalidateTag(`i18n-${locale}`);
79
+ await revalidateTag(`i18n-${locale}`, 'default');
69
80
  });
70
81
  await Promise.all(revalidatePromises);
71
82
  }
@@ -75,7 +86,7 @@ export class ApiLocaleService {
75
86
 
76
87
  // 清除所有支持的语言的缓存
77
88
  const revalidatePromises = i18nConfig.supportedLngs.map(async (locale) => {
78
- await revalidateTag(`i18n-${locale}`);
89
+ await revalidateTag(`i18n-${locale}`, 'default');
79
90
  });
80
91
  await Promise.all(revalidatePromises);
81
92
  }
@@ -94,6 +105,10 @@ export class ApiLocaleService {
94
105
  result[key]!['value'] = key;
95
106
  result[key]![locale] = value;
96
107
 
108
+ if (locale === i18nConfig.fallbackLng) {
109
+ result[key]!['description'] = value;
110
+ }
111
+
97
112
  const { namespace: namespace2 } = splitI18nKey(key);
98
113
  result[key]!['namespace'] = namespace2 || namespace;
99
114
  });
@@ -111,7 +126,7 @@ export class ApiLocaleService {
111
126
  if (upsertResult.successCount > 0) {
112
127
  const revalidatePromises = i18nConfig.supportedLngs.map(
113
128
  async (locale) => {
114
- await revalidateTag(`i18n-${locale}`);
129
+ await revalidateTag(`i18n-${locale}`, 'default');
115
130
  }
116
131
  );
117
132
  await Promise.all(revalidatePromises);
@@ -2,7 +2,10 @@ import { inject, injectable } from 'inversify';
2
2
  import type { PaginationInterface } from '@/server/port/PaginationInterface';
3
3
  import type { UserSchema } from '@migrations/schema/UserSchema';
4
4
  import { UserRepository } from '../repositorys/UserRepository';
5
- import { PaginationValidator } from '../validators/PaginationValidator';
5
+ import {
6
+ type PaginationParams,
7
+ PaginationValidator
8
+ } from '../validators/PaginationValidator';
6
9
  import type { UserRepositoryInterface } from '../port/UserRepositoryInterface';
7
10
  import type { ValidatorInterface } from '../port/ValidatorInterface';
8
11
 
@@ -12,7 +15,7 @@ export class ApiUserService {
12
15
  @inject(UserRepository)
13
16
  protected userRepository: UserRepositoryInterface,
14
17
  @inject(PaginationValidator)
15
- protected paginationValidator: ValidatorInterface
18
+ protected paginationValidator: ValidatorInterface<PaginationParams>
16
19
  ) {}
17
20
 
18
21
  async getUsers(params: {
@@ -11,7 +11,9 @@ import {
11
11
  import type { ExtendedExecutorError } from './ExtendedExecutorError';
12
12
  import type { ImportLocalesData } from '../services/ApiLocaleService';
13
13
 
14
- export class LocalesValidator implements ValidatorInterface {
14
+ export class LocalesValidator implements ValidatorInterface<
15
+ Omit<LocalesSchema, 'id' | 'created_at' | 'updated_at'>
16
+ > {
15
17
  validate(data: unknown): void | ValidationFaildResult {
16
18
  if (typeof data !== 'object' || data === null) {
17
19
  return {
@@ -44,7 +46,7 @@ export class LocalesValidator implements ValidatorInterface {
44
46
  }
45
47
  }
46
48
 
47
- export class LocalesImportValidator implements ValidatorInterface {
49
+ export class LocalesImportValidator implements ValidatorInterface<ImportLocalesData> {
48
50
  getHasAnyFilesLocale(
49
51
  values: FormData
50
52
  ): { language: LocaleType; value: FormDataEntryValue }[] {
@@ -26,7 +26,7 @@ const passwordSchema = z
26
26
  .max(50, { message: V_PASSWORD_MAX_LENGTH })
27
27
  .regex(/^\S+$/, { message: V_PASSWORD_SPECIAL_CHARS });
28
28
 
29
- export class LoginValidator implements ValidatorInterface {
29
+ export class LoginValidator implements ValidatorInterface<LoginValidatorData> {
30
30
  validateEmail(data: unknown): void | ValidationFaildResult {
31
31
  const emailResult = emailSchema.safeParse(data);
32
32
  if (!emailResult.success) {
@@ -1,5 +1,4 @@
1
1
  import { z } from 'zod';
2
- import type { PaginationInterface } from '@/server/port/PaginationInterface';
3
2
  import { API_PAGE_INVALID } from '@config/Identifier';
4
3
  import {
5
4
  type ValidationFaildResult,
@@ -22,7 +21,13 @@ const paginationSchema = z.object({
22
21
  order: z.string().optional().default('0')
23
22
  });
24
23
 
25
- export class PaginationValidator implements ValidatorInterface {
24
+ export type PaginationParams = {
25
+ page: number;
26
+ pageSize: number;
27
+ orders?: BridgeOrderBy;
28
+ };
29
+
30
+ export class PaginationValidator implements ValidatorInterface<PaginationParams> {
26
31
  protected defaultPageSize = 10;
27
32
 
28
33
  validate(data: unknown): void | ValidationFaildResult {
@@ -39,12 +44,7 @@ export class PaginationValidator implements ValidatorInterface {
39
44
  }
40
45
  }
41
46
 
42
- getThrow(data: unknown): Pick<
43
- PaginationInterface<unknown>,
44
- 'page' | 'pageSize'
45
- > & {
46
- orders: BridgeOrderBy;
47
- } {
47
+ getThrow<T>(data: unknown): T {
48
48
  const result = paginationSchema.safeParse(data);
49
49
  if (!result.success) {
50
50
  throw new Error(result.error.issues[0].message);
@@ -58,7 +58,7 @@ export class PaginationValidator implements ValidatorInterface {
58
58
  orders: [
59
59
  result.data.orderBy || 'updated_at',
60
60
  order == '0' || order == '1' ? (+order as 0 | 1) : 0
61
- ]
62
- };
61
+ ] as BridgeOrderBy
62
+ } as T;
63
63
  }
64
64
  }
@@ -277,48 +277,4 @@ html,
277
277
  --fe-layout-light-trigger-bg: rgb(var(--color-bg-base));
278
278
  --fe-layout-light-trigger-color: rgb(var(--text-primary));
279
279
  }
280
-
281
- /* 滚动条样式变量 */
282
- --fe-scrollbar-size: 6px;
283
- --fe-scrollbar-thumb-bg: rgba(15 23 42 / 0.2);
284
- --fe-scrollbar-thumb-hover-bg: rgba(15 23 42 / 0.3);
285
- --fe-scrollbar-track-bg: transparent;
286
-
287
- /* 滚动条样式 */
288
- ::-webkit-scrollbar {
289
- width: var(--fe-scrollbar-size);
290
- height: var(--fe-scrollbar-size);
291
- }
292
-
293
- ::-webkit-scrollbar-track {
294
- background: var(--fe-scrollbar-track-bg);
295
- border-radius: var(--fe-scrollbar-size);
296
- }
297
-
298
- ::-webkit-scrollbar-thumb {
299
- background: var(--fe-scrollbar-thumb-bg);
300
- border-radius: var(--fe-scrollbar-size);
301
- }
302
-
303
- ::-webkit-scrollbar-thumb:hover {
304
- background: var(--fe-scrollbar-thumb-hover-bg);
305
- }
306
-
307
- ::-webkit-scrollbar-corner {
308
- background: transparent;
309
- }
310
-
311
- /* 可选的细滚动条样式 */
312
- .scrollbar-thin {
313
- --fe-scrollbar-size: 4px;
314
- }
315
-
316
- /* 隐藏滚动条但保持可滚动 */
317
- .scrollbar-hidden {
318
- -ms-overflow-style: none;
319
- scrollbar-width: none;
320
- &::-webkit-scrollbar {
321
- display: none;
322
- }
323
- }
324
280
  }
@@ -230,48 +230,4 @@
230
230
  --fe-layout-light-trigger-bg: rgb(30 41 59);
231
231
  --fe-layout-light-trigger-color: rgb(var(--text-primary));
232
232
  }
233
-
234
- /* 滚动条样式变量 */
235
- --fe-scrollbar-size: 6px;
236
- --fe-scrollbar-thumb-bg: rgba(255 255 255 / 0.2);
237
- --fe-scrollbar-thumb-hover-bg: rgba(255 255 255 / 0.3);
238
- --fe-scrollbar-track-bg: transparent;
239
-
240
- /* 滚动条样式 */
241
- ::-webkit-scrollbar {
242
- width: var(--fe-scrollbar-size);
243
- height: var(--fe-scrollbar-size);
244
- }
245
-
246
- ::-webkit-scrollbar-track {
247
- background: var(--fe-scrollbar-track-bg);
248
- border-radius: var(--fe-scrollbar-size);
249
- }
250
-
251
- ::-webkit-scrollbar-thumb {
252
- background: var(--fe-scrollbar-thumb-bg);
253
- border-radius: var(--fe-scrollbar-size);
254
- }
255
-
256
- ::-webkit-scrollbar-thumb:hover {
257
- background: var(--fe-scrollbar-thumb-hover-bg);
258
- }
259
-
260
- ::-webkit-scrollbar-corner {
261
- background: transparent;
262
- }
263
-
264
- /* 可选的细滚动条样式 */
265
- .scrollbar-thin {
266
- --fe-scrollbar-size: 4px;
267
- }
268
-
269
- /* 隐藏滚动条但保持可滚动 */
270
- .scrollbar-hidden {
271
- -ms-overflow-style: none;
272
- scrollbar-width: none;
273
- &::-webkit-scrollbar {
274
- display: none;
275
- }
276
- }
277
233
  }
@@ -243,48 +243,4 @@
243
243
  --fe-layout-light-trigger-bg: var(--ant-color-bg-container);
244
244
  --fe-layout-light-trigger-color: var(--ant-color-text);
245
245
  }
246
-
247
- /* 滚动条样式变量 */
248
- --fe-scrollbar-size: 6px;
249
- --fe-scrollbar-thumb-bg: rgba(244 114 182 / 0.3);
250
- --fe-scrollbar-thumb-hover-bg: rgba(244 114 182 / 0.4);
251
- --fe-scrollbar-track-bg: transparent;
252
-
253
- /* 滚动条样式 */
254
- ::-webkit-scrollbar {
255
- width: var(--fe-scrollbar-size);
256
- height: var(--fe-scrollbar-size);
257
- }
258
-
259
- ::-webkit-scrollbar-track {
260
- background: var(--fe-scrollbar-track-bg);
261
- border-radius: var(--fe-scrollbar-size);
262
- }
263
-
264
- ::-webkit-scrollbar-thumb {
265
- background: var(--fe-scrollbar-thumb-bg);
266
- border-radius: var(--fe-scrollbar-size);
267
- }
268
-
269
- ::-webkit-scrollbar-thumb:hover {
270
- background: var(--fe-scrollbar-thumb-hover-bg);
271
- }
272
-
273
- ::-webkit-scrollbar-corner {
274
- background: transparent;
275
- }
276
-
277
- /* 可选的细滚动条样式 */
278
- .scrollbar-thin {
279
- --fe-scrollbar-size: 4px;
280
- }
281
-
282
- /* 隐藏滚动条但保持可滚动 */
283
- .scrollbar-hidden {
284
- -ms-overflow-style: none;
285
- scrollbar-width: none;
286
- &::-webkit-scrollbar {
287
- display: none;
288
- }
289
- }
290
246
  }
@@ -2,5 +2,5 @@
2
2
  @import './zIndex.css';
3
3
 
4
4
  @import './antd-themes/index.css';
5
-
5
+ @import './scrollbar.css';
6
6
  @import './page.css';
@@ -0,0 +1,34 @@
1
+ html,
2
+ .fe-theme {
3
+ --scrollbar-track-color: var(--color-secondary);
4
+ --scrollbar-thumb-color: var(--color-text-secondary);
5
+ --scrollbar-thumb-hover-color: var(--color-brand-hover);
6
+
7
+ /* Webkit browsers (Chrome, Safari, Edge) - 全局样式,使用最高优先级 */
8
+ body *::-webkit-scrollbar,
9
+ *::-webkit-scrollbar {
10
+ width: 10px;
11
+ height: 10px;
12
+ }
13
+
14
+ body *::-webkit-scrollbar-track,
15
+ *::-webkit-scrollbar-track {
16
+ background: var(--scrollbar-track-color);
17
+ }
18
+
19
+ body *::-webkit-scrollbar-thumb,
20
+ *::-webkit-scrollbar-thumb {
21
+ background-color: var(--scrollbar-thumb-color);
22
+ border-radius: 6px;
23
+ border: 1px solid var(--scrollbar-track-color);
24
+ transition: background-color 0.2s ease;
25
+ }
26
+
27
+ body *::-webkit-scrollbar-thumb:hover {
28
+ background-color: var(--scrollbar-thumb-hover-color) !important;
29
+ }
30
+
31
+ *::-webkit-scrollbar-thumb:hover {
32
+ background-color: var(--scrollbar-thumb-hover-color) !important;
33
+ }
34
+ }
@@ -13,12 +13,13 @@ import { clsx } from 'clsx';
13
13
  import { usePathname } from 'next/navigation';
14
14
  import React, { useMemo, type HTMLAttributes } from 'react';
15
15
  import { AdminPageManager } from '@/base/cases/AdminPageManager';
16
- import { BaseHeader } from './BaseHeader';
17
- import { LanguageSwitcher } from './LanguageSwitcher';
16
+ import { COMMON_ADMIN_TITLE } from '@config/Identifier';
18
17
  import { LocaleLink } from './LocaleLink';
19
- import { LogoutButton } from './LogoutButton';
20
- import { ThemeSwitcher } from './ThemeSwitcher';
18
+ import { LanguageSwitcher } from '../components-app/LanguageSwitcher';
19
+ import { LogoutButton } from '../components-app/LogoutButton';
20
+ import { ThemeSwitcher } from '../components-app/ThemeSwitcher';
21
21
  import { useIOC } from '../hook/useIOC';
22
+ import { useWarnTranslations } from '../hook/useWarnTranslations';
22
23
  import type { ItemType } from 'antd/es/menu/interface';
23
24
 
24
25
  const { Sider } = Layout;
@@ -41,10 +42,13 @@ export function AdminLayout(props: AdminLayoutProps) {
41
42
  const page = useIOC(AdminPageManager);
42
43
  const collapsedSidebar = useStore(page, page.selectors.collapsedSidebar);
43
44
  const navItems = useStore(page, page.selectors.navItems);
45
+ const t = useWarnTranslations();
46
+
47
+ const title = useMemo(() => t(COMMON_ADMIN_TITLE), [t]);
44
48
 
45
49
  const selectedKey = useMemo(() => {
46
50
  // 移除语言前缀,例如 /en/admin/users -> /admin/users
47
- const normalizedPath = pathname.replace(/^\/[^/]+/, '');
51
+ const normalizedPath = pathname?.replace(/^\/[^/]+/, '') ?? '';
48
52
  return navItems.find((item) => item.pathname === normalizedPath)?.key || '';
49
53
  }, [pathname, navItems]);
50
54
 
@@ -81,7 +85,7 @@ export function AdminLayout(props: AdminLayoutProps) {
81
85
 
82
86
  return (
83
87
  <Layout data-testid="AdminLayout" className={className} {...rest}>
84
- <div className="overflow-y-auto overflow-x-hidden h-screen sticky top-0 bottom-0 scrollbar-thin scrollbar-gutter-stable">
88
+ <div className="overflow-y-auto overflow-x-hidden h-screen sticky top-0 bottom-0">
85
89
  <Sider
86
90
  className="h-full relative"
87
91
  onCollapse={() => page.toggleSidebar()}
@@ -111,11 +115,30 @@ export function AdminLayout(props: AdminLayoutProps) {
111
115
  </div>
112
116
 
113
117
  <Layout>
114
- <BaseHeader
115
- href="/admin"
116
- className="max-w-full pl-0"
117
- rightActions={rightActions}
118
- />
118
+ <header
119
+ data-testid="AdminLayoutHeader"
120
+ className="h-14 bg-secondary border-b border-c-border sticky top-0 z-50"
121
+ >
122
+ <div
123
+ className={clsx(
124
+ 'flex items-center justify-between h-full px-4 mx-auto max-w-7xl',
125
+ className
126
+ )}
127
+ >
128
+ <div className="flex items-center">
129
+ <LocaleLink
130
+ href="/admin"
131
+ title={title}
132
+ className="flex items-center hover:opacity-80 transition-opacity"
133
+ >
134
+ <span className="text-lg font-semibold text-text">{title}</span>
135
+ </LocaleLink>
136
+ </div>
137
+ <div className="flex items-center gap-2 md:gap-4">
138
+ {rightActions}
139
+ </div>
140
+ </div>
141
+ </header>
119
142
  <main
120
143
  className={clsx('p-2 bg-primary text-text flex-1', mainClassName)}
121
144
  >