@qlover/create-app 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/templates/react-app/README.en.md +257 -0
  5. package/dist/templates/react-app/README.md +29 -231
  6. package/dist/templates/react-app/__tests__/__mocks__/I18nService.ts +13 -0
  7. package/dist/templates/react-app/__tests__/__mocks__/MockAppConfit.ts +48 -0
  8. package/dist/templates/react-app/__tests__/__mocks__/MockDialogHandler.ts +16 -0
  9. package/dist/templates/react-app/__tests__/__mocks__/MockLogger.ts +14 -0
  10. package/dist/templates/react-app/__tests__/__mocks__/createMockGlobals.ts +92 -0
  11. package/dist/templates/react-app/__tests__/setup/index.ts +51 -0
  12. package/dist/templates/react-app/__tests__/src/App.test.tsx +139 -0
  13. package/dist/templates/react-app/__tests__/src/base/cases/AppConfig.test.ts +288 -0
  14. package/dist/templates/react-app/__tests__/src/base/cases/AppError.test.ts +102 -0
  15. package/dist/templates/react-app/__tests__/src/base/cases/DialogHandler.test.ts +228 -0
  16. package/dist/templates/react-app/__tests__/src/base/cases/I18nKeyErrorPlugin.test.ts +207 -0
  17. package/dist/templates/react-app/__tests__/src/base/cases/InversifyContainer.test.ts +181 -0
  18. package/dist/templates/react-app/__tests__/src/base/cases/PublicAssetsPath.test.ts +61 -0
  19. package/dist/templates/react-app/__tests__/src/base/cases/RequestLogger.test.ts +199 -0
  20. package/dist/templates/react-app/__tests__/src/base/cases/RequestStatusCatcher.test.ts +192 -0
  21. package/dist/templates/react-app/__tests__/src/base/cases/RouterLoader.test.ts +235 -0
  22. package/dist/templates/react-app/__tests__/src/base/services/I18nService.test.ts +224 -0
  23. package/dist/templates/react-app/__tests__/src/core/IOC.test.ts +257 -0
  24. package/dist/templates/react-app/__tests__/src/core/bootstraps/BootstrapsApp.test.ts +72 -0
  25. package/dist/templates/react-app/__tests__/src/main.integration.test.tsx +62 -0
  26. package/dist/templates/react-app/__tests__/src/main.test.tsx +46 -0
  27. package/dist/templates/react-app/__tests__/src/uikit/components/BaseHeader.test.tsx +88 -0
  28. package/dist/templates/react-app/config/app.router.ts +155 -0
  29. package/dist/templates/react-app/config/common.ts +9 -1
  30. package/dist/templates/react-app/docs/en/bootstrap.md +562 -0
  31. package/dist/templates/react-app/docs/en/development-guide.md +523 -0
  32. package/dist/templates/react-app/docs/en/env.md +482 -0
  33. package/dist/templates/react-app/docs/en/global.md +509 -0
  34. package/dist/templates/react-app/docs/en/i18n.md +268 -0
  35. package/dist/templates/react-app/docs/en/index.md +173 -0
  36. package/dist/templates/react-app/docs/en/ioc.md +424 -0
  37. package/dist/templates/react-app/docs/en/project-structure.md +434 -0
  38. package/dist/templates/react-app/docs/en/request.md +425 -0
  39. package/dist/templates/react-app/docs/en/router.md +404 -0
  40. package/dist/templates/react-app/docs/en/store.md +321 -0
  41. package/dist/templates/react-app/docs/en/test-guide.md +782 -0
  42. package/dist/templates/react-app/docs/en/theme.md +424 -0
  43. package/dist/templates/react-app/docs/en/typescript-guide.md +473 -0
  44. package/dist/templates/react-app/docs/zh/bootstrap.md +7 -0
  45. package/dist/templates/react-app/docs/zh/development-guide.md +523 -0
  46. package/dist/templates/react-app/docs/zh/env.md +24 -25
  47. package/dist/templates/react-app/docs/zh/global.md +28 -27
  48. package/dist/templates/react-app/docs/zh/i18n.md +268 -0
  49. package/dist/templates/react-app/docs/zh/index.md +173 -0
  50. package/dist/templates/react-app/docs/zh/ioc.md +44 -32
  51. package/dist/templates/react-app/docs/zh/project-structure.md +434 -0
  52. package/dist/templates/react-app/docs/zh/request.md +429 -0
  53. package/dist/templates/react-app/docs/zh/router.md +408 -0
  54. package/dist/templates/react-app/docs/zh/store.md +321 -0
  55. package/dist/templates/react-app/docs/zh/test-guide.md +782 -0
  56. package/dist/templates/react-app/docs/zh/theme.md +424 -0
  57. package/dist/templates/react-app/docs/zh/typescript-guide.md +473 -0
  58. package/dist/templates/react-app/package.json +9 -20
  59. package/dist/templates/react-app/src/base/cases/AppConfig.ts +16 -9
  60. package/dist/templates/react-app/src/base/cases/PublicAssetsPath.ts +7 -1
  61. package/dist/templates/react-app/src/base/services/I18nService.ts +15 -4
  62. package/dist/templates/react-app/src/base/services/RouteService.ts +43 -7
  63. package/dist/templates/react-app/src/core/bootstraps/BootstrapApp.ts +31 -10
  64. package/dist/templates/react-app/src/core/bootstraps/BootstrapsRegistry.ts +1 -1
  65. package/dist/templates/react-app/src/core/globals.ts +1 -3
  66. package/dist/templates/react-app/src/core/registers/RegisterCommon.ts +5 -3
  67. package/dist/templates/react-app/src/main.tsx +6 -1
  68. package/dist/templates/react-app/src/pages/404.tsx +0 -1
  69. package/dist/templates/react-app/src/pages/500.tsx +1 -1
  70. package/dist/templates/react-app/src/pages/base/RedirectPathname.tsx +3 -1
  71. package/dist/templates/react-app/src/styles/css/antd-themes/dark.css +3 -1
  72. package/dist/templates/react-app/src/styles/css/antd-themes/index.css +1 -1
  73. package/dist/templates/react-app/src/styles/css/antd-themes/pink.css +6 -1
  74. package/dist/templates/react-app/src/styles/css/page.css +1 -1
  75. package/dist/templates/react-app/src/uikit/components/BaseHeader.tsx +9 -2
  76. package/dist/templates/react-app/src/uikit/components/LocaleLink.tsx +5 -3
  77. package/dist/templates/react-app/src/uikit/hooks/useI18nGuard.ts +4 -6
  78. package/dist/templates/react-app/tsconfig.json +2 -1
  79. package/dist/templates/react-app/tsconfig.test.json +13 -0
  80. package/dist/templates/react-app/vite.config.ts +3 -2
  81. package/package.json +1 -1
