eoss-ui 0.8.22 → 0.8.24

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 (148) hide show
  1. package/lib/button-group.js +228 -231
  2. package/lib/button.js +166 -153
  3. package/lib/calendar.js +2 -2
  4. package/lib/calogin.js +160 -146
  5. package/lib/card.js +2 -2
  6. package/lib/cascader.js +2 -2
  7. package/lib/checkbox-group.js +162 -148
  8. package/lib/clients.js +2 -2
  9. package/lib/config/api.js +5 -0
  10. package/lib/data-table-form.js +162 -148
  11. package/lib/data-table.js +165 -151
  12. package/lib/date-picker.js +159 -146
  13. package/lib/dialog.js +166 -153
  14. package/lib/enable-drag.js +2 -2
  15. package/lib/enterprise.js +2 -2
  16. package/lib/eoss-ui.common.js +732 -256
  17. package/lib/error-page.js +2 -2
  18. package/lib/flow-group.js +159 -146
  19. package/lib/flow-list.js +270 -257
  20. package/lib/flow.js +286 -272
  21. package/lib/form.js +161 -148
  22. package/lib/handle-user.js +160 -147
  23. package/lib/handler.js +160 -147
  24. package/lib/icon.js +160 -147
  25. package/lib/icons.js +2 -2
  26. package/lib/index.js +1 -1
  27. package/lib/input-number.js +159 -146
  28. package/lib/input.js +159 -146
  29. package/lib/label.js +2 -2
  30. package/lib/layout.js +2 -2
  31. package/lib/login.js +174 -160
  32. package/lib/main.js +263 -244
  33. package/lib/menu.js +2 -2
  34. package/lib/nav.js +159 -146
  35. package/lib/notify.js +157 -148
  36. package/lib/page.js +159 -146
  37. package/lib/pagination.js +159 -146
  38. package/lib/player.js +164 -151
  39. package/lib/qr-code.js +161 -148
  40. package/lib/radio-group.js +161 -148
  41. package/lib/retrial-auth.js +163 -150
  42. package/lib/select-ganged.js +161 -148
  43. package/lib/select.js +161 -148
  44. package/lib/selector-panel.js +178 -164
  45. package/lib/selector.js +161 -148
  46. package/lib/sizer.js +168 -155
  47. package/lib/steps.js +159 -146
  48. package/lib/switch.js +159 -146
  49. package/lib/table-form.js +159 -146
  50. package/lib/tabs-panel.js +2 -2
  51. package/lib/tabs.js +159 -146
  52. package/lib/theme-chalk/index.css +1 -1
  53. package/lib/theme-chalk/login.css +1 -1
  54. package/lib/theme-chalk/tree.css +1 -1
  55. package/lib/tips.js +160 -147
  56. package/lib/toolbar.js +2 -2
  57. package/lib/tree-group.js +159 -146
  58. package/lib/tree.js +160 -147
  59. package/lib/upload.js +698 -223
  60. package/lib/utils/util.js +5 -1
  61. package/lib/wujie.js +159 -146
  62. package/lib/wxlogin.js +159 -146
  63. package/package.json +162 -161
  64. package/packages/.DS_Store +0 -0
  65. package/packages/button-group/src/main.vue +346 -346
  66. package/packages/calogin/.DS_Store +0 -0
  67. package/packages/calogin/src/main.vue +412 -412
  68. package/packages/clients/src/main.vue +151 -151
  69. package/packages/date-picker/.DS_Store +0 -0
  70. package/packages/date-picker/src/.DS_Store +0 -0
  71. package/packages/dialog/.DS_Store +0 -0
  72. package/packages/flow/.DS_Store +0 -0
  73. package/packages/flow/src/.DS_Store +0 -0
  74. package/packages/flow/src/component/CommonOpinions.vue +376 -376
  75. package/packages/flow/src/component/FileList.vue +97 -97
  76. package/packages/flow/src/component/SendMsg.vue +242 -242
  77. package/packages/flow/src/component/SortFlow.vue +110 -110
  78. package/packages/flow/src/form.vue +123 -123
  79. package/packages/flow/src/table.vue +58 -58
  80. package/packages/flow-list/.DS_Store +0 -0
  81. package/packages/flow-list/src/main.vue +2337 -2337
  82. package/packages/form/.DS_Store +0 -0
  83. package/packages/form/src/table.vue +1512 -1512
  84. package/packages/icon/.DS_Store +0 -0
  85. package/packages/icon/src/main.vue +104 -104
  86. package/packages/login/.DS_Store +0 -0
  87. package/packages/login/src/resetPassword.vue +557 -557
  88. package/packages/main/.DS_Store +0 -0
  89. package/packages/main/src/.DS_Store +0 -0
  90. package/packages/main/src/public/online.vue +89 -89
  91. package/packages/main/src/public/search.vue +464 -464
  92. package/packages/main/src/public/settings.vue +273 -273
  93. package/packages/main/src/simplicity/apps.vue +388 -388
  94. package/packages/main/src/simplicity/avatar.vue +82 -82
  95. package/packages/main/src/simplicity/handler.vue +158 -158
  96. package/packages/main/src/simplicity/index.vue +11 -5
  97. package/packages/main/src/simplicity/menu-list.vue +135 -135
  98. package/packages/main/src/simplicity/message.vue +293 -293
  99. package/packages/main/src/simplicity/notice.vue +222 -222
  100. package/packages/main/src/simplicity/sub-menu.vue +276 -276
  101. package/packages/main/src/simplicity/user.vue +259 -259
  102. package/packages/main/src/simplicityTop/apps.vue +388 -388
  103. package/packages/main/src/simplicityTop/avatar.vue +82 -82
  104. package/packages/main/src/simplicityTop/handler.vue +215 -215
  105. package/packages/main/src/simplicityTop/lists.vue +84 -84
  106. package/packages/main/src/simplicityTop/menu-list.vue +135 -135
  107. package/packages/main/src/simplicityTop/message.vue +293 -293
  108. package/packages/main/src/simplicityTop/notice.vue +222 -222
  109. package/packages/main/src/simplicityTop/router-page.vue +45 -45
  110. package/packages/main/src/simplicityTop/sub-menu.vue +274 -274
  111. package/packages/main/src/simplicityTop/user.vue +259 -259
  112. package/packages/menu/.DS_Store +0 -0
  113. package/packages/nav/src/main.vue +351 -351
  114. package/packages/player/src/main.vue +1 -1
  115. package/packages/select/.DS_Store +0 -0
  116. package/packages/selector/.DS_Store +0 -0
  117. package/packages/selector/src/main.vue +761 -761
  118. package/packages/selector-panel/.DS_Store +0 -0
  119. package/packages/selector-panel/src/main.vue +1036 -1036
  120. package/packages/selector-panel/src/selection.vue +174 -174
  121. package/packages/switch/src/main.vue +170 -170
  122. package/packages/theme-chalk/lib/index.css +1 -1
  123. package/packages/theme-chalk/lib/login.css +1 -1
  124. package/packages/theme-chalk/lib/tree.css +1 -1
  125. package/packages/theme-chalk/src/.DS_Store +0 -0
  126. package/packages/theme-chalk/src/data-table.scss +297 -297
  127. package/packages/theme-chalk/src/flow-list.scss +55 -55
  128. package/packages/theme-chalk/src/form.scss +501 -501
  129. package/packages/theme-chalk/src/handler.scss +148 -148
  130. package/packages/theme-chalk/src/icon.scss +3452 -3452
  131. package/packages/theme-chalk/src/login.scss +1006 -1006
  132. package/packages/theme-chalk/src/main.scss +664 -664
  133. package/packages/theme-chalk/src/menu.scss +224 -224
  134. package/packages/theme-chalk/src/selector.scss +114 -114
  135. package/packages/theme-chalk/src/simplicity-top.scss +1845 -1845
  136. package/packages/theme-chalk/src/simplicity.scss +1403 -1403
  137. package/packages/theme-chalk/src/tree.scss +167 -165
  138. package/packages/theme-chalk/src/upload.scss +172 -172
  139. package/packages/tips/src/main.vue +141 -141
  140. package/packages/upload/.DS_Store +0 -0
  141. package/packages/upload/src/main.vue +482 -14
  142. package/packages/wujie/src/main.vue +146 -146
  143. package/src/.DS_Store +0 -0
  144. package/src/config/api.js +5 -0
  145. package/src/index.js +163 -163
  146. package/src/utils/.DS_Store +0 -0
  147. package/src/utils/rules.js +18 -18
  148. package/src/utils/util.js +6 -1
