@mxmweb/zui 1.1.13 → 1.3.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 (130) hide show
  1. package/README.md +61 -3
  2. package/assets/style.css +1 -0
  3. package/cluster_enter.d.ts +25 -0
  4. package/index.js +33 -0
  5. package/package.json +24 -39
  6. package/.editorconfig +0 -38
  7. package/.prettierignore +0 -16
  8. package/.prettierrc +0 -17
  9. package/.releaserc.json +0 -36
  10. package/CHANGELOG.md +0 -58
  11. package/CONTRIBUTING.md +0 -111
  12. package/NPMREADME.md +0 -0
  13. package/bash.exe.stackdump +0 -40
  14. package/components.json +0 -21
  15. package/dist/Preview.d.ts +0 -3
  16. package/dist/README.md +0 -4
  17. package/dist/assets/style.css +0 -1
  18. package/dist/containers/DashboardContainer.d.ts +0 -35
  19. package/dist/containers/DockContainer.d.ts +0 -24
  20. package/dist/elements/Button.d.ts +0 -17
  21. package/dist/elements/CustomDock.d.ts +0 -25
  22. package/dist/elements/DropDownButton.d.ts +0 -24
  23. package/dist/elements/DropdownMenu.d.ts +0 -17
  24. package/dist/elements/GoggleNavbar.d.ts +0 -31
  25. package/dist/elements/Uploader/UploadItem.d.ts +0 -35
  26. package/dist/elements/Uploader/example.d.ts +0 -7
  27. package/dist/elements/Uploader/index.d.ts +0 -9
  28. package/dist/elements/Uploader/styles.d.ts +0 -65
  29. package/dist/elements/Uploader/types.d.ts +0 -87
  30. package/dist/elements/Uploader/utils.d.ts +0 -31
  31. package/dist/elements/Uploader.d.ts +0 -2
  32. package/dist/examples/DockContainerExample.d.ts +0 -3
  33. package/dist/icons/Icon.d.ts +0 -7
  34. package/dist/icons/Icon.tsx +0 -82
  35. package/dist/icons/index.d.ts +0 -13
  36. package/dist/icons/index.tsx +0 -92
  37. package/dist/icons/lazyIndex.d.ts +0 -7
  38. package/dist/icons/lazyIndex.tsx +0 -49
  39. package/dist/icons/rag/csv.svg +0 -3
  40. package/dist/icons/rag/document.svg +0 -3
  41. package/dist/icons/rag/excel.svg +0 -3
  42. package/dist/icons/rag/file.svg +0 -3
  43. package/dist/icons/rag/folder.svg +0 -5
  44. package/dist/icons/rag/json.svg +0 -3
  45. package/dist/icons/rag/knowledgebase.svg +0 -3
  46. package/dist/icons/rag/netretrive.svg +0 -3
  47. package/dist/icons/rag/odf.svg +0 -7
  48. package/dist/icons/rag/pdf.svg +0 -3
  49. package/dist/icons/rag/pic.svg +0 -3
  50. package/dist/icons/rag/ppt.svg +0 -3
  51. package/dist/icons/rag/think.svg +0 -6
  52. package/dist/icons/rag/txt.svg +0 -3
  53. package/dist/icons/rag/url.svg +0 -3
  54. package/dist/icons/rag/word.svg +0 -3
  55. package/dist/icons/rag/wps.svg +0 -3
  56. package/dist/icons/rag/zip.svg +0 -7
  57. package/dist/index.js +0 -2299
  58. package/dist/lib_enter.d.ts +0 -13
  59. package/dist/main.d.ts +0 -1
  60. package/dist/mock.csv +0 -16
  61. package/dist/mock_/345/211/257/346/234/254.csv +0 -16
  62. package/dist/package.json +0 -30
  63. package/dist/theme/styledTheme.d.ts +0 -101
  64. package/dist/vite.svg +0 -1
  65. package/eslint.config.js +0 -92
  66. package/index.html +0 -13
  67. package/postcss.config.cjs +0 -19
  68. package/public/mock.csv +0 -16
  69. package/public/mock_/345/211/257/346/234/254.csv +0 -16
  70. package/public/vite.svg +0 -1
  71. package/src/Preview.tsx +0 -15
  72. package/src/assets/img/excel.png +0 -0
  73. package/src/assets/img/img.png +0 -0
  74. package/src/assets/img/pdf.png +0 -0
  75. package/src/assets/img/ppt.png +0 -0
  76. package/src/assets/img/txt.png +0 -0
  77. package/src/assets/img/word.png +0 -0
  78. package/src/containers/DashboardContainer.tsx +0 -507
  79. package/src/containers/DockContainer.tsx +0 -186
  80. package/src/containers/style.css +0 -37
  81. package/src/elements/Button.tsx +0 -118
  82. package/src/elements/CustomDock.tsx +0 -287
  83. package/src/elements/DropDownButton.tsx +0 -249
  84. package/src/elements/DropdownMenu.tsx +0 -184
  85. package/src/elements/GoggleNavbar.tsx +0 -184
  86. package/src/elements/Uploader/README.md +0 -249
  87. package/src/elements/Uploader/UploadItem.tsx +0 -298
  88. package/src/elements/Uploader/example.tsx +0 -95
  89. package/src/elements/Uploader/index.tsx +0 -702
  90. package/src/elements/Uploader/styles.tsx +0 -291
  91. package/src/elements/Uploader/types.ts +0 -119
  92. package/src/elements/Uploader/utils.ts +0 -200
  93. package/src/elements/Uploader.tsx +0 -3
  94. package/src/examples/DockContainerExample.tsx +0 -237
  95. package/src/icons/Icon.tsx +0 -82
  96. package/src/icons/index.tsx +0 -92
  97. package/src/icons/lazyIndex.tsx +0 -49
  98. package/src/icons/rag/csv.svg +0 -3
  99. package/src/icons/rag/document.svg +0 -3
  100. package/src/icons/rag/excel.svg +0 -3
  101. package/src/icons/rag/file.svg +0 -3
  102. package/src/icons/rag/folder.svg +0 -5
  103. package/src/icons/rag/json.svg +0 -3
  104. package/src/icons/rag/knowledgebase.svg +0 -3
  105. package/src/icons/rag/netretrive.svg +0 -3
  106. package/src/icons/rag/odf.svg +0 -7
  107. package/src/icons/rag/pdf.svg +0 -3
  108. package/src/icons/rag/pic.svg +0 -3
  109. package/src/icons/rag/ppt.svg +0 -3
  110. package/src/icons/rag/think.svg +0 -6
  111. package/src/icons/rag/txt.svg +0 -3
  112. package/src/icons/rag/url.svg +0 -3
  113. package/src/icons/rag/word.svg +0 -3
  114. package/src/icons/rag/wps.svg +0 -3
  115. package/src/icons/rag/zip.svg +0 -7
  116. package/src/lib_enter.ts +0 -27
  117. package/src/main.tsx +0 -11
  118. package/src/style.css +0 -9
  119. package/src/theme/styledTheme.tsx +0 -253
  120. package/src/type.d.ts +0 -0
  121. package/src/types/images.d.ts +0 -12
  122. package/src/types/svg-modules.d.ts +0 -24
  123. package/src/vite-env.d.ts +0 -11
  124. package/tailwind.config.js +0 -170
  125. package/tsconfig.app.json +0 -29
  126. package/tsconfig.app.tsbuildinfo +0 -11
  127. package/tsconfig.json +0 -13
  128. package/tsconfig.node.json +0 -22
  129. package/tsconfig.node.tsbuildinfo +0 -1
  130. package/vite.config.ts +0 -165
