@ebiz/designer-components 0.1.90 → 0.1.94

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 (223) hide show
  1. package/README.md +29 -29
  2. package/dist/designer-components.css +1 -1
  3. package/dist/index.mjs +14610 -14565
  4. package/dist/{pdf-BmLz-Xgo.js → pdf-C9oIcL2N.js} +75 -75
  5. package/package.json +1 -1
  6. package/src/App.vue +26 -26
  7. package/src/apiService/SIMPLE_DATA_SERVICE.md +284 -284
  8. package/src/apiService/mockDataService.js +115 -115
  9. package/src/apiService/simpleDataService.js +299 -299
  10. package/src/assets/base.css +86 -86
  11. package/src/assets/logo.svg +1 -1
  12. package/src/components/Button.vue +149 -149
  13. package/src/components/DataContainer.vue +40 -40
  14. package/src/components/EbizApproval.vue +338 -338
  15. package/src/components/EbizApprovalForm.vue +507 -507
  16. package/src/components/EbizAutoForm.vue +596 -596
  17. package/src/components/EbizAvatar.vue +115 -115
  18. package/src/components/EbizCheckbox.vue +93 -93
  19. package/src/components/EbizCheckboxGroup.vue +69 -69
  20. package/src/components/EbizDepartmentSelector.vue +149 -149
  21. package/src/components/EbizDescriptions.vue +340 -340
  22. package/src/components/EbizDescriptionsItem.vue +47 -47
  23. package/src/components/EbizDetailBlock.vue +81 -81
  24. package/src/components/EbizDetailItem.vue +559 -559
  25. package/src/components/EbizDetailView.md +438 -438
  26. package/src/components/EbizDetailView.vue +355 -355
  27. package/src/components/EbizDialog.vue +260 -260
  28. package/src/components/EbizDictionarySelect.vue +229 -229
  29. package/src/components/EbizDiv.vue +40 -40
  30. package/src/components/EbizDivider.vue +96 -96
  31. package/src/components/EbizDropdown.vue +135 -135
  32. package/src/components/EbizDropdownItem.vue +85 -85
  33. package/src/components/EbizEmployeeInfo.vue +144 -144
  34. package/src/components/EbizEmployeeSelector.vue +1160 -1160
  35. package/src/components/EbizFileList.vue +466 -466
  36. package/src/components/EbizMap.vue +541 -541
  37. package/src/components/EbizMeetingRoomSelector.vue +664 -664
  38. package/src/components/EbizMobileMeetingRoomSelector.vue +727 -727
  39. package/src/components/EbizOkrTree.vue +99 -99
  40. package/src/components/EbizPageHeader.vue +98 -98
  41. package/src/components/EbizPagination.vue +162 -162
  42. package/src/components/EbizPdfViewer.vue +540 -540
  43. package/src/components/EbizPopconfirm.vue +47 -47
  44. package/src/components/EbizQrCode.vue +73 -73
  45. package/src/components/EbizRadio.vue +86 -86
  46. package/src/components/EbizRadioGroup.vue +83 -83
  47. package/src/components/EbizRemoteSelect.vue +232 -232
  48. package/src/components/EbizRichTextEditor.vue +275 -275
  49. package/src/components/EbizRouteBreadcrumb.vue +46 -46
  50. package/src/components/EbizSApprovalProcess.vue +1429 -1429
  51. package/src/components/EbizSelect.vue +85 -85
  52. package/src/components/EbizSpace.vue +100 -100
  53. package/src/components/EbizStatistic.vue +149 -149
  54. package/src/components/EbizStatsCard.vue +113 -113
  55. package/src/components/EbizSwiper.vue +113 -113
  56. package/src/components/EbizSwiperItem.vue +13 -13
  57. package/src/components/EbizSwitch.vue +85 -85
  58. package/src/components/EbizTabHeader.vue +132 -132
  59. package/src/components/EbizTabPanel.vue +22 -22
  60. package/src/components/EbizTable.vue +469 -469
  61. package/src/components/EbizTableColumn.vue +116 -116
  62. package/src/components/EbizTableSort.vue +179 -179
  63. package/src/components/EbizTabs.vue +142 -142
  64. package/src/components/EbizTdesignButtonDialog.vue +332 -332
  65. package/src/components/EbizTdesignLoading.vue +107 -107
  66. package/src/components/EbizTimePicker.vue +143 -143
  67. package/src/components/EbizTitle.vue +91 -91
  68. package/src/components/EbizTree.vue +141 -141
  69. package/src/components/EbizTreeMergeTable.vue +1494 -1494
  70. package/src/components/EbizTreeSelector.vue +451 -451
  71. package/src/components/EbizVideo.vue +553 -553
  72. package/src/components/EbizVxeTable.vue +290 -290
  73. package/src/components/Form.vue +28 -28
  74. package/src/components/Home.vue +7 -7
  75. package/src/components/LaunchInterview.vue +403 -403
  76. package/src/components/MyComponent.vue +39 -39
  77. package/src/components/Table.vue +45 -45
  78. package/src/components/TdesignAlert.vue +115 -115
  79. package/src/components/TdesignButton.vue +135 -135
  80. package/src/components/TdesignCalendar/index.vue +145 -145
  81. package/src/components/TdesignCard.vue +195 -195
  82. package/src/components/TdesignCol.vue +101 -101
  83. package/src/components/TdesignCollapse.vue +142 -142
  84. package/src/components/TdesignCollapsePanel.vue +79 -79
  85. package/src/components/TdesignDatePicker.vue +124 -124
  86. package/src/components/TdesignDescriptions.vue +74 -74
  87. package/src/components/TdesignDescriptionsItem.vue +50 -50
  88. package/src/components/TdesignDialog.vue +225 -225
  89. package/src/components/TdesignForm.vue +138 -138
  90. package/src/components/TdesignFormItem.vue +105 -105
  91. package/src/components/TdesignGrid.vue +55 -55
  92. package/src/components/TdesignIcon.vue +67 -67
  93. package/src/components/TdesignImage.vue +162 -162
  94. package/src/components/TdesignImageViewer.vue +200 -200
  95. package/src/components/TdesignInput.vue +242 -242
  96. package/src/components/TdesignSelect.vue +446 -446
  97. package/src/components/TdesignTag.vue +117 -117
  98. package/src/components/TdesignTextarea.vue +142 -142
  99. package/src/components/TdesignTimeline.vue +58 -58
  100. package/src/components/TdesignTimelineItem.vue +71 -71
  101. package/src/components/TdesignUpload.vue +414 -414
  102. package/src/components/TdesignWatermark.vue +107 -107
  103. package/src/components/ebiz-form/components/cascader.vue +61 -61
  104. package/src/components/ebiz-form/components/checkbox.vue +37 -37
  105. package/src/components/ebiz-form/components/city.vue +137 -137
  106. package/src/components/ebiz-form/components/date-panel.vue +52 -52
  107. package/src/components/ebiz-form/components/date-range-panel.vue +52 -52
  108. package/src/components/ebiz-form/components/date-range.vue +56 -56
  109. package/src/components/ebiz-form/components/date.vue +52 -52
  110. package/src/components/ebiz-form/components/editor-multi-language.vue +47 -47
  111. package/src/components/ebiz-form/components/editor.vue +78 -78
  112. package/src/components/ebiz-form/components/file-multi-language.vue +52 -52
  113. package/src/components/ebiz-form/components/file.vue +149 -149
  114. package/src/components/ebiz-form/components/images-multi-language.vue +52 -52
  115. package/src/components/ebiz-form/components/images.vue +129 -129
  116. package/src/components/ebiz-form/components/img-multi-language.vue +51 -51
  117. package/src/components/ebiz-form/components/img.vue +129 -129
  118. package/src/components/ebiz-form/components/number.vue +50 -50
  119. package/src/components/ebiz-form/components/radio.vue +28 -28
  120. package/src/components/ebiz-form/components/select.vue +119 -119
  121. package/src/components/ebiz-form/components/switch.vue +23 -23
  122. package/src/components/ebiz-form/components/text-multi-language.vue +47 -47
  123. package/src/components/ebiz-form/components/text.vue +52 -52
  124. package/src/components/ebiz-form/components/textarea-multi-language.vue +48 -48
  125. package/src/components/ebiz-form/components/textarea.vue +29 -29
  126. package/src/components/ebiz-form/components/video-multi-language.vue +51 -51
  127. package/src/components/ebiz-form/components/video.vue +97 -97
  128. package/src/components/ebiz-form/index.vue +157 -157
  129. package/src/components/examples/PopconfirmExample.vue +149 -149
  130. package/src/components/icons/IconCommunity.vue +7 -7
  131. package/src/components/icons/IconDocumentation.vue +7 -7
  132. package/src/components/icons/IconEcosystem.vue +7 -7
  133. package/src/components/icons/IconSupport.vue +7 -7
  134. package/src/components/icons/IconTooling.vue +19 -19
  135. package/src/components/mItems/UserInfo.vue +349 -349
  136. package/src/components/senior/EbizSData/index.vue +273 -273
  137. package/src/components/senior/EbizSDialog/index.vue +770 -770
  138. package/src/components/senior/EbizSForm/README.md +157 -157
  139. package/src/components/senior/EbizSForm/index.vue +667 -667
  140. package/src/components/senior/EbizSForm/item.vue +998 -998
  141. package/src/components/senior/EbizSForm/mItems/DateTimePicker.vue +51 -51
  142. package/src/components/senior/EbizSForm/mItems/Picker.vue +63 -63
  143. package/src/index.js +317 -317
  144. package/src/main.js +55 -55
  145. package/src/router/index.js +422 -422
  146. package/src/utils/formatCode.js +24 -24
  147. package/src/utils/generateImportStatement.js +52 -52
  148. package/src/utils/hasJsx.js +25 -25
  149. package/src/utils/index.js +166 -166
  150. package/src/utils/mergeOptions.js +29 -29
  151. package/src/utils/parseRequiredBlocks.js +18 -18
  152. package/src/utils/vue-sfc-validator.js +155 -155
  153. package/src/views/Button.vue +23 -23
  154. package/src/views/CheckboxDemo.vue +104 -104
  155. package/src/views/DataContainer.vue +19 -19
  156. package/src/views/DialogDemo.vue +125 -125
  157. package/src/views/EbizApprovalDemo.vue +87 -87
  158. package/src/views/EbizApprovalFormDemo.vue +207 -207
  159. package/src/views/EbizAutoFormDemo.vue +129 -129
  160. package/src/views/EbizAvatar.vue +223 -223
  161. package/src/views/EbizDepartmentSelectorDemo.vue +169 -169
  162. package/src/views/EbizDetailBlockDemo.vue +30 -30
  163. package/src/views/EbizDetailViewDemo.vue +412 -412
  164. package/src/views/EbizEmployeeInfo.vue +249 -249
  165. package/src/views/EbizEmployeeSelector.vue +85 -85
  166. package/src/views/EbizFileListDemo.vue +339 -339
  167. package/src/views/EbizMap.vue +201 -201
  168. package/src/views/EbizMeetingRoomSelectorDemo.vue +293 -293
  169. package/src/views/EbizMobileMeetingRoomSelectorDemo.vue +566 -566
  170. package/src/views/EbizRadioDemo.vue +151 -151
  171. package/src/views/EbizSDataDemo.vue +136 -136
  172. package/src/views/EbizSDialogDemo.vue +303 -303
  173. package/src/views/EbizSForm/index.vue +352 -352
  174. package/src/views/EbizSFormDemo.vue +420 -420
  175. package/src/views/EbizSpace.vue +185 -185
  176. package/src/views/EbizSwiper.vue +157 -157
  177. package/src/views/EbizTdesignButtonDialogExample.vue +437 -437
  178. package/src/views/Form.vue +19 -19
  179. package/src/views/GridDemo.vue +238 -238
  180. package/src/views/Home.vue +154 -154
  181. package/src/views/LaunchInterviewDemo.vue +111 -111
  182. package/src/views/Mindmap.vue +17 -17
  183. package/src/views/MyComponent.vue +19 -19
  184. package/src/views/OkrTree.vue +19 -19
  185. package/src/views/PageHeaderDemo.vue +104 -104
  186. package/src/views/PaginationDemo.vue +96 -96
  187. package/src/views/PdfViewerDemo.vue +433 -433
  188. package/src/views/PermissionBoxDemo.vue +85 -85
  189. package/src/views/PopconfirmDemo.vue +80 -80
  190. package/src/views/RemoteSelect.vue +350 -350
  191. package/src/views/StatisticDemo.vue +190 -190
  192. package/src/views/SwitchDemo.vue +79 -79
  193. package/src/views/Table.vue +19 -19
  194. package/src/views/TableDemo.vue +334 -334
  195. package/src/views/TableSortDemo.vue +143 -143
  196. package/src/views/TableView.vue +68 -68
  197. package/src/views/TabsDemo.vue +282 -282
  198. package/src/views/TagDemo.vue +101 -101
  199. package/src/views/TdesignAlert.vue +98 -98
  200. package/src/views/TdesignButton.vue +190 -190
  201. package/src/views/TdesignCalendar.vue +94 -94
  202. package/src/views/TdesignCard.vue +296 -296
  203. package/src/views/TdesignCollapse.vue +293 -293
  204. package/src/views/TdesignDatePicker.vue +187 -187
  205. package/src/views/TdesignDescriptions.vue +101 -101
  206. package/src/views/TdesignForm.vue +248 -248
  207. package/src/views/TdesignIcon.vue +203 -203
  208. package/src/views/TdesignImage.vue +215 -215
  209. package/src/views/TdesignImageViewer.vue +198 -198
  210. package/src/views/TdesignInput.vue +252 -252
  211. package/src/views/TdesignSelect.vue +473 -473
  212. package/src/views/TdesignSwiper.vue +157 -157
  213. package/src/views/TextareaDemo.vue +93 -93
  214. package/src/views/TimePickerDemo.vue +146 -146
  215. package/src/views/TimelineDemo.vue +160 -160
  216. package/src/views/Title.vue +19 -19
  217. package/src/views/TreeDemo.vue +254 -254
  218. package/src/views/TreeMergeTableDemo.vue +239 -239
  219. package/src/views/TreeSelectorDemo.vue +245 -245
  220. package/src/views/UploadDemo.vue +128 -128
  221. package/src/views/VideoDemo.vue +245 -245
  222. package/src/views/VxeTableDemo.vue +279 -279
  223. package/src/views/WatermarkDemo.vue +85 -85
