xianniu-ui 0.9.13 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,7 @@
29
29
  <template v-else-if="listType === 'picture-card'">
30
30
  <div slot="trigger" class="upload-limit">
31
31
  <i class="el-icon el-icon-plus" />
32
+ <span class="text">{{ uploadText }}</span>
32
33
  </div>
33
34
  </template>
34
35
  <template v-else>
@@ -49,25 +50,36 @@
49
50
  <template v-if="['list'].includes(listType)">
50
51
  <a
51
52
  class="el-upload-list__item-name"
53
+ :class="{ 'is-error': file.status === 'fail' }"
52
54
  @click="handlePictureCardPreview(file)"
53
- v-if="$utils.isImg(file)"
55
+ v-if="$utils.isImg(file) && file.status !== 'fail'"
54
56
  ><i class="el-icon-document"></i>{{ file.name }}
55
57
  </a>
56
58
  <a
57
59
  class="el-upload-list__item-name"
60
+ :class="{ 'is-error': file.status === 'fail' }"
58
61
  @click="handleAVPreview(file)"
59
- v-else-if="$utils.isAV(file)"
62
+ v-else-if="$utils.isAV(file) && file.status !== 'fail'"
60
63
  ><i class="el-icon-document"></i>{{ file.name }}
61
64
  </a>
62
65
  <a
63
66
  class="el-upload-list__item-name"
67
+ :class="{ 'is-error': file.status === 'fail' }"
64
68
  @click="handleDownload(file)"
65
- v-else
69
+ v-else-if="file.status !== 'uploading' && file.status !== 'fail'"
66
70
  ><i class="el-icon-document"></i>{{ file.name }}
67
71
  </a>
68
72
  <a class="el-upload-list__item-name" v-if="file.status === 'uploading'"
69
73
  ><i class="el-icon-document"></i>{{ file.name }}
70
74
  </a>
75
+ <a
76
+ class="el-upload-list__item-name is-error"
77
+ v-if="file.status === 'fail'"
78
+ @click="retryUpload(file)"
79
+ >
80
+ <i class="el-icon-document"></i>{{ file.name }}
81
+ <span class="error-tip">({{ file.errorMessage }})</span>
82
+ </a>
71
83
  <el-progress
72
84
  v-if="file.status === 'uploading'"
73
85
  type="line"
@@ -86,6 +98,13 @@
86
98
  }"
87
99
  ></i>
88
100
  </label>
101
+ <i
102
+ v-if="file.status === 'fail'"
103
+ class="el-icon-refresh-right"
104
+ title="重新上传"
105
+ @click="retryUpload(file)"
106
+ style="cursor: pointer; color: #f56c6c; margin-right: 5px;"
107
+ ></i>
89
108
  <i
90
109
  class="el-icon-close"
91
110
  @click="handleRemove(file, fileList)"
@@ -130,7 +149,23 @@
130
149
  :stroke-width="6"
131
150
  />
132
151
  </div>
133
- <span class="el-upload-list__item-actions">
152
+ <div v-if="file.status === 'fail'" class="upload-error">
153
+ <div class="error-mask">
154
+ <p class="error-text">上传失败</p>
155
+ <div class="error-actions">
156
+ <el-link type="danger" :underline="false" icon="el-icon-refresh" @click.stop="retryUpload(file)"></el-link>
157
+ <el-link
158
+ v-if="!$attrs.disabled && !preview"
159
+ type="info"
160
+ :underline="false"
161
+ icon="el-icon-delete"
162
+ @click.stop="handleRemove(file, fileList)"
163
+ style="margin-left: 10px;"
164
+ ></el-link>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ <span class="el-upload-list__item-actions" v-if="file.status !== 'fail'">
134
169
  <span
135
170
  v-if="$utils.isImg(file)"
136
171
  class="el-upload-list__item-preview"
@@ -173,7 +208,7 @@
173
208
  </template>
174
209
 
175
210
  <script>
176
- import ElImageViewer from "element-ui/packages/image/src/image-viewer";
211
+ import ElImageViewer from "@liuzengwei/element-ui/packages/image/src/image-viewer";
177
212
  import Client from "@/oss";
178
213
  import uploadPop from "./upload-pop.vue";
179
214
  import idCard from "./idCard.vue";
@@ -227,7 +262,7 @@ export default {
227
262
  default: () => {},
228
263
  },
229
264
  hideUpload: {
230
- type: Boolean,
265
+ type: Boolean,
231
266
  default: false,
232
267
  },
