@truenewx/tnxvue3 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +3 -0
  2. package/package.json +76 -0
  3. package/sample/App.vue +19 -0
  4. package/sample/main.js +11 -0
  5. package/sample/pages/index.vue +79 -0
  6. package/sample/pages/info.vue +28 -0
  7. package/sample/tnx.js +31 -0
  8. package/src/aj-captcha/Verify/VerifyPoints.vue +258 -0
  9. package/src/aj-captcha/Verify/VerifySlide.vue +379 -0
  10. package/src/aj-captcha/Verify.vue +375 -0
  11. package/src/aj-captcha/api/index.js +19 -0
  12. package/src/aj-captcha/utils/ase.js +11 -0
  13. package/src/aj-captcha/utils/util.js +35 -0
  14. package/src/ant-design/tnxad-theme.css +5 -0
  15. package/src/ant-design/tnxad.css +8 -0
  16. package/src/ant-design/tnxad.js +23 -0
  17. package/src/element-plus/alert/Alert.vue +112 -0
  18. package/src/element-plus/avatar/Avatar.vue +124 -0
  19. package/src/element-plus/button/Button.vue +184 -0
  20. package/src/element-plus/check-icon/CheckIcon.vue +61 -0
  21. package/src/element-plus/close-error-button/CloseErrorButton.vue +45 -0
  22. package/src/element-plus/curd/Curd.vue +224 -0
  23. package/src/element-plus/date-picker/DatePicker.vue +206 -0
  24. package/src/element-plus/date-range/DateRange.vue +78 -0
  25. package/src/element-plus/datetime-picker/DateTimePicker.vue +129 -0
  26. package/src/element-plus/detail-form/DetailForm.vue +88 -0
  27. package/src/element-plus/dialog/Dialog.vue +259 -0
  28. package/src/element-plus/dialog/DialogContent.vue +13 -0
  29. package/src/element-plus/drawer/Drawer.vue +175 -0
  30. package/src/element-plus/dropdown-item/DropdownItem.vue +30 -0
  31. package/src/element-plus/enum-select/EnumSelect.vue +125 -0
  32. package/src/element-plus/fetch-cascader/FetchCascader.vue +138 -0
  33. package/src/element-plus/fetch-select/FetchSelect.vue +166 -0
  34. package/src/element-plus/fetch-tags/FetchTags.vue +122 -0
  35. package/src/element-plus/fss-upload/FssUpload.vue +306 -0
  36. package/src/element-plus/fss-view/FssView.vue +163 -0
  37. package/src/element-plus/icon/Icon.vue +221 -0
  38. package/src/element-plus/input-number/InputNumber.vue +150 -0
  39. package/src/element-plus/paged/Paged.vue +76 -0
  40. package/src/element-plus/permission-tree/PermissionTree.vue +184 -0
  41. package/src/element-plus/query-form/QueryForm.vue +138 -0
  42. package/src/element-plus/query-table/QueryTable.vue +402 -0
  43. package/src/element-plus/region-cascader/RegionCascader.vue +108 -0
  44. package/src/element-plus/select/Select.vue +446 -0
  45. package/src/element-plus/slider/Slider.vue +88 -0
  46. package/src/element-plus/steps-nav/StepsNav.vue +57 -0
  47. package/src/element-plus/submit-form/SubmitForm.vue +236 -0
  48. package/src/element-plus/table-column/TableColumn.vue +32 -0
  49. package/src/element-plus/tabs/Tabs.vue +93 -0
  50. package/src/element-plus/tnxel.css +890 -0
  51. package/src/element-plus/tnxel.js +528 -0
  52. package/src/element-plus/transfer/Transfer.vue +117 -0
  53. package/src/element-plus/upload/Upload.vue +856 -0
  54. package/src/percent/Percent.vue +12 -0
  55. package/src/text/Text.vue +33 -0
  56. package/src/tnxvue-cli.js +64 -0
  57. package/src/tnxvue-router.js +161 -0
  58. package/src/tnxvue-validator.js +365 -0
  59. package/src/tnxvue.css +12 -0
  60. package/src/tnxvue.js +343 -0
