@ticatec/uniface-element 0.2.0 → 0.2.1

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 (126) hide show
  1. package/README.md +273 -1
  2. package/README_CN.md +341 -0
  3. package/dist/common/MouseClickHandler.d.ts +1 -1
  4. package/dist/dialog/CommonDialog.svelte +6 -3
  5. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  6. package/dist/dialog/Dialog.svelte +36 -10
  7. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  8. package/dist/dialog/DialogBoard.svelte +11 -8
  9. package/dist/dialog/DialogWrapper.svelte +10 -0
  10. package/dist/dialog/DialogWrapper.svelte.d.ts +22 -0
  11. package/dist/dialog/Dialogs.js +1 -1
  12. package/dist/dialog/ModalResult.d.ts +5 -0
  13. package/dist/dialog/ModalResult.js +5 -0
  14. package/docs/buttons/cn/ActionBar.md +459 -0
  15. package/docs/buttons/cn/Button.md +228 -0
  16. package/docs/buttons/cn/IconButton.md +294 -0
  17. package/docs/buttons/cn/README.md +600 -0
  18. package/docs/buttons/cn/TextButton.md +163 -0
  19. package/docs/buttons/en/ActionBar.md +459 -0
  20. package/docs/buttons/en/Button.md +228 -0
  21. package/docs/buttons/en/IconButton.md +294 -0
  22. package/docs/buttons/en/README.md +600 -0
  23. package/docs/buttons/en/TextButton.md +163 -0
  24. package/docs/containers/cn/accordion.md +622 -0
  25. package/docs/containers/cn/box.md +445 -0
  26. package/docs/containers/cn/card.md +404 -0
  27. package/docs/containers/cn/page.md +576 -0
  28. package/docs/containers/cn/tabs.md +686 -0
  29. package/docs/containers/en/accordion.md +622 -0
  30. package/docs/containers/en/box.md +445 -0
  31. package/docs/containers/en/card.md +404 -0
  32. package/docs/containers/en/page.md +576 -0
  33. package/docs/containers/en/tabs.md +686 -0
  34. package/docs/data-display/cn/concise-data-table.md +731 -0
  35. package/docs/data-display/cn/listbox.md +786 -0
  36. package/docs/data-display/cn/treeview.md +863 -0
  37. package/docs/data-display/en/concise-data-table.md +732 -0
  38. package/docs/data-display/en/listbox.md +786 -0
  39. package/docs/data-display/en/treeview.md +863 -0
  40. package/docs/data-table/cn/README.md +596 -0
  41. package/docs/data-table/en/README.md +596 -0
  42. package/docs/dialog/cn/README.md +767 -0
  43. package/docs/dialog/en/README.md +767 -0
  44. package/docs/form/cn/README.md +847 -0
  45. package/docs/form/en/README.md +847 -0
  46. package/docs/form-components/cn/README.md +495 -0
  47. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  48. package/docs/form-components/cn/cascade-options-select.md +374 -0
  49. package/docs/form-components/cn/checkbox.md +58 -0
  50. package/docs/form-components/cn/date-picker.md +138 -0
  51. package/docs/form-components/cn/datetime-picker.md +228 -0
  52. package/docs/form-components/cn/group-checkbox.md +405 -0
  53. package/docs/form-components/cn/group-radiobox.md +525 -0
  54. package/docs/form-components/cn/imagefilesfield.md +651 -0
  55. package/docs/form-components/cn/input-options-select.md +250 -0
  56. package/docs/form-components/cn/lookup-editor.md +406 -0
  57. package/docs/form-components/cn/memoeditor.md +409 -0
  58. package/docs/form-components/cn/number-editor.md +261 -0
  59. package/docs/form-components/cn/options-multi-select.md +177 -0
  60. package/docs/form-components/cn/options-select.md +83 -0
  61. package/docs/form-components/cn/passwordeditor.md +538 -0
  62. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  63. package/docs/form-components/cn/radiobutton.md +84 -0
  64. package/docs/form-components/cn/switch.md +178 -0
  65. package/docs/form-components/cn/text-editor.md +142 -0
  66. package/docs/form-components/cn/time-editor.md +159 -0
  67. package/docs/form-components/cn/unit-number-editor.md +164 -0
  68. package/docs/form-components/en/README.md +463 -0
  69. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  70. package/docs/form-components/en/cascade-options-select.md +358 -0
  71. package/docs/form-components/en/checkbox.md +59 -0
  72. package/docs/form-components/en/date-picker.md +138 -0
  73. package/docs/form-components/en/datetime-picker.md +228 -0
  74. package/docs/form-components/en/group-checkbox.md +395 -0
  75. package/docs/form-components/en/group-radiobox.md +498 -0
  76. package/docs/form-components/en/imagefilesfield.md +651 -0
  77. package/docs/form-components/en/input-options-select.md +239 -0
  78. package/docs/form-components/en/lookup-editor.md +389 -0
  79. package/docs/form-components/en/memoeditor.md +409 -0
  80. package/docs/form-components/en/number-editor.md +249 -0
  81. package/docs/form-components/en/options-multi-select.md +177 -0
  82. package/docs/form-components/en/options-select.md +83 -0
  83. package/docs/form-components/en/passwordeditor.md +538 -0
  84. package/docs/form-components/en/prompts-text-editor.md +241 -0
  85. package/docs/form-components/en/radiobutton.md +84 -0
  86. package/docs/form-components/en/switch.md +180 -0
  87. package/docs/form-components/en/text-editor.md +142 -0
  88. package/docs/form-components/en/time-editor.md +159 -0
  89. package/docs/form-components/en/unit-number-editor.md +164 -0
  90. package/docs/form-layout/cn/criteria-field.md +652 -0
  91. package/docs/form-layout/cn/flex-form.md +454 -0
  92. package/docs/form-layout/cn/flex-row-form.md +542 -0
  93. package/docs/form-layout/cn/form-field.md +923 -0
  94. package/docs/form-layout/cn/grid-form.md +676 -0
  95. package/docs/form-layout/en/criteria-field.md +652 -0
  96. package/docs/form-layout/en/flex-form.md +454 -0
  97. package/docs/form-layout/en/flex-row-form.md +542 -0
  98. package/docs/form-layout/en/form-field.md +923 -0
  99. package/docs/form-layout/en/grid-form.md +676 -0
  100. package/docs/global/cn/README.md +534 -0
  101. package/docs/global/en/README.md +534 -0
  102. package/docs/layout/cn/classiclayout.md +460 -0
  103. package/docs/layout/cn/headerlayout.md +524 -0
  104. package/docs/layout/cn/sidebarlayout.md +263 -0
  105. package/docs/layout/en/classiclayout.md +951 -0
  106. package/docs/layout/en/headerlayout.md +524 -0
  107. package/docs/layout/en/sidebarlayout.md +262 -0
  108. package/docs/misc/cn/README.md +1217 -0
  109. package/docs/misc/en/README.md +1217 -0
  110. package/docs/navigation/cn/breadcrumb.md +291 -0
  111. package/docs/navigation/cn/navigator.md +322 -0
  112. package/docs/navigation/cn/navigatormenu.md +512 -0
  113. package/docs/navigation/cn/progressbar.md +199 -0
  114. package/docs/navigation/cn/progressstepbar.md +299 -0
  115. package/docs/navigation/en/breadcrumb.md +291 -0
  116. package/docs/navigation/en/navigator.md +322 -0
  117. package/docs/navigation/en/navigatormenu.md +512 -0
  118. package/docs/navigation/en/progressbar.md +199 -0
  119. package/docs/navigation/en/progressstepbar.md +299 -0
  120. package/docs/search-filters/cn/daterange.md +1156 -0
  121. package/docs/search-filters/cn/numberrange.md +688 -0
  122. package/docs/search-filters/cn/searchbox.md +1582 -0
  123. package/docs/search-filters/en/daterange.md +1156 -0
  124. package/docs/search-filters/en/numberrange.md +688 -0
  125. package/docs/search-filters/en/searchbox.md +1582 -0
  126. package/package.json +38 -5