233
268
  type: {
@@ -253,7 +288,7 @@ export default {
253
288
  isShowAV: false,
254
289
  avUrl: "",
255
290
  realFileList: [],
256
- reUploadFile: {},
291
+ failedFiles: {}, // 记录失败的文件 { uid: { file, error } }
257
292
  };
258
293
  },
259
294
  computed: {
@@ -265,9 +300,14 @@ export default {
265
300
  fileSize() {
266
301
  return this.$format.bytesToSize(this.file.size);
267
302
  },
268
- isMultiple() {
269
- return this.$attrs.multiple;
303
+ // 获取成功上传的文件列表(过滤掉失败的文件)
304
+ successFileList() {
305
+ return this.fileList.filter(file => file.status !== 'fail');
270
306
  },
307
+ // 上传按钮文字
308
+ uploadText(){
309
+ return this.$attrs.drag != undefined || this.drag === true ? '点击或拖拽上传' : '上传文件';
310
+ }
271
311
  },
272
312
  watch: {
273
313
  fileList: {
@@ -279,8 +319,6 @@ export default {
279
319
  },
280
320
  },
281
321
  created() {
282
- console.log(this.$attrs);
283
-
284
322
  this.client = new Client({
285
323
  stsUrl: this.$XN.stsUrl || "",
286
324
  setFileIdUrl: this.$XN.setFileIdUrl || "",
@@ -303,7 +341,6 @@ export default {
303
341
  return Promise.all([
304
342
  this.checkFileExt(file),
305
343
  this.onExceedSize(file.size),
306
- this.getStsToken(file),
307
344
  ])
308
345
  .then(() => {
309
346
  return Promise.resolve();
@@ -349,43 +386,115 @@ export default {
349
386
  });
350
387
  },
351
388
  onChange(file, fileList) {
352
- this.realFileList = fileList;
389
+ // 保持realFileList的引用稳定性
390
+ this.realFileList = [...fileList];
353
391
  },
354
392
  async onHttpUpload(file) {
355
393
  this.handleUpload(file);
356
394
  },
357
- handleUpload(file) {
395
+ async handleUpload(file, isRetry = false) {
358
396
  this.isUploading = true;
359
397
  this.$emit("on-uploaded", false);
398
+
399
+ // file 可能是 Element UI 的包装对象或原始文件对象
400
+ const fileUid = file.uid || file.file?.uid;
401
+
402
+ // 如果是重试,从失败列表中移除并重置状态
403
+ if (isRetry) {
404
+ if (this.failedFiles[fileUid]) {
405
+ delete this.failedFiles[fileUid];
406
+ }
407
+ const index = this.realFileList.findIndex(item => item.uid === fileUid);
408
+ if (index !== -1) {
409
+ this.$set(this.realFileList[index], 'status', 'uploading');
410
+ this.$set(this.realFileList[index], 'percentage', 0);
411
+ delete this.realFileList[index].errorMessage;
412
+ }
413
+ }
414
+
415
+ // 确保已获取 STS Token
416
+ if (!this.oss) {
417
+ try {
418
+ await this.getStsToken();
419
+ } catch (err) {
420
+ this.$notify.error({
421
+ title: '获取上传凭证失败',
422
+ message: err.message || '请稍后重试'
423
+ });
424
+ return;
425
+ }
426
+ }
427
+
360
428
  this.oss
361
429
  .upload(file)
362
430
  .then((res) => {
363
431
  this.successFiles.push(res);
364
432
 
365
- this.realFileList.forEach((item,idx,arr) => {
366
- if (item.uid === res.file.uid) {
367
- const obj = JSON.parse(JSON.stringify(res));
368
- delete obj.file;
369
- this.$set(arr, idx, obj);
370
- }
371
- });
433
+ // 更新文件列表,用上传成功的信息替换原文件
434
+ const index = this.realFileList.findIndex(item => item.uid === res.file.uid);
435
+ if (index !== -1) {
436
+ const obj = { ...res };
437
+ delete obj.file;
438
+ this.$set(this.realFileList, index, obj);
439
+ }
440
+
441
+ // 从失败列表中移除(如果存在)
442
+ if (this.failedFiles[fileUid]) {
443
+ delete this.failedFiles[fileUid];
444
+ }
445
+
372
446
  this.$emit("update:fileList", this.realFileList);
373
- this.$emit("on-file", this.res);
447
+ this.$emit("on-file", res);
374
448
  this.$emit("on-success", this.successFiles);
375
449
  this.$emit("on-uploaded", true);
376
450
  this.isUploading = false;
377
451
  })
378
- .catch(({ fileName }) => {
452
+ .catch((error) => {
453
+ // 上传失败,标记文件状态但保留在列表中
454
+ // 处理不同的错误格式
455
+ const fileName = error?.fileName || file.file?.name || file.name || '未知文件';
456
+ const message = error?.message || error?.err?.message || '网络错误,请检查网络连接后重试';
457
+ // const failedFile = error?.file || file;
458
+
459
+ const index = this.realFileList.findIndex(item => item.uid === fileUid);
460
+ if (index !== -1) {
461
+ // 标记为失败状态
462
+ this.$set(this.realFileList[index], 'status', 'fail');
463
+ this.$set(this.realFileList[index], 'errorMessage', message);
464
+
465
+ // 记录失败文件信息(包含原始file对象),用于重试
466
+ this.$set(this.failedFiles, fileUid, {
467
+ file: this.realFileList[index],
468
+ rawFile: file, // 保存原始file对象用于重试
469
+ error: message
470
+ });
471
+
472
+ // 触发更新事件
473
+ this.$emit("update:fileList", [...this.realFileList]);
474
+ }
475
+
379
476
  this.$notify.error({
380
477
  title: "上传失败",
381
478
  dangerouslyUseHTMLString: true,
382
- message: `<div><p>文件名:</p>${fileName}</div>`,
479
+ message: `<div><p>文件名:${fileName}</p><p>错误:${message}</p><p>点击重试按钮可重新上传</p></div>`,
480
+ duration: 5000
383
481
  });
482
+ this.$emit("on-uploaded", true);
483
+ this.isUploading = false;
384
484
  });
385
485
  },
386
486
  // onError() {
387
487
  // this.$message.error("上传失败,请重试");
388
488
  // },
489
+ retryUpload(file) {
490
+ // 重新上传失败的文件
491
+ if (this.failedFiles[file.uid]) {
492
+ const failedFileInfo = this.failedFiles[file.uid];
493
+ // 使用保存的原始 file 对象进行重试
494
+ const uploadFile = failedFileInfo.rawFile || failedFileInfo.file;
495
+ this.handleUpload(uploadFile, true);
496
+ }
497
+ },
389
498
  onSubmitUpload() {
390
499
  this.$refs.upload.submit();
391
500
  },
@@ -412,19 +521,16 @@ export default {
412
521
  return this.$utils.download({ url, name });
413
522
  },
414
523
  handleRemove(file, fileList) {
415
- fileList.forEach((item, idx) => {
416
- if (file.uid === item.uid) {
417
- fileList.splice(idx, 1);
418
- }
419
- });
524
+ // 使用filter避免遍历中删除的索引问题
525
+ const newFileList = fileList.filter(item => item.uid !== file.uid);
526
+
420
527
  if (this.viewList.length) {
421
- this.viewList.forEach((item, idx) => {
422
- if (item.url === file.url) {
423
- this.viewList.splice(idx, 1);
424
- }
425
- });
528
+ this.viewList = this.viewList.filter(item => item.url !== file.url);
426
529
  }
427
- this.$emit("update:fileList", fileList);
530
+
531
+ // 更新realFileList
532
+ this.realFileList = newFileList;
533
+ this.$emit("update:fileList", newFileList);
428
534
  },
429
535
  closeViewer() {
430
536
  this.isShowImageView = false;
@@ -432,7 +538,31 @@ export default {
432
538
  abortUpload() {
433
539
  return this.oss.oss.cancel();
434
540
  },
541
+ // 获取成功上传的文件列表(不包含失败的文件)
542
+ getSuccessFiles() {
543
+ return this.fileList.filter(file => file.status !== 'fail');
544
+ },
545
+ // 获取失败的文件列表
546
+ getFailedFiles() {
547
+ return this.fileList.filter(file => file.status === 'fail');
548
+ },
549
+ // 检查是否有失败的文件
550
+ hasFailedFiles() {
551
+ return this.fileList.some(file => file.status === 'fail');
552
+ },
435
553
  onPreviewFile(file) {
554
+ // 如果是失败的文件,点击重试
555
+ if (file.status === 'fail' && this.failedFiles[file.uid]) {
556
+ this.$confirm('上传失败,是否重新上传?', '提示', {
557
+ confirmButtonText: '重新上传',
558
+ cancelButtonText: '取消',
559
+ type: 'warning'
560
+ }).then(() => {
561
+ this.retryUpload(file);
562
+ }).catch(() => {});
563
+ return;
564
+ }
565
+
436
566
  if (file.isAV === 1) {
437
567
  this.handleAVPreview(file);
438
568
  } else if (file.imgFlag === 1) {