@@ -0,0 +1,856 @@
1
+ <template>
2
+ <el-upload ref="upload" name="file" class="tnxel-upload-container"
3
+ :class="{center: center, 'hide-file-list': !showFileList}"
4
+ :id="id"
5
+ :action="action"
6
+ :before-upload="_beforeUpload"
7
+ :on-progress="_onProgress"
8
+ :on-success="_onSuccess"
9
+ :on-error="_onError"
10
+ :with-credentials="true"
11
+ :list-type="listType"
12
+ :file-list="fileList"
13
+ :show-file-list="true"
14
+ :headers="uploadHeaders"
15
+ :multiple="uploadOptions?.number !== 1"
16
+ :accept="uploadAccept" :disabled="disabled" v-if="uploadOptions">
17
+ <template #file="{file}">
18
+ <div class="el-upload-list__panel" :data-file-id="getFileId(file)" :style="itemPanelStyle"
19
+ v-if="showFileList">
20
+ <img class="el-upload-list__item-thumbnail" :src="file.url" v-if="imageable">
21
+ <div class="el-upload-list__item-name" v-else>
22
+ <tnxel-icon :value="getFileIcon(file)"/>
23
+ <span>{{ file.name }}</span>
24
+ </div>
25
+ <span class="el-upload-list__item-uploading" v-if="isUploading(file) && listType !== 'text'">
26
+ <tnxel-icon value="Loading"/>
27
+ </span>
28
+ <label class="el-upload-list__item-status-label">
29
+ <el-progress type="circle" :percentage="file.percentage" :width="16" :stroke-width="3"
30
+ :show-text="false" v-if="isUploading(file)"/>
31
+ <tnxel-icon value="CircleCheck" class="text-success" v-else-if="listType === 'text'"/>
32
+ <tnxel-icon value="Check" style="margin-top: 8px;" v-else/>
33
+ </label>
34
+ <div class="el-upload-list__item-actions">
35
+ <tnxel-icon value="ZoomIn" @click="previewFile(file)" v-if="isPreviewable(file)"/>
36
+ <tnxel-icon :value="listType === 'text' ? 'Close' : 'Delete'" @click="removeFile(file)"/>
37
+ </div>
38
+ </div>
39
+ </template>
40
+ <template #trigger>
41
+ <el-tooltip :content="tipContent" placement="top" :disabled="tip !== 'tooltip'">
42
+ <div class="upload-trigger" :title="tip === 'title' ? tipContent : undefined"
43
+ :class="{'text-placeholder': disabled}" v-if="$slots.trigger">
44
+ <slot name="trigger"></slot>
45
+ </div>
46
+ <el-button class="upload-trigger" :title="tip === 'title' ? tipContent : undefined"
47
+ :disabled="disabled" v-else-if="listType === 'text'">
48
+ <tnxel-icon :value="triggerIcon" :size="uploadIconSize"/>
49
+ <div class="upload-trigger-text" v-if="triggerText">{{ triggerText }}</div>
50
+ </el-button>
51
+ <div class="upload-trigger" :title="tip === 'title' ? tipContent : undefined"
52
+ :class="{'text-placeholder': disabled}" v-else>
53
+ <tnxel-icon :value="triggerIcon" :size="uploadIconSize"/>
54
+ <div class="upload-trigger-text" v-if="triggerText">{{ triggerText }}</div>
55
+ </div>
56
+ </el-tooltip>
57
+ </template>
58
+ <template #tip v-if="(tipContent && (typeof tip !== 'string')) || errors.length">
59
+ <tnxel-alert type="error" class="w-fit-content my-0" v-if="errors.length">
60
+ <div v-for="error of errors" :key="error.code">
61
+ {{ error.message }}
62
+ </div>
63
+ </tnxel-alert>
64
+ <div class="el-upload__tip" v-text="tipContent" v-else></div>
65
+ </template>
66
+ </el-upload>
67
+ </template>
68
+
69
+ <script>
70
+ import $ from 'jquery';
71
+ import Alert from '../alert/Alert.vue';
72
+ import Icon from '../icon/Icon.vue';
73
+
74
+ function getExtension(file) {
75
+ return window.tnx.util.net.getExtension(file.name);
76
+ }
77
+
78
+ function isImage(file) {
79
+ let extension = getExtension(file);
80
+ return extension && window.tnx.util.file.isImage(extension);
81
+ }
82
+
83
+ export default {
84
+ name: 'TnxelUpload',
85
+ components: {
86
+ 'tnxel-alert': Alert,
87
+ 'tnxel-icon': Icon,
88
+ },
89
+ props: {
90
+ appName: String, // 上传目标应用名称
91
+ action: {
92
+ type: String,
93
+ required: true,
94
+ },
95
+ uploadOptions: Object,
96
+ fileList: {
97
+ type: Array,
98
+ default: () => [],
99
+ },
100
+ width: {
101
+ type: [Number, String],
102
+ },
103
+ height: {
104
+ type: [Number, String],
105
+ },
106
+ triggerIcon: {
107
+ type: String,
108
+ default: 'Plus',
109
+ },
110
+ triggerIconSize: Number,
111
+ triggerText: String,
112
+ beforeUpload: Function,
113
+ /**
114
+ * 所有可上传的文件均已开始上传时的钩子
115
+ */
116
+ onUpload: Function,
117
+ onProgress: Function,
118
+ onSuccess: Function,
119
+ onError: Function,
120
+ onRemoved: Function,
121
+ handleErrors: {
122
+ type: Function,
123
+ default(errors) {
124
+ this.errors = errors || [];
125
+ }
126
+ },
127
+ center: Boolean,
128
+ tip: {
129
+ type: [Boolean, String, Function],
130
+ default() {
131
+ return true;
132
+ }
133
+ },
134
+ showFileList: {
135
+ type: Boolean,
136
+ default() {
137
+ return true;
138
+ }
139
+ },
140
+ fileIcon: {
141
+ type: [String, Function],
142
+ default() {
143
+ return function (file) {
144
+ if (isImage(file)) {
145
+ return 'Picture';
146
+ }
147
+ return 'Document';
148
+ };
149
+ }
150
+ },
151
+ disabled: Boolean,
152
+ },
153
+ data() {
154
+ const tnx = window.tnx;
155
+ return {
156
+ tnx: tnx,
157
+ id: 'upload-container-' + tnx.util.string.random(32),
158
+ tipMessages: {
159
+ number: '一次最多上传{0}个文件',
160
+ capacity: '单个文件的大小不能超过{0}',
161
+ extensions: '只能上传{0}文件',
162
+ excludedExtensions: '不能上传{0}文件',
163
+ },
164
+ uploadHeaders: {
165
+ 'X-Requested-With': 'XMLHttpRequest'
166
+ },
167
+ files: [], // 文件清单,包含初始文件和新增成功的文件,不包含校验失败的文件
168
+ errors: [],
169
+ };
170
+ },
171
+ computed: {
172
+ fileNumExceed() {
173
+ return this.uploadOptions?.number && this.files.length >= this.uploadOptions.number;
174
+ },
175
+ imageable() {
176
+ let imageable = false;
177
+ if (this.uploadOptions?.extensions) {
178
+ for (let extension of this.uploadOptions.extensions) {
179
+ if (this.tnx.util.file.isImage(extension)) {
180
+ imageable = true;
181
+ } else {
182
+ return false;
183
+ }
184
+ }
185
+ }
186
+ return imageable;
187
+ },
188
+ listType() {
189
+ return this.imageable ? 'picture-card' : 'text';
190
+ },
191
+ tipContent() {
192
+ let content = '';
193
+ if (this.tip !== false && this.uploadOptions) {
194
+ const separator = ',';
195
+ if (this.uploadOptions.number > 1) {
196
+ content += separator + this.tipMessages.number.format(this.uploadOptions.number);
197
+ }
198
+ if (this.uploadOptions.capacity > 0) {
199
+ const capacity = this.tnx.util.string.getCapacityCaption(this.uploadOptions.capacity, 2);
200
+ content += separator + this.tipMessages.capacity.format(capacity);
201
+ }
202
+ if (this.uploadOptions.extensions && this.uploadOptions.extensions.length) {
203
+ const extensions = this.uploadOptions.extensions.join('、');
204
+ if (this.uploadOptions.extensionsRejected) {
205
+ content += separator + this.tipMessages.excludedExtensions.format(extensions);
206
+ } else {
207
+ content += separator + this.tipMessages.extensions.format(extensions);
208
+ }
209
+ }
210
+ if (content.length > 0) {
211
+ content = content.substr(separator.length);
212
+ }
213
+ if (this.uploadOptions.publicReadable) {
214
+ if (content.length) {
215
+ content += ';';
216
+ }
217
+ content += '该' + (this.imageable ? '图片' : '文件') + '可能对外公开,请慎重选择上传。';
218
+ }
219
+ if (typeof this.tip === 'function') {
220
+ content = this.tip(content);
221
+ }
222
+ }
223
+ return content;
224
+ },
225
+ uploadAccept() {
226
+ if (this.uploadOptions && !this.uploadOptions.extensionsRejected && this.uploadOptions.extensions?.length) {
227
+ let accept = '';
228
+ for (let extension of this.uploadOptions.extensions) {
229
+ accept += ',.' + extension;
230
+ }
231
+ return accept.substr(1);
232
+ }
233
+ return undefined;
234
+ },
235
+ uploadSize() {
236
+ let width = this.width;
237
+ let height = this.height;
238
+ let uploadSize = undefined;
239
+ if (this.uploadOptions.sizes && this.uploadOptions.sizes.length) {
240
+ uploadSize = this.uploadOptions.sizes[0];
241
+ }
242
+ if (uploadSize) {
243
+ width = width || uploadSize.width;
244
+ height = height || uploadSize.height;
245
+ }
246
+ return {width, height};
247
+ },
248
+ itemPanelStyle() {
249
+ let style = {
250
+ height: window.tnx.util.string.getPixelString(this.uploadSize.height),
251
+ }
252
+ if (this.imageable) {
253
+ style.width = window.tnx.util.string.getPixelString(this.uploadSize.width);
254
+ }
255
+ return style;
256
+ },
257
+ uploadIconSize() {
258
+ if (this.triggerIconSize) {
259
+ return this.triggerIconSize;
260
+ }
261
+ let width = this.uploadSize.width || 0;
262
+ let height = this.uploadSize.height || 0;
263
+ let iconSize = Math.floor(Math.min(width, height) / 3);
264
+ iconSize = Math.max(16, Math.min(iconSize, 32));
265
+ return iconSize;
266
+ },
267
+ },
268
+ watch: {
269
+ uploadOptions() {
270
+ this.init();
271
+ },
272
+ fileList() {
273
+ if (this.uploadOptions?.number) {
274
+ this.init();
275
+ }
276
+ }
277
+ },
278
+ methods: {
279
+ init() {
280
+ this.files = [].concat(this.fileList);
281
+ let vm = this;
282
+ // 需在vue渲染之后才可正常操作dom元素
283
+ this.$nextTick(function () {
284
+ // 初始化显示尺寸
285
+ let width = vm.uploadSize.width;
286
+ if (width) {
287
+ if (typeof width === 'string' && !width.endsWith('%')) {
288
+ width = window.tnx.util.string.getPixelNumber(width);
289
+ }
290
+ if (typeof width === 'number') {
291
+ width += 2; // 加上边框宽度
292
+ }
293
+ width = window.tnx.util.string.getPixelString(width);
294
+ }
295
+ let height = vm.uploadSize.height;
296
+ if (height) {
297
+ if (typeof height === 'string' && !height.endsWith('%')) {
298
+ height = window.tnx.util.string.getPixelNumber(height);
299
+ }
300
+ if (typeof height === 'number') {
301
+ height += 2; // 加上边框宽度
302
+ }
303
+ height = window.tnx.util.string.getPixelString(height);
304
+ }
305
+
306
+ let $container = $('#' + vm.id);
307
+ let $upload = $('.el-upload', $container);
308
+ $upload.css({
309
+ width: width,
310
+ height: height,
311
+ });
312
+
313
+ // 不显示文件清单,或文件数量未达到上限,则显示添加框
314
+ if (!vm.showFileList || !vm.fileNumExceed) {
315
+ $upload.removeClass('d-none');
316
+ }
317
+ // 构建初始化文件显示面板
318
+ if (vm.fileList) {
319
+ for (let file of vm.fileList) {
320
+ vm._resizeFilePanel(file, vm.fileList);
321
+ }
322
+ }
323
+ });
324
+ },
325
+ getFileId(file) {
326
+ if (!file.id) {
327
+ if (file.url) { // 有URL的文件通过URL即可唯一确定
328
+ file.id = this.tnx.util.string.md5(file.url);
329
+ } else {
330
+ // 没有URL的文件,通过文件类型+文件名+文件大小+最后修改时间,几乎可以唯一区分一个文件,重复的概率极低,即使重复也不破坏业务一致性和完整性
331
+ file.id = this.tnx.util.string.md5(
332
+ file.type + '-' + file.name + '-' + file.size + '-' + file.lastModified);
333
+ }
334
+ }
335
+ return file.id;
336
+ },
337
+ getFileIcon(file) {
338
+ if (typeof this.fileIcon === 'function') {
339
+ return this.fileIcon(file);
340
+ }
341
+ return this.fileIcon;
342
+ },
343
+ validate(file) {
344
+ // 在检查首个准备上传的文件前,清空可能存在的错误消息
345
+ if (this.files.length === 0) {
346
+ this.handleErrors([]);
347
+ }
348
+ // 校验文件重复
349
+ const vm = this;
350
+ if (this.files.contains(function (f) {
351
+ const raw = f.raw ? f.raw : f;
352
+ return file.uid !== raw.uid && vm.getFileId(file) === vm.getFileId(raw);
353
+ })) {
354
+ return false;
355
+ }
356
+ // 校验数量
357
+ if (this.fileNumExceed) {
358
+ let message = this.tipMessages.number.format(this.uploadOptions.number);
359
+ message += ',多余的文件未加入上传队列';
360
+ this.handleErrors([{
361
+ code: 'error.upload.number',
362
+ message: message,
363
+ file: file,
364
+ }]);
365
+ return false;
366
+ }
367
+ // 校验容量大小
368
+ if (this.uploadOptions.capacity > 0 && file.size > this.uploadOptions.capacity) {
369
+ const capacity = this.tnx.util.string.getCapacityCaption(this.uploadOptions.capacity);
370
+ let message = this.tipMessages.capacity.format(capacity, 2);
371
+ message += ',文件"' + file.name + '"大小为' + this.tnx.util.string.getCapacityCaption(file.size, 2)
372
+ + ',不符合要求,未加入上传队列';
373
+ this.handleErrors([{
374
+ code: 'error.upload.capacity',
375
+ message: message,
376
+ file: file,
377
+ }]);
378
+ return false;
379
+ }
380
+ // 校验扩展名
381
+ if (this.uploadOptions.extensions && this.uploadOptions.extensions.length) {
382
+ const extension = file.name.substr(file.name.lastIndexOf('.') + 1);
383
+ if (this.uploadOptions.extensionsRejected) { // 扩展名排除模式
384
+ if (this.uploadOptions.extensions.containsIgnoreCase(extension)) {
385
+ const extensions = this.uploadOptions.extensions.join('、');
386
+ let message = this.tipMessages.excludedExtensions.format(extensions);
387
+ this.handleErrors([{
388
+ code: 'error.upload.extension',
389
+ message: message,
390
+ file: file,
391
+ }]);
392
+ return false;
393
+ }
394
+ } else { // 扩展名包含模式
395
+ if (!this.uploadOptions.extensions.containsIgnoreCase(extension)) {
396
+ const extensions = this.uploadOptions.extensions.join('、');
397
+ let message = this.tipMessages.extensions.format(extensions);
398
+ message += ',文件"' + file.name + '"不符合要求,未加入上传队列';
399
+ this.handleErrors([{
400
+ code: 'error.upload.extension',
401
+ message: message,
402
+ file: file,
403
+ }]);
404
+ return false;
405
+ }
406
+ }
407
+ }
408
+ // 校验通过才加入上传中文件清单
409
+ this.files.push(file);
410
+ return true;
411
+ },
412
+ isUploading(file) {
413
+ return file.status === 'ready' || file.status === 'uploading';
414
+ },
415
+ _beforeUpload(file) {
416
+ this.handleErrors([]); // 先清除可能存在的错误提示,以便于重新提示
417
+
418
+ const vm = this;
419
+ const rpc = this.tnx.app.rpc;
420
+ return new Promise(function (resolve, reject) {
421
+ if (vm.validate(file)) {
422
+ let $upload = $('#' + vm.id + ' .el-upload');
423
+ if (vm.showFileList && vm.fileNumExceed) {
424
+ $upload.css('visibility', 'hidden');
425
+ }
426
+
427
+ // 上传前需确保用户在fss应用中已登录
428
+ rpc.ensureLogined(function () {
429
+ if (vm.beforeUpload) {
430
+ let promise = vm.beforeUpload(file);
431
+ if (promise instanceof Promise) {
432
+ promise.then(function () {
433
+ resolve(file);
434
+ }).catch(function () {
435
+ reject(file);
436
+ });
437
+ } else if (promise === false) {
438
+ reject(file);
439
+ } else {
440
+ resolve(file);
441
+ }
442
+ } else {
443
+ resolve(file);
444
+ }
445
+ }, {
446
+ app: vm.appName,
447
+ toLogin(loginFormUrl, originalUrl, originalMethod) {
448
+ $upload.css('visibility', 'unset');
449
+ // 从当前应用登录表单地址
450
+ rpc.get('/authentication/login-url', function (loginUrl) {
451
+ if (loginUrl) {
452
+ // 默认登录后跳转回当前页面
453
+ loginUrl += loginUrl.contains('?') ? '&' : '?';
454
+ loginUrl += rpc.loginSuccessRedirectParameter + '=' + window.location.href;
455
+ rpc.toLogin(loginUrl, vm.action, 'POST');
456
+ } else {
457
+ // 获取登录地址为空,则说明实际上是登录状态
458
+ resolve(file);
459
+ }
460
+ });
461
+ return true;
462
+ }
463
+ });
464
+ } else {
465
+ reject(file);
466
+ }
467
+ });
468
+ },
469
+ _onProgress(event, file, fileList) {
470
+ if (file.percentage === 0) { // 首次执行
471
+ this._resizeFilePanel(file, fileList);
472
+ if (this.onUpload) {
473
+ // 最后一个文件开始上传,触发onUpload事件处理
474
+ if (file.uid === fileList[fileList.length - 1].uid) {
475
+ let resolvedNum = fileList.length;
476
+ let rejectedNum = this.files.length - resolvedNum;
477
+ this.onUpload(resolvedNum, rejectedNum);
478
+ }
479
+ }
480
+ } else if (this.onProgress) {
481
+ this.onProgress(file);
482
+ }
483
+ },
484
+ _resizeFilePanel(file, fileList) {
485
+ const $container = $('#' + this.id);
486
+ const $upload = $('.el-upload', $container);
487
+ // 显示文件清单且文件数量已达上限,则隐藏添加框
488
+ if (this.showFileList && this.fileNumExceed) {
489
+ // 隐藏添加框
490
+ $upload.addClass('d-none');
491
+ $upload.css({
492
+ visibility: 'unset',
493
+ });
494
+ }
495
+
496
+ let fileId = this.getFileId(file);
497
+ let $fileItem = $('.el-upload-list__panel[data-file-id="' + fileId + '"]', $container).parent();
498
+ let uploadStyle = $upload.attr('style');
499
+ if (uploadStyle) {
500
+ $fileItem.attr('style', uploadStyle);
501
+ }
502
+ },
503
+ _onSuccess(result, file, fileList) {
504
+ if (this.onSuccess) {
505
+ this.onSuccess(result, file, fileList);
506
+ }
507
+ },
508
+ _onError(error, file, fileList) {
509
+ $('#' + this.id + ' .el-upload').show();
510
+ let message;
511
+ try {
512
+ message = JSON.parse(error.message);
513
+ } catch (e) {
514
+ // 忽略JSON解析错误
515
+ }
516
+ if (message) {
517
+ if (message.status === 500) {
518
+ if (this.onError) {
519
+ this.onError(file, message.message);
520
+ } else {
521
+ this.tnx.app.rpc.handle500Error(message.message, {
522
+ error: this.handleErrors
523
+ });
524
+ }
525
+ return;
526
+ } else if (message.errors) {
527
+ if (this.onError) {
528
+ let errorMessage = '';
529
+ for (let error of message.errors) {
530
+ errorMessage += error.message + '\n';
531
+ }
532
+ errorMessage = errorMessage.trim();
533
+ this.onError(file, errorMessage);
534
+ } else {
535
+ this.handleErrors(message.errors);
536
+ }
537
+ return;
538
+ }
539
+ }
540
+ console.error(error.message);
541
+ if (this.onError) {
542
+ this.onError(file, error.message);
543
+ }
544
+ },
545
+ removeFile(file) {
546
+ this.files.remove(function (f) {
547
+ return file.uid === f.uid;
548
+ });
549
+ this.$refs.upload.handleRemove(file);
550
+ if (!this.fileNumExceed) {
551
+ this.handleErrors([]); // 移除一个文件后,此时如果有错误提示则一定为数量超限,需清空错误提示
552
+ let container = $('#' + this.id);
553
+ this.$nextTick(function () {
554
+ // 去掉文件列表的样式,以免其占高度
555
+ $('.el-upload-list', container).removeAttr('style');
556
+ // 恢复添加框默认样式
557
+ $('.el-upload', container).removeClass('d-none');
558
+ });
559
+ }
560
+ if (this.onRemoved) {
561
+ this.onRemoved(file);
562
+ }
563
+ },
564
+ previewFile(file) {
565
+ let url = file.previewUrl || file.url;
566
+ if (url) {
567
+ let extension = getExtension(file);
568
+ if (extension === 'pdf') {
569
+ url = this.tnx.util.net.appendParams(url, {
570
+ inline: true
571
+ });
572
+ window.open(url);
573
+ } else {
574
+ if (!file.width || !file.height) {
575
+ const image = new Image();
576
+ image.src = url;
577
+ const _this = this;
578
+ image.onload = function () {
579
+ file.width = image.width;
580
+ file.height = image.height;
581
+ _this._doPreviewImage(file);
582
+ }
583
+ } else {
584
+ this._doPreviewImage(file);
585
+ }
586
+ }
587
+ }
588
+ },
589
+ _doPreviewImage(file) {
590
+ const dialogPadding = 16;
591
+ let top = (this.tnx.util.dom.getDocHeight() - file.height) / 2 - dialogPadding;
592
+ top = Math.max(top, 5); // 最高顶部留5px空隙
593
+ let width = file.width;
594
+ width = Math.min(width, this.tnx.util.dom.getDocWidth() - 10); // 最宽两边各留10px空隙
595
+ const content = '<img src="' + (file.previewUrl || file.url) + '" style="max-width: 100%;">';
596
+ this.tnx.dialog(content, '', [], {
597
+ top: top + 'px',
598
+ width: width + 'px',
599
+ });
600
+ },
601
+ size() {
602
+ if (this.files && this.files.length) {
603
+ return this.files.length;
604
+ }
605
+ return 0;
606
+ },
607
+ isPreviewable(file) {
608
+ return isImage(file) || getExtension(file) === 'pdf';
609
+ },
610
+ }
611
+ }
612
+ </script>
613
+
614
+ <style>
615
+ .tnxel-upload-container {
616
+ display: flex;
617
+ flex-direction: column;
618
+ }
619
+
620
+ .tnxel-upload-container.center {
621
+ align-items: center;
622
+ }
623
+
624
+ .tnxel-upload-container.hide-file-list .el-upload-list__item,
625
+ .tnxel-upload-container.hide-file-list .el-upload-list--text {
626
+ display: none;
627
+ }
628
+
629
+ .tnxel-upload-container .el-upload {
630
+ border-radius: .25rem;
631
+ display: inline-flex;
632
+ align-items: center;
633
+ justify-content: center;
634
+ margin-bottom: 0.5rem;
635
+ width: fit-content;
636
+ height: fit-content;
637
+ background-color: transparent;
638
+ }
639
+
640
+ .tnxel-upload-container:not(.center) .el-upload.el-upload--text {
641
+ justify-content: unset;
642
+ }
643
+
644
+ .tnxel-upload-container .el-upload:hover {
645
+ color: inherit;
646
+ }
647
+
648
+ .tnxel-upload-container .upload-trigger {
649
+ display: flex;
650
+ align-items: center;
651
+ min-height: 32px;
652
+ color: var(--el-text-color-regular);
653
+ }
654
+
655
+ .tnxel-upload-container div.upload-trigger {
656
+ margin: 0 0.5rem;
657
+ }
658
+
659
+ .tnxel-upload-container .upload-trigger-text {
660
+ margin-left: 0.25rem;
661
+ line-height: 1rem;
662
+ font-size: 14px;
663
+ }
664
+
665
+ .tnxel-upload-container.center .el-upload {
666
+ margin-right: auto;
667
+ margin-left: auto;
668
+ }
669
+
670
+ .tnxel-upload-container.center upload-trigger {
671
+ flex-direction: column;
672
+ }
673
+
674
+ .tnxel-upload-container.center .upload-trigger-text {
675
+ margin-left: 0;
676
+ margin-top: 0.25rem;
677
+ }
678
+
679
+ .tnxel-upload-container .el-upload i {
680
+ margin-top: 0;
681
+ color: inherit;
682
+ }
683
+
684
+ .tnxel-upload-container .el-upload-list--text {
685
+ margin: 0;
686
+ }
687
+
688
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item {
689
+ transition: none;
690
+ display: inline-flex;
691
+ align-items: center;
692
+ margin: 0.5rem 0.5rem 0 0;
693
+ border: 1px solid var(--el-border-color);
694
+ border-radius: .25rem;
695
+ width: fit-content;
696
+ }
697
+
698
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-name {
699
+ display: flex;
700
+ align-items: center;
701
+ margin-right: 0.5rem;
702
+ padding: 0;
703
+ }
704
+
705
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-name:hover {
706
+ color: inherit;
707
+ }
708
+
709
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-name i {
710
+ margin: 0;
711
+ }
712
+
713
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-status-label {
714
+ display: flex;
715
+ align-items: center;
716
+ justify-content: right;
717
+ position: unset;
718
+ width: 3rem;
719
+ }
720
+
721
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-status-label i {
722
+ font-size: 1rem;
723
+ margin-left: 0.25rem;
724
+ margin-right: 0.25rem;
725
+ }
726
+
727
+ .tnxel-upload-container .el-upload-list--picture-card {
728
+ display: inline-flex;
729
+ align-items: center;
730
+ max-width: 100%;
731
+ }
732
+
733
+ .tnxel-upload-container.center .el-upload-list--picture-card {
734
+ justify-content: center;
735
+ }
736
+
737
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:hover .el-upload-list__item-status-label,
738
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:focus .el-upload-list__item-actions,
739
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:active .el-upload-list__item-actions,
740
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item .el-upload-list__item-actions {
741
+ display: none;
742
+ }
743
+
744
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:hover .el-upload-list__item-actions,
745
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:focus:not(:hover) .el-upload-list__item-status-label,
746
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item:active:not(:hover) .el-upload-list__item-status-label {
747
+ display: flex;
748
+ opacity: 100;
749
+ }
750
+
751
+ .tnxel-upload-container .el-upload-list__panel {
752
+ min-width: 32px;
753
+ min-height: 32px;
754
+ }
755
+
756
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__panel {
757
+ display: flex;
758
+ align-items: center;
759
+ justify-content: space-between;
760
+ width: 100%;
761
+ padding: 0 0.5rem;
762
+ }
763
+
764
+ .tnxel-upload-container .el-upload-list--picture-card .el-upload-list__item {
765
+ transition: none;
766
+ border-radius: .25rem;
767
+ width: unset;
768
+ height: unset;
769
+ line-height: 0;
770
+ }
771
+
772
+ .tnxel-upload-container .el-upload-list__item-thumbnail {
773
+ object-fit: contain;
774
+ }
775
+
776
+ .tnxel-upload-container .el-upload-list__item-actions {
777
+ font-size: 1rem;
778
+ display: flex;
779
+ align-items: center;
780
+ justify-content: center;
781
+ min-width: 3rem;
782
+ }
783
+
784
+ .tnxel-upload-container .el-upload-list--text .el-upload-list__item-actions {
785
+ justify-content: right;
786
+ }
787
+
788
+ .tnxel-upload-container .el-upload-list__item-actions i {
789
+ cursor: pointer;
790
+ margin-left: 0.25rem;
791
+ margin-right: 0.25rem;
792
+ }
793
+
794
+ .tnxel-upload-container .el-upload-list__item-uploading {
795
+ position: absolute;
796
+ width: 100%;
797
+ height: 100%;
798
+ left: 0;
799
+ top: 0;
800
+ display: flex;
801
+ align-items: center;
802
+ justify-content: center;
803
+ font-size: 1rem;
804
+ color: white;
805
+ opacity: 0.5;
806
+ background-color: black;
807
+ }
808
+
809
+ .tnxel-upload-container .el-upload__tip {
810
+ line-height: 1;
811
+ margin-top: 0;
812
+ }
813
+
814
+ .tnxel-upload-container.center .el-upload__tip {
815
+ text-align: center;
816
+ }
817
+
818
+ .tnxel-upload-container .el-upload-list__item-status-label .el-progress {
819
+ position: unset;
820
+ top: unset;
821
+ width: unset;
822
+ margin: 0 0.25rem;
823
+ }
824
+
825
+ .el-form-item__content .tnxel-upload-container ul {
826
+ padding-left: 0;
827
+ }
828
+
829
+ .el-dropdown-menu__item .tnxel-upload-container {
830
+ width: 100%;
831
+ }
832
+
833
+ .el-dropdown-menu__item .tnxel-upload-container .el-upload {
834
+ margin-bottom: 0;
835
+ border: none;
836
+ justify-content: unset;
837
+ }
838
+
839
+ .el-dropdown-menu__item:hover .tnxel-upload-container .el-upload .upload-trigger,
840
+ .el-dropdown-menu__item:focus .tnxel-upload-container .el-upload .upload-trigger {
841
+ color: var(--el-dropdown-menuItem-hover-color);
842
+ }
843
+
844
+ .el-dropdown-menu__item .tnxel-upload-container .el-upload .upload-trigger {
845
+ min-height: 0;
846
+ margin: 0;
847
+ }
848
+
849
+ .el-dropdown-menu__item .tnxel-upload-container .el-upload .upload-trigger-text {
850
+ margin-left: 2px;
851
+ }
852
+
853
+ .el-form-item__content .tnxel-upload-container .el-upload.d-none + .el-upload__tip {
854
+ margin-top: 11px;
855
+ }
856
+ </style>