@mxmweb/zui 1.0.3 → 1.1.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 (132) hide show
  1. package/.editorconfig +38 -0
  2. package/.prettierignore +16 -0
  3. package/.prettierrc +17 -0
  4. package/.releaserc.json +36 -0
  5. package/CHANGELOG.md +58 -0
  6. package/CONTRIBUTING.md +111 -0
  7. package/NPMREADME.md +0 -0
  8. package/README.md +4 -1
  9. package/bash.exe.stackdump +40 -0
  10. package/components.json +21 -0
  11. package/dist/README.md +0 -0
  12. package/dist/assets/style.css +1 -0
  13. package/{containers → dist/containers}/DashboardContainer.d.ts +8 -0
  14. package/dist/containers/DockContainer.d.ts +24 -0
  15. package/{elements → dist/elements}/Button.d.ts +3 -4
  16. package/dist/elements/CustomDock.d.ts +25 -0
  17. package/dist/elements/DropDownButton.d.ts +24 -0
  18. package/{elements → dist/elements}/DropdownMenu.d.ts +2 -4
  19. package/dist/elements/GoggleNavbar.d.ts +31 -0
  20. package/{elements → dist/elements}/Uploader/UploadItem.d.ts +17 -0
  21. package/{elements → dist/elements}/Uploader/styles.d.ts +10 -1
  22. package/dist/elements/Uploader/types.d.ts +87 -0
  23. package/dist/examples/DockContainerExample.d.ts +3 -0
  24. package/dist/icons/Icon.d.ts +7 -0
  25. package/dist/icons/Icon.tsx +82 -0
  26. package/dist/icons/index.d.ts +13 -0
  27. package/dist/icons/index.tsx +92 -0
  28. package/dist/icons/lazyIndex.d.ts +7 -0
  29. package/dist/icons/lazyIndex.tsx +49 -0
  30. package/dist/icons/rag/csv.svg +3 -0
  31. package/dist/icons/rag/document.svg +3 -0
  32. package/dist/icons/rag/excel.svg +3 -0
  33. package/dist/icons/rag/file.svg +3 -0
  34. package/dist/icons/rag/folder.svg +5 -0
  35. package/dist/icons/rag/json.svg +3 -0
  36. package/dist/icons/rag/knowledgebase.svg +3 -0
  37. package/dist/icons/rag/netretrive.svg +3 -0
  38. package/dist/icons/rag/odf.svg +7 -0
  39. package/dist/icons/rag/pdf.svg +3 -0
  40. package/dist/icons/rag/pic.svg +3 -0
  41. package/dist/icons/rag/ppt.svg +3 -0
  42. package/dist/icons/rag/think.svg +6 -0
  43. package/dist/icons/rag/txt.svg +3 -0
  44. package/dist/icons/rag/url.svg +3 -0
  45. package/dist/icons/rag/word.svg +3 -0
  46. package/dist/icons/rag/wps.svg +3 -0
  47. package/dist/icons/rag/zip.svg +7 -0
  48. package/dist/index.js +2299 -0
  49. package/dist/lib_enter.d.ts +13 -0
  50. package/dist/package.json +26 -0
  51. package/{theme → dist/theme}/styledTheme.d.ts +40 -54
  52. package/eslint.config.js +92 -0
  53. package/index.html +13 -0
  54. package/package.json +42 -14
  55. package/postcss.config.cjs +19 -0
  56. package/public/mock.csv +16 -0
  57. package/public/mock_/345/211/257/346/234/254.csv +16 -0
  58. package/public/vite.svg +1 -0
  59. package/src/Preview.tsx +15 -0
  60. package/src/assets/img/excel.png +0 -0
  61. package/src/assets/img/img.png +0 -0
  62. package/src/assets/img/pdf.png +0 -0
  63. package/src/assets/img/ppt.png +0 -0
  64. package/src/assets/img/txt.png +0 -0
  65. package/src/assets/img/word.png +0 -0
  66. package/src/containers/DashboardContainer.tsx +507 -0
  67. package/src/containers/DockContainer.tsx +186 -0
  68. package/src/containers/style.css +37 -0
  69. package/src/elements/Button.tsx +118 -0
  70. package/src/elements/CustomDock.tsx +287 -0
  71. package/src/elements/DropDownButton.tsx +249 -0
  72. package/src/elements/DropdownMenu.tsx +184 -0
  73. package/src/elements/GoggleNavbar.tsx +184 -0
  74. package/src/elements/Uploader/README.md +249 -0
  75. package/src/elements/Uploader/UploadItem.tsx +298 -0
  76. package/src/elements/Uploader/example.tsx +95 -0
  77. package/src/elements/Uploader/index.tsx +702 -0
  78. package/src/elements/Uploader/styles.tsx +291 -0
  79. package/src/elements/Uploader/types.ts +119 -0
  80. package/src/elements/Uploader/utils.ts +200 -0
  81. package/src/elements/Uploader.tsx +3 -0
  82. package/src/examples/DockContainerExample.tsx +237 -0
  83. package/src/icons/Icon.tsx +82 -0
  84. package/src/icons/index.tsx +92 -0
  85. package/src/icons/lazyIndex.tsx +49 -0
  86. package/src/icons/rag/csv.svg +3 -0
  87. package/src/icons/rag/document.svg +3 -0
  88. package/src/icons/rag/excel.svg +3 -0
  89. package/src/icons/rag/file.svg +3 -0
  90. package/src/icons/rag/folder.svg +5 -0
  91. package/src/icons/rag/json.svg +3 -0
  92. package/src/icons/rag/knowledgebase.svg +3 -0
  93. package/src/icons/rag/netretrive.svg +3 -0
  94. package/src/icons/rag/odf.svg +7 -0
  95. package/src/icons/rag/pdf.svg +3 -0
  96. package/src/icons/rag/pic.svg +3 -0
  97. package/src/icons/rag/ppt.svg +3 -0
  98. package/src/icons/rag/think.svg +6 -0
  99. package/src/icons/rag/txt.svg +3 -0
  100. package/src/icons/rag/url.svg +3 -0
  101. package/src/icons/rag/word.svg +3 -0
  102. package/src/icons/rag/wps.svg +3 -0
  103. package/src/icons/rag/zip.svg +7 -0
  104. package/src/lib_enter.ts +27 -0
  105. package/src/main.tsx +11 -0
  106. package/src/style.css +9 -0
  107. package/src/theme/styledTheme.tsx +253 -0
  108. package/src/type.d.ts +0 -0
  109. package/src/types/images.d.ts +12 -0
  110. package/src/types/svg-modules.d.ts +24 -0
  111. package/src/vite-env.d.ts +11 -0
  112. package/tailwind.config.js +170 -0
  113. package/tsconfig.app.json +29 -0
  114. package/tsconfig.app.tsbuildinfo +11 -0
  115. package/tsconfig.json +13 -0
  116. package/tsconfig.node.json +22 -0
  117. package/tsconfig.node.tsbuildinfo +1 -0
  118. package/vite.config.ts +180 -0
  119. package/assets/zui.css +0 -1
  120. package/elements/Uploader/types.d.ts +0 -50
  121. package/index-CgFHm4CL-O5tUkbrp.js +0 -48009
  122. package/index.js +0 -49394
  123. package/lib_enter.d.ts +0 -6
  124. /package/{Preview.d.ts → dist/Preview.d.ts} +0 -0
  125. /package/{elements → dist/elements}/Uploader/example.d.ts +0 -0
  126. /package/{elements → dist/elements}/Uploader/index.d.ts +0 -0
  127. /package/{elements → dist/elements}/Uploader/utils.d.ts +0 -0
  128. /package/{elements → dist/elements}/Uploader.d.ts +0 -0
  129. /package/{main.d.ts → dist/main.d.ts} +0 -0
  130. /package/{mock.csv → dist/mock.csv} +0 -0
  131. /package/{mock_ → dist/mock_}/345/211/257/346/234/254.csv" +0 -0
  132. /package/{vite.svg → dist/vite.svg} +0 -0
