@ticatec/uniface-element 0.2.0 → 0.2.2

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 (209) hide show
  1. package/README.md +320 -1
  2. package/README_CN.md +392 -0
  3. package/dist/accordion/Accordion.svelte +1 -1
  4. package/dist/action-bar/ActionBar.svelte +7 -1
  5. package/dist/attachment-files/FileRender.svelte +1 -1
  6. package/dist/attachment-files/FileUploadBar.svelte +7 -6
  7. package/dist/attachment-files/FileUploadPanel.svelte +2 -2
  8. package/dist/base-calendar/DateContext.js +5 -4
  9. package/dist/base-calendar/YearPickupPanel.svelte +4 -4
  10. package/dist/card/Card.svelte +1 -1
  11. package/dist/card/Card.svelte.d.ts +1 -1
  12. package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
  13. package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
  14. package/dist/cascade-options-select/CascadePanel.svelte +1 -1
  15. package/dist/checkbox/CheckBox.svelte +1 -1
  16. package/dist/color-picker/ColorPicker.svelte +9 -9
  17. package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
  18. package/dist/color-picker/ColorPickerPanel.svelte +11 -15
  19. package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
  20. package/dist/common/CommonPicker.svelte +2 -2
  21. package/dist/common/CommonPicker.svelte.d.ts +1 -1
  22. package/dist/common/MouseClickHandler.d.ts +1 -1
  23. package/dist/common/Popover.svelte +28 -6
  24. package/dist/common/Popover.svelte.d.ts +9 -3
  25. package/dist/common/uniface-utils.js +6 -4
  26. package/dist/common-editor/CommonEditor.svelte +1 -1
  27. package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
  28. package/dist/common-editor/NumberInput.svelte +1 -1
  29. package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
  30. package/dist/concise-data-table/ConciseListTable.svelte +7 -2
  31. package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
  32. package/dist/concise-data-table/DataRow.svelte +1 -1
  33. package/dist/concise-data-table/TableOptions.d.ts +1 -1
  34. package/dist/data-table/DataTable.svelte +1 -1
  35. package/dist/data-table/parts/ActionsPanel.svelte +4 -3
  36. package/dist/data-table/parts/ActionsRow.svelte +3 -3
  37. package/dist/data-table/parts/ContentPanel.svelte +3 -2
  38. package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
  39. package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
  40. package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
  41. package/dist/data-table/parts/FixedRow.svelte +2 -1
  42. package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
  43. package/dist/data-table/parts/HrefCell.svelte +23 -2
  44. package/dist/data-table/parts/PopupMenu.svelte +25 -2
  45. package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
  46. package/dist/date-picker/DatePicker.svelte +0 -1
  47. package/dist/date-picker/DateTimePicker.svelte +3 -3
  48. package/dist/date-picker/ScrollBar.svelte +4 -7
  49. package/dist/date-range/DateRangeEditor.svelte +1 -1
  50. package/dist/dialog/CommonDialog.svelte +8 -5
  51. package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
  52. package/dist/dialog/Dialog.svelte +37 -12
  53. package/dist/dialog/Dialog.svelte.d.ts +4 -3
  54. package/dist/dialog/DialogBoard.svelte +11 -8
  55. package/dist/dialog/DialogWrapper.svelte +10 -0
  56. package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
  57. package/dist/dialog/Dialogs.js +1 -1
  58. package/dist/dialog/ModalResult.d.ts +5 -0
  59. package/dist/dialog/ModalResult.js +5 -0
  60. package/dist/drawer/Drawer.svelte +1 -1
  61. package/dist/form-field/FormField.svelte +2 -1
  62. package/dist/form-field/FormField.svelte.d.ts +1 -0
  63. package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
  64. package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
  65. package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
  66. package/dist/form-panel/grid-form/GridField.svelte +1 -1
  67. package/dist/i18nRes/i18nRes.d.ts +2 -0
  68. package/dist/i18nRes/i18nRes.js +44 -0
  69. package/dist/i18nRes/index.d.ts +2 -0
  70. package/dist/i18nRes/index.js +2 -0
  71. package/dist/image-files/ImageFilesField.svelte +1 -1
  72. package/dist/indicator/IndicatorBoard.svelte +1 -1
  73. package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
  74. package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
  75. package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
  76. package/dist/layout/flex/FlexBlock.svelte +1 -1
  77. package/dist/lookup-editor/LookupEditor.svelte +1 -1
  78. package/dist/memo-editor/MemoEditor.svelte +2 -2
  79. package/dist/message-box/IMessageBox.d.ts +2 -4
  80. package/dist/message-box/IMessageBox.js +4 -6
  81. package/dist/message-box/MessageBoxBoard.svelte +1 -3
  82. package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
  83. package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
  84. package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
  85. package/dist/property-editor/PropertyEditor.svelte +3 -3
  86. package/dist/text-editor/PasswordEditor.svelte +1 -1
  87. package/dist/transfer/Transfer.svelte +17 -12
  88. package/dist/transfer/Transfer.svelte.d.ts +0 -1
  89. package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
  90. package/docs/buttons/cn/ActionBar.md +459 -0
  91. package/docs/buttons/cn/Button.md +228 -0
  92. package/docs/buttons/cn/IconButton.md +294 -0
  93. package/docs/buttons/cn/README.md +600 -0
  94. package/docs/buttons/cn/TextButton.md +163 -0
  95. package/docs/buttons/en/ActionBar.md +459 -0
  96. package/docs/buttons/en/Button.md +228 -0
  97. package/docs/buttons/en/IconButton.md +294 -0
  98. package/docs/buttons/en/README.md +600 -0
  99. package/docs/buttons/en/TextButton.md +163 -0
  100. package/docs/containers/cn/accordion.md +622 -0
  101. package/docs/containers/cn/box.md +445 -0
  102. package/docs/containers/cn/card.md +404 -0
  103. package/docs/containers/cn/page.md +576 -0
  104. package/docs/containers/cn/tabs.md +686 -0
  105. package/docs/containers/en/accordion.md +622 -0
  106. package/docs/containers/en/box.md +445 -0
  107. package/docs/containers/en/card.md +404 -0
  108. package/docs/containers/en/page.md +576 -0
  109. package/docs/containers/en/tabs.md +686 -0
  110. package/docs/data-display/cn/concise-data-table.md +731 -0
  111. package/docs/data-display/cn/listbox.md +786 -0
  112. package/docs/data-display/cn/treeview.md +863 -0
  113. package/docs/data-display/en/concise-data-table.md +732 -0
  114. package/docs/data-display/en/listbox.md +786 -0
  115. package/docs/data-display/en/treeview.md +863 -0
  116. package/docs/data-table/cn/README.md +596 -0
  117. package/docs/data-table/en/README.md +596 -0
  118. package/docs/dialog/cn/README.md +767 -0
  119. package/docs/dialog/en/README.md +767 -0
  120. package/docs/form/cn/README.md +847 -0
  121. package/docs/form/en/README.md +847 -0
  122. package/docs/form-components/cn/README.md +495 -0
  123. package/docs/form-components/cn/attachmentfilesfield.md +757 -0
  124. package/docs/form-components/cn/cascade-options-select.md +374 -0
  125. package/docs/form-components/cn/checkbox.md +58 -0
  126. package/docs/form-components/cn/date-picker.md +138 -0
  127. package/docs/form-components/cn/datetime-picker.md +228 -0
  128. package/docs/form-components/cn/group-checkbox.md +405 -0
  129. package/docs/form-components/cn/group-radiobox.md +525 -0
  130. package/docs/form-components/cn/imagefilesfield.md +651 -0
  131. package/docs/form-components/cn/input-options-select.md +250 -0
  132. package/docs/form-components/cn/lookup-editor.md +406 -0
  133. package/docs/form-components/cn/memoeditor.md +409 -0
  134. package/docs/form-components/cn/number-editor.md +261 -0
  135. package/docs/form-components/cn/options-multi-select.md +177 -0
  136. package/docs/form-components/cn/options-select.md +83 -0
  137. package/docs/form-components/cn/passwordeditor.md +538 -0
  138. package/docs/form-components/cn/prompts-text-editor.md +241 -0
  139. package/docs/form-components/cn/radiobutton.md +84 -0
  140. package/docs/form-components/cn/switch.md +178 -0
  141. package/docs/form-components/cn/text-editor.md +142 -0
  142. package/docs/form-components/cn/time-editor.md +159 -0
  143. package/docs/form-components/cn/unit-number-editor.md +164 -0
  144. package/docs/form-components/en/README.md +463 -0
  145. package/docs/form-components/en/attachmentfilesfield.md +757 -0
  146. package/docs/form-components/en/cascade-options-select.md +358 -0
  147. package/docs/form-components/en/checkbox.md +59 -0
  148. package/docs/form-components/en/date-picker.md +138 -0
  149. package/docs/form-components/en/datetime-picker.md +228 -0
  150. package/docs/form-components/en/group-checkbox.md +395 -0
  151. package/docs/form-components/en/group-radiobox.md +498 -0
  152. package/docs/form-components/en/imagefilesfield.md +651 -0
  153. package/docs/form-components/en/input-options-select.md +239 -0
  154. package/docs/form-components/en/lookup-editor.md +389 -0
  155. package/docs/form-components/en/memoeditor.md +409 -0
  156. package/docs/form-components/en/number-editor.md +249 -0
  157. package/docs/form-components/en/options-multi-select.md +177 -0
  158. package/docs/form-components/en/options-select.md +83 -0
  159. package/docs/form-components/en/passwordeditor.md +538 -0
  160. package/docs/form-components/en/prompts-text-editor.md +241 -0
  161. package/docs/form-components/en/radiobutton.md +84 -0
  162. package/docs/form-components/en/switch.md +180 -0
  163. package/docs/form-components/en/text-editor.md +142 -0
  164. package/docs/form-components/en/time-editor.md +159 -0
  165. package/docs/form-components/en/unit-number-editor.md +164 -0
  166. package/docs/form-layout/cn/criteria-field.md +652 -0
  167. package/docs/form-layout/cn/flex-form.md +454 -0
  168. package/docs/form-layout/cn/flex-row-form.md +542 -0
  169. package/docs/form-layout/cn/form-field.md +923 -0
  170. package/docs/form-layout/cn/grid-form.md +676 -0
  171. package/docs/form-layout/en/criteria-field.md +652 -0
  172. package/docs/form-layout/en/flex-form.md +454 -0
  173. package/docs/form-layout/en/flex-row-form.md +542 -0
  174. package/docs/form-layout/en/form-field.md +923 -0
  175. package/docs/form-layout/en/grid-form.md +676 -0
  176. package/docs/global/cn/README.md +534 -0
  177. package/docs/global/en/README.md +534 -0
  178. package/docs/layout/cn/classiclayout.md +460 -0
  179. package/docs/layout/cn/headerlayout.md +524 -0
  180. package/docs/layout/cn/sidebarlayout.md +263 -0
  181. package/docs/layout/en/classiclayout.md +951 -0
  182. package/docs/layout/en/headerlayout.md +524 -0
  183. package/docs/layout/en/sidebarlayout.md +262 -0
  184. package/docs/misc/cn/README.md +1217 -0
  185. package/docs/misc/en/README.md +1217 -0
  186. package/docs/navigation/cn/breadcrumb.md +291 -0
  187. package/docs/navigation/cn/navigator.md +322 -0
  188. package/docs/navigation/cn/navigatormenu.md +512 -0
  189. package/docs/navigation/cn/progressbar.md +199 -0
  190. package/docs/navigation/cn/progressstepbar.md +299 -0
  191. package/docs/navigation/en/breadcrumb.md +291 -0
  192. package/docs/navigation/en/navigator.md +322 -0
  193. package/docs/navigation/en/navigatormenu.md +512 -0
  194. package/docs/navigation/en/progressbar.md +199 -0
  195. package/docs/navigation/en/progressstepbar.md +299 -0
  196. package/docs/search-filters/cn/daterange.md +1156 -0
  197. package/docs/search-filters/cn/numberrange.md +688 -0
  198. package/docs/search-filters/cn/searchbox.md +1582 -0
  199. package/docs/search-filters/en/daterange.md +1156 -0
  200. package/docs/search-filters/en/numberrange.md +688 -0
  201. package/docs/search-filters/en/searchbox.md +1582 -0
  202. package/package.json +53 -13
  203. package/dist/common-editor/DateInput.svelte +0 -27
  204. package/dist/i18n_resources/index.d.ts +0 -3
  205. package/dist/i18n_resources/index.js +0 -3
  206. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
  207. /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
  208. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
  209. /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