@@ -0,0 +1,473 @@
1
+ # TypeScript 开发指南
2
+
3
+ ## 概述
4
+
5
+ 本指南介绍项目中 TypeScript 的使用规范,重点强调三种编程范式:
6
+
7
+ - 面向对象编程(OOP)
8
+ - 面向接口编程(Interface-based)
9
+ - 面向配置编程(Configuration-based)
10
+
11
+ ## 编程范式
12
+
13
+ ### 1. 面向对象编程(OOP)
14
+
15
+ 在项目中,我们大量使用面向对象的思想来组织代码,主要体现在:
16
+
17
+ #### 类的设计
18
+
19
+ ```typescript
20
+ // 基类设计
21
+ abstract class StoreInterface<T extends StoreStateInterface> {
22
+ protected state: T;
23
+
24
+ constructor(initialState: T) {
25
+ this.state = initialState;
26
+ }
27
+
28
+ abstract setState(state: Partial<T>): void;
29
+
30
+ getState(): T {
31
+ return this.state;
32
+ }
33
+ }
34
+
35
+ // 具体实现
36
+ @injectable()
37
+ class UserStore extends StoreInterface<UserState> {
38
+ constructor() {
39
+ super({
40
+ user: null,
41
+ loading: false
42
+ });
43
+ }
44
+
45
+ setState(state: Partial<UserState>): void {
46
+ this.state = { ...this.state, ...state };
47
+ this.notify();
48
+ }
49
+
50
+ private notify(): void {
51
+ // 通知观察者
52
+ }
53
+ }
54
+ ```
55
+
56
+ #### 继承和多态
57
+
58
+ ```typescript
59
+ // 基础服务接口
60
+ interface ServiceInterface {
61
+ initialize(): Promise<void>;
62
+ destroy(): void;
63
+ }
64
+
65
+ // 抽象基类
66
+ abstract class BaseService implements ServiceInterface {
67
+ abstract initialize(): Promise<void>;
68
+
69
+ destroy(): void {
70
+ // 通用清理逻辑
71
+ }
72
+ }
73
+
74
+ // 具体服务实现
75
+ class UserService extends BaseService {
76
+ async initialize(): Promise<void> {
77
+ // 用户服务初始化逻辑
78
+ }
79
+
80
+ // 扩展特定方法
81
+ async login(): Promise<void> {
82
+ // 登录逻辑
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### 2. 面向接口编程
88
+
89
+ 项目强调使用接口来定义契约,实现松耦合:
90
+
91
+ #### 接口定义
92
+
93
+ ```typescript
94
+ // 定义接口契约
95
+ interface StorageInterface<T> {
96
+ get(key: string): T | null;
97
+ set(key: string, value: T): void;
98
+ remove(key: string): void;
99
+ clear(): void;
100
+ }
101
+
102
+ // 实现接口
103
+ class LocalStorage<T> implements StorageInterface<T> {
104
+ get(key: string): T | null {
105
+ const value = localStorage.getItem(key);
106
+ return value ? JSON.parse(value) : null;
107
+ }
108
+
109
+ set(key: string, value: T): void {
110
+ localStorage.setItem(key, JSON.stringify(value));
111
+ }
112
+
113
+ remove(key: string): void {
114
+ localStorage.removeItem(key);
115
+ }
116
+
117
+ clear(): void {
118
+ localStorage.clear();
119
+ }
120
+ }
121
+ ```
122
+
123
+ #### 依赖注入
124
+
125
+ ```typescript
126
+ // 服务接口
127
+ interface UserServiceInterface {
128
+ getCurrentUser(): User | null;
129
+ updateProfile(data: UserProfile): Promise<void>;
130
+ }
131
+
132
+ // 服务实现
133
+ @injectable()
134
+ class UserService implements UserServiceInterface {
135
+ constructor(
136
+ @inject('StorageService') private storage: StorageInterface<User>,
137
+ @inject('ApiService') private api: ApiInterface
138
+ ) {}
139
+
140
+ getCurrentUser(): User | null {
141
+ return this.storage.get('currentUser');
142
+ }
143
+
144
+ async updateProfile(data: UserProfile): Promise<void> {
145
+ await this.api.put('/user/profile', data);
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### 3. 面向配置编程
151
+
152
+ 项目采用配置驱动的方式来管理功能特性:
153
+
154
+ #### 配置定义
155
+
156
+ ```typescript
157
+ // 配置接口
158
+ interface AppConfig {
159
+ api: {
160
+ baseUrl: string;
161
+ timeout: number;
162
+ retries: number;
163
+ };
164
+ auth: {
165
+ tokenKey: string;
166
+ tokenPrefix: string;
167
+ expiresIn: number;
168
+ };
169
+ theme: {
170
+ defaultTheme: 'light' | 'dark';
171
+ enableUserTheme: boolean;
172
+ };
173
+ }
174
+
175
+ // 配置实现
176
+ const appConfig: AppConfig = {
177
+ api: {
178
+ baseUrl: process.env.API_BASE_URL || '/api',
179
+ timeout: 5000,
180
+ retries: 3
181
+ },
182
+ auth: {
183
+ tokenKey: 'auth_token',
184
+ tokenPrefix: 'Bearer',
185
+ expiresIn: 7200
186
+ },
187
+ theme: {
188
+ defaultTheme: 'light',
189
+ enableUserTheme: true
190
+ }
191
+ };
192
+ ```
193
+
194
+ #### 配置驱动
195
+
196
+ ```typescript
197
+ // 配置驱动的功能实现
198
+ @injectable()
199
+ class ApiService {
200
+ constructor(
201
+ @inject('AppConfig') private config: AppConfig,
202
+ @inject('HttpClient') private http: HttpClient
203
+ ) {
204
+ // 使用配置初始化服务
205
+ this.http.setBaseUrl(config.api.baseUrl);
206
+ this.http.setTimeout(config.api.timeout);
207
+ }
208
+
209
+ async request<T>(options: RequestOptions): Promise<T> {
210
+ let retries = this.config.api.retries;
211
+
212
+ while (retries > 0) {
213
+ try {
214
+ return await this.http.request(options);
215
+ } catch (error) {
216
+ retries--;
217
+ if (retries === 0) throw error;
218
+ }
219
+ }
220
+ }
221
+ }
222
+ ```
223
+
224
+ ## 类型系统使用
225
+
226
+ ### 1. 泛型
227
+
228
+ ```typescript
229
+ // 泛型接口
230
+ interface Repository<T> {
231
+ findById(id: string): Promise<T>;
232
+ findAll(): Promise<T[]>;
233
+ create(data: Omit<T, 'id'>): Promise<T>;
234
+ update(id: string, data: Partial<T>): Promise<T>;
235
+ }
236
+
237
+ // 泛型类
238
+ class ApiRepository<T> implements Repository<T> {
239
+ constructor(private endpoint: string) {}
240
+
241
+ async findById(id: string): Promise<T> {
242
+ return api.get(`${this.endpoint}/${id}`);
243
+ }
244
+
245
+ // ... 其他方法实现
246
+ }
247
+ ```
248
+
249
+ ### 2. 类型工具
250
+
251
+ ```typescript
252
+ // 类型映射
253
+ type Nullable<T> = { [P in keyof T]: T[P] | null };
254
+
255
+ // 条件类型
256
+ type ArrayType<T> = T extends Array<infer U> ? U : never;
257
+
258
+ // 工具类型
259
+ type PartialDeep<T> = {
260
+ [P in keyof T]?: T[P] extends object ? PartialDeep<T[P]> : T[P];
261
+ };
262
+ ```
263
+
264
+ ### 3. 装饰器
265
+
266
+ ```typescript
267
+ // 方法装饰器
268
+ function Cached(ttl: number = 3600) {
269
+ return function (
270
+ target: any,
271
+ propertyKey: string,
272
+ descriptor: PropertyDescriptor
273
+ ) {
274
+ const original = descriptor.value;
275
+ const cache = new Map();
276
+
277
+ descriptor.value = async function (...args: any[]) {
278
+ const key = JSON.stringify(args);
279
+ if (cache.has(key)) {
280
+ const { value, timestamp } = cache.get(key);
281
+ if (Date.now() - timestamp < ttl * 1000) {
282
+ return value;
283
+ }
284
+ }
285
+
286
+ const result = await original.apply(this, args);
287
+ cache.set(key, { value: result, timestamp: Date.now() });
288
+ return result;
289
+ };
290
+ };
291
+ }
292
+
293
+ // 使用装饰器
294
+ class UserService {
295
+ @Cached(1800)
296
+ async getUserProfile(id: string): Promise<UserProfile> {
297
+ return this.api.get(`/users/${id}`);
298
+ }
299
+ }
300
+ ```
301
+
302
+ ## 最佳实践
303
+
304
+ ### 1. 类型定义
305
+
306
+ - 使用 interface 定义对象结构
307
+ - 使用 type 定义联合类型和工具类型
308
+ - 优先使用 readonly 确保不可变性
309
+ - 合理使用可选属性
310
+
311
+ ```typescript
312
+ // 好的实践
313
+ interface UserProfile {
314
+ readonly id: string;
315
+ name: string;
316
+ email: string;
317
+ avatar?: string;
318
+ preferences: Readonly<UserPreferences>;
319
+ }
320
+
321
+ // 避免的实践
322
+ interface BadUserProfile {
323
+ [key: string]: any; // 避免使用索引签名
324
+ data: any; // 避免使用 any
325
+ }
326
+ ```
327
+
328
+ ### 2. 错误处理
329
+
330
+ ```typescript
331
+ // 自定义错误类
332
+ class ApiError extends Error {
333
+ constructor(
334
+ public code: string,
335
+ message: string,
336
+ public data?: any
337
+ ) {
338
+ super(message);
339
+ this.name = 'ApiError';
340
+ }
341
+ }
342
+
343
+ // 类型守卫
344
+ function isApiError(error: unknown): error is ApiError {
345
+ return error instanceof ApiError;
346
+ }
347
+
348
+ // 错误处理
349
+ try {
350
+ await api.request();
351
+ } catch (error) {
352
+ if (isApiError(error)) {
353
+ handleApiError(error);
354
+ } else {
355
+ handleUnknownError(error);
356
+ }
357
+ }
358
+ ```
359
+
360
+ ### 3. 异步处理
361
+
362
+ ```typescript
363
+ // 异步结果类型
364
+ interface AsyncResult<T, E = Error> {
365
+ data: T | null;
366
+ error: E | null;
367
+ loading: boolean;
368
+ }
369
+
370
+ // 异步操作包装器
371
+ async function asyncWrapper<T, E = Error>(
372
+ promise: Promise<T>
373
+ ): Promise<AsyncResult<T, E>> {
374
+ try {
375
+ const data = await promise;
376
+ return { data, error: null, loading: false };
377
+ } catch (error) {
378
+ return { data: null, error: error as E, loading: false };
379
+ }
380
+ }
381
+ ```
382
+
383
+ ## 开发工具配置
384
+
385
+ ### 1. TSConfig 配置
386
+
387
+ ```json
388
+ {
389
+ "compilerOptions": {
390
+ "target": "ES2020",
391
+ "module": "ESNext",
392
+ "strict": true,
393
+ "esModuleInterop": true,
394
+ "skipLibCheck": true,
395
+ "forceConsistentCasingInFileNames": true,
396
+ "experimentalDecorators": true,
397
+ "emitDecoratorMetadata": true
398
+ }
399
+ }
400
+ ```
401
+
402
+ ### 2. ESLint 配置
403
+
404
+ ```json
405
+ {
406
+ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
407
+ "rules": {
408
+ "@typescript-eslint/explicit-function-return-type": "error",
409
+ "@typescript-eslint/no-explicit-any": "error",
410
+ "@typescript-eslint/no-unused-vars": "error"
411
+ }
412
+ }
413
+ ```
414
+
415
+ ## 调试技巧
416
+
417
+ ### 1. 类型断言
418
+
419
+ ```typescript
420
+ // 类型断言
421
+ function processValue(value: unknown) {
422
+ if (typeof value === 'string') {
423
+ return value.toUpperCase();
424
+ }
425
+ return String(value);
426
+ }
427
+
428
+ // 类型收窄
429
+ function isString(value: unknown): value is string {
430
+ return typeof value === 'string';
431
+ }
432
+ ```
433
+
434
+ ### 2. 调试工具
435
+
436
+ ```typescript
437
+ // 类型检查
438
+ type Debug<T> = {
439
+ [P in keyof T]: T[P];
440
+ };
441
+
442
+ // 运行时类型检查
443
+ function assertType<T>(value: unknown, message?: string): asserts value is T {
444
+ if (!isValidType<T>(value)) {
445
+ throw new TypeError(message || 'Type assertion failed');
446
+ }
447
+ }
448
+ ```
449
+
450
+ ## 性能优化
451
+
452
+ ### 1. 类型优化
453
+
454
+ ```typescript
455
+ // 使用类型缓存
456
+ type CachedType<T> = T extends Function ? T : Readonly<T>;
457
+
458
+ // 避免过度使用泛型
459
+ type SimpleType = string | number;
460
+ type ComplexType<T> = T extends SimpleType ? T : never;
461
+ ```
462
+
463
+ ### 2. 编译优化
464
+
465
+ - 使用 project references
466
+ - 启用增量编译
467
+ - 优化类型导入
468
+
469
+ ## 参考资源
470
+
471
+ - [TypeScript 官方文档](https://www.typescriptlang.org/docs/)
472
+ - [TypeScript 设计模式](https://refactoring.guru/design-patterns/typescript)
473
+ - [Clean Code TypeScript](https://github.com/labs42io/clean-code-typescript)
@@ -7,34 +7,21 @@
7
7
  "homepage": "",
8
8
  "author": "qlover",
9
9
  "license": "ISC",
10
- "main": "./dist/es/index.js",
11
- "module": "./dist/es/index.js",
12
- "types": "./dist/es/index.d.ts",
13
- "exports": {
14
- ".": {
15
- "types": "./dist/es/index.d.ts",
16
- "import": "./dist/es/index.js",
17
- "require": "./dist/cjs/index.js"
18
- },
19
- "./cjs/*": "./dist/cjs/*",
20
- "./es/*": "./dist/es/*",
21
- "./package.json": "./package.json"
22
- },
23
10
  "repository": {
24
11
  "type": "git",
25
12
  "url": "git+https://github.com/qlover/fe-base.git",
26
13
  "directory": ""
27
14
  },
28
15
  "files": [
29
- "bin",
30
16
  "dist",
31
17
  "package.json",
32
- "README.md"
18
+ "README.md",
19
+ "README.en.md",
20
+ "docs"
33
21
  ],
34
22
  "keywords": [
35
- "scripts",
36
- "release",
37
- "fe-release"
23
+ "react-app",
24
+ "react-app-template"
38
25
  ],
39
26
  "publishConfig": {
40
27
  "access": "public"
@@ -48,7 +35,7 @@
48
35
  "dev:prod": "vite --mode production",
49
36
  "build": "npm run lint && vite build",
50
37
  "lint": "tsc -b --noEmit && eslint ./src --fix",
51
- "prettier": "prettier --write ./src",
38
+ "prettier": "prettier --write ./src ./docs ./config",
52
39
  "preview": "vite preview",
53
40
  "test": "vitest run"
54
41
  },
@@ -57,7 +44,7 @@
57
44
  "@qlover/corekit-bridge": "latest",
58
45
  "@qlover/fe-corekit": "latest",
59
46
  "@qlover/logger": "^0.1.1",
60
- "@qlover/slice-store-react": "^1.0.8",
47
+ "@qlover/slice-store-react": "^1.2.6",
61
48
  "@tailwindcss/postcss": "^4.1.8",
62
49
  "@tailwindcss/vite": "^4.1.8",
63
50
  "antd": "^5.25.3",
@@ -80,6 +67,7 @@
80
67
  "@qlover/eslint-plugin-fe-dev": "^0.2.0",
81
68
  "@qlover/fe-scripts": "latest",
82
69
  "@qlover/fe-standard": "^0.0.4",
70
+ "@testing-library/react": "^16.3.0",
83
71
  "@types/lodash": "^4.17.13",
84
72
  "@types/react": "^18.3.11",
85
73
  "@types/react-dom": "^18.3.0",
@@ -95,6 +83,7 @@
95
83
  "eslint-plugin-react-refresh": "^0.4.14",
96
84
  "eslint-plugin-vitest": "^0.5.4",
97
85
  "globals": "^15.12.0",
86
+ "jsdom": "^26.1.0",
98
87
  "postcss": "^8.5.4",
99
88
  "prettier": "^3.5.3",
100
89
  "sass-embedded": "^1.79.4",
@@ -28,6 +28,17 @@ import type { EnvConfigInterface } from '@qlover/corekit-bridge';
28
28
  * console.log(config.aiApiBaseUrl); // Value from VITE_AI_API_BASE_URL
29
29
  */
30
30
  export class AppConfig implements EnvConfigInterface {
31
+ constructor(
32
+ /**
33
+ * Current environment mode for Vite
34
+ * @description Represents the running environment (development, production, etc.)
35
+ * Automatically set based on the current .env file being used
36
+ *
37
+ * from vite.config `VITE_USER_NODE_ENV`
38
+ */
39
+ readonly env: string = import.meta.env.VITE_USER_NODE_ENV
40
+ ) {}
41
+
31
42
  /**
32
43
  * Application name identifier
33
44
  * @description Injected from VITE_APP_NAME environment variable
@@ -40,15 +51,6 @@ export class AppConfig implements EnvConfigInterface {
40
51
  */
41
52
  readonly appVersion = '';
42
53
 
43
- /**
44
- * Current environment mode for Vite
45
- * @description Represents the running environment (development, production, etc.)
46
- * Automatically set based on the current .env file being used
47
- *
48
- * from vite.config `mode`
49
- */
50
- readonly env: string = import.meta.env.MODE;
51
-
52
54
  /**
53
55
  * Storage key for user authentication token
54
56
  * @description Injected from VITE_USER_TOKEN_STORAGE_KEY environment variable
@@ -113,4 +115,9 @@ export class AppConfig implements EnvConfigInterface {
113
115
  * Project startup href, usually from window.location.href
114
116
  */
115
117
  readonly bootHref = '';
118
+
119
+ /** Flag indicating if the current environment is production */
120
+ get isProduction(): boolean {
121
+ return this.env === 'production';
122
+ }
116
123
  }
@@ -12,6 +12,12 @@ export class PublicAssetsPath {
12
12
  constructor(protected prefix: string = routerPrefix) {}
13
13
 
14
14
  getPath(path: string): string {
15
- return this.prefix + `/${path}`;
15
+ if (!this.prefix) {
16
+ return path.startsWith('/') ? path : `/${path}`;
17
+ }
18
+ const prefix = this.prefix.endsWith('/')
19
+ ? this.prefix.slice(0, -1)
20
+ : this.prefix;
21
+ return prefix + (path.startsWith('/') ? path : `/${path}`);
16
22
  }
17
23
  }
@@ -9,6 +9,8 @@ import {
9
9
  type StoreStateInterface,
10
10
  StoreInterface
11
11
  } from '@qlover/corekit-bridge';
12
+ import { useLocaleRoutes } from '@config/common';
13
+
12
14
  const { supportedLngs, fallbackLng } = i18nConfig;
13
15
 
14
16
  export type I18nServiceLocale = (typeof supportedLngs)[number];
@@ -46,8 +48,10 @@ export class I18nService
46
48
  merge({}, i18nConfig, {
47
49
  debug,
48
50
  detection: {
49
- order: ['pathLanguageDetector', 'navigator'], // use custom detector
50
- caches: []
51
+ order: useLocaleRoutes
52
+ ? ['pathLanguageDetector', 'navigator', 'localStorage']
53
+ : ['localStorage', 'navigator'],
54
+ caches: useLocaleRoutes ? [] : ['localStorage']
51
55
  }
52
56
  })
53
57
  );
@@ -67,8 +71,11 @@ export class I18nService
67
71
 
68
72
  return fallbackLng;
69
73
  },
70
- cacheUserLanguage() {
71
- // no cache, because we get language from URL
74
+ cacheUserLanguage(lng: string) {
75
+ // Only cache language if not using locale routes
76
+ if (!useLocaleRoutes) {
77
+ localStorage.setItem('i18nextLng', lng);
78
+ }
72
79
  }
73
80
  };
74
81
  i18n.services.languageDetector.addDetector(pathLanguageDetector);
@@ -76,6 +83,10 @@ export class I18nService
76
83
 
77
84
  async changeLanguage(language: I18nServiceLocale): Promise<void> {
78
85
  await i18n.changeLanguage(language);
86
+ // 如果不使用本地化路由,则保存语言设置到本地存储
87
+ if (!useLocaleRoutes) {
88
+ localStorage.setItem('i18nextLng', language);
89
+ }
79
90
  }
80
91
 
81
92
  changeLoading(loading: boolean): void {