1. <template>
    2. <div>
    3. <input type="file" @change="uploadChange" multiple="multiple" />
    4. <div style="text-align: left">
    5. <div
    6. v-for="(item, index) in waitList"
    7. :key="item.lastModified + item.name"
    8. style="margin-bottom: 20px"
    9. >
    10. <div style="margin: 5px">
    11. <button @click="startUploadFile(item, index)">开始上传</button>
    12. <span>{{ item.name }}</span>
    13. </div>
    14. </div>
    15. <div v-for="item in startList" :key="item.lastModified + item.name">
    16. <span>{{ item.name }}</span>
    17. <div style="width: 600px; height: 4px; background: gray">
    18. <div
    19. :style="{
    20. background: 'blue',
    21. height: '4px',
    22. width: item.precent ? item.precent + '%' : '0px',
    23. }"
    24. ></div>
    25. </div>
    26. {{ item.precent }}%
    27. <div v-if="item.uploadStatus !== 'success'">
    28. <div
    29. v-if="item.isStop"
    30. style="margin: 20px"
    31. @click="item.startUpload()"
    32. class="pointer"
    33. >
    34. 继续
    35. </div>
    36. <div
    37. v-else
    38. style="margin: 20px"
    39. @click="item.stopUpload()"
    40. class="pointer"
    41. >
    42. 暂停
    43. </div>
    44. </div>
    45. </div>
    46. </div>
    47. </div>
    48. </template>
    49. <script>
    50. import axios from "axios";
    51. // axios.defaults.baseURL = 'http://localhost';
    52. import CryptoJs from "crypto-js";
    53. export default {
    54. data() {
    55. return {
    56. chunkSize: 1024 * 1024 * 1, // 50M
    57. fileList: [],
    58. fileInfoList: [],
    59. waitList: [], //存储所有的上传文件
    60. startList: [], //开始上传的分片
    61. endList: [], //已上传的分片
    62. };
    63. },
    64. methods: {
    65. sendFile(file, sourceToken) { //文件不大于的单独上传
    66. let formdata = new FormData();
    67. formdata.append("file", file);
    68. formdata.append("suffix", file.name.split(".").pop().toLowerCase());
    69. axios({
    70. url: "/test/upload",
    71. method: "post",
    72. data: formdata,
    73. headers: { "Content-Type": "multipart/form-data" },
    74. cancelToken: sourceToken,
    75. onUploadProgress: (e) => { //获取上传文件得进度
    76. file.uploadedChunkSize = e.loaded;
    77. file.precent = ((e.loaded / e.total) * 100).toFixed(2);
    78. if (file.precent == 100) {
    79. // this.endUploadFile(file);
    80. }
    81. this.$forceUpdate(); // 刷新视图
    82. },
    83. }).then(
    84. ({ data }) => {
    85. console.log(data, "upload/file");
    86. },
    87. (err) => {
    88. console.log(err);
    89. }
    90. );
    91. },
    92. startUploadFile(item, index) { //开始上传
    93. this.waitList.splice(index, 1); //移除上传的文件
    94. this.startList.push(item); //将需要上传的文件,放入数组
    95. this.uploadFile(item); //上传
    96. },
    97. endUploadFile(item, index) {
    98. this.startList.splice(index, 1);
    99. this.endList.push(item);
    100. },
    101. uploadChange(e) {
    102. const files = e.target.files;
    103. this.waitList = [...this.waitList, ...files];
    104. },
    105. async uploadFile(file) {
    106. file.precent = 0; // 百分比进度
    107. file.uploadedChunkSize = 0; // 记录上传切片大小
    108. file.uploadStartTime = new Date().getTime;
    109. file.isStop = false; //文件进度
    110. file.hash = CryptoJs.MD5(file.name + file.uploadStartTime)
    111. .toString()
    112. .toUpperCase(); //文件进度
    113. // 暂停
    114. const {
    115. token,
    116. // cancel
    117. } = axios.CancelToken.source();
    118. const sourceToken = (file.sourceToken = token);
    119. console.log(sourceToken);
    120. file.stopUpload = () => {
    121. file.isStop = true;
    122. this.$forceUpdate();
    123. // cancel();
    124. };
    125. file.startUpload = () => {};
    126. const chunkSize = this.chunkSize;
    127. // 文件> 五倍切片大小 不进行切片上传
    128. if (file.size < chunkSize * 1) {
    129. this.sendFile(file, sourceToken);
    130. } else {
    131. const chunkInfo = await this.cutBlob(file, chunkSize); //文件分片
    132. const chunkArr = chunkInfo.chunkArr;
    133. this.queueReq({ //递归顺序上传切片文件
    134. file,
    135. sourceToken,
    136. chunkArr,
    137. progressCallback: (chunkItem, data) => {
    138. console.log(chunkItem, data);
    139. this.progressCallback(file, chunkItem, data);
    140. },
    141. endCallBack: () => {
    142. this.endCallBack();
    143. file.uploadStatus = "success";
    144. this.$forceUpdate();
    145. },
    146. errorCallBack: (data) => {
    147. file.startUpload = () => {
    148. const {
    149. token,
    150. // cancel
    151. } = axios.CancelToken.source(); //中断请求
    152. const sourceToken = (file.sourceToken = token);
    153. file.isStop = false;
    154. file.stopUpload = () => {
    155. file.isStop = true;
    156. this.$forceUpdate();
    157. // cancel();
    158. };
    159. this.queueReq({ ...data, sourceToken, file });
    160. this.$forceUpdate();
    161. };
    162. this.errorCallBack(data);
    163. },
    164. });
    165. }
    166. },
    167. cutBlob(file, chunkSize) {
    168. const chunkArr = []; // 所有切片缓存数组
    169. const blobSlice =
    170. File.prototype.slice ||
    171. File.prototype.mozSlice ||
    172. File.prototype.webkitSlice; // 切割Api不同浏览器分割处理
    173. const chunkNums = Math.ceil(file.size / chunkSize); // 切片总数
    174. return new Promise((resolve, reject) => {
    175. const reader = new FileReader();
    176. reader.readAsArrayBuffer(file);
    177. reader.addEventListener("loadend", () => {
    178. let startIndex = "";
    179. let endIndex = "";
    180. let contentItem = "";
    181. // 文件切割分片
    182. for (let i = 0; i < chunkNums; i++) {
    183. startIndex = i * chunkSize;
    184. endIndex = startIndex + chunkSize;
    185. endIndex > file.size && (endIndex = file.size);
    186. contentItem = blobSlice.call(file, startIndex, endIndex);
    187. chunkArr.push({
    188. index: i, // 用于合并文件
    189. hash: file.hash, // 区分上传
    190. total: chunkNums, // 分片总文件数
    191. name: file.name, // 文件名称
    192. size: file.size, // 文件大小
    193. chunk: contentItem, // 分片文件
    194. });
    195. }
    196. resolve({
    197. chunkArr,
    198. fileInfo: {
    199. hash: file.hash,
    200. total: chunkNums,
    201. name: file.name,
    202. size: file.size,
    203. },
    204. });
    205. });
    206. reader.addEventListener("error", function _error(err) {
    207. reject(err);
    208. });
    209. });
    210. },
    211. queueReq({
    212. file,
    213. sourceToken,
    214. chunkArr,
    215. result,
    216. progressCallback,
    217. endCallBack,
    218. errorCallBack,
    219. }) {
    220. if (chunkArr.length == 0) {
    221. return endCallBack();
    222. }
    223. const chunkItem = chunkArr.shift();
    224. let formdata = new FormData();
    225. formdata.append("file", chunkItem.chunk);
    226. formdata.append("hash", chunkItem.hash);
    227. formdata.append("index", chunkItem.index);
    228. formdata.append("name", chunkItem.name);
    229. let params = {
    230. suffix: chunkItem.name.split(".").pop().toLowerCase(),
    231. subscript: chunkItem.index,
    232. };
    233. if (result) {
    234. // 第二次发起拿到上次请求的返回
    235. params.noGroupPath = result.data.data;
    236. }
    237. if (file.isStop) {
    238. chunkArr.unshift(chunkItem); //移除已经上传的分片
    239. errorCallBack({
    240. errMessage: { message: "暂停" },
    241. chunkItem,
    242. sourceToken,
    243. chunkArr,
    244. result,
    245. progressCallback,
    246. endCallBack,
    247. errorCallBack,
    248. });
    249. return;
    250. }
    251. this.sendChunk({
    252. chunkItem,
    253. data: formdata,
    254. params,
    255. progressCallback,
    256. cancelToken: sourceToken,
    257. }).then(
    258. (res) => {
    259. this.queueReq({
    260. file,
    261. sourceToken,
    262. chunkArr,
    263. progressCallback,
    264. errorCallBack,
    265. endCallBack,
    266. result: res,
    267. });
    268. },
    269. (err) => {
    270. chunkArr.unshift(chunkItem);
    271. errorCallBack({
    272. errMessage: err,
    273. chunkItem,
    274. sourceToken,
    275. chunkArr,
    276. result,
    277. progressCallback,
    278. endCallBack,
    279. errorCallBack,
    280. });
    281. }
    282. );
    283. },
    284. sendChunk({ chunkItem, data, params, progressCallback, cancelToken }) {
    285. return axios({
    286. url: "/test/upload",
    287. method: "post",
    288. data,
    289. params,
    290. headers: { "Content-Type": "multipart/form-data" },
    291. cancelToken,
    292. onUploadProgress: (e) => {
    293. chunkItem.loaded = e.loaded;
    294. progressCallback(chunkItem, e); // 回调
    295. },
    296. });
    297. },
    298. progressCallback(file, chunkItem, data) {
    299. // console.log(data, "====");
    300. // 上传中回调
    301. const { loaded, total } = data;
    302. file.uploadedChunkSize += loaded < total ? 0 : +loaded;
    303. file.uploadedChunkSize > chunkItem.size &&
    304. (file.uploadedChunkSize = chunkItem.size);
    305. file.precent = ((file.uploadedChunkSize / chunkItem.size) * 100).toFixed(
    306. 2
    307. );
    308. this.$forceUpdate(); // 刷新视图
    309. },
    310. endCallBack() {
    311. console.log("上传完成");
    312. },
    313. errorCallBack({ chunkArr, sourceToken }) {
    314. console.log(chunkArr, sourceToken);
    315. },
    316. },
    317. };
    318. </script>
    319. <style>
    320. .pointer {
    321. cursor: pointer;
    322. }
    323. </style>