@@ -10,6 +10,8 @@
10
10
  :height="boxHeight"
11
11
  :multiple="portrait ? false : multiple"
12
12
  :action="uploadUrl"
13
+ :http-request="chunkHttpRequest"
14
+ :handles="chunkHandles"
13
15
  :show-file-list="showList"
14
16
  :file-list="lists"
15
17
  :result-file="resultFile"
@@ -98,10 +100,8 @@
98
100
  :type="selectType"
99
101
  :size="btnSize"
100
102
  :disabled="isDisabled"
101
- >{{
102
- text ? text : autoUpload ? '点击上传' : '选择文件'
103
- }}</el-button
104
- >
103
+ >{{ text ? text : autoUpload ? '点击上传' : '选择文件' }}
104
+ </el-button>
105
105
  <el-button
106
106
  class="es-upload-button"
107
107
  v-if="!autoUpload"
@@ -178,23 +178,29 @@
178
178
  </template>
179
179
  <script>
180
180
  import {
181
- getAdjunctProperties,
182
- uploads,
183
- uploadOnlyOne,
181
+ chunkCheckFile,
182
+ chunkMergeChunk,
183
+ chunkUploadChunk,
184
+ delAdjunct,
185
+ downloadByAdjunctId,
184
186
  getAdjunctFileInfos,
185
- uploadDownloads,
186
- previewAdjunctOffice,
187
+ getAdjunctProperties,
188
+ getDochubBucket,
187
189
  previewAdjunct,
188
190
  previewAdjunct2,
189
- uploadSort,
190
- delAdjunct,
191
- downloadByAdjunctId
191
+ previewAdjunctOffice,
192
+ uploadDownloads,
193
+ uploadOnlyOne,
194
+ uploads,
195
+ uploadSort
192
196
  } from 'eoss-ui/src/config/api.js';