@@ -1,467 +1,467 @@
1
- <template>
2
- <div class="ebiz-file-list" :class="{ 'mini-mode': size === 'mini' }">
3
- <div v-if="computedFiles.length === 0" class="empty-state">
4
- <t-icon name="file" size="20px" />
5
- <p>暂无文件</p>
6
- </div>
7
- <div v-else class="file-list-container" :class="{ 'mini-container': size === 'mini' }">
8
- <div v-for="(file, index) in computedFiles" :key="index" class="file-item"
9
- :class="{ 'mini-item': size === 'mini' }" @click="handleFileClick(file)">
10
- <!-- 图片文件直接显示缩略图 -->
11
- <div v-if="isImage(file.extension)" class="file-icon">
12
- <t-image :src="file.url" :alt="file.name" fit="cover" loading="lazy"
13
- :style="{ width: actualImageSize, height: actualImageSize, borderRadius: '4px' }" error="" />
14
- </div>
15
-
16
- <!-- 非图片文件显示图标 -->
17
- <div v-else class="file-icon">
18
- <t-icon :name="getFileIcon(file.extension)" :size="actualImageSize"
19
- :style="{ color: getFileColor(file.extension) }" />
20
- </div>
21
-
22
- <div v-if="size !== 'mini'" class="file-info">
23
- <div class="file-name" :title="file.name">{{ file.name }}</div>
24
- <div class="file-meta">
25
- <span v-if="showFileType" class="file-type">{{ file.extension?.toUpperCase() || '未知' }}</span>
26
- <span v-if="showFileSize && file.size" class="file-size">{{ formatFileSize(file.size) }}</span>
27
- </div>
28
- </div>
29
-
30
- <!-- 操作按钮 -->
31
- <div v-if="showActions && size !== 'mini'" class="file-actions">
32
- <t-button size="small" variant="text" @click.stop="handleDownload(file)"
33
- :title="isImage(file.extension) ? '查看' : '下载'">
34
- <t-icon :name="isImage(file.extension) ? 'view-list' : 'download'" />
35
- </t-button>
36
- <t-button v-if="allowDelete" size="small" variant="text" theme="danger"
37
- @click.stop="handleDelete(file, index)" title="删除">
38
- <t-icon name="delete" />
39
- </t-button>
40
- </div>
41
- </div>
42
- </div>
43
-
44
- <!-- 图片预览弹窗 -->
45
- <t-image-viewer v-model:visible="previewVisible" :images="previewImages" :index="previewIndex" :close-on-esc="true"
46
- @close="previewVisible = false" />
47
- </div>
48
- </template>
49
-
50
- <script>
51
- export default {
52
- name: "EbizFileList"
53
- }
54
- </script>
55
-
56
- <script setup>
57
- import { defineProps, defineEmits, computed, ref } from 'vue';
58
- import { Image as TImage, ImageViewer as TImageViewer, Icon as TIcon, Button as TButton } from 'tdesign-vue-next';
59
-
60
- const props = defineProps({
61
- // 文件列表数据,支持数组或字符串(逗号分隔)
62
- files: {
63
- type: [Array, String],
64
- default: () => []
65
- },
66
- // 尺寸大小,支持default和mini
67
- size: {
68
- type: String,
69
- default: 'default'
70
- },
71
- // 图片尺寸
72
- imageSize: {
73
- type: String,
74
- default: '50px'
75
- },
76
- // 是否显示文件大小
77
- showFileSize: {
78
- type: Boolean,
79
- default: true
80
- },
81
- // 是否显示文件类型
82
- showFileType: {
83
- type: Boolean,
84
- default: true
85
- },
86
- // 是否显示操作按钮
87
- showActions: {
88
- type: Boolean,
89
- default: true
90
- },
91
- // 是否允许删除
92
- allowDelete: {
93
- type: Boolean,
94
- default: false
95
- },
96
- // 文件基础URL(用于相对路径)
97
- baseUrl: {
98
- type: String,
99
- default: ''
100
- }
101
- });
102
-
103
- const emit = defineEmits(['file-click', 'file-download', 'file-delete', 'file-preview']);
104
-
105
- // 预览相关状态
106
- const previewVisible = ref(false);
107
- const previewImages = ref([]);
108
- const previewIndex = ref(0);
109
-
110
- // 获取完整URL
111
- const getFullUrl = (url) => {
112
- if (!url) return '';
113
- if (url.startsWith('http') || url.startsWith('data:')) return url;
114
- return props.baseUrl + url;
115
- };
116
-
117
- // 获取文件扩展名
118
- const getFileExtension = (filename) => {
119
- if (!filename) return '';
120
- // 移除查询参数和锚点
121
- const cleanFilename = filename.split('?')[0].split('#')[0];
122
- const parts = cleanFilename.split('.');
123
- return parts.length > 1 ? parts.pop().toLowerCase() : '';
124
- };
125
-
126
- // 提取文件名
127
- const extractFileName = (url) => {
128
- if (!url) return '未知文件';
129
-
130
- // 移除查询参数和锚点
131
- const cleanUrl = url.split('?')[0].split('#')[0];
132
- const parts = cleanUrl.split('/');
133
- let fileName = parts[parts.length - 1] || '未知文件';
134
-
135
- // 如果文件名为空或只是扩展名,生成一个默认名称
136
- if (!fileName || fileName.startsWith('.')) {
137
- const extension = getFileExtension(fileName || url);
138
- fileName = extension ? `文件.${extension}` : '未知文件';
139
- }
140
-
141
- return fileName;
142
- };
143
-
144
- // 解析文件信息
145
- const parseFileInfo = (url) => {
146
- const fullUrl = getFullUrl(url);
147
- const fileName = extractFileName(url);
148
- const extension = getFileExtension(fileName);
149
-
150
- return {
151
- name: fileName,
152
- url: fullUrl,
153
- extension,
154
- size: null
155
- };
156
- };
157
-
158
- // 计算文件列表
159
- const computedFiles = computed(() => {
160
- let fileList = [];
161
-
162
- if (typeof props.files === 'string') {
163
- if (!props.files.trim()) {
164
- return [];
165
- }
166
-
167
- // 判断是单个URL还是逗号分隔的多个URL
168
- if (props.files.includes(',')) {
169
- // 逗号分隔的多个URL
170
- const urls = props.files.split(',').map(url => url.trim()).filter(url => url);
171
- fileList = urls.map(url => parseFileInfo(url));
172
- } else {
173
- // 单个URL
174
- fileList = [parseFileInfo(props.files.trim())];
175
- }
176
- } else if (Array.isArray(props.files)) {
177
- // 数组格式
178
- fileList = props.files.map(file => {
179
- if (typeof file === 'string') {
180
- // 数组中的字符串元素,直接作为URL处理
181
- return parseFileInfo(file);
182
- } else if (file && typeof file === 'object') {
183
- // 对象格式的文件信息
184
- const url = file.url || file.src || file.path || '';
185
- return {
186
- name: file.name || extractFileName(url),
187
- url: getFullUrl(url),
188
- size: file.size || null,
189
- extension: file.extension || getFileExtension(url || file.name || ''),
190
- ...file
191
- };
192
- } else {
193
- // 其他情况,返回默认对象
194
- return {
195
- name: '未知文件',
196
- url: '',
197
- extension: '',
198
- size: null
199
- };
200
- }
201
- }).filter(file => file.url); // 过滤掉没有URL的文件
202
- }
203
-
204
- return fileList;
205
- });
206
-
207
- // 判断是否为图片
208
- const isImage = (extension) => {
209
- const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
210
- return imageExtensions.includes(extension?.toLowerCase());
211
- };
212
-
213
- // 获取文件图标
214
- const getFileIcon = (extension) => {
215
- const iconMap = {
216
- // 文档类
217
- 'pdf': 'file-pdf',
218
- 'doc': 'file-word',
219
- 'docx': 'file-word',
220
- 'xls': 'file-excel',
221
- 'xlsx': 'file-excel',
222
- 'ppt': 'file-powerpoint',
223
- 'pptx': 'file-powerpoint',
224
- 'txt': 'file-text',
225
- 'rtf': 'file-text',
226
- // 代码类
227
- 'js': 'file-code',
228
- 'ts': 'file-code',
229
- 'html': 'file-code',
230
- 'css': 'file-code',
231
- 'json': 'file-code',
232
- 'xml': 'file-code',
233
- // 压缩类
234
- 'zip': 'file-zip',
235
- 'rar': 'file-zip',
236
- '7z': 'file-zip',
237
- 'tar': 'file-zip',
238
- 'gz': 'file-zip',
239
- // 音视频类
240
- 'mp3': 'file-music',
241
- 'wav': 'file-music',
242
- 'mp4': 'file-video',
243
- 'avi': 'file-video',
244
- 'mov': 'file-video',
245
- 'wmv': 'file-video'
246
- };
247
-
248
- return iconMap[extension?.toLowerCase()] || 'file';
249
- };
250
-
251
- // 获取文件颜色
252
- const getFileColor = (extension) => {
253
- const colorMap = {
254
- 'pdf': '#FF4757',
255
- 'doc': '#2E5BFF',
256
- 'docx': '#2E5BFF',
257
- 'xls': '#00A854',
258
- 'xlsx': '#00A854',
259
- 'ppt': '#FF8C42',
260
- 'pptx': '#FF8C42',
261
- 'txt': '#666666',
262
- 'zip': '#9C27B0',
263
- 'rar': '#9C27B0',
264
- 'mp3': '#FF9800',
265
- 'mp4': '#795548'
266
- };
267
-
268
- return colorMap[extension?.toLowerCase()] || '#666666';
269
- };
270
-
271
- // 计算实际使用的图片尺寸
272
- const actualImageSize = computed(() => {
273
- return props.size === 'mini' ? '20px' : props.imageSize;
274
- });
275
-
276
- // 格式化文件大小
277
- const formatFileSize = (bytes) => {
278
- if (!bytes) return '';
279
- const units = ['B', 'KB', 'MB', 'GB'];
280
- let size = bytes;
281
- let unitIndex = 0;
282
-
283
- while (size >= 1024 && unitIndex < units.length - 1) {
284
- size /= 1024;
285
- unitIndex++;
286
- }
287
-
288
- return `${size.toFixed(1)} ${units[unitIndex]}`;
289
- };
290
-
291
- // 是否可在线预览
292
- const canPreviewOnline = (extension) => {
293
- const onlinePreviewExtensions = ['pdf', 'txt', 'html', 'xml', 'json'];
294
- return onlinePreviewExtensions.includes(extension?.toLowerCase());
295
- };
296
-
297
- // 文件下载
298
- const handleDownload = (file) => {
299
- const link = document.createElement('a');
300
- link.href = file.url;
301
- link.download = file.name;
302
- link.target = '_blank';
303
- document.body.appendChild(link);
304
- link.click();
305
- document.body.removeChild(link);
306
-
307
- emit('file-download', file);
308
- };
309
-
310
- // 图片预览
311
- const handlePreview = (file) => {
312
- const imageFiles = computedFiles.value.filter(f => isImage(f.extension));
313
- previewImages.value = imageFiles.map(f => f.url);
314
- previewIndex.value = imageFiles.findIndex(f => f.url === file.url);
315
- previewVisible.value = true;
316
-
317
- emit('file-preview', file);
318
- };
319
-
320
- // 文件点击处理
321
- const handleFileClick = (file) => {
322
- emit('file-click', file);
323
-
324
- if (isImage(file.extension)) {
325
- // 图片文件,显示预览
326
- handlePreview(file);
327
- } else if (canPreviewOnline(file.extension)) {
328
- // 可在线预览的文件
329
- window.open(file.url, '_blank');
330
- } else {
331
- // 其他文件,触发下载
332
- handleDownload(file);
333
- }
334
- };
335
-
336
- // 文件删除
337
- const handleDelete = (file, index) => {
338
- emit('file-delete', file, index);
339
- };
340
-
341
- // 图片加载错误处理
342
- const handleImageError = () => {
343
- // 图片加载失败时的处理
344
- };
345
- </script>
346
-
347
- <style lang="less" scoped>
348
- .ebiz-file-list {
349
- width: 100%;
350
-
351
- &.mini-mode {
352
- .file-list-container {
353
- display: flex;
354
- flex-direction: row;
355
- flex-wrap: wrap;
356
- gap: 4px;
357
- }
358
- }
359
-
360
- .empty-state {
361
- display: flex;
362
- flex-direction: column;
363
- align-items: center;
364
- justify-content: center;
365
- padding: 10px;
366
- color: #999;
367
-
368
- p {
369
- margin: 8px 0 0 0;
370
- font-size: 12px;
371
- }
372
- }
373
-
374
- .file-list-container {
375
- display: flex;
376
- flex-direction: column;
377
- gap: 8px;
378
-
379
- &.mini-container {
380
- flex-direction: row;
381
- flex-wrap: wrap;
382
- }
383
- }
384
-
385
- .file-item {
386
- display: flex;
387
- align-items: center;
388
- padding: 8px 12px;
389
- border: 1px solid #e6e6e6;
390
- border-radius: 6px;
391
- cursor: pointer;
392
- transition: all 0.3s;
393
- background: #fff;
394
-
395
- &.mini-item {
396
- padding: 0;
397
- border: none;
398
- background: transparent;
399
- box-shadow: none;
400
- margin-right: 8px;
401
- margin-bottom: 8px;
402
-
403
- &:hover {
404
- border-color: transparent;
405
- box-shadow: none;
406
- }
407
- }
408
-
409
- &:hover {
410
- border-color: #0052d9;
411
- box-shadow: 0 2px 8px rgba(0, 82, 217, 0.15);
412
-
413
- .file-actions {
414
- opacity: 1;
415
- }
416
- }
417
- }
418
-
419
- .file-icon {
420
- margin-right: 12px;
421
- flex-shrink: 0;
422
- display: flex;
423
- align-items: center;
424
- justify-content: center;
425
- width: v-bind(actualImageSize);
426
- height: v-bind(actualImageSize);
427
-
428
- .mini-item & {
429
- margin-right: 0;
430
- }
431
- }
432
-
433
- .file-info {
434
- flex: 1;
435
- min-width: 0;
436
-
437
- .file-name {
438
- font-size: 14px;
439
- font-weight: 500;
440
- color: #333;
441
- margin-bottom: 3px;
442
- overflow: hidden;
443
- text-overflow: ellipsis;
444
- white-space: nowrap;
445
- }
446
-
447
- .file-meta {
448
- display: flex;
449
- gap: 8px;
450
-
451
- .file-size,
452
- .file-type {
453
- font-size: 12px;
454
- color: #999;
455
- }
456
- }
457
- }
458
-
459
- .file-actions {
460
- display: flex;
461
- gap: 4px;
462
- opacity: 0;
463
- transition: opacity 0.3s;
464
- flex-shrink: 0;
465
- }
466
- }
1
+ <template>
2
+ <div class="ebiz-file-list" :class="{ 'mini-mode': size === 'mini' }">
3
+ <div v-if="computedFiles.length === 0" class="empty-state">
4
+ <t-icon name="file" size="20px" />
5
+ <p>暂无文件</p>
6
+ </div>
7
+ <div v-else class="file-list-container" :class="{ 'mini-container': size === 'mini' }">
8
+ <div v-for="(file, index) in computedFiles" :key="index" class="file-item"
9
+ :class="{ 'mini-item': size === 'mini' }" @click="handleFileClick(file)">
10
+ <!-- 图片文件直接显示缩略图 -->
11
+ <div v-if="isImage(file.extension)" class="file-icon">
12
+ <t-image :src="file.url" :alt="file.name" fit="cover" loading="lazy"
13
+ :style="{ width: actualImageSize, height: actualImageSize, borderRadius: '4px' }" error="" />
14
+ </div>
15
+
16
+ <!-- 非图片文件显示图标 -->
17
+ <div v-else class="file-icon">
18
+ <t-icon :name="getFileIcon(file.extension)" :size="actualImageSize"
19
+ :style="{ color: getFileColor(file.extension) }" />
20
+ </div>
21
+
22
+ <div v-if="size !== 'mini'" class="file-info">
23
+ <div class="file-name" :title="file.name">{{ file.name }}</div>
24
+ <div class="file-meta">
25
+ <span v-if="showFileType" class="file-type">{{ file.extension?.toUpperCase() || '未知' }}</span>
26
+ <span v-if="showFileSize && file.size" class="file-size">{{ formatFileSize(file.size) }}</span>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- 操作按钮 -->
31
+ <div v-if="showActions && size !== 'mini'" class="file-actions">
32
+ <t-button size="small" variant="text" @click.stop="handleDownload(file)"
33
+ :title="isImage(file.extension) ? '查看' : '下载'">
34
+ <t-icon :name="isImage(file.extension) ? 'view-list' : 'download'" />
35
+ </t-button>
36
+ <t-button v-if="allowDelete" size="small" variant="text" theme="danger"
37
+ @click.stop="handleDelete(file, index)" title="删除">
38
+ <t-icon name="delete" />
39
+ </t-button>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- 图片预览弹窗 -->
45
+ <t-image-viewer v-model:visible="previewVisible" :images="previewImages" :index="previewIndex" :close-on-esc="true"
46
+ @close="previewVisible = false" />
47
+ </div>
48
+ </template>
49
+
50
+ <script>
51
+ export default {
52
+ name: "EbizFileList"
53
+ }
54
+ </script>
55
+
56
+ <script setup>
57
+ import { defineProps, defineEmits, computed, ref } from 'vue';
58
+ import { Image as TImage, ImageViewer as TImageViewer, Icon as TIcon, Button as TButton } from 'tdesign-vue-next';
59
+
60
+ const props = defineProps({
61
+ // 文件列表数据,支持数组或字符串(逗号分隔)
62
+ files: {
63
+ type: [Array, String],
64
+ default: () => []
65
+ },
66
+ // 尺寸大小,支持default和mini
67
+ size: {
68
+ type: String,
69
+ default: 'default'
70
+ },
71
+ // 图片尺寸
72
+ imageSize: {
73
+ type: String,
74
+ default: '50px'
75
+ },
76
+ // 是否显示文件大小
77
+ showFileSize: {
78
+ type: Boolean,
79
+ default: true
80
+ },
81
+ // 是否显示文件类型
82
+ showFileType: {
83
+ type: Boolean,
84
+ default: true
85
+ },
86
+ // 是否显示操作按钮
87
+ showActions: {
88
+ type: Boolean,
89
+ default: true
90
+ },
91
+ // 是否允许删除
92
+ allowDelete: {
93
+ type: Boolean,
94
+ default: false
95
+ },
96
+ // 文件基础URL(用于相对路径)
97
+ baseUrl: {
98
+ type: String,
99
+ default: ''
100
+ }
101
+ });
102
+
103
+ const emit = defineEmits(['file-click', 'file-download', 'file-delete', 'file-preview']);
104
+
105
+ // 预览相关状态
106
+ const previewVisible = ref(false);
107
+ const previewImages = ref([]);
108
+ const previewIndex = ref(0);
109
+
110
+ // 获取完整URL
111
+ const getFullUrl = (url) => {
112
+ if (!url) return '';
113
+ if (url.startsWith('http') || url.startsWith('data:')) return url;
114
+ return props.baseUrl + url;
115
+ };
116
+
117
+ // 获取文件扩展名
118
+ const getFileExtension = (filename) => {
119
+ if (!filename) return '';
120
+ // 移除查询参数和锚点
121
+ const cleanFilename = filename.split('?')[0].split('#')[0];
122
+ const parts = cleanFilename.split('.');
123
+ return parts.length > 1 ? parts.pop().toLowerCase() : '';
124
+ };
125
+
126
+ // 提取文件名
127
+ const extractFileName = (url) => {
128
+ if (!url) return '未知文件';
129
+
130
+ // 移除查询参数和锚点
131
+ const cleanUrl = url.split('?')[0].split('#')[0];
132
+ const parts = cleanUrl.split('/');
133
+ let fileName = parts[parts.length - 1] || '未知文件';
134
+
135
+ // 如果文件名为空或只是扩展名,生成一个默认名称
136
+ if (!fileName || fileName.startsWith('.')) {
137
+ const extension = getFileExtension(fileName || url);
138
+ fileName = extension ? `文件.${extension}` : '未知文件';
139
+ }
140
+
141
+ return fileName;
142
+ };
143
+
144
+ // 解析文件信息
145
+ const parseFileInfo = (url) => {
146
+ const fullUrl = getFullUrl(url);
147
+ const fileName = extractFileName(url);
148
+ const extension = getFileExtension(fileName);
149
+
150
+ return {
151
+ name: fileName,
152
+ url: fullUrl,
153
+ extension,
154
+ size: null
155
+ };
156
+ };
157
+
158
+ // 计算文件列表
159
+ const computedFiles = computed(() => {
160
+ let fileList = [];
161
+
162
+ if (typeof props.files === 'string') {
163
+ if (!props.files.trim()) {
164
+ return [];
165
+ }
166
+
167
+ // 判断是单个URL还是逗号分隔的多个URL
168
+ if (props.files.includes(',')) {
169
+ // 逗号分隔的多个URL
170
+ const urls = props.files.split(',').map(url => url.trim()).filter(url => url);
171
+ fileList = urls.map(url => parseFileInfo(url));
172
+ } else {
173
+ // 单个URL
174
+ fileList = [parseFileInfo(props.files.trim())];
175
+ }
176
+ } else if (Array.isArray(props.files)) {
177
+ // 数组格式
178
+ fileList = props.files.map(file => {
179
+ if (typeof file === 'string') {
180
+ // 数组中的字符串元素,直接作为URL处理
181
+ return parseFileInfo(file);
182
+ } else if (file && typeof file === 'object') {
183
+ // 对象格式的文件信息
184
+ const url = file.url || file.src || file.path || '';
185
+ return {
186
+ name: file.name || extractFileName(url),
187
+ url: getFullUrl(url),
188
+ size: file.size || null,
189
+ extension: file.extension || getFileExtension(url || file.name || ''),
190
+ ...file
191
+ };
192
+ } else {
193
+ // 其他情况,返回默认对象
194
+ return {
195
+ name: '未知文件',
196
+ url: '',
197
+ extension: '',
198
+ size: null
199
+ };
200
+ }
201
+ }).filter(file => file.url); // 过滤掉没有URL的文件
202
+ }
203
+
204
+ return fileList;
205
+ });
206
+
207
+ // 判断是否为图片
208
+ const isImage = (extension) => {
209
+ const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
210
+ return imageExtensions.includes(extension?.toLowerCase());
211
+ };
212
+
213
+ // 获取文件图标
214
+ const getFileIcon = (extension) => {
215
+ const iconMap = {
216
+ // 文档类
217
+ 'pdf': 'file-pdf',
218
+ 'doc': 'file-word',
219
+ 'docx': 'file-word',
220
+ 'xls': 'file-excel',
221
+ 'xlsx': 'file-excel',
222
+ 'ppt': 'file-powerpoint',
223
+ 'pptx': 'file-powerpoint',
224
+ 'txt': 'file-text',
225
+ 'rtf': 'file-text',
226
+ // 代码类
227
+ 'js': 'file-code',
228
+ 'ts': 'file-code',
229
+ 'html': 'file-code',
230
+ 'css': 'file-code',
231
+ 'json': 'file-code',
232
+ 'xml': 'file-code',
233
+ // 压缩类
234
+ 'zip': 'file-zip',
235
+ 'rar': 'file-zip',
236
+ '7z': 'file-zip',
237
+ 'tar': 'file-zip',
238
+ 'gz': 'file-zip',
239
+ // 音视频类
240
+ 'mp3': 'file-music',
241
+ 'wav': 'file-music',
242
+ 'mp4': 'file-video',
243
+ 'avi': 'file-video',
244
+ 'mov': 'file-video',
245
+ 'wmv': 'file-video'
246
+ };
247
+
248
+ return iconMap[extension?.toLowerCase()] || 'file';
249
+ };
250
+
251
+ // 获取文件颜色
252
+ const getFileColor = (extension) => {
253
+ const colorMap = {
254
+ 'pdf': '#FF4757',
255
+ 'doc': '#2E5BFF',
256
+ 'docx': '#2E5BFF',
257
+ 'xls': '#00A854',
258
+ 'xlsx': '#00A854',
259
+ 'ppt': '#FF8C42',
260
+ 'pptx': '#FF8C42',
261
+ 'txt': '#666666',
262
+ 'zip': '#9C27B0',
263
+ 'rar': '#9C27B0',
264
+ 'mp3': '#FF9800',
265
+ 'mp4': '#795548'
266
+ };
267
+
268
+ return colorMap[extension?.toLowerCase()] || '#666666';
269
+ };
270
+
271
+ // 计算实际使用的图片尺寸
272
+ const actualImageSize = computed(() => {
273
+ return props.size === 'mini' ? '20px' : props.imageSize;
274
+ });
275
+
276
+ // 格式化文件大小
277
+ const formatFileSize = (bytes) => {
278
+ if (!bytes) return '';
279
+ const units = ['B', 'KB', 'MB', 'GB'];
280
+ let size = bytes;
281
+ let unitIndex = 0;
282
+
283
+ while (size >= 1024 && unitIndex < units.length - 1) {
284
+ size /= 1024;
285
+ unitIndex++;
286
+ }
287
+
288
+ return `${size.toFixed(1)} ${units[unitIndex]}`;
289
+ };
290
+
291
+ // 是否可在线预览
292
+ const canPreviewOnline = (extension) => {
293
+ const onlinePreviewExtensions = ['pdf', 'txt', 'html', 'xml', 'json'];
294
+ return onlinePreviewExtensions.includes(extension?.toLowerCase());
295
+ };
296
+
297
+ // 文件下载
298
+ const handleDownload = (file) => {
299
+ const link = document.createElement('a');
300
+ link.href = file.url;
301
+ link.download = file.name;
302
+ link.target = '_blank';
303
+ document.body.appendChild(link);
304
+ link.click();
305
+ document.body.removeChild(link);
306
+
307
+ emit('file-download', file);
308
+ };
309
+
310
+ // 图片预览
311
+ const handlePreview = (file) => {
312
+ const imageFiles = computedFiles.value.filter(f => isImage(f.extension));
313
+ previewImages.value = imageFiles.map(f => f.url);
314
+ previewIndex.value = imageFiles.findIndex(f => f.url === file.url);
315
+ previewVisible.value = true;
316
+
317
+ emit('file-preview', file);
318
+ };
319
+
320
+ // 文件点击处理
321
+ const handleFileClick = (file) => {
322
+ emit('file-click', file);
323
+
324
+ if (isImage(file.extension)) {
325
+ // 图片文件,显示预览
326
+ handlePreview(file);
327
+ } else if (canPreviewOnline(file.extension)) {
328
+ // 可在线预览的文件
329
+ window.open(file.url, '_blank');
330
+ } else {
331
+ // 其他文件,触发下载
332
+ handleDownload(file);
333
+ }
334
+ };
335
+
336
+ // 文件删除
337
+ const handleDelete = (file, index) => {
338
+ emit('file-delete', file, index);
339
+ };
340
+
341
+ // 图片加载错误处理
342
+ const handleImageError = () => {
343
+ // 图片加载失败时的处理
344
+ };
345
+ </script>
346
+
347
+ <style lang="less" scoped>
348
+ .ebiz-file-list {
349
+ width: 100%;
350
+
351
+ &.mini-mode {
352
+ .file-list-container {
353
+ display: flex;
354
+ flex-direction: row;
355
+ flex-wrap: wrap;
356
+ gap: 4px;
357
+ }
358
+ }
359
+
360
+ .empty-state {
361
+ display: flex;
362
+ flex-direction: column;
363
+ align-items: center;
364
+ justify-content: center;
365
+ padding: 10px;
366
+ color: #999;
367
+
368
+ p {
369
+ margin: 8px 0 0 0;
370
+ font-size: 12px;
371
+ }
372
+ }
373
+
374
+ .file-list-container {
375
+ display: flex;
376
+ flex-direction: column;
377
+ gap: 8px;
378
+
379
+ &.mini-container {
380
+ flex-direction: row;
381
+ flex-wrap: wrap;
382
+ }
383
+ }
384
+
385
+ .file-item {
386
+ display: flex;
387
+ align-items: center;
388
+ padding: 8px 12px;
389
+ border: 1px solid #e6e6e6;
390
+ border-radius: 6px;
391
+ cursor: pointer;
392
+ transition: all 0.3s;
393
+ background: #fff;
394
+
395
+ &.mini-item {
396
+ padding: 0;
397
+ border: none;
398
+ background: transparent;
399
+ box-shadow: none;
400
+ margin-right: 8px;
401
+ margin-bottom: 8px;
402
+
403
+ &:hover {
404
+ border-color: transparent;
405
+ box-shadow: none;
406
+ }
407
+ }
408
+
409
+ &:hover {
410
+ border-color: #0052d9;
411
+ box-shadow: 0 2px 8px rgba(0, 82, 217, 0.15);
412
+
413
+ .file-actions {
414
+ opacity: 1;
415
+ }
416
+ }
417
+ }
418
+
419
+ .file-icon {
420
+ margin-right: 12px;
421
+ flex-shrink: 0;
422
+ display: flex;
423
+ align-items: center;
424
+ justify-content: center;
425
+ width: v-bind(actualImageSize);
426
+ height: v-bind(actualImageSize);
427
+
428
+ .mini-item & {
429
+ margin-right: 0;
430
+ }
431
+ }
432
+
433
+ .file-info {
434
+ flex: 1;
435
+ min-width: 0;
436
+
437
+ .file-name {
438
+ font-size: 14px;
439
+ font-weight: 500;
440
+ color: #333;
441
+ margin-bottom: 3px;
442
+ overflow: hidden;
443
+ text-overflow: ellipsis;
444
+ white-space: nowrap;
445
+ }
446
+
447
+ .file-meta {
448
+ display: flex;
449
+ gap: 8px;
450
+
451
+ .file-size,
452
+ .file-type {
453
+ font-size: 12px;
454
+ color: #999;
455
+ }
456
+ }
457
+ }
458
+
459
+ .file-actions {
460
+ display: flex;
461
+ gap: 4px;
462
+ opacity: 0;
463
+ transition: opacity 0.3s;
464
+ flex-shrink: 0;
465
+ }
466
+ }
467
467
  </style>