@@ -0,0 +1,291 @@
1
+ import styled from 'styled-components';
2
+ import { type AppTheme } from '../../theme/styledTheme';
3
+ import { type UploadStatus } from './types';
4
+
5
+ // 样式组件
6
+ export const UploaderContainer = styled.div<{ $theme: AppTheme; $isDragOver: boolean }>`
7
+ border: 2px dashed ${props => props.$isDragOver ? (props.$theme?.colors?.primary || '#007bff') : (props.$theme?.colors?.border || '#dee2e6')};
8
+ border-radius: 4px;
9
+ background: ${props => props.$isDragOver ? `${props.$theme?.colors?.primary || '#007bff'}10` : (props.$theme?.colors?.background || '#f8f9fa')};
10
+ padding: 24px;
11
+ text-align: center;
12
+ transition: all 0.2s ease;
13
+ cursor: pointer;
14
+ min-height: 200px;
15
+ display: flex;
16
+ flex-direction: column;
17
+ align-items: center;
18
+ justify-content: center;
19
+ gap: 16px;
20
+
21
+ &:hover {
22
+ border-color: ${props => props.$theme?.colors?.primary || '#007bff'};
23
+ background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
24
+ }
25
+ `;
26
+
27
+ export const UploadIcon = styled.div<{ $theme: AppTheme }>`
28
+ color: ${props => props.$theme?.colors?.primary || '#007bff'};
29
+ font-size: 48px;
30
+ margin-bottom: 8px;
31
+ `;
32
+
33
+ export const UploadText = styled.div<{ $theme: AppTheme }>`
34
+ color: ${props => props.$theme?.colors?.text || '#343a40'};
35
+ font-size: 16px;
36
+ font-weight: 500;
37
+ margin-bottom: 4px;
38
+ `;
39
+
40
+ export const UploadHint = styled.div<{ $theme: AppTheme }>`
41
+ color: ${props => props.$theme?.colors?.textSecondary || props.$theme?.colors?.disabledText || '#6b7280'};
42
+ font-size: 14px;
43
+ `;
44
+
45
+ export const FileInput = styled.input`
46
+ display: none;
47
+ `;
48
+
49
+ export const UploadList = styled.div<{ $theme: AppTheme }>`
50
+
51
+ width: 100%;
52
+ max-width: 600px;
53
+ `;
54
+
55
+ export const UploadItem = styled.div.attrs({
56
+ className: 'upload-item'
57
+ })<{ $theme: AppTheme; $status: UploadStatus; $hasForm?: boolean }>`
58
+ display: flex;
59
+ align-items: center;
60
+ padding: 8px 12px;
61
+ border-bottom: 1px solid ${props => props.$theme?.colors?.border || '#dee2e6'};
62
+ border: ${props => {
63
+ // 当有表单时,不显示边框,避免与外层容器边框重复
64
+ if (props.$hasForm) {
65
+ return 'none';
66
+ }
67
+ // 失败状态使用红色边框突出效果
68
+ if (props.$status === 'error') {
69
+ return `1px solid ${props.$theme?.colors?.error || '#FF0000'}`;
70
+ }
71
+ return `1px solid ${props.$theme?.colors?.border || '#dee2e6'}`;
72
+ }};
73
+ border-radius: 4px;
74
+ background: ${props => {
75
+ // 失败状态使用淡红色背景
76
+ // if (props.$status === 'error') {
77
+ // return `${props.$theme?.colors?.error || '#FF0000'}10`;
78
+ // }
79
+ return props.$theme?.colors?.background || '#ffffff';
80
+ }};
81
+ transition: all 0.2s ease;
82
+
83
+ &:hover {
84
+ border-color: ${props => {
85
+ // 当有表单时,hover 不改变边框
86
+ if (props.$hasForm) {
87
+ return 'transparent';
88
+ }
89
+ // 失败状态hover时保持红色边框
90
+ if (props.$status === 'error') {
91
+ return props.$theme?.colors?.error || '#FF0000';
92
+ }
93
+ return props.$theme?.colors?.primary || '#007bff';
94
+ }};
95
+ }
96
+ `;
97
+
98
+ export const FileIcon = styled.div<{ $theme: AppTheme }>`
99
+ color: ${props => props.$theme?.colors?.primary || '#007bff'};
100
+ margin-right: 12px;
101
+ flex-shrink: 0;
102
+ `;
103
+
104
+ export const FileInfo = styled.div`
105
+ flex: 1;
106
+ min-width: 0;
107
+ `;
108
+
109
+ export const FileName = styled.div<{ $theme: AppTheme }>`
110
+ color: ${props => props.$theme?.colors?.text || '#343a40'};
111
+ font-size: 12px;
112
+ font-weight: 400;
113
+ margin-bottom: 2px;
114
+ white-space: nowrap;
115
+ overflow: hidden;
116
+ text-overflow: ellipsis;
117
+ `;
118
+
119
+ export const FileSize = styled.div<{ $theme: AppTheme }>`
120
+ color: ${props => props.$theme?.colors?.textSecondary || props.$theme?.colors?.disabledText || '#6b7280'};
121
+ font-size: 10px;
122
+ `;
123
+
124
+ export const ProgressBar = styled.div<{ $theme: AppTheme; $progress: number }>`
125
+ width: 100%;
126
+ height: 4px;
127
+ background: ${props => props.$theme?.colors?.border || '#dee2e6'};
128
+ border-radius: 2px;
129
+ overflow: hidden;
130
+ margin-top: 8px;
131
+ position: relative;
132
+
133
+ &::after {
134
+ content: '';
135
+ position: absolute;
136
+ top: 0;
137
+ left: 0;
138
+ height: 100%;
139
+ width: ${props => props.$progress}%;
140
+ background: ${props => props.$theme?.colors?.primary || '#007bff'};
141
+ transition: width 0.3s ease;
142
+ }
143
+ `;
144
+
145
+ export const StatusIcon = styled.div<{ $theme: AppTheme; $status: UploadStatus }>`
146
+ margin-left: 12px;
147
+ flex-shrink: 0;
148
+ color: ${props => {
149
+ switch (props.$status) {
150
+ case 'success': return props.$theme?.colors?.success || '#008000';
151
+ case 'error': return props.$theme?.colors?.error || '#FF0000';
152
+ case 'uploading': return props.$theme?.colors?.primary || '#007bff';
153
+ case 'pending': return props.$theme?.colors?.warning || '#FFA500';
154
+ default: return props.$theme?.colors?.disabledText || '#808080';
155
+ }
156
+ }};
157
+ `;
158
+
159
+ export const RemoveButton = styled.button<{ $theme: AppTheme }>`
160
+ background: none;
161
+ border: none;
162
+ color: ${props => props.$theme?.colors?.error || '#FF0000'};
163
+ cursor: pointer;
164
+ padding: 4px;
165
+ border-radius: 4px;
166
+ margin-left: 8px;
167
+ transition: all 0.2s ease;
168
+
169
+ &:hover {
170
+ background: ${props => (props.$theme?.colors?.error || '#FF0000')}20;
171
+ }
172
+ `;
173
+
174
+ export const AddButton = styled.button<{ $theme: AppTheme }>`
175
+ background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
176
+ border: 1px solid ${props => props.$theme?.colors?.border || '#dee2e6'};
177
+ color: ${props => props.$theme?.colors?.primary || '#007bff'};
178
+ padding: 8px 16px;
179
+ border-radius: 4px;
180
+ cursor: pointer;
181
+ display: flex;
182
+ align-items: center;
183
+ gap: 8px;
184
+ font-size: 14px;
185
+ transition: all 0.2s ease;
186
+
187
+ &:hover {
188
+ border-color: ${props => props.$theme?.colors?.primary || '#007bff'};
189
+ background: ${props => (props.$theme?.colors?.primary || '#007bff')}10;
190
+ }
191
+ `;
192
+
193
+ export const ExpandButton = styled.button<{ $theme: AppTheme }>`
194
+ background: none;
195
+ border: none;
196
+ color: ${props => props.$theme?.colors?.primary || '#007bff'};
197
+ cursor: pointer;
198
+ padding: 4px;
199
+ border-radius: 4px;
200
+ margin-left: 8px;
201
+ transition: all 0.2s ease;
202
+ display: flex;
203
+ align-items: center;
204
+
205
+ &:hover {
206
+ background: ${props => (props.$theme?.colors?.primary || '#007bff')}20;
207
+ }
208
+ `;
209
+
210
+ export const FormContainer = styled.div<{ $theme: AppTheme; $hasError?: boolean }>`
211
+ padding: 0px;
212
+ background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
213
+
214
+ /* 移除边框,避免与外层容器边框重叠 */
215
+ border: none;
216
+ border-radius: 0;
217
+
218
+ /* 添加上方隔离线 */
219
+ border-top: 1px solid ${props => props.$theme?.colors?.border || '#dee2e6'};
220
+
221
+ /* 当有错误时,使用错误状态的隔离线颜色 */
222
+ ${props => props.$hasError && `
223
+ border-top-color: ${props.$theme?.colors?.error || '#FF0000'};
224
+ `}
225
+
226
+ /* 添加左右内边距,与外层容器对齐 */
227
+ margin: 0 1px;
228
+
229
+ /* 添加背景色区分 */
230
+ background: ${props => props.$theme?.colors?.listBackground || '#f9f9f9'};
231
+ `;
232
+
233
+ export const MetadataInput = styled.input<{ $theme: AppTheme }>`
234
+ border: 1px solid ${props => props.$theme?.colors?.border || '#dee2e6'};
235
+ border-radius: 4px;
236
+ padding: 8px 12px;
237
+ font-size: 14px;
238
+ width: 100%;
239
+ margin-top: 8px;
240
+ background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
241
+ color: ${props => props.$theme?.colors?.text || '#343a40'};
242
+
243
+ &:focus {
244
+ outline: none;
245
+ border-color: ${props => props.$theme?.colors?.primary || '#007bff'};
246
+ }
247
+ `;
248
+
249
+ export const UploadItemContainer = styled.div<{
250
+ $theme: AppTheme;
251
+ $status: UploadStatus;
252
+ $hasForm: boolean;
253
+ }>`
254
+ /* 基础样式 */
255
+ margin-top: 5px;
256
+ background: ${props => props.$theme?.colors?.background || '#f8f9fa'};
257
+ transition: all 0.2s ease;
258
+
259
+ /* 边框逻辑:有表单时显示边框,无表单时不显示 */
260
+ border: ${props => props.$hasForm
261
+ ? (props.$status === 'error'
262
+ ? `1px solid ${props.$theme?.colors?.error || '#FF0000'}`
263
+ : `1px solid ${props.$theme?.colors?.border || '#dee2e6'}`)
264
+ : 'none'};
265
+
266
+ /* 圆角设置:有表单时设置圆角,无表单时不设置 */
267
+ border-radius: ${props => props.$hasForm ? '4px' : '0'};
268
+
269
+ /* 当有表单时的特殊样式 */
270
+ ${props => props.$hasForm && `
271
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
272
+
273
+ /* 确保边框完整包裹整个容器 */
274
+ overflow: hidden;
275
+ `}
276
+
277
+ /* 错误状态的特殊样式 */
278
+ ${props => props.$status === 'error' && `
279
+ background: ${props.$theme?.colors?.error || '#FF0000'}05;
280
+ `}
281
+
282
+ /* 样式覆盖 */
283
+ .list-my div{
284
+ padding: 2px 2px !important;
285
+ }
286
+
287
+ :where(.css-dev-only-do-not-override-1v28nim).ant-input-textarea-affix-wrapper.ant-input-affix-wrapper >textarea.ant-input{
288
+ min-height: 32px !important;
289
+ }
290
+ `;
291
+
@@ -0,0 +1,119 @@
1
+ import { type AppTheme, type Styles } from '../../theme/styledTheme';
2
+
3
+ // 上传状态类型
4
+ export type UploadStatus = 'pending' | 'uploading' | 'success' | 'error' | 'cancelled';
5
+
6
+ // 上传项接口
7
+ export interface UploadItem {
8
+ id: string;
9
+ file: File;
10
+ name: string;
11
+ size: number;
12
+ type: string;
13
+ status: UploadStatus;
14
+ progress: number;
15
+ error?: string;
16
+ uploadApiRes?: any; //接口返回
17
+ headers?: Record<string, any>;
18
+ metadata?: Record<string, any>;
19
+ formData?: Record<string, any>; // 动态表单数据
20
+ isExpanded?: boolean; // 是否展开表单
21
+ }
22
+
23
+ // 上传器事件类型
24
+ export type UploaderEventName =
25
+ | 'uploader:start'
26
+ | 'uploader:progress'
27
+ | 'uploader:success'
28
+ | 'uploader:error'
29
+ | 'uploader:complete'
30
+ | 'uploader:formDataChange'
31
+ | 'uploader:toggleExpand'
32
+ | 'uploader:remove'
33
+ | 'uploader:dataChange'
34
+ | 'uploader:clear'
35
+ | 'uploader:errorSet'
36
+ | 'uploader:errorCleared';
37
+
38
+ // 上传器方法接口
39
+ export interface UploaderMethods {
40
+ getUploadItems: () => UploadItem[];
41
+ getUploadItemsData: (options?: {
42
+ includeStatus?: UploadStatus[]; // 指定要包含的状态,默认包含所有状态
43
+ excludeStatus?: UploadStatus[]; // 指定要排除的状态
44
+ }) => Array<{
45
+ id: string;
46
+ name: string;
47
+ status: UploadStatus;
48
+ error?: string; // 新增:错误信息
49
+ uploadApiRes: any;
50
+ formData: Record<string, any>;
51
+ file: File;
52
+ size: number;
53
+ type: string;
54
+ }>;
55
+ validateFormData: () => Promise<boolean>;
56
+ clearUploadItems: () => void;
57
+ // 新增:设置某个 item 的错误信息
58
+ setItemError: (itemId: string, error: string) => void;
59
+ // 新增:清除某个 item 的错误信息
60
+ clearItemError: (itemId: string) => void;
61
+ }
62
+
63
+ // 上传器按钮接口
64
+ export interface UploaderButton {
65
+ label: string;
66
+ mode?: 'primary' | 'default' | 'error' | 'text';
67
+ icon?: React.ReactNode;
68
+ disabled?: boolean;
69
+ onClick: () => void;
70
+ }
71
+
72
+ // 上传器属性接口
73
+ export interface UploaderProps {
74
+ multiple?: boolean;
75
+ accept?: string;
76
+ maxSize?: number; // 单位:MB
77
+ maxFiles?: number;
78
+ autoUpload?: boolean;
79
+ url?: string; // 上传接口地址
80
+ headers?: Record<string, string>; // 自定义请求头
81
+ queryParams?: Record<string, string>; // 查询参数
82
+ itemForm?: any[]; // 每个上传项的动态表单配置
83
+ styles?: Styles;
84
+ buttons?: UploaderButton[]; // 新增:自定义按钮数组
85
+ // 新增:文案定制
86
+ placeholder?: React.ReactNode; // 顶部区域整段说明文案(传则完全替换默认两段,支持 React 组件)
87
+ listName?: string; // 上传列表标题(传则替换"上传文件列表")
88
+ // 新增:错误提示配置
89
+ errorMessages?: {
90
+ noUrl?: string; // 未提供上传地址时的提示
91
+ uploadFailed?: string; // 上传失败时的通用提示
92
+ fileTooLarge?: string; // 文件过大的提示
93
+ invalidFileType?: string; // 文件类型无效的提示
94
+ };
95
+ eventsEmit?: (eventName: UploaderEventName, data: any, innerFn?: () => void) => void;
96
+ onRef?: (methods: UploaderMethods) => void; // 新增:暴露方法给父组件
97
+ // 新增:图标配置
98
+ icons?: {
99
+ // 拖拽上传区域图标
100
+ dragUpload?: React.ReactNode;
101
+ // 文件项状态图标配置
102
+ itemStatus?: {
103
+ pending?: React.ReactNode; // 上传前/待上传
104
+ uploading?: React.ReactNode; // 上传中
105
+ success?: React.ReactNode; // 上传成功
106
+ error?: React.ReactNode; // 上传失败
107
+ };
108
+ // 文件类型图标配置
109
+ fileType?: {
110
+ default?: React.ReactNode; // 默认文件图标
111
+ image?: React.ReactNode; // 图片文件
112
+ document?: React.ReactNode; // 文档文件
113
+ spreadsheet?: React.ReactNode; // 表格文件
114
+ archive?: React.ReactNode; // 压缩文件
115
+ video?: React.ReactNode; // 视频文件
116
+ audio?: React.ReactNode; // 音频文件
117
+ };
118
+ };
119
+ }
@@ -0,0 +1,200 @@
1
+ import { type UploadItem } from './types';
2
+
3
+ /**
4
+ * 格式化文件大小显示
5
+ * @param bytes - 文件字节数
6
+ * @returns 格式化后的文件大小字符串
7
+ */
8
+ export const formatFileSize = (bytes: number): string => {
9
+ if (bytes === 0) return '0 B';
10
+ const k = 1024;
11
+ const sizes = ['B', 'KB', 'MB', 'GB'];
12
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
13
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
14
+ };
15
+
16
+ /**
17
+ * 生成唯一ID
18
+ * @returns 唯一ID字符串
19
+ */
20
+ export const generateId = (): string => {
21
+ return Math.random().toString(36).substr(2, 9);
22
+ };
23
+
24
+ /**
25
+ * 根据表单配置自动填充文件信息到表单数据中
26
+ * @param item - 上传项
27
+ * @param itemForm - 表单配置
28
+ * @returns 填充后的表单数据
29
+ */
30
+ export const autoFillFormData = (item: UploadItem, itemForm: any[]): Record<string, any> => {
31
+ const formData = { ...item.formData };
32
+
33
+ // 常见的文件名相关字段名
34
+ const fileNameFields = ['name', 'filename', 'title', 'displayName', 'fileName'];
35
+ const fileSizeFields = ['size', 'fileSize', 'file_size'];
36
+ const fileTypeFields = ['type', 'fileType', 'mimeType', 'contentType'];
37
+
38
+ itemForm.forEach(formItem => {
39
+ const fieldName = formItem.name || formItem.field;
40
+ if (!fieldName) return;
41
+
42
+ // 自动填充文件名
43
+ if (fileNameFields.includes(fieldName.toLowerCase()) && !formData[fieldName]) {
44
+ formData[fieldName] = item.name;
45
+ }
46
+
47
+ // 自动填充文件大小
48
+ if (fileSizeFields.includes(fieldName.toLowerCase()) && !formData[fieldName]) {
49
+ formData[fieldName] = formatFileSize(item.size);
50
+ }
51
+
52
+ // 自动填充文件类型
53
+ if (fileTypeFields.includes(fieldName.toLowerCase()) && !formData[fieldName]) {
54
+ formData[fieldName] = item.type;
55
+ }
56
+ });
57
+
58
+ return formData;
59
+ };
60
+
61
+ /**
62
+ * 标准文件上传函数
63
+ * 使用 XMLHttpRequest 实现文件上传,支持进度回调和自定义请求头
64
+ * 支持 FormData 和 JSON 两种格式
65
+ * @param file - 要上传的文件
66
+ * @param uploadUrl - 上传接口地址
67
+ * @param headers - 自定义请求头
68
+ * @param queryParams - 查询参数
69
+ * @param onProgress - 上传进度回调函数
70
+ * @returns Promise<string> - 返回上传成功后的文件URL或响应数据
71
+ */
72
+ export const uploadFile = async (
73
+ file: File,
74
+ uploadUrl: string,
75
+ headers: Record<string, string> = {},
76
+ queryParams: Record<string, string> = {},
77
+ onProgress?: (progress: number) => void
78
+ ): Promise<string> => {
79
+ return new Promise((resolve, reject) => {
80
+ const xhr = new XMLHttpRequest();
81
+
82
+ // 构建带查询参数的URL
83
+ const urlWithParams = new URL(uploadUrl);
84
+ Object.entries(queryParams).forEach(([key, value]) => {
85
+ urlWithParams.searchParams.append(key, value);
86
+ });
87
+
88
+ // 检查是否为 JSON 格式上传
89
+ const isJsonUpload = headers['Content-Type']?.includes('application/json');
90
+
91
+ let requestData: FormData | string; // 用于存储请求数据
92
+
93
+ if (isJsonUpload) {
94
+ // JSON 格式上传 - 将文件转换为 base64 字符串
95
+ const reader = new FileReader();
96
+ reader.onload = () => {
97
+ const base64String = reader.result as string;
98
+ const jsonData = JSON.stringify({
99
+ file: base64String.split(',')[1], // 移除 data:application/octet-stream;base64, 前缀
100
+ });
101
+
102
+ // 发送 JSON 请求
103
+ sendRequest(jsonData);
104
+ };
105
+ reader.onerror = () => reject(new Error('文件读取失败'));
106
+ reader.readAsDataURL(file);
107
+ } else {
108
+ // FormData 格式上传
109
+ const formData = new FormData();
110
+ formData.append('tableFile', file); // 使用 tableFile 作为文件字段名
111
+ sendRequest(formData);
112
+ }
113
+
114
+ function sendRequest(data: FormData | string) {
115
+ // 监听上传进度
116
+ xhr.upload.addEventListener('progress', event => {
117
+ if (event.lengthComputable) {
118
+ const progress = Math.round((event.loaded / event.total) * 100);
119
+ onProgress?.(progress);
120
+ }
121
+ });
122
+
123
+ // 监听请求完成
124
+ xhr.addEventListener('load', () => {
125
+ if (xhr.status >= 200 && xhr.status < 300) {
126
+ try {
127
+ const response = JSON.parse(xhr.responseText);
128
+ if (response.data) {
129
+ resolve(response.data);
130
+ } else {
131
+ resolve(xhr.responseText);
132
+ }
133
+ } catch (error) {
134
+ // 如果响应不是JSON格式,直接返回响应文本
135
+ resolve(xhr.responseText);
136
+ }
137
+ } else {
138
+ // console.log('====xhr', xhr);
139
+ console.log('====xhr.responseText', xhr.responseText);
140
+
141
+ // 尝试从响应中提取错误信息
142
+ let errorMessage = '';
143
+ try {
144
+ const errorResponse = JSON.parse(xhr.responseText);
145
+ if (errorResponse.errorMsg) {
146
+ errorMessage = errorResponse.errorMsg;
147
+ } else if (errorResponse.message) {
148
+ errorMessage = errorResponse.message;
149
+ } else if (errorResponse.error) {
150
+ errorMessage = errorResponse.error;
151
+ } else {
152
+ errorMessage = `上传失败: ${xhr.status} ${xhr.statusText}`;
153
+ }
154
+ } catch (parseError) {
155
+ // 如果响应不是JSON格式,使用状态码
156
+ errorMessage = `上传失败: ${xhr.status} ${xhr.statusText}`;
157
+ }
158
+
159
+ // 构造一个包含完整错误信息的错误对象
160
+ const error = new Error(errorMessage);
161
+ (error as any).response = {
162
+ status: xhr.status,
163
+ statusText: xhr.statusText,
164
+ data: xhr.responseText ? JSON.parse(xhr.responseText) : null,
165
+ headers: xhr.getAllResponseHeaders()
166
+ };
167
+
168
+ reject(error);
169
+ }
170
+ });
171
+
172
+ // 监听请求错误
173
+ xhr.addEventListener('error', () => {
174
+ const error = new Error('网络错误,上传失败');
175
+ (error as any).request = xhr;
176
+ (error as any).type = 'network';
177
+ reject(error);
178
+ });
179
+
180
+ // 监听请求超时
181
+ xhr.addEventListener('timeout', () => {
182
+ const error = new Error('请求超时,上传失败');
183
+ (error as any).request = xhr;
184
+ (error as any).type = 'timeout';
185
+ reject(error);
186
+ });
187
+
188
+ // 打开请求
189
+ xhr.open('POST', urlWithParams.toString());
190
+
191
+ // 设置请求头
192
+ Object.entries(headers).forEach(([key, value]) => {
193
+ xhr.setRequestHeader(key, value);
194
+ });
195
+
196
+ // 发送请求
197
+ xhr.send(data);
198
+ }
199
+ });
200
+ };
@@ -0,0 +1,3 @@
1
+ // 导出新的Uploader组件
2
+ export { default } from './Uploader/index';
3
+ export * from './Uploader/types';