193
197
  import { debounce } from 'throttle-debounce';
194
198
  import util from 'eoss-ui/src/utils/util.js';
195
199
  import picture from './picture.js';
196
200
  import store from 'eoss-ui/src/utils/store';
197
201
  import { Base64 } from 'js-base64';
202
+ import SparkMD5 from 'spark-md5';
203
+
198
204
  export default {
199
205
  name: 'EsUpload',
200
206
  components: {
@@ -446,7 +452,9 @@ export default {
446
452
  boxHeight: this.listHeight,
447
453
  previewAdjunct: previewAdjunct,
448
454
  kkfileview: null,
449
- dochubConfig: {}
455
+ dochubConfig: {},
456
+ // 文档中台桶配置下发的分片上传配置(baseBucketConfig),null 表示未下发 → 回退组件 props
457
+ bucketChunkConfig: null
450
458
  };
451
459
  },
452
460
  computed: {
@@ -635,6 +643,80 @@ export default {
635
643
  }
636
644
  }
637
645
  return this.photo;
646
+ },
647
+ // 是否走分片上传:完全由后台桶配置(/v2/chunk/config)的开关决定,且具备桶编码(code)与业务id(ownId)
648
+ isChunk() {
649
+ const cfg = this.bucketChunkConfig;
650
+ return !!(cfg && cfg.enableChunkUpload) && !!this.code && !!this.ownId;
651
+ },
652
+ // 生效的分片大小(字节):桶配置 > 默认 5MB
653
+ _chunkSize() {
654
+ const cfg = this.bucketChunkConfig;
655
+ return (
656
+ (cfg && Number(cfg.chunkSize) > 0 && Number(cfg.chunkSize)) ||
657
+ 5 * 1024 * 1024
658
+ );
659
+ },
660
+ // 生效的分片并发数:桶配置 > 默认 3
661
+ _chunkConcurrent() {
662
+ const cfg = this.bucketChunkConfig;
663
+ return (
664
+ (cfg &&
665
+ Number(cfg.chunkConcurrent) > 0 &&
666
+ Number(cfg.chunkConcurrent)) ||
667
+ 3
668
+ );
669
+ },
670
+ // 分片三接口地址:优先 dochubConfig 下发,否则回退 api.js 常量(与本组件 dochubConfig.x || 常量 的写法一致)
671
+ chunkUrls() {
672
+ const cfg = this.dochubConfig || {};
673
+ return {
674
+ check: cfg.chunkCheckUrl || chunkCheckFile,
675
+ upload: cfg.chunkUploadUrl || chunkUploadChunk,
676
+ merge: cfg.chunkMergeUrl || chunkMergeChunk
677
+ };
678
+ },
679
+ // 传给 el-upload 的自定义上传实现;非分片模式返回 undefined 以走默认上传
680
+ chunkHttpRequest() {
681
+ return this.isChunk ? this.chunkUpload : undefined;
682
+ },
683
+ // 操作列按钮:非分片模式回退用户经 $attrs 传入的 handles(或 undefined → el-upload 默认),
684
+ // 分片模式注入"暂停/继续"并保留预览/下载/删除(emit 复用既有事件链)。仅分片模式改变渲染,不影响存量业务。
685
+ chunkHandles() {
686
+ if (!this.isChunk) return this.$attrs.handles;
687
+ const done = (f) => f.status === 'success' || f.status == 0;
688
+ return [
689
+ {
690
+ icon: 'el-icon-video-pause',
691
+ title: '暂停',
692
+ rules: (f) => this.isChunkUploading(f) && !f.chunkPaused,
693
+ event: (f) => this.pauseUpload(f)
694
+ },
695
+ {
696
+ icon: 'el-icon-video-play',
697
+ title: '继续',
698
+ rules: (f) => this.isChunkUploading(f) && !!f.chunkPaused,
699
+ event: (f) => this.resumeUpload(f)
700
+ },
701
+ {
702
+ icon: 'el-icon-document-copy',
703
+ title: '预览',
704
+ rules: (f) => !!this.preview && done(f),
705
+ emit: 'preview'
706
+ },
707
+ {
708
+ icon: 'el-icon-download',
709
+ title: '下载',
710
+ rules: (f) => this.isDownload && done(f),
711
+ emit: 'download'
712
+ },
713
+ {
714
+ icon: 'el-icon-delete',
715
+ title: '删除',
716
+ rules: (f) => this.isRemove,
717
+ emit: 'remove'
718
+ }
719
+ ];
638
720
  }
