generator-mico-cli 0.1.18

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 (155) hide show
  1. package/README.md +84 -0
  2. package/bin/mico.js +316 -0
  3. package/generators/micro-react/ignore-list.json +8 -0
  4. package/generators/micro-react/index.js +158 -0
  5. package/generators/micro-react/templates/.commitlintrc.js +6 -0
  6. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +129 -0
  7. package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +143 -0
  8. package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +206 -0
  9. package/generators/micro-react/templates/.cursor/rules/commit-conventions.mdc +111 -0
  10. package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +295 -0
  11. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +275 -0
  12. package/generators/micro-react/templates/.cursor/rules/micro-frontend.mdc +196 -0
  13. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +128 -0
  14. package/generators/micro-react/templates/.cursor/rules/request-auth.mdc +220 -0
  15. package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +206 -0
  16. package/generators/micro-react/templates/.editorconfig +16 -0
  17. package/generators/micro-react/templates/.env +3 -0
  18. package/generators/micro-react/templates/.eslintrc.js +30 -0
  19. package/generators/micro-react/templates/.husky/commit-msg +2 -0
  20. package/generators/micro-react/templates/.husky/pre-commit +2 -0
  21. package/generators/micro-react/templates/.lintstagedrc +5 -0
  22. package/generators/micro-react/templates/.stylelintrc.js +25 -0
  23. package/generators/micro-react/templates/AGENTS.md +39 -0
  24. package/generators/micro-react/templates/CICD/start_dev.sh +30 -0
  25. package/generators/micro-react/templates/CICD/start_local.sh +30 -0
  26. package/generators/micro-react/templates/CICD/start_prod.sh +30 -0
  27. package/generators/micro-react/templates/CICD/start_test.sh +30 -0
  28. package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +19 -0
  29. package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +19 -0
  30. package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +19 -0
  31. package/generators/micro-react/templates/CLAUDE.md +106 -0
  32. package/generators/micro-react/templates/README.md +84 -0
  33. package/generators/micro-react/templates/_gitignore +57 -0
  34. package/generators/micro-react/templates/_npmrc +2 -0
  35. package/generators/micro-react/templates/apps/layout/.env +4 -0
  36. package/generators/micro-react/templates/apps/layout/.eslintrc.js +10 -0
  37. package/generators/micro-react/templates/apps/layout/.lintstagedrc +17 -0
  38. package/generators/micro-react/templates/apps/layout/.prettierignore +3 -0
  39. package/generators/micro-react/templates/apps/layout/.prettierrc +8 -0
  40. package/generators/micro-react/templates/apps/layout/.stylelintrc.js +20 -0
  41. package/generators/micro-react/templates/apps/layout/README.md +37 -0
  42. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +54 -0
  43. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +37 -0
  44. package/generators/micro-react/templates/apps/layout/config/config.testing.ts +27 -0
  45. package/generators/micro-react/templates/apps/layout/config/config.ts +132 -0
  46. package/generators/micro-react/templates/apps/layout/config/routes.ts +13 -0
  47. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +78 -0
  48. package/generators/micro-react/templates/apps/layout/mock/menus.json +100 -0
  49. package/generators/micro-react/templates/apps/layout/mock/menus.ts +11 -0
  50. package/generators/micro-react/templates/apps/layout/mock/user.mock.ts +20 -0
  51. package/generators/micro-react/templates/apps/layout/package.json +45 -0
  52. package/generators/micro-react/templates/apps/layout/public/font/ar-SA.js +54 -0
  53. package/generators/micro-react/templates/apps/layout/public/font/default.js +54 -0
  54. package/generators/micro-react/templates/apps/layout/src/app.tsx +123 -0
  55. package/generators/micro-react/templates/apps/layout/src/assets/.gitkeep +0 -0
  56. package/generators/micro-react/templates/apps/layout/src/common/auth/cs-auth-manager.ts +220 -0
  57. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +41 -0
  58. package/generators/micro-react/templates/apps/layout/src/common/auth/tool.ts +3 -0
  59. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +6 -0
  60. package/generators/micro-react/templates/apps/layout/src/common/constants.ts +38 -0
  61. package/generators/micro-react/templates/apps/layout/src/common/env.ts +73 -0
  62. package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +69 -0
  63. package/generators/micro-react/templates/apps/layout/src/common/locale.ts +123 -0
  64. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +45 -0
  65. package/generators/micro-react/templates/apps/layout/src/common/menu/index.ts +2 -0
  66. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +143 -0
  67. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +92 -0
  68. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +73 -0
  69. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +188 -0
  70. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +186 -0
  71. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +132 -0
  72. package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +136 -0
  73. package/generators/micro-react/templates/apps/layout/src/common/request/types.ts +44 -0
  74. package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +75 -0
  75. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +107 -0
  76. package/generators/micro-react/templates/apps/layout/src/common/types.ts +7 -0
  77. package/generators/micro-react/templates/apps/layout/src/common/upload/index.ts +2 -0
  78. package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +401 -0
  79. package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +47 -0
  80. package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +35 -0
  81. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +25 -0
  82. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +44 -0
  83. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +121 -0
  84. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +15 -0
  85. package/generators/micro-react/templates/apps/layout/src/global.less +13 -0
  86. package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +3 -0
  87. package/generators/micro-react/templates/apps/layout/src/hooks/useAuth.ts +75 -0
  88. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +35 -0
  89. package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +112 -0
  90. package/generators/micro-react/templates/apps/layout/src/hooks/useTheme.ts +124 -0
  91. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +109 -0
  92. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +97 -0
  93. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +164 -0
  94. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +165 -0
  95. package/generators/micro-react/templates/apps/layout/src/layouts/index.less +71 -0
  96. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +91 -0
  97. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +20 -0
  98. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +19 -0
  99. package/generators/micro-react/templates/apps/layout/src/models/global.ts +13 -0
  100. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
  101. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -0
  102. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +171 -0
  103. package/generators/micro-react/templates/apps/layout/src/services/auth.ts +37 -0
  104. package/generators/micro-react/templates/apps/layout/src/services/oss.ts +40 -0
  105. package/generators/micro-react/templates/apps/layout/src/styles/arco-override.less +78 -0
  106. package/generators/micro-react/templates/apps/layout/src/styles/themes/dark/custom-var.less +244 -0
  107. package/generators/micro-react/templates/apps/layout/src/styles/themes/normal/custom-var.less +195 -0
  108. package/generators/micro-react/templates/apps/layout/src/styles/variables.less +5 -0
  109. package/generators/micro-react/templates/apps/layout/src/utils/format.ts +4 -0
  110. package/generators/micro-react/templates/apps/layout/tailwind.config.js +7 -0
  111. package/generators/micro-react/templates/apps/layout/tailwind.css +3 -0
  112. package/generators/micro-react/templates/apps/layout/tsconfig.json +3 -0
  113. package/generators/micro-react/templates/apps/layout/typings.d.ts +1 -0
  114. package/generators/micro-react/templates/deployDesc.md +60 -0
  115. package/generators/micro-react/templates/docs/commit-message.md +98 -0
  116. package/generators/micro-react/templates/package.json +35 -0
  117. package/generators/micro-react/templates/packages/shared-styles/README.md +125 -0
  118. package/generators/micro-react/templates/packages/shared-styles/arco-override.less +78 -0
  119. package/generators/micro-react/templates/packages/shared-styles/index.less +14 -0
  120. package/generators/micro-react/templates/packages/shared-styles/package.json +27 -0
  121. package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +10 -0
  122. package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +246 -0
  123. package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +195 -0
  124. package/generators/micro-react/templates/packages/shared-styles/variables-only.less +301 -0
  125. package/generators/micro-react/templates/packages/shared-styles/variables.less +363 -0
  126. package/generators/micro-react/templates/pnpm-workspace.yaml +9 -0
  127. package/generators/micro-react/templates/scripts/collect-dist.js +68 -0
  128. package/generators/micro-react/templates/scripts/create-umi-app.sh +61 -0
  129. package/generators/micro-react/templates/scripts/dev.js +133 -0
  130. package/generators/micro-react/templates/turbo.json +68 -0
  131. package/generators/subapp-react/ignore-list.json +7 -0
  132. package/generators/subapp-react/index.js +189 -0
  133. package/generators/subapp-react/templates/homepage/.env +4 -0
  134. package/generators/subapp-react/templates/homepage/README.md +116 -0
  135. package/generators/subapp-react/templates/homepage/_gitignore +9 -0
  136. package/generators/subapp-react/templates/homepage/config/config.dev.ts +59 -0
  137. package/generators/subapp-react/templates/homepage/config/config.prod.ts +41 -0
  138. package/generators/subapp-react/templates/homepage/config/config.testing.ts +40 -0
  139. package/generators/subapp-react/templates/homepage/config/config.ts +102 -0
  140. package/generators/subapp-react/templates/homepage/config/routes.ts +7 -0
  141. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +59 -0
  142. package/generators/subapp-react/templates/homepage/package.json +30 -0
  143. package/generators/subapp-react/templates/homepage/src/app.tsx +80 -0
  144. package/generators/subapp-react/templates/homepage/src/assets/yay.jpg +0 -0
  145. package/generators/subapp-react/templates/homepage/src/common/logger.ts +42 -0
  146. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +53 -0
  147. package/generators/subapp-react/templates/homepage/src/common/request.ts +49 -0
  148. package/generators/subapp-react/templates/homepage/src/global.less +26 -0
  149. package/generators/subapp-react/templates/homepage/src/pages/index.less +139 -0
  150. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +342 -0
  151. package/generators/subapp-react/templates/homepage/src/styles/theme.less +6 -0
  152. package/generators/subapp-react/templates/homepage/tsconfig.json +3 -0
  153. package/generators/subapp-react/templates/homepage/typings.d.ts +17 -0
  154. package/lib/utils.js +165 -0
  155. package/package.json +31 -0