@@ -1,702 +0,0 @@
1
- import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
2
- import { Upload } from 'lucide-react';
3
- import { defaultTheme, defaultStyles, deepMergeTheme } from '../../theme/styledTheme';
4
- import {
5
- type UploaderProps,
6
- type UploadItem as UploadItemType,
7
- type UploaderMethods,
8
- type UploadStatus, // Added type for UploadStatus
9
- } from './types';
10
- import { generateId, uploadFile, autoFillFormData } from './utils';
11
- import {
12
- UploaderContainer,
13
- UploadIcon,
14
- UploadText,
15
- UploadHint,
16
- FileInput,
17
- UploadList,
18
- } from './styles';
19
- import UploadItem from './UploadItem';
20
- import Button from '../Button'; // Added import for Button
21
-
22
- /**
23
- * 文件上传器组件
24
- * 支持拖拽上传、多文件上传、独立表单数据管理
25
- * 每个上传项都有独立的表单,上传完成后自动填充文件名
26
- */
27
- const Uploader: React.FC<UploaderProps> = ({
28
- multiple = true,
29
- accept = '*/*',
30
- maxSize = 100, // 100MB
31
- maxFiles = 10, // 预留接口,后续可扩展文件数量限制功能
32
- autoUpload = true,
33
- url = 'http://localhost:3000/upload', // 默认上传地址
34
- headers = {}, // 默认请求头
35
- queryParams = {}, // 默认查询参数
36
- itemForm = [],
37
- styles,
38
- buttons = [], // 新增:自定义按钮数组
39
- errorMessages = {}, // 新增:错误提示配置
40
- icons, // 新增:图标配置
41
- eventsEmit,
42
- onRef,
43
- placeholder,
44
- listName,
45
- }) => {
46
- // 使用统一的主题合并函数,确保 theme 有完整的结构
47
- const theme = deepMergeTheme(defaultStyles, styles);
48
- const fileInputRef = useRef<HTMLInputElement>(null);
49
- const [isDragOver, setIsDragOver] = useState(false);
50
- const [uploadItems, setUploadItems] = useState<UploadItemType[]>([]);
51
-
52
- // 统一对外数据快照(与 getUploadItemsData 结构一致)
53
- const snapshotItems = useCallback((items?: UploadItemType[]) => {
54
- const source = items ?? uploadItems;
55
- return source.map(item => ({
56
- id: item.id,
57
- name: item.name,
58
- status: item.status,
59
- error: item.error,
60
- uploadApiRes: item.uploadApiRes || {},
61
- formData: item.formData || {},
62
- file: item.file,
63
- size: item.size,
64
- type: item.type,
65
- }));
66
- }, [uploadItems]);
67
-
68
- // 处理 placeholder 文本,支持 React 组件
69
- const placeholderContent = useMemo(() => {
70
- if (!placeholder) return null;
71
-
72
- // 如果传入的是字符串,进行换行符转译
73
- if (typeof placeholder === 'string') {
74
- let text = placeholder;
75
- text = text.replace(/<br\s*\/?>(?=\s*)/gi, '\n'); // <br>, <br/>, <br />
76
- text = text.replace(/\s*\/n\s*/gi, '\n'); // /n
77
- text = text.replace(/\\n/g, '\n'); // \\
78
- return (
79
- <UploadHint
80
- $theme={theme.theme || defaultTheme}
81
- style={{
82
- textAlign: 'center',
83
- whiteSpace: 'pre-line',
84
- }}
85
- >
86
- {text}
87
- </UploadHint>
88
- );
89
- }
90
-
91
- // 如果传入的是 React 组件,直接渲染
92
- return placeholder;
93
- }, [placeholder, theme.theme]);
94
-
95
- // 默认错误提示
96
- const defaultErrorMessages = {
97
- noUrl: '请提供上传接口地址 (url)',
98
- uploadFailed: '上传失败,请重试',
99
- fileTooLarge: `文件大小不能超过 ${maxSize}MB`,
100
- invalidFileType: '不支持的文件类型',
101
- };
102
-
103
- // 合并用户自定义错误提示和默认提示
104
- const finalErrorMessages = { ...defaultErrorMessages, ...errorMessages };
105
-
106
- // 获取错误提示信息的函数
107
- const getErrorMessage = (type: keyof typeof defaultErrorMessages, fallback?: string) => {
108
- // 如果用户设置了自定义错误提示,优先使用
109
- if (finalErrorMessages[type]) {
110
- return finalErrorMessages[type];
111
- }
112
- // 否则使用 fallback 或默认提示
113
- return fallback || defaultErrorMessages[type];
114
- };
115
-
116
- const formatSizeMB = (bytes: number) => {
117
- const mb = bytes / (1024 * 1024);
118
- // 保留两位小数,但去掉多余的0
119
- return `${Number(mb.toFixed(2))}MB`;
120
- };
121
-
122
- // 统一文件校验与限流逻辑
123
- const normalizeIncomingFiles = useCallback(
124
- (files: File[], currentCount: number) => {
125
- const MAX_BYTES = maxSize * 1024 * 1024;
126
- const oversize: File[] = [];
127
- const withinSize: File[] = [];
128
- files.forEach(f => {
129
- if (f.size > MAX_BYTES) oversize.push(f); else withinSize.push(f);
130
- });
131
-
132
- if (oversize.length > 0) {
133
- const details = oversize.map(f => `${f.name}(${formatSizeMB(f.size)})`).join('、');
134
- const message = `${getErrorMessage('fileTooLarge')}:${details}`;
135
- eventsEmit?.('uploader:error', {
136
- type: 'fileTooLarge',
137
- message,
138
- files: oversize.map(f => ({ name: f.name, size: f.size })),
139
- limit: `${maxSize}MB`,
140
- data: snapshotItems(),
141
- });
142
- }
143
-
144
- const remainingSlots = Math.max(0, maxFiles - currentCount);
145
- if (remainingSlots <= 0) {
146
- const message = `最多只能上传 ${maxFiles} 个文件,已存在 ${currentCount} 个,无法继续添加。`;
147
- eventsEmit?.('uploader:error', {
148
- type: 'tooManyFiles',
149
- message,
150
- currentCount,
151
- tryAdd: files.length,
152
- limit: maxFiles,
153
- data: snapshotItems(),
154
- });
155
- return [] as File[];
156
- }
157
-
158
- const clipped = withinSize.slice(0, remainingSlots);
159
- if (withinSize.length > remainingSlots) {
160
- const dropped = withinSize.length - clipped.length;
161
- const message = `最多只能上传 ${maxFiles} 个文件,本次仅接受 ${clipped.length} 个,忽略 ${dropped} 个。`;
162
- eventsEmit?.('uploader:error', {
163
- type: 'tooManyFiles',
164
- message,
165
- currentCount,
166
- accepted: clipped.length,
167
- dropped,
168
- limit: maxFiles,
169
- data: snapshotItems(),
170
- });
171
- }
172
-
173
- return clipped;
174
- },
175
- [maxSize, maxFiles, getErrorMessage, eventsEmit, snapshotItems]
176
- );
177
-
178
- // 暴露方法给父组件
179
- const uploaderMethods: UploaderMethods = useMemo(
180
- () => ({
181
- // 获取所有上传项
182
- getUploadItems: () => uploadItems,
183
-
184
- // 获取所有上传项的数据(包含表单数据)
185
- getUploadItemsData: (options?: {
186
- includeStatus?: UploadStatus[];
187
- excludeStatus?: UploadStatus[];
188
- }) => {
189
- let filteredItems = uploadItems;
190
-
191
- // 根据状态过滤
192
- if (options?.includeStatus) {
193
- filteredItems = filteredItems.filter(item =>
194
- options.includeStatus!.includes(item.status)
195
- );
196
- }
197
- if (options?.excludeStatus) {
198
- filteredItems = filteredItems.filter(item =>
199
- !options.excludeStatus!.includes(item.status)
200
- );
201
- }
202
-
203
- return filteredItems.map(item => ({
204
- id: item.id,
205
- name: item.name,
206
- status: item.status,
207
- error: item.error, // 新增:错误信息
208
- uploadApiRes: item.uploadApiRes || {},
209
- formData: item.formData || {},
210
- file: item.file,
211
- size: item.size,
212
- type: item.type,
213
- }));
214
- },
215
-
216
- // 清空所有上传项
217
- clearUploadItems: () => {
218
- setUploadItems([]);
219
- eventsEmit?.('uploader:clear', {});
220
- },
221
-
222
- // 新增:设置某个 item 的错误信息
223
- setItemError: (itemId: string, error: string) => {
224
- setUploadItems(prev => prev.map(item =>
225
- item.id === itemId
226
- ? { ...item, status: 'error' as const, error }
227
- : item
228
- ));
229
- eventsEmit?.('uploader:errorSet', { itemId, error, data: snapshotItems() });
230
- },
231
-
232
- // 新增:清除某个 item 的错误信息
233
- clearItemError: (itemId: string) => {
234
- setUploadItems(prev => prev.map(item =>
235
- item.id === itemId
236
- ? { ...item, status: 'pending' as const, error: undefined }
237
- : item
238
- ));
239
- eventsEmit?.('uploader:errorCleared', { itemId, data: snapshotItems() });
240
- },
241
-
242
- // 添加新的上传项(对外方法也应用限流)
243
- addUploadItems: (files: File[]) => {
244
- const toAdd = normalizeIncomingFiles(files, uploadItems.length);
245
- if (toAdd.length === 0) return;
246
- const newItems: UploadItemType[] = toAdd.map(file => ({
247
- id: generateId(),
248
- file,
249
- name: file.name,
250
- size: file.size,
251
- type: file.type,
252
- status: 'pending' as const,
253
- progress: 0,
254
- formData: {},
255
- isExpanded: false,
256
- }));
257
-
258
- setUploadItems(prev => [...prev, ...newItems]);
259
-
260
- if (autoUpload) {
261
- handleUpload(newItems);
262
- }
263
- },
264
-
265
- // 移除指定上传项
266
- removeUploadItem: (id: string) => {
267
- setUploadItems(prev => {
268
- const itemToRemove = prev.find(item => item.id === id);
269
- if (itemToRemove) {
270
- eventsEmit?.('uploader:remove', {
271
- itemId: id,
272
- item: itemToRemove,
273
- data: snapshotItems(prev),
274
- });
275
- }
276
- return prev.filter(item => item.id !== id);
277
- });
278
- },
279
-
280
- // 验证表单数据
281
- validateFormData: async () => {
282
- // 检查是否有上传失败的项目
283
- const hasError = uploadItems.some(item => item.status === 'error');
284
- if (hasError) {
285
- return false;
286
- }
287
-
288
- // 检查是否有正在上传的项目
289
- const hasUploading = uploadItems.some(item => item.status === 'uploading');
290
- if (hasUploading) {
291
- return false;
292
- }
293
-
294
- // 检查是否有上传成功的项目
295
- const hasSuccess = uploadItems.some(item => item.status === 'success');
296
- if (!hasSuccess) {
297
- return false;
298
- }
299
-
300
- return true;
301
- },
302
- }),
303
- [uploadItems, autoUpload, eventsEmit, normalizeIncomingFiles, snapshotItems]
304
- );
305
-
306
- // 通过ref暴露方法给父组件
307
- useEffect(() => {
308
- if (onRef) {
309
- onRef(uploaderMethods);
310
- }
311
- }, [uploaderMethods, onRef]);
312
-
313
- /**
314
- * 处理文件选择
315
- * @param files - 选择的文件列表
316
- */
317
- const handleFileSelect = useCallback(
318
- (files: FileList | null) => {
319
- if (!files) return;
320
-
321
- const toAddFiles = normalizeIncomingFiles(Array.from(files), uploadItems.length);
322
- if (toAddFiles.length === 0) return;
323
-
324
- const newItems: UploadItemType[] = toAddFiles.map(file => ({
325
- id: generateId(),
326
- file,
327
- name: file.name,
328
- size: file.size,
329
- type: file.type,
330
- status: 'pending' as const,
331
- progress: 0,
332
- formData: {}, // 每个item独立的表单数据
333
- isExpanded: false,
334
- }));
335
-
336
- setUploadItems(prev => [...prev, ...newItems]);
337
-
338
- if (autoUpload) {
339
- handleUpload(newItems);
340
- }
341
- },
342
- [autoUpload, uploadItems.length, normalizeIncomingFiles]
343
- );
344
-
345
- /**
346
- * 处理拖拽事件
347
- */
348
- const handleDragOver = useCallback((e: React.DragEvent) => {
349
- e.preventDefault();
350
- setIsDragOver(true);
351
- }, []);
352
-
353
- const handleDragLeave = useCallback((e: React.DragEvent) => {
354
- e.preventDefault();
355
- setIsDragOver(false);
356
- }, []);
357
-
358
- const handleDrop = useCallback(
359
- (e: React.DragEvent) => {
360
- e.preventDefault();
361
- setIsDragOver(false);
362
- handleFileSelect(e.dataTransfer.files);
363
- },
364
- [handleFileSelect]
365
- );
366
-
367
- /**
368
- * 处理文件上传
369
- * @param items - 要上传的文件项列表
370
- */
371
- const handleUpload = useCallback(
372
- async (items: UploadItemType[]) => {
373
- // 检查是否提供了上传URL
374
- if (!url) {
375
- console.error(getErrorMessage('noUrl'));
376
- return;
377
- }
378
-
379
- eventsEmit?.('uploader:start', { items, data: snapshotItems() });
380
-
381
- for (const item of items) {
382
- try {
383
- setUploadItems(prev =>
384
- prev.map(i => (i.id === item.id ? { ...i, status: 'uploading' as const } : i))
385
- );
386
-
387
- const res = await uploadFile(item.file, url, headers, queryParams, progress => {
388
- setUploadItems(prev => prev.map(i => (i.id === item.id ? { ...i, progress } : i)));
389
- eventsEmit?.('uploader:progress', { item: { ...item, progress }, data: snapshotItems() });
390
- });
391
-
392
- // 上传成功后,自动填充表单数据
393
- const autoFilledFormData = autoFillFormData(item, itemForm);
394
-
395
- const updatedItem: UploadItemType = {
396
- ...item,
397
- status: 'success' as const,
398
- uploadApiRes: res,
399
- progress: 100,
400
- formData: autoFilledFormData, // 使用自动填充后的表单数据
401
- isExpanded: itemForm.length > 0 ? true : false, // 上传成功后,如果有表单配置则默认展开
402
- };
403
-
404
- const nextItems = uploadItems.map(i => (i.id === item.id ? updatedItem : i));
405
- setUploadItems(prev => prev.map(i => (i.id === item.id ? updatedItem : i)));
406
- eventsEmit?.('uploader:success', { item: updatedItem, data: snapshotItems(nextItems) });
407
- } catch (error) {
408
- // 尝试从错误响应中提取 errorMsg
409
- let errorMessage = '';
410
- console.log('上传错误详情:', error);
411
-
412
- if (error && typeof error === 'object') {
413
- const errorObj = error as any;
414
-
415
- // 优先检查是否有 response 对象(来自 utils.ts)
416
- if (errorObj.response && errorObj.response.data) {
417
- const responseData = errorObj.response.data;
418
- console.log('错误响应数据:', responseData);
419
-
420
- if (responseData.errorMsg) {
421
- // 优先使用接口返回的 errorMsg
422
- errorMessage = responseData.errorMsg;
423
- } else if (responseData.message) {
424
- // 其次使用 message
425
- errorMessage = responseData.message;
426
- } else if (responseData.error) {
427
- // 再次使用 error
428
- errorMessage = responseData.error;
429
- } else if (responseData.errorCode) {
430
- // 如果有错误码,生成友好提示
431
- switch (responseData.errorCode) {
432
- case '30013':
433
- errorMessage = '登录失效已过期,请重新登录';
434
- break;
435
- case '30014':
436
- errorMessage = '没有权限执行此操作';
437
- break;
438
- case '30015':
439
- errorMessage = '文件格式不支持';
440
- break;
441
- case '30016':
442
- errorMessage = '文件大小超出限制';
443
- break;
444
- default:
445
- errorMessage = `操作失败 (${responseData.errorCode})`;
446
- }
447
- }
448
- } else if (errorObj.message) {
449
- // 如果没有 response 对象,尝试解析错误消息
450
- try {
451
- const errorData = JSON.parse(errorObj.message);
452
- console.log('解析的错误数据:', errorData);
453
-
454
- if (errorData.errorMsg) {
455
- errorMessage = errorData.errorMsg;
456
- } else if (errorData.message) {
457
- errorMessage = errorData.message;
458
- } else if (errorData.error) {
459
- errorMessage = errorData.error;
460
- } else if (errorData.errorCode) {
461
- switch (errorData.errorCode) {
462
- case '30013':
463
- errorMessage = '登录失效已过期,请重新登录';
464
- break;
465
- case '30014':
466
- errorMessage = '没有权限执行此操作';
467
- break;
468
- case '30015':
469
- errorMessage = '文件格式不支持';
470
- break;
471
- case '30016':
472
- errorMessage = '文件大小超出限制';
473
- break;
474
- default:
475
- errorMessage = `操作失败 (${errorData.errorCode})`;
476
- }
477
- }
478
- } catch (parseError) {
479
- // 如果解析失败,直接使用错误消息
480
- console.log('错误消息解析失败:', parseError);
481
- errorMessage = errorObj.message;
482
- }
483
- }
484
- }
485
-
486
- // 如果没有从接口获取到错误信息,使用自定义错误提示或默认提示
487
- if (!errorMessage) {
488
- errorMessage = getErrorMessage('uploadFailed');
489
- }
490
-
491
- console.log('最终使用的错误信息:', errorMessage);
492
- const errorItem = { ...item, status: 'error' as const, error: errorMessage };
493
- const nextItems = uploadItems.map(i => (i.id === item.id ? errorItem : i));
494
- setUploadItems(prev => prev.map(i => (i.id === item.id ? errorItem : i)));
495
- eventsEmit?.('uploader:error', { item: errorItem, error: errorMessage, data: snapshotItems(nextItems) });
496
- }
497
- }
498
-
499
- eventsEmit?.('uploader:complete', { items: uploadItems, data: snapshotItems() });
500
- },
501
- [url, headers, queryParams, eventsEmit, uploadItems, itemForm, finalErrorMessages.uploadFailed, snapshotItems]
502
- );
503
-
504
- /**
505
- * 移除文件
506
- * @param id - 文件项ID
507
- */
508
- const handleRemoveFile = useCallback(
509
- (id: string) => {
510
- setUploadItems(prev => {
511
- const itemToRemove = prev.find(item => item.id === id);
512
- if (itemToRemove) {
513
- eventsEmit?.('uploader:remove', {
514
- itemId: id,
515
- item: itemToRemove,
516
- data: snapshotItems(prev),
517
- });
518
- }
519
- return prev.filter(item => item.id !== id);
520
- });
521
- },
522
- [eventsEmit, snapshotItems]
523
- );
524
-
525
- /**
526
- * 切换表单展开状态
527
- * @param id - 文件项ID
528
- */
529
- const handleToggleExpand = useCallback(
530
- (id: string) => {
531
- setUploadItems(prev =>
532
- prev.map(item => {
533
- if (item.id === id) {
534
- const updatedItem = {
535
- ...item,
536
- isExpanded: !item.isExpanded,
537
- };
538
- console.log('Toggle Expand:', id, updatedItem.isExpanded, updatedItem.formData);
539
- eventsEmit?.('uploader:toggleExpand', {
540
- itemId: id,
541
- isExpanded: updatedItem.isExpanded,
542
- item: updatedItem,
543
- data: snapshotItems(prev),
544
- });
545
- return updatedItem;
546
- }
547
- return item;
548
- })
549
- );
550
- },
551
- [eventsEmit, snapshotItems]
552
- );
553
-
554
- /**
555
- * 更新表单数据
556
- * @param id - 文件项ID
557
- * @param formData - 表单数据
558
- */
559
- const handleFormDataChange = useCallback(
560
- (id: string, formData: Record<string, any>) => {
561
- console.log('FormData Change:', id, formData);
562
-
563
- setUploadItems(prev => {
564
- const updatedItems = prev.map(item => {
565
- if (item.id === id) {
566
- const updatedItem = {
567
- ...item,
568
- formData: { ...formData }, // 确保数据是深拷贝
569
- };
570
- eventsEmit?.('uploader:formDataChange', {
571
- itemId: id,
572
- formData: updatedItem.formData,
573
- item: updatedItem,
574
- data: snapshotItems(prev),
575
- });
576
- return updatedItem;
577
- }
578
- return item;
579
- });
580
-
581
- // 触发数据变化事件
582
- eventsEmit?.('uploader:dataChange', {
583
- items: updatedItems,
584
- changedItemId: id,
585
- changedFormData: formData,
586
- data: snapshotItems(updatedItems),
587
- });
588
-
589
- return updatedItems;
590
- });
591
- },
592
- [eventsEmit, snapshotItems]
593
- );
594
-
595
- return (
596
- <div>
597
- <UploaderContainer
598
- $theme={theme.theme || defaultTheme}
599
- $isDragOver={isDragOver}
600
- onDragOver={handleDragOver}
601
- onDragLeave={handleDragLeave}
602
- onDrop={handleDrop}
603
- onClick={() => {
604
- if (fileInputRef.current) {
605
- // 关键:清空 input 值,允许选择相同文件再次触发 onChange
606
- fileInputRef.current.value = '';
607
- }
608
- fileInputRef.current?.click();
609
- }}
610
- >
611
- <UploadIcon $theme={theme.theme || defaultTheme}>
612
- {icons?.dragUpload || <Upload size={48} />}
613
- </UploadIcon>
614
-
615
- {placeholderContent || (
616
- <>
617
- <UploadText $theme={theme.theme || defaultTheme}>
618
- 拖拽文件到此处或点击选择文件
619
- </UploadText>
620
- <UploadHint $theme={theme.theme || defaultTheme}>
621
- 支持 {accept} 格式,单个文件最大 {maxSize}MB
622
- {!url && (
623
- <div style={{ color: theme.theme?.colors?.error || '#FF0000', marginTop: '8px', fontSize: '12px' }}>
624
- {getErrorMessage('noUrl')}
625
- </div>
626
- )}
627
- </UploadHint>
628
- </>
629
- )}
630
- </UploaderContainer>
631
-
632
- <FileInput
633
- ref={fileInputRef}
634
- type="file"
635
- multiple={multiple}
636
- accept={accept}
637
- onChange={(e) => {
638
- handleFileSelect(e.target.files);
639
- // 关键:选择完成后重置值,避免再次选择相同文件不触发 onChange
640
- e.target.value = '';
641
- }}
642
- />
643
-
644
- {/* 上传列表 */}
645
- {uploadItems.length > 0 && (
646
- <div>
647
- {/* 标题和按钮区域 - 使用 flex 布局 */}
648
- <div style={{
649
- display: 'flex',
650
- justifyContent: 'space-between',
651
- alignItems: 'center',
652
- margin: '6px 0px'
653
- }}>
654
- <div className="text-[14px] font-bold opacity-60">{listName || '上传文件列表'}</div>
655
-
656
- {/* 右侧按钮区域 */}
657
- {buttons.length > 0 && (
658
- <div style={{
659
- display: 'flex',
660
- gap: '8px',
661
- alignItems: 'center'
662
- }}>
663
- {buttons.map((button, index) => (
664
- <Button
665
- key={index}
666
- mode={button.mode || 'default'}
667
- label={button.label}
668
- icon={button.icon}
669
- disabled={button.disabled}
670
- onClick={button.onClick}
671
- styles={styles}
672
- />
673
- ))}
674
- </div>
675
- )}
676
- </div>
677
-
678
- {/* 文件列表 - 独立垂直排列 */}
679
- <UploadList $theme={theme.theme || defaultTheme}>
680
- {uploadItems.map((item, index) => (
681
- <UploadItem
682
- key={item.id}
683
- item={item}
684
- index={index}
685
- itemForm={itemForm}
686
- onRemove={handleRemoveFile}
687
- onToggleExpand={handleToggleExpand}
688
- onFormDataChange={handleFormDataChange}
689
- theme={theme.theme || defaultTheme}
690
- icons={icons} // 传递图标配置
691
- />
692
- ))}
693
- </UploadList>
694
- </div>
695
- )}
696
-
697
-
698
- </div>
699
- );
700
- };
701
-
702
- export default Uploader;