You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

App.vue 12 kB

5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <template>
  2. <uploader
  3. ref="uploader"
  4. :options="options"
  5. :autoStart="false"
  6. @file-added="onFileAdded"
  7. @file-success="onFileSuccess"
  8. @file-progress="onFileProgress"
  9. @file-error="onFileError"
  10. class="uploader-app">
  11. <uploader-unsupport></uploader-unsupport>
  12. <uploader-drop>
  13. <p>拖动文件</p>
  14. <uploader-btn>选择文件</uploader-btn>
  15. </uploader-drop>
  16. <uploader-list></uploader-list>
  17. </uploader>
  18. </template>
  19. <script>
  20. import SparkMD5 from 'spark-md5';
  21. import axios from 'axios'
  22. import qs from 'qs'
  23. const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
  24. export default {
  25. data () {
  26. return {
  27. options: {
  28. target: 'http://localhost:9000/upload',
  29. testChunks: false,
  30. chunkSize: 1024*1024*128, //128MB
  31. simultaneousUploads: 1, //并发上传数
  32. headers: {
  33. 'access-token': 'abcd1234'
  34. },
  35. maxChunkRetries: 3, //最大自动失败重试上传次数
  36. parseTimeRemaining: function (timeRemaining, parsedTimeRemaining) { //格式化时间
  37. return parsedTimeRemaining
  38. .replace(/\syears?/, '年')
  39. .replace(/\days?/, '天')
  40. .replace(/\shours?/, '小时')
  41. .replace(/\sminutes?/, '分钟')
  42. .replace(/\sseconds?/, '秒')
  43. }
  44. },
  45. attrs: {
  46. accept: '*'
  47. },
  48. panelShow: false, //选择文件后,展示上传panel
  49. collapse: false,
  50. statusTextMap: {
  51. success: '上传成功',
  52. error: '上传出错了',
  53. uploading: '上传中...',
  54. paused: '暂停',
  55. waiting: '等待中...',
  56. cmd5: '计算md5...'
  57. },
  58. fileStatusText: (status, response) => {
  59. return this.statusTextMap[status];
  60. },
  61. }
  62. },
  63. created() {
  64. //const uploaderInstance = this.$refs.uploader.uploader;
  65. },
  66. methods: {
  67. onFileAdded(file) {
  68. // 计算MD5
  69. this.computeMD5(file);
  70. },
  71. getSuccessChunks(file) {
  72. return new Promise((resolve, reject) => {
  73. axios.get('/attachments/get_chunks', {params :{
  74. md5: file.uniqueIdentifier,
  75. _csrf: csrf
  76. }}).then(function (response) {
  77. console.log(response.data.uploadID, response.data.uuid, response.data.uploaded, response.data.chunks);
  78. file.uploadID = response.data.uploadID;
  79. file.uuid = response.data.uuid;
  80. file.uploaded = response.data.uploaded;
  81. file.chunks = response.data.chunks;
  82. resolve(response);
  83. }).catch(function (error) {
  84. console.log(error);
  85. reject(error);
  86. });
  87. })
  88. },
  89. newMultiUpload(file) {
  90. return new Promise((resolve, reject) => {
  91. axios.get('/attachments/new_multipart', {params :{
  92. totalChunkCounts: file.totalChunkCounts,
  93. md5: file.uniqueIdentifier,
  94. size: file.size,
  95. fileType: file.fileType,
  96. _csrf: csrf
  97. }}).then(function (response) {
  98. console.log(response.data.uploadID, response.data.uuid);
  99. file.uploadID = response.data.uploadID;
  100. file.uuid = response.data.uuid;
  101. resolve(response);
  102. }).catch(function (error) {
  103. console.log(error);
  104. reject(error);
  105. });
  106. })
  107. },
  108. multipartUpload(file) {
  109. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  110. chunkSize = 1024*1024*128,
  111. chunks = Math.ceil(file.size / chunkSize),
  112. currentChunk = 0,
  113. fileReader = new FileReader(),
  114. time = new Date().getTime();
  115. function loadNext() {
  116. let start = currentChunk * chunkSize;
  117. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  118. fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  119. }
  120. function checkSuccessChunks() {
  121. var index = successChunks.indexOf((currentChunk+1).toString())
  122. if (index == -1) {
  123. return false;
  124. }
  125. return true;
  126. }
  127. function uploadChunk(e) {
  128. //判断是否已上传
  129. if (!checkSuccessChunks()) {
  130. let start = currentChunk * chunkSize;
  131. let partSize = ((start + chunkSize) >= file.size) ? file.size -start : chunkSize;
  132. //获取分片url
  133. axios.get('/attachments/get_multipart_url', {params :{
  134. uuid: file.uuid,
  135. uploadID: file.uploadID,
  136. size: partSize,
  137. chunkNumber: currentChunk+1,
  138. _csrf: csrf
  139. }}).then(function (response) {
  140. //todo:分片上传
  141. axios.put(response.data.url, e.target.result
  142. ).then(function (res) {
  143. console.log(res.headers.etag);
  144. }).catch(function (error) {
  145. console.log(error);
  146. });
  147. }).catch(function (error) {
  148. console.log(error);
  149. });
  150. }
  151. }
  152. var successChunks = new Array();
  153. successChunks = file.chunks.split(",");
  154. console.log('上传分片...');
  155. loadNext();
  156. fileReader.onload = (e) => {
  157. uploadChunk(e);
  158. currentChunk++;
  159. if (currentChunk < chunks && !checkSuccessChunks()) {
  160. console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1} / ${chunks}个分片上传`);
  161. loadNext();
  162. } else {
  163. console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`);
  164. //this.computeMD5Success(file);
  165. }
  166. };
  167. },
  168. chkMd5(file) {
  169. let time = new Date().getTime();
  170. let fileReader = new FileReader();
  171. let spark = new SparkMD5(); //创建md5对象(基于SparkMD5)
  172. fileReader.readAsBinaryString(file.file);
  173. console.log('开始计算MD5...')
  174. //文件读取完毕之后的处理
  175. fileReader.onload = (e) => {
  176. spark.appendBinary(e.target.result);
  177. let md5 = spark.end();
  178. console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n用时:${new Date().getTime() - time} ms`);
  179. spark.destroy();
  180. };
  181. },
  182. //计算MD5
  183. computeMD5(file) {
  184. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  185. chunkSize = 1024*1024*128,
  186. chunks = Math.ceil(file.size / chunkSize),
  187. currentChunk = 0,
  188. spark = new SparkMD5.ArrayBuffer(),
  189. fileReader = new FileReader();
  190. let time = new Date().getTime();
  191. console.log('计算MD5...')
  192. file.cmd5 = true;
  193. file.totalChunkCounts = chunks;
  194. loadNext();
  195. fileReader.onload = (e) => {
  196. spark.append(e.target.result); // Append array buffer
  197. currentChunk++;
  198. if (currentChunk < chunks) {
  199. console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1} / ${chunks}分片解析`);
  200. // let percent = Math.floor(currentChunk / chunks * 100);
  201. // console.log(percent);
  202. // file.cmd5progress = percent;
  203. loadNext();
  204. } else {
  205. let md5 = spark.end();
  206. console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`);
  207. spark.destroy(); //释放缓存
  208. file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
  209. file.cmd5 = false; //取消计算md5状态
  210. this.computeMD5Success(file);
  211. }
  212. };
  213. fileReader.onerror = () => {
  214. console.warn('oops, something went wrong.');
  215. file.cancel();
  216. };
  217. function loadNext() {
  218. let start = currentChunk * chunkSize;
  219. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  220. fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  221. }
  222. },
  223. async computeMD5Success(file) {
  224. await this.getSuccessChunks(file);
  225. if (file.uploadID == "" || file.uuid == "") { //未上传过
  226. console.log("test1");
  227. await this.newMultiUpload(file);
  228. if (file.uploadID != "" && file.uuid != "") {
  229. file.chunks = "";
  230. //todo:开始分片上传:分片,获取分片上传地址,上传
  231. this.multipartUpload(file);
  232. } else {
  233. return;
  234. }
  235. } else {
  236. console.log("test2");
  237. if (file.uploaded == "1") { //已上传成功
  238. //todo:结束上传
  239. } else {
  240. //todo:查询已上传成功的分片,重新上传未成功上传的分片
  241. }
  242. }
  243. },
  244. // 文件进度的回调
  245. onFileProgress(rootFile, file, chunk) {
  246. console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
  247. },
  248. onFileSuccess(rootFile, file, response, chunk) {
  249. let resp = JSON.parse(response);
  250. if (resp.code === 0 && resp.merge === false) {
  251. console.log('上传成功,不需要合并');
  252. } else {
  253. axios.post('http://localhost:9999/up.php?action=merge', {
  254. filename: file.name,
  255. identifier: file.uniqueIdentifier,
  256. totalSize: file.size,
  257. totalChunks: chunk.offset + 1
  258. }).then(function(res){
  259. if (res.code === 0) {
  260. console.log('上传成功')
  261. } else {
  262. console.log(res.message);
  263. }
  264. })
  265. .catch(function(error){
  266. console.log(error);
  267. });
  268. }
  269. },
  270. onFileError(rootFile, file, response, chunk) {
  271. console.log('Error:', response)
  272. },
  273. }
  274. }
  275. </script>
  276. <style>
  277. .uploader-app {
  278. width: 880px;
  279. padding: 15px;
  280. margin: 40px auto 0;
  281. font-size: 12px;
  282. box-shadow: 0 0 10px rgba(0, 0, 0, .4);
  283. }
  284. .uploader-app .uploader-btn {
  285. margin-right: 40px;
  286. }
  287. .uploader-app .uploader-list {
  288. max-height: 440px;
  289. overflow: auto;
  290. overflow-x: hidden;
  291. overflow-y: auto;
  292. }
  293. </style>