@@ -0,0 +1,401 @@
1
+ import { fetchUploadSignedUrl } from '@/services/oss';
2
+ import type { UploadProps } from '@arco-design/web-react';
3
+ import { Message } from '@arco-design/web-react';
4
+ import type { UploadItem } from '@arco-design/web-react/es/Upload';
5
+ import SparkMD5 from 'spark-md5';
6
+ import type { TDirCategory } from '../constants';
7
+ import type {
8
+ OssUploadError,
9
+ UploadLifecycleEvent,
10
+ UploadNamedFile,
11
+ UploadOptions,
12
+ UploadResult,
13
+ } from './types';
14
+
15
+ const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
16
+ const DEFAULT_SIZE_LIMIT = 100 * 1024 * 1024; // 100MB
17
+
18
+ export interface FileDigestResult {
19
+ hex: string;
20
+ base64: string;
21
+ }
22
+
23
+ const binaryStringToHex = (input: string): string => {
24
+ let hex = '';
25
+ for (let i = 0; i < input.length; i += 1) {
26
+ hex += input.charCodeAt(i).toString(16).padStart(2, '0');
27
+ }
28
+ return hex;
29
+ };
30
+
31
+ const binaryStringToBase64 = (input: string): string => {
32
+ if (typeof window === 'undefined') {
33
+ return Buffer.from(input, 'binary').toString('base64');
34
+ }
35
+ return btoa(input);
36
+ };
37
+
38
+ export async function calculateFileMD5(
39
+ file: File,
40
+ options?: {
41
+ chunkSize?: number;
42
+ signal?: AbortSignal;
43
+ onProgress?: (percent: number) => void;
44
+ },
45
+ ): Promise<FileDigestResult> {
46
+ const chunkSize = options?.chunkSize ?? DEFAULT_CHUNK_SIZE;
47
+ const spark = new SparkMD5.ArrayBuffer();
48
+ const chunks = Math.ceil(file.size / chunkSize);
49
+ let currentChunk = 0;
50
+
51
+ return new Promise<FileDigestResult>((resolve, reject) => {
52
+ const reader = new FileReader();
53
+
54
+ const abort = (reason?: Error) => {
55
+ cleanup();
56
+ reject(reason ?? new DOMException('Aborted', 'AbortError'));
57
+ };
58
+
59
+ const onAbort = () => abort();
60
+
61
+ const cleanup = () => {
62
+ reader.onload = null;
63
+ reader.onerror = null;
64
+ options?.signal?.removeEventListener('abort', onAbort);
65
+ };
66
+
67
+ if (options?.signal?.aborted) {
68
+ abort();
69
+ return;
70
+ }
71
+
72
+ options?.signal?.addEventListener('abort', onAbort);
73
+
74
+ reader.onload = (event) => {
75
+ const target = event.target;
76
+ if (!target?.result) {
77
+ abort(new Error('读取文件分片失败'));
78
+ return;
79
+ }
80
+
81
+ spark.append(target.result as ArrayBuffer);
82
+ currentChunk += 1;
83
+ options?.onProgress?.(Number(((currentChunk / chunks) * 100).toFixed(2)));
84
+
85
+ if (currentChunk < chunks) {
86
+ loadNextChunk();
87
+ } else {
88
+ const raw = spark.end(true) as string;
89
+ const hex = binaryStringToHex(raw);
90
+ const base64 = binaryStringToBase64(raw);
91
+ cleanup();
92
+ resolve({ hex, base64 });
93
+ }
94
+ };
95
+
96
+ reader.onerror = () => {
97
+ abort(reader.error ?? new Error('读取文件失败'));
98
+ };
99
+
100
+ function loadNextChunk() {
101
+ const start = currentChunk * chunkSize;
102
+ const end = Math.min(start + chunkSize, file.size);
103
+ const slice =
104
+ file.slice ??
105
+ (
106
+ file as File & {
107
+ mozSlice?: File['slice'];
108
+ webkitSlice?: File['slice'];
109
+ }
110
+ )?.mozSlice ??
111
+ (file as File & { webkitSlice?: File['slice'] })?.webkitSlice;
112
+ if (!slice) {
113
+ abort(new Error('当前环境不支持文件分片读取'));
114
+ return;
115
+ }
116
+ reader.readAsArrayBuffer(slice.call(file, start, end));
117
+ }
118
+
119
+ loadNextChunk();
120
+ });
121
+ }
122
+
123
+ export function getFileExtension(file: File): string {
124
+ const name = file.name;
125
+ if (name?.includes('.')) {
126
+ const explicitExt = name.split('.').pop();
127
+ if (explicitExt && explicitExt.length > 0 && explicitExt !== name) {
128
+ return explicitExt.toLowerCase();
129
+ }
130
+ }
131
+
132
+ const mime = file.type;
133
+ if (mime) {
134
+ const parts = mime.split('/');
135
+ const candidate = parts.pop();
136
+ if (candidate && candidate.length > 0) {
137
+ return candidate.toLowerCase();
138
+ }
139
+ }
140
+
141
+ return 'bin';
142
+ }
143
+
144
+ function createAbortError(message: string): DOMException {
145
+ return new DOMException(message, 'AbortError');
146
+ }
147
+
148
+ async function uploadViaSignedUrl(
149
+ uploadUrl: string,
150
+ payload: {
151
+ data: Blob | File | ArrayBuffer;
152
+ contentType?: string;
153
+ contentMd5?: string;
154
+ },
155
+ options?: {
156
+ signal?: AbortSignal;
157
+ onProgress?: (event: UploadLifecycleEvent) => void;
158
+ },
159
+ ): Promise<void> {
160
+ await new Promise<void>((resolve, reject) => {
161
+ const xhr = new XMLHttpRequest();
162
+ xhr.open('PUT', uploadUrl, true);
163
+ xhr.responseType = 'text';
164
+
165
+ const resolvedType =
166
+ payload.contentType ||
167
+ (payload.data instanceof Blob ? payload.data.type : '') ||
168
+ 'application/octet-stream';
169
+ xhr.setRequestHeader('Content-Type', resolvedType);
170
+ if (payload.contentMd5) {
171
+ xhr.setRequestHeader('Content-MD5', payload.contentMd5);
172
+ }
173
+
174
+ const abort = () => {
175
+ xhr.abort();
176
+ reject(createAbortError('上传被取消'));
177
+ };
178
+
179
+ const handleAbort = () => abort();
180
+
181
+ if (options?.signal?.aborted) {
182
+ abort();
183
+ return;
184
+ }
185
+
186
+ options?.signal?.addEventListener('abort', handleAbort);
187
+
188
+ xhr.upload.onprogress = (event) => {
189
+ options?.onProgress?.({
190
+ stage: 'uploading',
191
+ loaded: event.loaded,
192
+ total: event.total,
193
+ percent: Number(((event.loaded / event.total) * 100).toFixed(2)),
194
+ });
195
+ };
196
+
197
+ xhr.onerror = () => {
198
+ options?.signal?.removeEventListener('abort', handleAbort);
199
+ reject(new Error('上传失败,请重试'));
200
+ };
201
+
202
+ xhr.onreadystatechange = () => {
203
+ if (xhr.readyState === 4) {
204
+ options?.signal?.removeEventListener('abort', handleAbort);
205
+ if (xhr.status >= 200 && xhr.status < 300) {
206
+ resolve();
207
+ } else {
208
+ reject(
209
+ new Error(
210
+ `上传失败,状态码 ${xhr.status}${
211
+ xhr.responseText ? `: ${xhr.responseText}` : ''
212
+ }`,
213
+ ),
214
+ );
215
+ }
216
+ }
217
+ };
218
+
219
+ xhr.send(payload.data);
220
+ });
221
+ }
222
+
223
+ export async function uploadToOss(
224
+ options: UploadOptions,
225
+ ): Promise<UploadResult> {
226
+ const { file, dirCategory, signal, onProgress } = options;
227
+ const sizeLimit =
228
+ typeof options.sizeLimit === 'number'
229
+ ? options.sizeLimit
230
+ : DEFAULT_SIZE_LIMIT;
231
+
232
+ if (sizeLimit > 0 && file.size > sizeLimit) {
233
+ const error: OssUploadError = Object.assign(new Error('文件超出大小限制'), {
234
+ code: 'FILE_TOO_LARGE',
235
+ });
236
+ throw error;
237
+ }
238
+
239
+ onProgress?.({ stage: 'hashing', percent: 0 });
240
+ const digest = await calculateFileMD5(file, {
241
+ signal,
242
+ onProgress: (percent) => {
243
+ onProgress?.({ stage: 'hashing', percent });
244
+ },
245
+ });
246
+
247
+ onProgress?.({ stage: 'signing', percent: 50 });
248
+ const ext = getFileExtension(file);
249
+ const sign = await fetchUploadSignedUrl({
250
+ md5_content: digest.base64,
251
+ file_extension: ext,
252
+ dir_category: dirCategory,
253
+ });
254
+
255
+ const blob = new Blob([file], {
256
+ type: sign.content_type || file.type || 'application/octet-stream',
257
+ });
258
+
259
+ await uploadViaSignedUrl(
260
+ sign.upload_url,
261
+ {
262
+ data: blob,
263
+ contentType: blob.type,
264
+ contentMd5: sign.content_md5 ?? digest.base64,
265
+ },
266
+ {
267
+ signal,
268
+ onProgress: (event) => {
269
+ onProgress?.({
270
+ ...event,
271
+ stage: 'uploading',
272
+ });
273
+ },
274
+ },
275
+ );
276
+
277
+ onProgress?.({ stage: 'uploading', percent: 100 });
278
+
279
+ return {
280
+ fileUrl: sign.file_url,
281
+ uploadUrl: sign.upload_url,
282
+ contentType: sign.content_type,
283
+ md5: digest.hex,
284
+ fileName: file.name,
285
+ fileKey: sign.file_key,
286
+ };
287
+ }
288
+
289
+ export interface CreateArcoUploadHandlerOptions {
290
+ dirCategory: TDirCategory;
291
+ sizeLimit?: number;
292
+ beforeUpload?: (file: File) => boolean | Promise<boolean>;
293
+ onUploaded?: (result: UploadResult) => void;
294
+ onUploadError?: (error: Error) => void;
295
+ }
296
+
297
+ export function createArcoUploadHandler(
298
+ options: CreateArcoUploadHandlerOptions,
299
+ ): UploadProps['customRequest'] {
300
+ type CustomRequestOptions = Parameters<
301
+ NonNullable<UploadProps['customRequest']>
302
+ >[0];
303
+
304
+ return (requestOptions: CustomRequestOptions) => {
305
+ const { file, onProgress, onError, onSuccess } = requestOptions;
306
+ const rawFile = file as File;
307
+
308
+ const controller = new AbortController();
309
+
310
+ const uploadRoutine = async () => {
311
+ try {
312
+ if (options.beforeUpload) {
313
+ const canUpload = await options.beforeUpload(rawFile);
314
+ if (!canUpload) {
315
+ throw Object.assign(new Error('文件未通过预检查'), {
316
+ code: 'PRE_CHECK_FAILED',
317
+ });
318
+ }
319
+ }
320
+
321
+ const result = await uploadToOss({
322
+ file: rawFile,
323
+ dirCategory: options.dirCategory,
324
+ sizeLimit: options.sizeLimit,
325
+ signal: controller.signal,
326
+ onProgress: (event) => {
327
+ let percent = 0;
328
+ if (event.stage === 'hashing') {
329
+ percent = (event.percent ?? 0) * 0.3;
330
+ } else if (event.stage === 'signing') {
331
+ percent = 40;
332
+ } else {
333
+ percent = 40 + (event.percent ?? 0) * 0.6;
334
+ }
335
+ onProgress?.(Math.min(100, Number(percent.toFixed(2))));
336
+ },
337
+ });
338
+
339
+ options.onUploaded?.(result);
340
+
341
+ onSuccess?.(result as unknown as UploadNamedFile);
342
+ } catch (error) {
343
+ if (error instanceof DOMException && error.name === 'AbortError') {
344
+ Message.info('上传已取消');
345
+ } else if ((error as OssUploadError)?.code === 'FILE_TOO_LARGE') {
346
+ Message.error('文件大小超过限制,无法上传');
347
+ } else if (error instanceof Error) {
348
+ Message.error(error.message);
349
+ }
350
+
351
+ options.onUploadError?.(error as Error);
352
+ onError?.(error as Error);
353
+ }
354
+ };
355
+
356
+ uploadRoutine().catch((error) => {
357
+ options.onUploadError?.(error as Error);
358
+ onError?.(error as Error);
359
+ });
360
+
361
+ return {
362
+ abort() {
363
+ controller.abort();
364
+ },
365
+ };
366
+ };
367
+ }
368
+
369
+ export function mapUploadItemsToNamedFiles(
370
+ items: UploadItem[],
371
+ ): UploadNamedFile[] {
372
+ return items
373
+ .filter((item) => item.status === 'done')
374
+ .map((item) => {
375
+ const response = item.response as
376
+ | UploadResult
377
+ | UploadNamedFile
378
+ | undefined;
379
+ if (response && 'fileUrl' in response) {
380
+ return {
381
+ name: item.name ?? response.fileName ?? '文件',
382
+ url: response.fileUrl,
383
+ fileKey: response.fileKey,
384
+ };
385
+ }
386
+ if (response && 'url' in response) {
387
+ return {
388
+ name: response.name ?? item.name ?? '文件',
389
+ url: response.url,
390
+ fileKey: response.fileKey,
391
+ };
392
+ }
393
+ if (item.url) {
394
+ return {
395
+ name: item.name ?? '文件',
396
+ url: item.url,
397
+ };
398
+ }
399
+ throw new Error('上传项缺少可用的文件地址');
400
+ });
401
+ }
@@ -0,0 +1,47 @@
1
+ import type { TDirCategory } from '../constants';
2
+
3
+ export interface UploadLifecycleEvent {
4
+ stage: 'hashing' | 'signing' | 'uploading';
5
+ /**
6
+ * 百分比 (0-100),可选。
7
+ */
8
+ percent?: number;
9
+ /**
10
+ * 已上传/已处理字节。
11
+ */
12
+ loaded?: number;
13
+ /**
14
+ * 总字节数。
15
+ */
16
+ total?: number;
17
+ }
18
+
19
+ export interface UploadResult {
20
+ fileUrl: string;
21
+ uploadUrl: string;
22
+ contentType?: string;
23
+ md5: string;
24
+ fileName: string;
25
+ fileKey?: string;
26
+ }
27
+
28
+ export interface UploadOptions {
29
+ file: File;
30
+ dirCategory: TDirCategory;
31
+ /**
32
+ * 默认 20MB,可传入 0 或 undefined 表示不限制。
33
+ */
34
+ sizeLimit?: number;
35
+ signal?: AbortSignal;
36
+ onProgress?: (event: UploadLifecycleEvent) => void;
37
+ }
38
+
39
+ export interface OssUploadError extends Error {
40
+ code?: string;
41
+ }
42
+
43
+ export interface UploadNamedFile {
44
+ name: string;
45
+ url: string;
46
+ fileKey?: string;
47
+ }
@@ -0,0 +1,35 @@
1
+ // ! 这个函数不能放到 ./helpers.ts 中,因为 ./request/index.ts 从 ./helpers.ts 导入了 getFromStorage, safeParseJSON,如果放到 ./helpers.ts 中,会导致循环引入
2
+ // ! 开发态下,发生循环引入时, mako 不会有编译时报错,但运行时会报错
3
+
4
+ import type { TDirCategory } from './constants';
5
+ import { uploadToOss } from './upload/oss';
6
+
7
+ /** 上传文件到 OSS 并获取 URL */
8
+ const uploadFiles = async ({
9
+ files,
10
+ dirCategory,
11
+ }: {
12
+ files: Array<File>;
13
+ dirCategory: TDirCategory;
14
+ }): Promise<string[]> => {
15
+ if (files.length === 0) {
16
+ return [];
17
+ }
18
+
19
+ const uploadPromises = files.map(async (file) => {
20
+ try {
21
+ const result = await uploadToOss({
22
+ file,
23
+ dirCategory,
24
+ });
25
+ return new URL(result.fileUrl).pathname;
26
+ } catch (error) {
27
+ console.error('文件上传失败:', error);
28
+ throw new Error(`文件 ${file.name} 上传失败`);
29
+ }
30
+ });
31
+
32
+ return Promise.all(uploadPromises);
33
+ };
34
+
35
+ export default uploadFiles;
@@ -0,0 +1,25 @@
1
+ import { getCurrentLocale, getIconFontPath } from '@/common/locale';
2
+ import { Icon } from '@arco-design/web-react';
3
+
4
+ // 该组件用于自定义的图标,用法见https://arco.design/react/components/icon#%E6%B7%BB%E5%8A%A0-iconbox-%E9%A1%B9%E7%9B%AE
5
+ // 图标的名称可以在设计图ued里面找到 https://www.figma.com/design/skTJZPfwLCDgEV9JC6wzpP/%E5%AE%A2%E6%9C%8D%E4%B8%AD%E5%BF%83_%E4%BC%9A%E8%AF%9D%E7%AE%A1%E7%90%86_%E5%B7%A5%E4%BD%9C%E5%8F%B0?node-id=647-4835&t=8swGel7i3bPnKkul-0
6
+ // 目前设置的字体前缀为webcs-,因此使用时需要加上前缀,如<IconFont type="webcs-outline_copy" />
7
+ // 字体库地址:https://arco.design/iconbox/mime/lib/1945/0
8
+
9
+ /**
10
+ * 根据当前语言加载对应的图标库
11
+ * 语言切换时会重新加载页面,所以这里只需要在初始化时根据当前语言加载
12
+ */
13
+ function createIconFont() {
14
+ // 获取当前语言(按优先级:URL参数 > localStorage > 浏览器默认)
15
+ const currentLocale = getCurrentLocale();
16
+ const iconPath = getIconFontPath(currentLocale);
17
+
18
+ // 创建 IconFont 实例
19
+ return Icon.addFromIconFontCn({ src: iconPath });
20
+ }
21
+
22
+ // 初始化 IconFont(在模块加载时根据当前语言加载对应的图标库)
23
+ const IconFont = createIconFont();
24
+
25
+ export default IconFont;
@@ -0,0 +1,44 @@
1
+ .micro-app-container {
2
+ width: 100%;
3
+ height: 100%;
4
+ position: relative;
5
+ overflow: auto;
6
+ }
7
+
8
+ .micro-app-loading {
9
+ position: absolute;
10
+ top: 0;
11
+ left: 0;
12
+ right: 0;
13
+ bottom: 0;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ background: var(--color-bg-1);
18
+ z-index: 10;
19
+ }
20
+
21
+ .micro-app-iframe {
22
+ width: 100%;
23
+ height: 100%;
24
+ border: none;
25
+ transition: opacity 0.3s;
26
+ }
27
+
28
+ .micro-app-content {
29
+ width: 100%;
30
+ height: 100%;
31
+ }
32
+
33
+ .micro-app-error {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: center;
37
+ justify-content: center;
38
+ height: 100%;
39
+ color: var(--color-text-3);
40
+
41
+ p {
42
+ margin: 8px 0;
43
+ }
44
+ }
@@ -0,0 +1,121 @@
1
+ import { microAppLogger } from '@/common/logger';
2
+ import { request } from '@/common/request';
3
+ import { Spin } from '@arco-design/web-react';
4
+ import { loadMicroApp, type MicroApp } from 'qiankun';
5
+ import React, { useEffect, useRef, useState } from 'react';
6
+ import './index.less';
7
+
8
+ interface MicroAppLoaderProps {
9
+ /** 微应用入口 URL */
10
+ entry: string;
11
+ /** 微应用唯一标识(用于生成容器 ID,应为无空格的技术名称) */
12
+ name: string;
13
+ /** 微应用显示名称(用于 loading 提示,可包含中文) */
14
+ displayName?: string;
15
+ }
16
+
17
+ /**
18
+ * 将路由路径转换为有效的 HTML ID
19
+ * 例如:/homepage → homepage, /audit/pending → audit-pending
20
+ */
21
+ const sanitizeId = (path: string): string => {
22
+ return (
23
+ path
24
+ .replace(/^\/+/, '') // 移除开头的斜杠
25
+ .replace(/\/+/g, '-') // 斜杠替换为连字符
26
+ .replace(/\s+/g, '-') // 空格替换为连字符
27
+ .replace(/[^a-zA-Z0-9_-]/g, '') || // 移除其他特殊字符
28
+ `app-${Date.now()}`
29
+ ); // 如果结果为空,使用时间戳
30
+ };
31
+
32
+ /**
33
+ * qiankun 微应用加载器
34
+ * 使用 loadMicroApp API 动态加载微前端应用
35
+ */
36
+ const MicroAppLoader: React.FC<MicroAppLoaderProps> = ({
37
+ entry,
38
+ name,
39
+ displayName,
40
+ }) => {
41
+ const containerRef = useRef<HTMLDivElement>(null);
42
+ const microAppRef = useRef<MicroApp | null>(null);
43
+ const [loading, setLoading] = useState(true);
44
+ const [error, setError] = useState<string | null>(null);
45
+
46
+ // 生成安全的容器 ID(避免空格和特殊字符导致选择器失效)
47
+ const safeAppName = sanitizeId(name);
48
+ const containerId = `micro-app-${safeAppName}`;
49
+ // 用于显示的名称(支持中文)
50
+ const label = displayName || name;
51
+
52
+ useEffect(() => {
53
+ if (!containerRef.current) return;
54
+
55
+ microAppLogger.log('Loading micro app:', {
56
+ name,
57
+ entry,
58
+ containerId,
59
+ containerExists: !!document.getElementById(containerId),
60
+ });
61
+
62
+ // 加载微应用
63
+ const loadApp = async () => {
64
+ try {
65
+ microAppRef.current = loadMicroApp({
66
+ name,
67
+ entry,
68
+ container: `#${containerId}`,
69
+ props: {
70
+ // 传递给子应用的数据
71
+ mainApp: '<%= projectName %>',
72
+ // 共享主应用的 request 实例
73
+ request,
74
+ },
75
+ });
76
+
77
+ // 等待微应用挂载完成
78
+ await microAppRef.current.mountPromise;
79
+ setLoading(false);
80
+ } catch (err) {
81
+ microAppLogger.error(`Failed to load micro app [${label}]:`, err);
82
+ setError(err instanceof Error ? err.message : 'Unknown error');
83
+ setLoading(false);
84
+ }
85
+ };
86
+
87
+ loadApp();
88
+
89
+ // 卸载微应用
90
+ return () => {
91
+ if (microAppRef.current) {
92
+ microAppRef.current.unmount();
93
+ microAppRef.current = null;
94
+ }
95
+ };
96
+ }, [entry, name, containerId, label]);
97
+
98
+ if (error) {
99
+ return (
100
+ <div className="micro-app-container">
101
+ <div className="micro-app-error">
102
+ <p>微应用加载失败</p>
103
+ <p className="error-detail">{error}</p>
104
+ </div>
105
+ </div>
106
+ );
107
+ }
108
+
109
+ return (
110
+ <div className="micro-app-container" ref={containerRef}>
111
+ {loading && (
112
+ <div className="micro-app-loading">
113
+ <Spin dot tip={`正在加载 ${label}...`} />
114
+ </div>
115
+ )}
116
+ <div id={containerId} className="micro-app-content" />
117
+ </div>
118
+ );
119
+ };
120
+
121
+ export default MicroAppLoader;
@@ -0,0 +1,15 @@
1
+ export const DEFAULT_NAME = 'Umi Max';
2
+
3
+ /**
4
+ * 无需认证的路由路径
5
+ */
6
+ export const NO_AUTH_ROUTES = {
7
+ LOGIN: '/user/login',
8
+ FORBIDDEN: '/403',
9
+ NOT_FOUND: '/404',
10
+ } as const;
11
+
12
+ /**
13
+ * 无需认证的路由列表
14
+ */
15
+ export const NO_AUTH_ROUTE_LIST = Object.values(NO_AUTH_ROUTES);
@@ -0,0 +1,13 @@
1
+ // ==================== 全局样式 ====================
2
+ // 导入共享主题变量(CSS Variables + Less Variables)
3
+ @import '<%= packageScope %>/shared-styles';
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ #<%= projectName %> {
10
+ height: 100vh;
11
+ min-height: 800px;
12
+ display: flex;
13
+ }
@@ -0,0 +1,3 @@
1
+ export { useAuth } from './useAuth';
2
+ export { useMenu } from './useMenu';
3
+ export { useTheme } from './useTheme';