uploadimages.ui.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. const getChunks = (file, chunkSize) => {
  2. const chunks = [];
  3. let start = 0;
  4. let end = Math.min(chunkSize, file.size);
  5. while (start < end)
  6. {
  7. chunks.push(file.slice(start, end));
  8. start = end;
  9. end = Math.min(start + chunkSize, file.size);
  10. }
  11. return chunks;
  12. };
  13. const uploadChunk = (url, chunk, headers) => {
  14. return fetch(url, {
  15. method: 'POST',
  16. body: chunk,
  17. headers,
  18. }).then(response => response.json()).then(json => {
  19. if(json.result == 'fail') return Promise.reject(json);
  20. return new Promise(resolve => setTimeout(() => resolve(json), 100)); // 增加延迟,给后端处理session的时间
  21. });
  22. }
  23. function uploadFileByChunk(url, file, chunkSize = 1024 * 1024, onProgress = null)
  24. {
  25. const chunks = getChunks(file, chunkSize);
  26. let i = 0;
  27. return new Promise((resolve, reject) => {
  28. const uploadNextChunk = () => {
  29. if(i >= chunks.length)
  30. {
  31. if(typeof onProgress === 'function') onProgress(1);
  32. resolve();
  33. return;
  34. }
  35. const headers = {
  36. 'X-CHUNK-INDEX': i,
  37. 'X-TOTAL-CHUNKS': chunks.length,
  38. 'X-FILENAME': encodeURIComponent(file.name),
  39. 'X-FILESIZE': file.size,
  40. };
  41. uploadChunk(url, chunks[i], headers)
  42. .then(() => {
  43. i++;
  44. if(typeof onProgress === 'function') onProgress(i / chunks.length);
  45. uploadNextChunk();
  46. })
  47. .catch(reject);
  48. };
  49. uploadNextChunk();
  50. });
  51. };
  52. /**
  53. * 控制并发上传数量
  54. * @param {File[]} files
  55. * @param {string} uploadUrl
  56. * @param {number} chunkSize
  57. * @param {number} maxConcurrency
  58. */
  59. const uploadFilesWithConcurrency = async (files, uploadUrl, chunkSize, maxConcurrency = 2) => {
  60. const results = [];
  61. const inProgress = new Set();
  62. const uploadFile = async (file, fileIndex) => {
  63. return new Promise((resolve, reject) => {
  64. uploadFileByChunk(uploadUrl, file, chunkSize, function(progress) {
  65. }).then(() => {
  66. resolve({ file, fileIndex, success: true });
  67. }).catch(error => {
  68. reject({ file, fileIndex, error });
  69. });
  70. });
  71. };
  72. const processQueue = async () => {
  73. for (let i = 0; i < files.length; i++) {
  74. if (inProgress.size >= maxConcurrency) {
  75. await Promise.race(inProgress);
  76. }
  77. const file = files[i];
  78. const uploadPromise = uploadFile(file, i).finally(() => {
  79. inProgress.delete(uploadPromise);
  80. });
  81. inProgress.add(uploadPromise);
  82. results.push(uploadPromise);
  83. }
  84. await Promise.all(results);
  85. };
  86. await processQueue();
  87. return results;
  88. };
  89. window.uploadImages = function(selector, options, $uploadBtn)
  90. {
  91. const $fileBox = $(selector);
  92. const fileBox = $fileBox.zui();
  93. const files = fileBox.$.files;
  94. if(!files.length)
  95. {
  96. if(!$uploadBtn.hasClass('disabled')) zui.Modal.alert(options.errorUploadEmpty);
  97. return;
  98. }
  99. const progressMap = new Map();
  100. let uploadedCount = 0;
  101. $uploadBtn.attr('disabled', 'disabled');
  102. $uploadBtn.find('.as-progress').text(' 0%');
  103. const render = () =>
  104. {
  105. fileBox.render({disabled: true, itemProps: (file) =>
  106. {
  107. const progress = progressMap.get(file.file);
  108. if(progress === undefined) return {};
  109. if(progress === 1) return {icon: 'check text-success absolute right-0 bottom-2'};
  110. return {title: Math.round(progress * 100) + '% | ' + file.name};
  111. }});
  112. };
  113. render();
  114. /* 串行上传文件 */
  115. const uploadFilesSequentially = async (fileIndex = 0) => {
  116. if (fileIndex >= files.length) {
  117. const modalID = $uploadBtn.closest('.modal').attr('id');
  118. zui.Modal.hide('#' + modalID);
  119. const $form = $('body').find('form.form-batch[data-zui-batchform]');
  120. const $modal = $form.closest('.modal')
  121. if($modal.length > 0)
  122. {
  123. $.ajax(
  124. {
  125. url: options.locateUrl,
  126. headers:{'X-Zui-Modal': true},
  127. dataType: 'json',
  128. success: function(data)
  129. {
  130. $modal.find('[data-zui-ajaxform]').zui('ajaxform').destroy();
  131. $modal.find('[data-zui-batchform]').zui('batchForm').destroy();
  132. setTimeout(function()
  133. {
  134. loadModal(data.load, $modal.attr('id'));
  135. }, 500);
  136. }
  137. });
  138. return;
  139. }
  140. loadPage(options.locateUrl);
  141. return;
  142. }
  143. const file = files[fileIndex];
  144. try {
  145. await uploadFileByChunk(options.uploadUrl, file, options.chunkSize, function(progress)
  146. {
  147. progressMap.set(file, progress);
  148. render();
  149. $uploadBtn.find('.as-progress').text(' ' + Math.round((uploadedCount + progress) / files.length * 100) + '%' );
  150. });
  151. uploadedCount++;
  152. uploadFilesSequentially(fileIndex + 1);
  153. } catch (error) {
  154. $uploadBtn.removeAttr('disabled');
  155. $uploadBtn.find('.as-progress').text('');
  156. fileBox.render({disabled: false});
  157. if(typeof(error.message) != 'undefined') zui.Modal.alert(error.message);
  158. }
  159. };
  160. uploadFilesSequentially();
  161. };