639
721
  },
640
722
  watch: {
@@ -701,6 +783,8 @@ export default {
701
783
  });
702
784
  },
703
785
  created() {
786
+ // 分片上传的运行态(按 file.uid 索引,非响应式,仅用于流程控制)
787
+ this._chunkState = {};
704
788
  const dochubConfig = sessionStorage.getItem('dochubConfig');
705
789
  if (dochubConfig) {
706
790
  this.dochubConfig = JSON.parse(dochubConfig);
@@ -737,6 +821,11 @@ export default {
737
821
  this.fileTotalSize = config.totalSize;
738
822
  if (config.dochubConfig) {
739
823
  this.dochubConfig = config.dochubConfig;
824
+ if (config.bucketChunkConfig !== undefined) {
825
+ this.bucketChunkConfig = config.bucketChunkConfig;
826
+ } else {
827
+ this.getBucketChunkConfig();
828
+ }
740
829
  }
741
830
 
742
831
  let url =
@@ -772,6 +861,8 @@ export default {
772
861
  'dochubConfig',
773
862
  JSON.stringify(res.results.dochubConfig)
774
863
  );
864
+ // dochubConfig 有数据 → 走文档中台,再拉取该桶的桶配置(分片上传开关/参数)
865
+ this.getBucketChunkConfig();
775
866
  }
776
867
  let url = this.portrait
777
868
  ? this.dochubConfig.reuploadDocumentUrl || uploadOnlyOne
@@ -803,6 +894,42 @@ export default {
803
894
  }
804
895
  }
805
896
  },