@@ -0,0 +1,757 @@
1
+ # AttachmentFilesField 附件文件字段组件
2
+
3
+ AttachmentFilesField 组件提供全面的文件上传和管理界面,支持多种文件类型、拖拽上传、进度跟踪和文件删除确认。
4
+
5
+ ## 功能特性
6
+
7
+ - **多文件上传**:支持同时上传多个文件
8
+ - **文件类型检测**:自动检测和分类文件类型(图片、PDF、文档等)
9
+ - **拖拽支持**:直观的拖拽文件上传界面
10
+ - **上传进度**:实时上传进度跟踪和取消功能
11
+ - **文件管理**:查看、删除和管理已上传的文件
12
+ - **显示模式**:编辑和查看模式适应不同场景
13
+ - **变体支持**:多种样式变体(plain、outlined、filled)
14
+ - **确认对话框**:文件删除的可选确认对话框
15
+ - **响应式设计**:适应不同屏幕尺寸和布局
16
+
17
+ ## 导入
18
+
19
+ ```typescript
20
+ import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
21
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
22
+ ```
23
+
24
+ ## 基本用法
25
+
26
+ ### 简单文件上传字段
27
+
28
+ ```svelte
29
+ <script>
30
+ import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
31
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
32
+
33
+ let attachments: AttachmentFile[] = [];
34
+
35
+ const uploadFile: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
36
+ // 模拟文件上传
37
+ let uploadedBytes = 0;
38
+ const totalBytes = file.size;
39
+
40
+ const uploadInterval = setInterval(() => {
41
+ uploadedBytes += Math.min(1024 * 100, totalBytes - uploadedBytes); // 100KB 块
42
+ progressUpdate(uploadedBytes);
43
+
44
+ if (uploadedBytes >= totalBytes) {
45
+ clearInterval(uploadInterval);
46
+ // 模拟成功上传并返回模拟URL
47
+ const mockUrl = URL.createObjectURL(file);
48
+ onUploaded(mockUrl);
49
+ }
50
+ }, 100);
51
+
52
+ return {
53
+ cancel: async () => {
54
+ clearInterval(uploadInterval);
55
+ return true;
56
+ }
57
+ };
58
+ };
59
+ </script>
60
+
61
+ <AttachmentFilesField
62
+ bind:files={attachments}
63
+ {uploadFile}
64
+ />
65
+ ```
66
+
67
+ ### 带验证的样式化文件上传
68
+
69
+ ```svelte
70
+ <script>
71
+ import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
72
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
73
+
74
+ let documents: AttachmentFile[] = [];
75
+
76
+ const uploadDocument: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
77
+ // 验证文件大小(最大 10MB)
78
+ if (file.size > 10 * 1024 * 1024) {
79
+ errorHandler(new Error('文件大小不能超过 10MB'));
80
+ return { cancel: async () => true };
81
+ }
82
+
83
+ // 验证文件类型
84
+ const allowedTypes = ['pdf', 'doc', 'docx', 'txt', 'jpg', 'png'];
85
+ const fileExtension = file.name.split('.').pop()?.toLowerCase();
86
+
87
+ if (!fileExtension || !allowedTypes.includes(fileExtension)) {
88
+ errorHandler(new Error('不支持的文件类型。允许的类型:PDF、DOC、DOCX、TXT、JPG、PNG'));
89
+ return { cancel: async () => true };
90
+ }
91
+
92
+ // 模拟 API 上传
93
+ const formData = new FormData();
94
+ formData.append('file', file);
95
+
96
+ return {
97
+ cancel: async () => {
98
+ // 取消上传的实现
99
+ return true;
100
+ }
101
+ };
102
+ };
103
+
104
+ const confirmRemoval = async (file: AttachmentFile) => {
105
+ return confirm(`确定要删除"${file.name}"吗?`);
106
+ };
107
+ </script>
108
+
109
+ <div class="document-upload">
110
+ <label>项目文档</label>
111
+ <AttachmentFilesField
112
+ bind:files={documents}
113
+ variant="outlined"
114
+ uploadFile={uploadDocument}
115
+ removeFileConfirm={confirmRemoval}
116
+ style="margin-top: 8px;"
117
+ />
118
+ </div>
119
+ ```
120
+
121
+ ## API 参考
122
+
123
+ ### 属性
124
+
125
+ | 属性 | 类型 | 默认值 | 描述 |
126
+ |------|------|--------|------|
127
+ | `files` | `Array<AttachmentFile>` | `[]` | 附件文件数组 |
128
+ | `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 视觉变体 |
129
+ | `disabled` | `boolean` | `false` | 字段是否禁用 |
130
+ | `readonly` | `boolean` | `false` | 字段是否只读 |
131
+ | `style` | `string` | `''` | 附加CSS样式 |
132
+ | `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 显示模式(编辑/查看) |
133
+ | `removeFileConfirm` | `RemoveConfirm` | `null` | 文件删除的可选确认函数 |
134
+ | `uploadFile` | `FileUpload` | 必需 | 处理文件上传的函数 |
135
+ | `text` | `string \| null` | `null` | 显示文本(从文件名自动生成) |
136
+
137
+ ### 类型定义
138
+
139
+ #### AttachmentFile 接口
140
+
141
+ ```typescript
142
+ interface AttachmentFile {
143
+ name: string; // 文件名
144
+ type: FileType; // 文件类型分类
145
+ uri: string; // 文件URL或路径
146
+ }
147
+ ```
148
+
149
+ #### FileType 枚举
150
+
151
+ ```typescript
152
+ enum FileType {
153
+ IMAGE = 'img', // 图片
154
+ PDF = 'pdf', // PDF文档
155
+ DOC = 'doc', // Word文档
156
+ XLS = 'xml', // Excel表格
157
+ PPT = 'ppt', // PowerPoint演示文稿
158
+ AUDIO = 'wav', // 音频文件
159
+ VIDEO = 'mov', // 视频文件
160
+ OTHER = 'dat' // 其他文件
161
+ }
162
+ ```
163
+
164
+ #### FileUpload 函数类型
165
+
166
+ ```typescript
167
+ type FileUpload = (
168
+ file: File,
169
+ progressUpdate: ProgressUpdate,
170
+ onUploaded: OnUploaded,
171
+ errorHandler: ErrorHandler
172
+ ) => UploadProgress;
173
+
174
+ type ProgressUpdate = (uploadBytes: number) => void;
175
+ type OnUploaded = (url: string, thumbnail?: string) => void;
176
+ type ErrorHandler = (e: Error) => void;
177
+
178
+ interface UploadProgress {
179
+ cancel: () => Promise<boolean>;
180
+ }
181
+ ```
182
+
183
+ #### RemoveConfirm 函数类型
184
+
185
+ ```typescript
186
+ type RemoveConfirm = (file: AttachmentFile) => Promise<boolean>;
187
+ ```
188
+
189
+ ## 高级示例
190
+
191
+ ### 文档管理系统
192
+
193
+ ```svelte
194
+ <script>
195
+ import AttachmentFilesField, { DisplayMode } from '@ticatec/uniface-element/AttachmentFilesField';
196
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
197
+
198
+ let projectFiles: AttachmentFile[] = [
199
+ {
200
+ name: "项目提案.pdf",
201
+ type: FileType.PDF,
202
+ uri: "/documents/proposal.pdf"
203
+ },
204
+ {
205
+ name: "需求文档.docx",
206
+ type: FileType.DOC,
207
+ uri: "/documents/requirements.docx"
208
+ }
209
+ ];
210
+
211
+ let isEditMode = true;
212
+ let uploadProgress = new Map<string, number>();
213
+
214
+ const uploadToServer: FileUpload = async (file, progressUpdate, onUploaded, errorHandler) => {
215
+ try {
216
+ const formData = new FormData();
217
+ formData.append('file', file);
218
+ formData.append('category', 'project-documents');
219
+
220
+ const xhr = new XMLHttpRequest();
221
+
222
+ xhr.upload.addEventListener('progress', (e) => {
223
+ if (e.lengthComputable) {
224
+ progressUpdate(e.loaded);
225
+ }
226
+ });
227
+
228
+ xhr.addEventListener('load', () => {
229
+ if (xhr.status === 200) {
230
+ const response = JSON.parse(xhr.responseText);
231
+ onUploaded(response.url, response.thumbnail);
232
+ } else {
233
+ errorHandler(new Error(`上传失败: ${xhr.statusText}`));
234
+ }
235
+ });
236
+
237
+ xhr.addEventListener('error', () => {
238
+ errorHandler(new Error('上传过程中发生网络错误'));
239
+ });
240
+
241
+ xhr.open('POST', '/api/upload');
242
+ xhr.send(formData);
243
+
244
+ return {
245
+ cancel: async () => {
246
+ xhr.abort();
247
+ return true;
248
+ }
249
+ };
250
+ } catch (error) {
251
+ errorHandler(error as Error);
252
+ return { cancel: async () => true };
253
+ }
254
+ };
255
+
256
+ const confirmFileRemoval = async (file: AttachmentFile) => {
257
+ const result = await showCustomDialog({
258
+ title: '删除文件',
259
+ message: `确定要删除"${file.name}"吗?此操作无法撤销。`,
260
+ buttons: ['取消', '删除'],
261
+ type: 'warning'
262
+ });
263
+
264
+ return result === '删除';
265
+ };
266
+
267
+ const showCustomDialog = (options: any) => {
268
+ return new Promise((resolve) => {
269
+ // 自定义对话框的实现
270
+ const confirmed = confirm(`${options.title}: ${options.message}`);
271
+ resolve(confirmed ? '删除' : '取消');
272
+ });
273
+ };
274
+
275
+ const toggleEditMode = () => {
276
+ isEditMode = !isEditMode;
277
+ };
278
+ </script>
279
+
280
+ <div class="document-manager">
281
+ <div class="manager-header">
282
+ <h3>项目文档</h3>
283
+ <button on:click={toggleEditMode}>
284
+ {isEditMode ? '查看模式' : '编辑模式'}
285
+ </button>
286
+ </div>
287
+
288
+ <AttachmentFilesField
289
+ bind:files={projectFiles}
290
+ variant="outlined"
291
+ displayMode={isEditMode ? DisplayMode.Edit : DisplayMode.View}
292
+ uploadFile={uploadToServer}
293
+ removeFileConfirm={confirmFileRemoval}
294
+ style="margin-top: 15px; min-height: 120px;"
295
+ />
296
+
297
+ <div class="file-summary">
298
+ <p>文件总数: {projectFiles.length}</p>
299
+ <p>类型: {[...new Set(projectFiles.map(f => f.type))].join(', ')}</p>
300
+ </div>
301
+ </div>
302
+
303
+ <style>
304
+ .document-manager {
305
+ max-width: 600px;
306
+ margin: 20px 0;
307
+ }
308
+
309
+ .manager-header {
310
+ display: flex;
311
+ justify-content: space-between;
312
+ align-items: center;
313
+ margin-bottom: 10px;
314
+ }
315
+
316
+ .file-summary {
317
+ margin-top: 10px;
318
+ padding: 10px;
319
+ background: #f5f5f5;
320
+ border-radius: 4px;
321
+ font-size: 0.9em;
322
+ color: #666;
323
+ }
324
+ </style>
325
+ ```
326
+
327
+ ### 带附件的表单
328
+
329
+ ```svelte
330
+ <script>
331
+ import AttachmentFilesField from '@ticatec/uniface-element/AttachmentFilesField';
332
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
333
+
334
+ let formData = {
335
+ title: '',
336
+ description: '',
337
+ attachments: [] as AttachmentFile[]
338
+ };
339
+
340
+ let errors = {};
341
+ let isSubmitting = false;
342
+
343
+ const uploadFile: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
344
+ // 文件大小限制:5MB
345
+ if (file.size > 5 * 1024 * 1024) {
346
+ errorHandler(new Error('文件大小必须小于 5MB'));
347
+ return { cancel: async () => true };
348
+ }
349
+
350
+ // 模拟上传过程
351
+ let uploaded = 0;
352
+ const total = file.size;
353
+
354
+ const interval = setInterval(() => {
355
+ uploaded += Math.min(1024 * 50, total - uploaded);
356
+ progressUpdate(uploaded);
357
+
358
+ if (uploaded >= total) {
359
+ clearInterval(interval);
360
+ setTimeout(() => {
361
+ const mockUrl = `/files/${Date.now()}_${file.name}`;
362
+ onUploaded(mockUrl);
363
+ }, 200);
364
+ }
365
+ }, 50);
366
+
367
+ return {
368
+ cancel: async () => {
369
+ clearInterval(interval);
370
+ return true;
371
+ }
372
+ };
373
+ };
374
+
375
+ const validateForm = () => {
376
+ errors = {};
377
+
378
+ if (!formData.title.trim()) {
379
+ errors.title = '标题是必填项';
380
+ }
381
+
382
+ if (!formData.description.trim()) {
383
+ errors.description = '描述是必填项';
384
+ }
385
+
386
+ if (formData.attachments.length === 0) {
387
+ errors.attachments = '至少需要一个附件';
388
+ }
389
+
390
+ return Object.keys(errors).length === 0;
391
+ };
392
+
393
+ const submitForm = async () => {
394
+ if (!validateForm()) return;
395
+
396
+ isSubmitting = true;
397
+
398
+ try {
399
+ // 模拟表单提交
400
+ await new Promise(resolve => setTimeout(resolve, 2000));
401
+
402
+ alert('表单提交成功!');
403
+ formData = { title: '', description: '', attachments: [] };
404
+ } catch (error) {
405
+ alert('提交失败,请重试。');
406
+ } finally {
407
+ isSubmitting = false;
408
+ }
409
+ };
410
+
411
+ const confirmRemoval = async (file: AttachmentFile) => {
412
+ return confirm(`从附件中删除"${file.name}"?`);
413
+ };
414
+ </script>
415
+
416
+ <form class="attachment-form" on:submit|preventDefault={submitForm}>
417
+ <div class="form-group">
418
+ <label for="title">标题 *</label>
419
+ <input
420
+ id="title"
421
+ type="text"
422
+ bind:value={formData.title}
423
+ disabled={isSubmitting}
424
+ class:error={errors.title}
425
+ />
426
+ {#if errors.title}
427
+ <div class="error-message">{errors.title}</div>
428
+ {/if}
429
+ </div>
430
+
431
+ <div class="form-group">
432
+ <label for="description">描述 *</label>
433
+ <textarea
434
+ id="description"
435
+ rows="4"
436
+ bind:value={formData.description}
437
+ disabled={isSubmitting}
438
+ class:error={errors.description}
439
+ ></textarea>
440
+ {#if errors.description}
441
+ <div class="error-message">{errors.description}</div>
442
+ {/if}
443
+ </div>
444
+
445
+ <div class="form-group">
446
+ <label>附件 *</label>
447
+ <AttachmentFilesField
448
+ bind:files={formData.attachments}
449
+ variant="outlined"
450
+ {uploadFile}
451
+ removeFileConfirm={confirmRemoval}
452
+ disabled={isSubmitting}
453
+ />
454
+ {#if errors.attachments}
455
+ <div class="error-message">{errors.attachments}</div>
456
+ {/if}
457
+ </div>
458
+
459
+ <div class="form-actions">
460
+ <button
461
+ type="button"
462
+ on:click={() => formData = { title: '', description: '', attachments: [] }}
463
+ disabled={isSubmitting}
464
+ >
465
+ 清除
466
+ </button>
467
+ <button
468
+ type="submit"
469
+ disabled={isSubmitting}
470
+ class="primary"
471
+ >
472
+ {isSubmitting ? '提交中...' : '提交'}
473
+ </button>
474
+ </div>
475
+ </form>
476
+
477
+ <style>
478
+ .attachment-form {
479
+ max-width: 600px;
480
+ margin: 0 auto;
481
+ padding: 20px;
482
+ }
483
+
484
+ .form-group {
485
+ margin-bottom: 20px;
486
+ }
487
+
488
+ .form-group label {
489
+ display: block;
490
+ margin-bottom: 5px;
491
+ font-weight: bold;
492
+ color: #333;
493
+ }
494
+
495
+ .form-group input, .form-group textarea {
496
+ width: 100%;
497
+ padding: 10px;
498
+ border: 1px solid #ddd;
499
+ border-radius: 4px;
500
+ font-size: 14px;
501
+ }
502
+
503
+ .form-group input.error, .form-group textarea.error {
504
+ border-color: #d32f2f;
505
+ }
506
+
507
+ .error-message {
508
+ color: #d32f2f;
509
+ font-size: 0.875em;
510
+ margin-top: 4px;
511
+ }
512
+
513
+ .form-actions {
514
+ display: flex;
515
+ gap: 10px;
516
+ justify-content: flex-end;
517
+ }
518
+
519
+ button {
520
+ padding: 10px 20px;
521
+ border: 1px solid #ddd;
522
+ border-radius: 4px;
523
+ cursor: pointer;
524
+ font-size: 14px;
525
+ }
526
+
527
+ button.primary {
528
+ background: #1976d2;
529
+ color: white;
530
+ border-color: #1976d2;
531
+ }
532
+
533
+ button:disabled {
534
+ opacity: 0.6;
535
+ cursor: not-allowed;
536
+ }
537
+ </style>
538
+ ```
539
+
540
+ ### 多分类文件管理器
541
+
542
+ ```svelte
543
+ <script>
544
+ import AttachmentFilesField, { DisplayMode } from '@ticatec/uniface-element/AttachmentFilesField';
545
+ import type { AttachmentFile, FileUpload } from '@ticatec/uniface-element/AttachmentFilesField';
546
+
547
+ let fileCategories = {
548
+ documents: [] as AttachmentFile[],
549
+ images: [] as AttachmentFile[],
550
+ archives: [] as AttachmentFile[]
551
+ };
552
+
553
+ let activeCategory = 'documents';
554
+
555
+ const categoryConfigs = {
556
+ documents: {
557
+ title: '文档',
558
+ accept: ['.pdf', '.doc', '.docx', '.txt', '.rtf'],
559
+ maxSize: 10 * 1024 * 1024, // 10MB
560
+ description: 'PDF、DOC、DOCX、TXT、RTF 文件,最大 10MB'
561
+ },
562
+ images: {
563
+ title: '图片',
564
+ accept: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'],
565
+ maxSize: 5 * 1024 * 1024, // 5MB
566
+ description: '图片文件,最大 5MB'
567
+ },
568
+ archives: {
569
+ title: '压缩包',
570
+ accept: ['.zip', '.rar', '.7z', '.tar', '.gz'],
571
+ maxSize: 50 * 1024 * 1024, // 50MB
572
+ description: '压缩包文件,最大 50MB'
573
+ }
574
+ };
575
+
576
+ const createUploadFunction = (category: string): FileUpload => {
577
+ return (file, progressUpdate, onUploaded, errorHandler) => {
578
+ const config = categoryConfigs[category];
579
+
580
+ // 验证文件大小
581
+ if (file.size > config.maxSize) {
582
+ const maxSizeMB = Math.round(config.maxSize / (1024 * 1024));
583
+ errorHandler(new Error(`文件大小超过 ${maxSizeMB}MB 限制`));
584
+ return { cancel: async () => true };
585
+ }
586
+
587
+ // 验证文件类型
588
+ const fileExt = '.' + file.name.split('.').pop()?.toLowerCase();
589
+ if (!config.accept.includes(fileExt)) {
590
+ errorHandler(new Error(`不支持的文件类型。允许的类型:${config.accept.join(', ')}`));
591
+ return { cancel: async () => true };
592
+ }
593
+
594
+ // 模拟分类上传
595
+ let uploaded = 0;
596
+ const total = file.size;
597
+
598
+ const interval = setInterval(() => {
599
+ uploaded += Math.min(1024 * 100, total - uploaded);
600
+ progressUpdate(uploaded);
601
+
602
+ if (uploaded >= total) {
603
+ clearInterval(interval);
604
+ setTimeout(() => {
605
+ const mockUrl = `/files/${category}/${Date.now()}_${file.name}`;
606
+ onUploaded(mockUrl);
607
+ }, 300);
608
+ }
609
+ }, 100);
610
+
611
+ return {
612
+ cancel: async () => {
613
+ clearInterval(interval);
614
+ return true;
615
+ }
616
+ };
617
+ };
618
+ };
619
+
620
+ const confirmRemoval = async (file: AttachmentFile) => {
621
+ return confirm(`从${categoryConfigs[activeCategory].title.toLowerCase()}中删除"${file.name}"?`);
622
+ };
623
+
624
+ const getTotalFiles = () => {
625
+ return Object.values(fileCategories).reduce((sum, files) => sum + files.length, 0);
626
+ };
627
+ </script>
628
+
629
+ <div class="multi-category-manager">
630
+ <div class="category-tabs">
631
+ {#each Object.keys(categoryConfigs) as category}
632
+ <button
633
+ class="tab"
634
+ class:active={activeCategory === category}
635
+ on:click={() => activeCategory = category}
636
+ >
637
+ {categoryConfigs[category].title}
638
+ ({fileCategories[category].length})
639
+ </button>
640
+ {/each}
641
+ </div>
642
+
643
+ <div class="category-content">
644
+ <div class="category-header">
645
+ <h3>{categoryConfigs[activeCategory].title}</h3>
646
+ <p class="category-description">{categoryConfigs[activeCategory].description}</p>
647
+ </div>
648
+
649
+ <AttachmentFilesField
650
+ bind:files={fileCategories[activeCategory]}
651
+ variant="outlined"
652
+ uploadFile={createUploadFunction(activeCategory)}
653
+ removeFileConfirm={confirmRemoval}
654
+ style="min-height: 150px;"
655
+ />
656
+ </div>
657
+
658
+ <div class="summary">
659
+ <p>所有分类的文件总数: {getTotalFiles()}</p>
660
+ </div>
661
+ </div>
662
+
663
+ <style>
664
+ .multi-category-manager {
665
+ max-width: 800px;
666
+ margin: 20px auto;
667
+ border: 1px solid #e0e0e0;
668
+ border-radius: 8px;
669
+ overflow: hidden;
670
+ }
671
+
672
+ .category-tabs {
673
+ display: flex;
674
+ background: #f5f5f5;
675
+ border-bottom: 1px solid #e0e0e0;
676
+ }
677
+
678
+ .tab {
679
+ flex: 1;
680
+ padding: 15px 20px;
681
+ border: none;
682
+ background: transparent;
683
+ cursor: pointer;
684
+ transition: background-color 0.2s;
685
+ }
686
+
687
+ .tab:hover {
688
+ background: #e8e8e8;
689
+ }
690
+
691
+ .tab.active {
692
+ background: white;
693
+ border-bottom: 2px solid #1976d2;
694
+ color: #1976d2;
695
+ font-weight: bold;
696
+ }
697
+
698
+ .category-content {
699
+ padding: 20px;
700
+ }
701
+
702
+ .category-header {
703
+ margin-bottom: 15px;
704
+ }
705
+
706
+ .category-header h3 {
707
+ margin: 0 0 5px 0;
708
+ color: #333;
709
+ }
710
+
711
+ .category-description {
712
+ margin: 0;
713
+ font-size: 0.9em;
714
+ color: #666;
715
+ }
716
+
717
+ .summary {
718
+ padding: 15px 20px;
719
+ background: #f9f9f9;
720
+ border-top: 1px solid #e0e0e0;
721
+ font-size: 0.9em;
722
+ color: #666;
723
+ }
724
+ </style>
725
+ ```
726
+
727
+ ## 最佳实践
728
+
729
+ 1. **文件验证**:始终在上传前验证文件大小和类型
730
+ 2. **进度反馈**:在上传过程中提供清晰的进度指示器
731
+ 3. **错误处理**:实施全面的错误处理以应对网络问题
732
+ 4. **确认对话框**:对破坏性操作使用确认对话框
733
+ 5. **无障碍访问**:确保适当的ARIA标签和键盘导航
734
+ 6. **性能优化**:对大型文件列表考虑延迟加载
735
+ 7. **安全性**:在服务器端也要验证文件
736
+ 8. **用户体验**:明确提供文件限制和要求的反馈
737
+
738
+ ## 样式说明
739
+
740
+ - 组件支持标准变体样式(plain、outlined、filled)
741
+ - 文件图标根据文件类型自动选择
742
+ - 上传进度通过进度条可视化显示
743
+ - 自定义样式可以通过`style`属性应用
744
+
745
+ ## 无障碍访问注意事项
746
+
747
+ - 上传按钮支持键盘访问
748
+ - 文件删除按钮包含适当的ARIA标签
749
+ - 屏幕阅读器可以导航文件列表
750
+ - 进度更新会通知辅助技术
751
+ - 颜色对比度符合无障碍标准
752
+
753
+ ## 浏览器支持
754
+
755
+ - 支持File API的现代浏览器
756
+ - 拖拽功能需要HTML5支持
757
+ - 进度跟踪在支持XMLHttpRequest Level 2的浏览器中工作