@@ -0,0 +1,651 @@
1
+ # ImageFilesField 图片文件字段组件
2
+
3
+ ImageFilesField 组件提供专门的图片上传和管理界面,具有缩略图预览、图片预览功能和对多种图片格式的支持。
4
+
5
+ ## 功能特性
6
+
7
+ - **图片专用上传**:针对图片文件处理优化,自动生成缩略图
8
+ - **图片预览**:在模态对话框中全尺寸图片预览
9
+ - **缩略图显示**:自动生成和显示缩略图
10
+ - **多图片支持**:可配置的最大图片数量
11
+ - **图片格式支持**:支持常见图片格式(JPG、PNG、GIF、WebP等)
12
+ - **拖拽上传**:直观的拖拽图片上传
13
+ - **进度跟踪**:实时上传进度和取消功能
14
+ - **显示模式**:编辑和查看模式适应不同场景
15
+ - **变体支持**:多种样式变体(plain、outlined、filled)
16
+ - **响应式布局**:适应不同屏幕尺寸
17
+
18
+ ## 导入
19
+
20
+ ```typescript
21
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
22
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
23
+ ```
24
+
25
+ ## 基本用法
26
+
27
+ ### 简单图片上传字段
28
+
29
+ ```svelte
30
+ <script>
31
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
32
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
33
+
34
+ let images: ImageFile[] = [];
35
+
36
+ const uploadImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
37
+ // 验证是否为图片文件
38
+ if (!file.type.startsWith('image/')) {
39
+ errorHandler(new Error('请选择图片文件'));
40
+ return { cancel: async () => true };
41
+ }
42
+
43
+ // 模拟带缩略图生成的图片上传
44
+ let uploadedBytes = 0;
45
+ const totalBytes = file.size;
46
+
47
+ const uploadInterval = setInterval(() => {
48
+ uploadedBytes += Math.min(1024 * 100, totalBytes - uploadedBytes);
49
+ progressUpdate(uploadedBytes);
50
+
51
+ if (uploadedBytes >= totalBytes) {
52
+ clearInterval(uploadInterval);
53
+
54
+ // 创建缩略图
55
+ const canvas = document.createElement('canvas');
56
+ const ctx = canvas.getContext('2d');
57
+ const img = new Image();
58
+
59
+ img.onload = () => {
60
+ canvas.width = 150;
61
+ canvas.height = 150;
62
+ ctx?.drawImage(img, 0, 0, 150, 150);
63
+
64
+ const thumbnailUrl = canvas.toDataURL();
65
+ const fullUrl = URL.createObjectURL(file);
66
+
67
+ onUploaded(fullUrl, thumbnailUrl);
68
+ };
69
+
70
+ img.src = URL.createObjectURL(file);
71
+ }
72
+ }, 100);
73
+
74
+ return {
75
+ cancel: async () => {
76
+ clearInterval(uploadInterval);
77
+ return true;
78
+ }
79
+ };
80
+ };
81
+ </script>
82
+
83
+ <ImageFilesField
84
+ bind:files={images}
85
+ {uploadImage}
86
+ maxFiles={5}
87
+ />
88
+ ```
89
+
90
+ ### 单张头像上传
91
+
92
+ ```svelte
93
+ <script>
94
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
95
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
96
+
97
+ let avatar: ImageFile[] = [];
98
+
99
+ const uploadAvatar: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
100
+ // 验证图片尺寸和大小
101
+ const img = new Image();
102
+ img.onload = () => {
103
+ if (img.width < 100 || img.height < 100) {
104
+ errorHandler(new Error('图片尺寸至少为 100x100 像素'));
105
+ return;
106
+ }
107
+
108
+ if (file.size > 2 * 1024 * 1024) {
109
+ errorHandler(new Error('图片大小必须小于 2MB'));
110
+ return;
111
+ }
112
+
113
+ // 创建正方形缩略图
114
+ const canvas = document.createElement('canvas');
115
+ const ctx = canvas.getContext('2d');
116
+ const size = Math.min(img.width, img.height);
117
+
118
+ canvas.width = 100;
119
+ canvas.height = 100;
120
+
121
+ const sx = (img.width - size) / 2;
122
+ const sy = (img.height - size) / 2;
123
+
124
+ ctx?.drawImage(img, sx, sy, size, size, 0, 0, 100, 100);
125
+
126
+ const thumbnailUrl = canvas.toDataURL();
127
+ const fullUrl = URL.createObjectURL(file);
128
+
129
+ onUploaded(fullUrl, thumbnailUrl);
130
+ };
131
+
132
+ img.src = URL.createObjectURL(file);
133
+
134
+ return {
135
+ cancel: async () => true
136
+ };
137
+ };
138
+
139
+ const confirmRemoval = async (file: ImageFile) => {
140
+ return confirm('删除头像图片?');
141
+ };
142
+ </script>
143
+
144
+ <div class="avatar-upload">
145
+ <label>头像</label>
146
+ <ImageFilesField
147
+ bind:files={avatar}
148
+ variant="outlined"
149
+ uploadFile={uploadAvatar}
150
+ removeFileConfirm={confirmRemoval}
151
+ maxFiles={1}
152
+ style="margin-top: 8px;"
153
+ />
154
+ </div>
155
+ ```
156
+
157
+ ## API 参考
158
+
159
+ ### 属性
160
+
161
+ | 属性 | 类型 | 默认值 | 描述 |
162
+ |------|------|--------|------|
163
+ | `files` | `Array<ImageFile>` | `[]` | 图片文件数组 |
164
+ | `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | 视觉变体 |
165
+ | `disabled` | `boolean` | `false` | 字段是否禁用 |
166
+ | `readonly` | `boolean` | `false` | 字段是否只读 |
167
+ | `style` | `string` | `''` | 附加CSS样式 |
168
+ | `displayMode` | `DisplayMode` | `DisplayMode.Edit` | 显示模式(编辑/查看) |
169
+ | `removeFileConfirm` | `RemoveConfirm` | `null` | 文件删除的可选确认函数 |
170
+ | `uploadFile` | `FileUpload` | 必需 | 处理文件上传的函数 |
171
+ | `maxFiles` | `number` | `1` | 允许的最大图片数量 |
172
+ | `text` | `string \| null` | `null` | 显示文本(从文件名自动生成) |
173
+
174
+ ### 类型定义
175
+
176
+ #### ImageFile 接口
177
+
178
+ ```typescript
179
+ interface ImageFile {
180
+ name: string; // 图片文件名
181
+ uri: string; // 全尺寸图片URL或路径
182
+ thumbnail?: string; // 缩略图URL(可选)
183
+ }
184
+ ```
185
+
186
+ #### FileUpload 函数类型
187
+
188
+ ```typescript
189
+ type FileUpload = (
190
+ file: File,
191
+ progressUpdate: ProgressUpdate,
192
+ onUploaded: OnUploaded,
193
+ errorHandler: ErrorHandler
194
+ ) => UploadProgress;
195
+
196
+ type ProgressUpdate = (uploadBytes: number) => void;
197
+ type OnUploaded = (url: string, thumbnail?: string) => void;
198
+ type ErrorHandler = (e: Error) => void;
199
+
200
+ interface UploadProgress {
201
+ cancel: () => Promise<boolean>;
202
+ }
203
+ ```
204
+
205
+ #### RemoveConfirm 函数类型
206
+
207
+ ```typescript
208
+ type RemoveConfirm = (file: ImageFile) => Promise<boolean>;
209
+ ```
210
+
211
+ ## 高级示例
212
+
213
+ ### 照片相册上传
214
+
215
+ ```svelte
216
+ <script>
217
+ import ImageFilesField, { DisplayMode } from '@ticatec/uniface-element/ImageFilesField';
218
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
219
+
220
+ let galleryImages: ImageFile[] = [];
221
+ let isEditMode = true;
222
+
223
+ const uploadToGallery: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
224
+ // 验证图片文件
225
+ if (!file.type.startsWith('image/')) {
226
+ errorHandler(new Error('只允许图片文件'));
227
+ return { cancel: async () => true };
228
+ }
229
+
230
+ // 检查文件大小(最大 10MB)
231
+ if (file.size > 10 * 1024 * 1024) {
232
+ errorHandler(new Error('图片大小不能超过 10MB'));
233
+ return { cancel: async () => true };
234
+ }
235
+
236
+ const img = new Image();
237
+ img.onload = async () => {
238
+ try {
239
+ // 创建优化缩略图
240
+ const thumbnail = await createThumbnail(img, 200, 200);
241
+
242
+ // 模拟 API 上传
243
+ const formData = new FormData();
244
+ formData.append('image', file);
245
+ formData.append('gallery', 'main');
246
+
247
+ let uploadedBytes = 0;
248
+ const totalBytes = file.size;
249
+
250
+ const simulateUpload = setInterval(() => {
251
+ uploadedBytes += Math.min(1024 * 200, totalBytes - uploadedBytes);
252
+ progressUpdate(uploadedBytes);
253
+
254
+ if (uploadedBytes >= totalBytes) {
255
+ clearInterval(simulateUpload);
256
+ const fullUrl = URL.createObjectURL(file);
257
+ onUploaded(fullUrl, thumbnail);
258
+ }
259
+ }, 100);
260
+
261
+ } catch (error) {
262
+ errorHandler(new Error('处理图片失败'));
263
+ }
264
+ };
265
+
266
+ img.onerror = () => {
267
+ errorHandler(new Error('无效的图片文件'));
268
+ };
269
+
270
+ img.src = URL.createObjectURL(file);
271
+
272
+ return {
273
+ cancel: async () => true
274
+ };
275
+ };
276
+
277
+ const createThumbnail = (img: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<string> => {
278
+ return new Promise((resolve) => {
279
+ const canvas = document.createElement('canvas');
280
+ const ctx = canvas.getContext('2d');
281
+
282
+ // 计算保持宽高比的尺寸
283
+ let { width, height } = img;
284
+
285
+ if (width > height) {
286
+ if (width > maxWidth) {
287
+ height = (height * maxWidth) / width;
288
+ width = maxWidth;
289
+ }
290
+ } else {
291
+ if (height > maxHeight) {
292
+ width = (width * maxHeight) / height;
293
+ height = maxHeight;
294
+ }
295
+ }
296
+
297
+ canvas.width = width;
298
+ canvas.height = height;
299
+
300
+ ctx?.drawImage(img, 0, 0, width, height);
301
+ resolve(canvas.toDataURL('image/jpeg', 0.8));
302
+ });
303
+ };
304
+
305
+ const confirmImageRemoval = async (image: ImageFile) => {
306
+ const result = await showConfirmDialog({
307
+ title: '删除图片',
308
+ message: `从相册中删除"${image.name}"?`,
309
+ confirmText: '删除',
310
+ cancelText: '取消'
311
+ });
312
+
313
+ return result;
314
+ };
315
+
316
+ const showConfirmDialog = (options: any) => {
317
+ return new Promise<boolean>((resolve) => {
318
+ // 自定义确认对话框实现
319
+ const confirmed = confirm(`${options.title}: ${options.message}`);
320
+ resolve(confirmed);
321
+ });
322
+ };
323
+ </script>
324
+
325
+ <div class="photo-gallery">
326
+ <div class="gallery-header">
327
+ <h3>照片相册</h3>
328
+ <div class="gallery-actions">
329
+ <button on:click={() => isEditMode = !isEditMode}>
330
+ {isEditMode ? '查看模式' : '编辑模式'}
331
+ </button>
332
+ </div>
333
+ </div>
334
+
335
+ <ImageFilesField
336
+ bind:files={galleryImages}
337
+ variant="outlined"
338
+ displayMode={isEditMode ? DisplayMode.Edit : DisplayMode.View}
339
+ uploadFile={uploadToGallery}
340
+ removeFileConfirm={confirmImageRemoval}
341
+ maxFiles={20}
342
+ style="min-height: 200px; border-radius: 8px;"
343
+ />
344
+
345
+ <div class="gallery-stats">
346
+ <p>图片: {galleryImages.length} / 20</p>
347
+ <p>支持格式: JPG、PNG、GIF、WebP</p>
348
+ </div>
349
+ </div>
350
+
351
+ <style>
352
+ .photo-gallery {
353
+ max-width: 800px;
354
+ margin: 20px auto;
355
+ padding: 20px;
356
+ border: 1px solid #e0e0e0;
357
+ border-radius: 12px;
358
+ background: #fafafa;
359
+ }
360
+
361
+ .gallery-header {
362
+ display: flex;
363
+ justify-content: space-between;
364
+ align-items: center;
365
+ margin-bottom: 20px;
366
+ }
367
+
368
+ .gallery-stats {
369
+ margin-top: 15px;
370
+ padding: 10px;
371
+ background: white;
372
+ border-radius: 4px;
373
+ font-size: 0.9em;
374
+ color: #666;
375
+ }
376
+
377
+ .gallery-stats p {
378
+ margin: 2px 0;
379
+ }
380
+ </style>
381
+ ```
382
+
383
+ ### 产品图片管理
384
+
385
+ ```svelte
386
+ <script>
387
+ import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
388
+ import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
389
+
390
+ let productImages: ImageFile[] = [];
391
+ let uploadErrors: string[] = [];
392
+
393
+ const uploadProductImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
394
+ // 清除之前的错误
395
+ uploadErrors = [];
396
+
397
+ // 验证产品图片要求
398
+ if (!file.type.startsWith('image/')) {
399
+ const error = '产品只允许上传图片文件';
400
+ uploadErrors = [...uploadErrors, error];
401
+ errorHandler(new Error(error));
402
+ return { cancel: async () => true };
403
+ }
404
+
405
+ // 检查文件大小
406
+ if (file.size > 5 * 1024 * 1024) {
407
+ const error = '产品图片必须小于 5MB';
408
+ uploadErrors = [...uploadErrors, error];
409
+ errorHandler(new Error(error));
410
+ return { cancel: async () => true };
411
+ }
412
+
413
+ const img = new Image();
414
+ img.onload = async () => {
415
+ // 验证产品图片尺寸
416
+ if (img.width < 500 || img.height < 500) {
417
+ const error = '产品图片尺寸至少为 500x500 像素';
418
+ uploadErrors = [...uploadErrors, error];
419
+ errorHandler(new Error(error));
420
+ return;
421
+ }
422
+
423
+ try {
424
+ // 为产品创建多种缩略图尺寸
425
+ const thumbnailSmall = await createProductThumbnail(img, 150, 150);
426
+ const thumbnailMedium = await createProductThumbnail(img, 300, 300);
427
+
428
+ // 模拟带进度的上传
429
+ let uploaded = 0;
430
+ const total = file.size;
431
+
432
+ const uploadInterval = setInterval(() => {
433
+ uploaded += Math.min(1024 * 150, total - uploaded);
434
+ progressUpdate(uploaded);
435
+
436
+ if (uploaded >= total) {
437
+ clearInterval(uploadInterval);
438
+
439
+ // 返回全尺寸URL和缩略图
440
+ const fullUrl = URL.createObjectURL(file);
441
+ onUploaded(fullUrl, thumbnailSmall);
442
+ }
443
+ }, 80);
444
+
445
+ } catch (error) {
446
+ const errorMsg = '处理产品图片失败';
447
+ uploadErrors = [...uploadErrors, errorMsg];
448
+ errorHandler(new Error(errorMsg));
449
+ }
450
+ };
451
+
452
+ img.onerror = () => {
453
+ const error = '无效或损坏的图片文件';
454
+ uploadErrors = [...uploadErrors, error];
455
+ errorHandler(new Error(error));
456
+ };
457
+
458
+ img.src = URL.createObjectURL(file);
459
+
460
+ return {
461
+ cancel: async () => true
462
+ };
463
+ };
464
+
465
+ const createProductThumbnail = (img: HTMLImageElement, width: number, height: number): Promise<string> => {
466
+ return new Promise((resolve) => {
467
+ const canvas = document.createElement('canvas');
468
+ const ctx = canvas.getContext('2d');
469
+
470
+ canvas.width = width;
471
+ canvas.height = height;
472
+
473
+ // 居中裁剪图片以填充正方形
474
+ const size = Math.min(img.width, img.height);
475
+ const sx = (img.width - size) / 2;
476
+ const sy = (img.height - size) / 2;
477
+
478
+ ctx?.drawImage(img, sx, sy, size, size, 0, 0, width, height);
479
+ resolve(canvas.toDataURL('image/jpeg', 0.9));
480
+ });
481
+ };
482
+
483
+ const confirmProductImageRemoval = async (image: ImageFile) => {
484
+ return confirm(`删除产品图片"${image.name}"?这将影响产品展示。`);
485
+ };
486
+
487
+ const clearErrors = () => {
488
+ uploadErrors = [];
489
+ };
490
+ </script>
491
+
492
+ <div class="product-image-manager">
493
+ <div class="manager-header">
494
+ <h3>产品图片</h3>
495
+ <div class="requirements">
496
+ <h4>图片要求:</h4>
497
+ <ul>
498
+ <li>最小尺寸 500×500 像素</li>
499
+ <li>最大文件大小 5MB</li>
500
+ <li>格式:JPG、PNG、WebP</li>
501
+ <li>每个产品最多 8 张图片</li>
502
+ </ul>
503
+ </div>
504
+ </div>
505
+
506
+ {#if uploadErrors.length > 0}
507
+ <div class="error-panel">
508
+ <h4>上传错误:</h4>
509
+ <ul>
510
+ {#each uploadErrors as error}
511
+ <li>{error}</li>
512
+ {/each}
513
+ </ul>
514
+ <button on:click={clearErrors}>清除错误</button>
515
+ </div>
516
+ {/if}
517
+
518
+ <ImageFilesField
519
+ bind:files={productImages}
520
+ variant="outlined"
521
+ uploadFile={uploadProductImage}
522
+ removeFileConfirm={confirmProductImageRemoval}
523
+ maxFiles={8}
524
+ style="margin: 20px 0; min-height: 180px;"
525
+ />
526
+
527
+ <div class="image-summary">
528
+ <p><strong>当前图片:</strong> {productImages.length} / 8</p>
529
+ {#if productImages.length > 0}
530
+ <p><strong>图片名称:</strong></p>
531
+ <ul class="image-list">
532
+ {#each productImages as image}
533
+ <li>{image.name}</li>
534
+ {/each}
535
+ </ul>
536
+ {/if}
537
+ </div>
538
+ </div>
539
+
540
+ <style>
541
+ .product-image-manager {
542
+ max-width: 700px;
543
+ margin: 20px auto;
544
+ padding: 25px;
545
+ border: 1px solid #ddd;
546
+ border-radius: 8px;
547
+ background: white;
548
+ }
549
+
550
+ .manager-header {
551
+ display: flex;
552
+ justify-content: space-between;
553
+ align-items: flex-start;
554
+ margin-bottom: 20px;
555
+ }
556
+
557
+ .requirements {
558
+ background: #f0f8ff;
559
+ padding: 15px;
560
+ border-radius: 6px;
561
+ max-width: 250px;
562
+ }
563
+
564
+ .requirements h4 {
565
+ margin: 0 0 10px 0;
566
+ color: #1976d2;
567
+ font-size: 0.9em;
568
+ }
569
+
570
+ .requirements ul {
571
+ margin: 0;
572
+ padding-left: 20px;
573
+ font-size: 0.85em;
574
+ color: #555;
575
+ }
576
+
577
+ .error-panel {
578
+ background: #ffebee;
579
+ border: 1px solid #f44336;
580
+ padding: 15px;
581
+ border-radius: 4px;
582
+ margin-bottom: 20px;
583
+ }
584
+
585
+ .error-panel h4 {
586
+ margin: 0 0 10px 0;
587
+ color: #d32f2f;
588
+ }
589
+
590
+ .error-panel ul {
591
+ margin: 0 0 10px 0;
592
+ padding-left: 20px;
593
+ color: #d32f2f;
594
+ }
595
+
596
+ .error-panel button {
597
+ background: #d32f2f;
598
+ color: white;
599
+ border: none;
600
+ padding: 5px 10px;
601
+ border-radius: 3px;
602
+ cursor: pointer;
603
+ }
604
+
605
+ .image-summary {
606
+ background: #f5f5f5;
607
+ padding: 15px;
608
+ border-radius: 4px;
609
+ font-size: 0.9em;
610
+ }
611
+
612
+ .image-list {
613
+ margin: 5px 0 0 0;
614
+ padding-left: 20px;
615
+ color: #666;
616
+ }
617
+ </style>
618
+ ```
619
+
620
+ ## 最佳实践
621
+
622
+ 1. **图片验证**:始终验证图片尺寸、文件大小和格式
623
+ 2. **缩略图生成**:创建缩略图以提高性能和用户体验
624
+ 3. **进度反馈**:对大型图片文件显示上传进度
625
+ 4. **错误处理**:为验证失败提供清晰的错误消息
626
+ 5. **预览功能**:允许用户在上传前后预览图片
627
+ 6. **宽高比**:考虑保持或强制特定的宽高比
628
+ 7. **优化**:适当时压缩图片以减少文件大小
629
+ 8. **无障碍访问**:为图片提供替代文本和适当的ARIA标签
630
+
631
+ ## 样式说明
632
+
633
+ - 组件以网格布局显示图片缩略图
634
+ - 图片预览在模态覆盖层中打开
635
+ - 上传进度通过进度条显示
636
+ - 自定义样式可以通过`style`属性应用
637
+
638
+ ## 无障碍访问注意事项
639
+
640
+ - 图片包含适当的alt属性
641
+ - 所有交互元素支持键盘导航
642
+ - 屏幕阅读器可以访问图片信息
643
+ - 颜色对比度符合无障碍标准
644
+ - 在模态框中适当处理焦点管理
645
+
646
+ ## 浏览器支持
647
+
648
+ - 需要Canvas API支持的现代浏览器以生成缩略图
649
+ - 需要File API支持进行文件处理
650
+ - 拖拽功能需要HTML5支持
651
+ - 图片预览功能在所有现代浏览器中工作