897
+ // 根据桶编码调中台获取桶配置接口(/v2/getBucket),取 baseBucketConfig 中的分片上传开关与参数。
898
+ getBucketChunkConfig() {
899
+ if (!this.code) return;
900
+ let url = getDochubBucket;
901
+ util
902
+ .ajax({
903
+ method: this.method,
904
+ url: url,
905
+ data: { bucketCode: this.code },
906
+ params: { bucketCode: this.code }
907
+ })
908
+ .then((res) => {
909
+ if (res.rCode === 0 && res.results && res.results.baseBucketConfig) {
910
+ const cfg = res.results.baseBucketConfig;
911
+ this.bucketChunkConfig = {
912
+ enableChunkUpload: cfg.enableChunkUpload,
913
+ chunkSize: cfg.chunkSize,
914
+ chunkConcurrent: cfg.chunkConcurrent
915
+ };
916
+ } else {
917
+ this.bucketChunkConfig = null;
918
+ }
919
+ // 回写本地缓存,避免同 code 的其他实例重复请求
920
+ let cached = store.get(this.code);
921
+ if (cached) {
922
+ store.set(this.code, {
923
+ ...cached,
924
+ bucketChunkConfig: this.bucketChunkConfig
925
+ });
926
+ }
927
+ })
928
+ .catch(() => {
929
+ // 桶配置获取失败不阻断上传,维持 props 行为
930
+ this.bucketChunkConfig = null;
931
+ });
932
+ },
806
933
  getFileLists() {
807
934
  if (
808
935
  !this.show ||
@@ -1226,7 +1353,12 @@ export default {
1226
1353
  if (this.onSort) {
1227
1354
  this.onSort(files);
1228
1355
  } else {
1229
- if (!this.ownId || !this.code || (this.fileList && this.fileList.length > 0)) return;
1356
+ if (
1357
+ !this.ownId ||
1358
+ !this.code ||
1359
+ (this.fileList && this.fileList.length > 0)
1360
+ )
1361
+ return;
1230
1362
  let ids = files.map((item) => {
1231
1363
  if (item.status === 'success' || item.status == 0) {
1232
1364
  return item.adjunctId || item.response.adjunctId;
@@ -1318,6 +1450,7 @@ export default {
1318
1450
  this.onChange && this.onChange(file, fileList);
1319
1451
  },
1320
1452
  handleRemove(file, fileList) {
1453
+ this.cleanupChunk(file);
1321
1454
  this.$emit('input', fileList.length ? fileList : '');
1322
1455
  this.$emit('remove', file, fileList);
1323
1456
  this.$emit('change', fileList);
@@ -1339,6 +1472,341 @@ export default {
1339
1472
  }
1340
1473
  this.$emit('error', err, file, fileList);
1341
1474
  this.onError && this.onError(err, file, fileList);
1475
+ },
1476
+ // el-upload 的自定义上传实现:建立可暂停的运行态并返回受控 Promise。
1477
+ // 暂停时不 resolve/reject(promise 维持 pending,文件保持"上传中"而非失败),继续时再次进入流程。
1478
+ chunkUpload(options) {
1479
+ const file = options.file;
1480
+ const uid = file.uid;
1481
+ const state = {
1482
+ paused: false,
1483
+ controller: null,
1484
+ fileMd5: '',
1485
+ totalChunks: Math.max(1, Math.ceil(file.size / this._chunkSize)),
1486
+ options: options
1487
+ };
1488
+ this._chunkState[uid] = state;
1489
+ return new Promise((resolve, reject) => {
1490
+ state.resolve = resolve;
1491
+ state.reject = reject;
1492
+ this.runChunkFlow(uid);
1493
+ });
1494
+ },
1495
+ // 分片主流程(可被"继续"重新进入):MD5(缓存) → 秒传/断点续传检测 → 并发上传缺失分片 → 合并入库。
1496
+ runChunkFlow(uid) {
1497
+ const state = this._chunkState[uid];
1498
+ if (!state) return;
1499
+ const options = state.options;
1500
+ const file = options.file;
1501
+ const onProgress = options.onProgress || function () {};
1502
+ const baseParams = () => ({
1503
+ ...this.param,
1504
+ fileMd5: state.fileMd5,
1505
+ fileName: file.name,
1506
+ fileSize: file.size,
1507
+ totalChunks: state.totalChunks,
1508
+ bucketCode: this.code,
1509
+ businessId: this.ownId
1510
+ });
1511
+ const isAbort = (e) =>
1512
+ !!e &&
1513
+ (e.__aborted === true ||
1514
+ e.name === 'CanceledError' ||
1515
+ e.name === 'AbortError' ||
1516
+ (typeof e.message === 'string' && /cancel|abort/i.test(e.message)));
1517
+
1518
+ Promise.resolve()
1519
+ .then(() => state.fileMd5 || this.computeFileMd5(file, onProgress))
1520
+ .then((md5) => {
1521
+ state.fileMd5 = md5;
1522
+ if (state.paused) return null;
1523
+ return util.ajax({
1524
+ method: 'post',
1525
+ url: this.chunkUrls.check,
1526
+ data: baseParams(),
1527
+ params: baseParams(),
1528
+ headers: options.headers
1529
+ });
1530
+ })
1531
+ .then((checkRes) => {
1532
+ if (!checkRes) return; // 暂停于 MD5/检测阶段,维持 pending
1533
+ if (checkRes.rCode !== 0) return Promise.reject(checkRes);
1534
+ const info = checkRes.results || {};
1535
+ // 秒传命中:该业务桶下已存在同 MD5 文件,直接视为成功
1536
+ if (info.instant) {
1537
+ onProgress({ percent: 100 });
1538
+ this.normalizeChunkDoc(info, file.name);
1539
+ this.finishChunk(uid, {
1540
+ rCode: 0,
1541
+ msg: checkRes.msg || '秒传成功',
1542
+ results: info
1543
+ });
1544
+ // 秒传文件已存在于服务端,刷新列表以展示其完整信息(含可下载/预览的 id)
1545
+ this.requestFiles && this.getFiles();
1546
+ return;
1547
+ }
1548
+ // 断点续传:跳过服务端已存在的分片
1549
+ const uploaded = new Set(info.uploadedChunks || []);
1550
+ const pending = [];
1551
+ for (let i = 0; i < state.totalChunks; i++) {
1552
+ if (!uploaded.has(i)) pending.push(i);
1553
+ }
1554
+ let finished = state.totalChunks - pending.length;
1555
+ // 上传阶段占 5~95%(MD5 校验已占 0~5%),保证进度单调不回退
1556
+ onProgress({
1557
+ percent: Math.min(
1558
+ 95,
1559
+ 5 + Math.round((finished / state.totalChunks) * 90)
1560
+ )
1561
+ });
1562
+ state.controller =
1563
+ typeof AbortController !== 'undefined'
1564
+ ? new AbortController()
1565
+ : null;
1566
+ const signal = state.controller ? state.controller.signal : undefined;
1567
+ return this.runChunkQueue(pending, this._chunkConcurrent, (index) => {
1568
+ if (state.paused) return Promise.reject({ __aborted: true });
1569
+ return this.uploadOneChunk(
1570
+ file,
1571
+ index,
1572
+ state.totalChunks,
1573
+ state.fileMd5,
1574
+ options.headers,
1575
+ signal
1576
+ ).then(() => {
1577
+ finished++;
1578
+ onProgress({
1579
+ percent: Math.min(
1580
+ 95,
1581
+ 5 + Math.round((finished / state.totalChunks) * 90)
1582
+ )
1583
+ });
1584
+ });
1585
+ }).then(() => {
1586
+ if (state.paused) return; // 暂停于上传阶段,维持 pending
1587
+ return util
1588
+ .ajax({
1589
+ method: 'post',
1590
+ url: this.chunkUrls.merge,
1591
+ data: baseParams(),
1592
+ params: baseParams(),
1593
+ headers: options.headers
1594
+ })
1595
+ .then((mergeRes) => {
1596
+ if (mergeRes.rCode !== 0) return Promise.reject(mergeRes);
1597
+ onProgress({ percent: 100 });
1598
+ this.normalizeChunkDoc(mergeRes.results, file.name);
1599
+ this.finishChunk(uid, mergeRes);
1600
+ // 刷新列表:让刚入库的文件以与其他行一致的结构展示(时间/大小/上传人等)
1601
+ this.requestFiles && this.getFiles();
1602
+ });
1603
+ });
1604
+ })
1605
+ .catch((e) => {
1606
+ // 暂停导致的中止:保持 pending 等待"继续";其余才算失败
1607
+ if (state.paused || isAbort(e)) return;
1608
+ this.failChunk(uid, e);
1609
+ });
1610
+ },
1611
+ // 暂停:置暂停标记并中止在传分片;promise 维持 pending,文件保持"上传中"
1612
+ pauseUpload(file) {
1613
+ const state = file && this._chunkState[file.uid];
1614
+ if (!state) return;
1615
+ state.paused = true;
1616
+ if (state.controller) {
1617
+ try {
1618
+ state.controller.abort();
1619
+ } catch (e) {
1620
+ /* ignore */
1621
+ }
1622
+ }
1623
+ this.$set(file, 'chunkPaused', true);
1624
+ this.$emit('chunk-pause', file);
1625
+ },
1626
+ // 继续:清暂停标记,重新进入流程(checkFile 会返回已传分片,自动续传)
1627
+ resumeUpload(file) {
1628
+ const state = file && this._chunkState[file.uid];
1629
+ if (!state || !state.paused) return; // 仅暂停态可续传,防止重复触发并发流程
1630
+ state.paused = false;
1631
+ this.$set(file, 'chunkPaused', false);
1632
+ this.$emit('chunk-resume', file);
1633
+ this.runChunkFlow(file.uid);
1634
+ },
1635
+ // 该文件是否处于分片上传运行态(用于操作列按钮显隐)
1636
+ isChunkUploading(file) {
1637
+ return !!(file && this._chunkState && this._chunkState[file.uid]);
1638
+ },
1639
+ // 归一化合并响应:dochub Document 用 docTagId 标识、且无 suffix 字段,
1640
+ // 而组件下载读 adjunctId、预览读 suffix/originalName,这里统一映射补齐,避免预览取 suffix 报错。
1641
+ normalizeChunkDoc(results, fileName) {
1642
+ if (!results || typeof results !== 'object') return results;
1643
+ const id =
1644
+ results.adjunctId ||
1645
+ results.docTagId ||
1646
+ results.documentId ||
1647
+ results.id;
1648
+ if (id) {
1649
+ if (!results.adjunctId) results.adjunctId = id;
1650
+ if (!results.documentId) results.documentId = id;
1651
+ }
1652
+ // 文件名:优先用上传时的原始名,其次后端 documentFile.docName
1653
+ const name =
1654
+ fileName ||
1655
+ (results.documentFile && results.documentFile.docName) ||
1656
+ results.originalName ||
1657
+ results.name ||
1658
+ '';
1659
+ if (!results.originalName && name) results.originalName = name;
1660
+ // 后缀:预览按后缀判断类型,缺省从文件名推导;始终保证为字符串
1661
+ if (results.suffix === undefined || results.suffix === null) {
1662
+ results.suffix =
1663
+ name && name.lastIndexOf('.') > -1
1664
+ ? name.slice(name.lastIndexOf('.') + 1)
1665
+ : '';
1666
+ }
1667
+ // 展示字段:dochub 的元数据在 documentFile 下且字段名不同,映射成 showInfo 默认读取的扁平字段
1668
+ const df = results.documentFile;
1669
+ if (df && typeof df === 'object') {
1670
+ if (!results.uploadTime) {
1671
+ results.uploadTime = df.createTime || df.lastModifyTime || '';
1672
+ }
1673
+ if (!results.userName) {
1674
+ results.userName = df.createUserName || df.lastModifyUserName || '';
1675
+ }
1676
+ if (!results.fileSize && (df.docSize || df.docSize === 0)) {
1677
+ results.fileSize = this.formatFileSize(df.docSize);
1678
+ }
1679
+ }
1680
+ return results;
1681
+ },
1682
+ // 字节数格式化为可读大小(与列表展示风格一致:1 位小数、无空格)
1683
+ formatFileSize(bytes) {
1684
+ const n = Number(bytes);
1685
+ if (!n || n <= 0) return '0B';
1686
+ if (n >= 1073741824) return (n / 1073741824).toFixed(1) + 'GB';
1687
+ if (n >= 1048576) return (n / 1048576).toFixed(1) + 'MB';
1688
+ if (n >= 1024) return (n / 1024).toFixed(1) + 'KB';
1689
+ return n + 'B';
1690
+ },
1691
+ // 完成:兑现 promise 并清理运行态
1692
+ finishChunk(uid, response) {
1693
+ const state = this._chunkState[uid];
1694
+ if (!state) return;
1695
+ state.resolve && state.resolve(response);
1696
+ delete this._chunkState[uid];
1697
+ },
1698
+ // 失败:拒绝 promise 并清理运行态
1699
+ failChunk(uid, err) {
1700
+ const state = this._chunkState[uid];
1701
+ if (!state) return;
1702
+ state.reject && state.reject(err);
1703
+ delete this._chunkState[uid];
1704
+ },
1705
+ // 移除文件时清理分片运行态(中止在传分片,孤立 pending promise 不再触发回调)
1706
+ cleanupChunk(file) {
1707
+ const uid = file && file.uid;
1708
+ const state = uid && this._chunkState && this._chunkState[uid];
1709
+ if (!state) return;
1710
+ state.paused = true;
1711
+ state.resolve = null;
1712
+ state.reject = null;
1713
+ if (state.controller) {
1714
+ try {
1715
+ state.controller.abort();
1716
+ } catch (e) {
1717
+ /* ignore */
1718
+ }
1719
+ }
1720
+ delete this._chunkState[uid];
1721
+ },
1722
+ // 上传单个分片(multipart/form-data);signal 用于暂停时中止
1723
+ uploadOneChunk(file, index, totalChunks, fileMd5, headers, signal) {
1724
+ const start = index * this._chunkSize;
1725
+ const end = Math.min(start + this._chunkSize, file.size);
1726
+ const formData = new FormData();
1727
+ formData.append('file', file.slice(start, end), file.name);
1728
+ formData.append('fileMd5', fileMd5);
1729
+ formData.append('fileName', file.name);
1730
+ formData.append('fileSize', file.size);
1731
+ formData.append('chunkIndex', index);
1732
+ formData.append('totalChunks', totalChunks);
1733
+ formData.append('bucketCode', this.code);
1734
+ formData.append('businessId', this.ownId);
1735
+ Object.keys(this.param || {}).forEach((key) => {
1736
+ formData.append(key, this.param[key]);
1737
+ });
1738
+ return util
1739
+ .ajax({
1740
+ method: 'post',
1741
+ url: this.chunkUrls.upload,
1742
+ data: formData,
1743
+ format: false,
1744
+ headers: headers,
1745
+ signal: signal
1746
+ })
1747
+ .then((res) => {
1748
+ if (res.rCode !== 0) return Promise.reject(res);
1749
+ return res;
1750
+ });
1751
+ },
1752
+ // 分块读取文件并增量计算 MD5(避免大文件一次性读入内存)
1753
+ computeFileMd5(file, onProgress) {
1754
+ return new Promise((resolve, reject) => {
1755
+ const size = this._chunkSize;
1756
+ const chunks = Math.max(1, Math.ceil(file.size / size));
1757
+ const spark = new SparkMD5.ArrayBuffer();
1758
+ const reader = new FileReader();
1759
+ let current = 0;
1760
+ const loadNext = () => {
1761
+ const start = current * size;
1762
+ reader.readAsArrayBuffer(
1763
+ file.slice(start, Math.min(start + size, file.size))
1764
+ );
1765
+ };
1766
+ reader.onload = (e) => {
1767
+ spark.append(e.target.result);
1768
+ current++;
1769
+ // 校验阶段占进度 0~5%,剩余留给上传与合并
1770
+ onProgress &&
1771
+ onProgress({ percent: Math.round((current / chunks) * 5) });
1772
+ if (current < chunks) {
1773
+ loadNext();
1774
+ } else {
1775
+ // 取标准 32 位 MD5 的中间 16 位,与后端 hutool digestHex16(即 doc_md5)口径一致,
1776
+ // 否则秒传按 md5 比对永远不命中
1777
+ resolve(spark.end().substring(8, 24));
1778
+ }
1779
+ };
1780
+ reader.onerror = () => reject({ msg: '文件读取失败,无法计算MD5' });
1781
+ loadNext();
1782
+ });
1783
+ },
1784
+ // 限制并发的分片任务队列
1785
+ runChunkQueue(indexes, concurrency, worker) {
1786
+ return new Promise((resolve, reject) => {
1787
+ if (!indexes.length) return resolve();
1788
+ let cursor = 0;
1789
+ let active = 0;
1790
+ let failed = false;
1791
+ const schedule = () => {
1792
+ if (failed) return;
1793
+ if (cursor >= indexes.length && active === 0) return resolve();
1794
+ while (active < concurrency && cursor < indexes.length) {
1795
+ const index = indexes[cursor++];
1796
+ active++;
1797
+ worker(index)
1798
+ .then(() => {
1799
+ active--;
1800
+ schedule();
1801
+ })
1802
+ .catch((err) => {
1803
+ failed = true;
1804
+ reject(err);
1805
+ });
1806
+ }
1807
+ };
1808
+ schedule();
1809
+ });
1342
1810
  }
1343
1811
  }
1344
1812
  };