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 14 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  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*64, //64MB
  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: 'img/*'
  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. file.uploadID = response.data.uploadID;
  78. file.uuid = response.data.uuid;
  79. file.uploaded = response.data.uploaded;
  80. file.chunks = response.data.chunks;
  81. resolve(response);
  82. }).catch(function (error) {
  83. console.log(error);
  84. reject(error);
  85. });
  86. })
  87. },
  88. newMultiUpload(file) {
  89. return new Promise((resolve, reject) => {
  90. axios.get('/attachments/new_multipart', {params :{
  91. totalChunkCounts: file.totalChunkCounts,
  92. md5: file.uniqueIdentifier,
  93. size: file.size,
  94. fileType: file.fileType,
  95. _csrf: csrf
  96. }}).then(function (response) {
  97. file.uploadID = response.data.uploadID;
  98. file.uuid = response.data.uuid;
  99. resolve(response);
  100. }).catch(function (error) {
  101. console.log(error);
  102. reject(error);
  103. });
  104. })
  105. },
  106. multipartUpload(file) {
  107. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  108. chunkSize = 1024*1024*64,
  109. chunks = Math.ceil(file.size / chunkSize),
  110. currentChunk = 0,
  111. fileReader = new FileReader(),
  112. time = new Date().getTime();
  113. function loadNext() {
  114. let start = currentChunk * chunkSize;
  115. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  116. fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  117. }
  118. function checkSuccessChunks() {
  119. var index = successChunks.indexOf((currentChunk+1).toString())
  120. if (index == -1) {
  121. return false;
  122. }
  123. return true;
  124. }
  125. function getUploadChunkUrl(currentChunk, partSize) {
  126. return new Promise((resolve, reject) => {
  127. axios.get('/attachments/get_multipart_url', {params :{
  128. uuid: file.uuid,
  129. uploadID: file.uploadID,
  130. size: partSize,
  131. chunkNumber: currentChunk+1,
  132. _csrf: csrf
  133. }}).then(function (response) {
  134. urls[currentChunk] = response.data.url
  135. resolve(response);
  136. }).catch(function (error) {
  137. console.log(error);
  138. reject(error);
  139. });
  140. })
  141. }
  142. function uploadMinio(url, e) {
  143. return new Promise((resolve, reject) => {
  144. axios.put(url, e.target.result
  145. ).then(function (res) {
  146. etags[currentChunk] = res.headers.etag;
  147. resolve(res);
  148. }).catch(function (err) {
  149. console.log(err);
  150. reject(err);
  151. });
  152. });
  153. }
  154. function updateChunk(currentChunk) {
  155. return new Promise((resolve, reject) => {
  156. axios.post('/attachments/update_chunk', qs.stringify({
  157. uuid: file.uuid,
  158. chunkNumber: currentChunk+1,
  159. etag: etags[currentChunk],
  160. _csrf: csrf
  161. })).then(function (response) {
  162. resolve(response);
  163. }).catch(function (error) {
  164. console.log(error);
  165. reject(error);
  166. });
  167. })
  168. }
  169. async function uploadChunk(e) {
  170. if (!checkSuccessChunks()) {
  171. let start = currentChunk * chunkSize;
  172. let partSize = ((start + chunkSize) >= file.size) ? file.size -start : chunkSize;
  173. //获取分片上传url
  174. await getUploadChunkUrl(currentChunk, partSize);
  175. if (urls[currentChunk] != "") {
  176. //上传到minio
  177. await uploadMinio(urls[currentChunk], e);
  178. if (etags[currentChunk] != "") {
  179. //更新数据库:分片上传结果
  180. await updateChunk(currentChunk);
  181. } else {
  182. return;
  183. }
  184. } else {
  185. return;
  186. }
  187. }
  188. };
  189. function completeUpload(){
  190. return new Promise((resolve, reject) => {
  191. axios.post('/attachments/complete_multipart', qs.stringify({
  192. uuid: file.uuid,
  193. uploadID: file.uploadID,
  194. file_name: file.name,
  195. size: file.size,
  196. //dataset_id: datasetID,
  197. _csrf: csrf
  198. })).then(function (response) {
  199. resolve(response);
  200. }).catch(function (error) {
  201. console.log(error);
  202. reject(error);
  203. });
  204. })
  205. }
  206. function upload() {
  207. loadNext();
  208. fileReader.onload = async (e) => {
  209. await uploadChunk(e);
  210. currentChunk++;
  211. if (currentChunk < chunks) {
  212. console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk +1}/${chunks}个分片上传`);
  213. await loadNext();
  214. } else {
  215. //console.log(dataset_id)
  216. await completeUpload();
  217. console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`);
  218. //window.location.reload();
  219. }
  220. };
  221. }
  222. var successChunks = new Array();
  223. var successParts = new Array();
  224. successParts = file.chunks.split(",");
  225. for (let i = 0; i < successParts.length; i++) {
  226. successChunks[i] = successParts[i].split("-")[0].split("\"")[1];
  227. }
  228. var urls = new Array();
  229. var etags = new Array();
  230. console.log('上传分片...');
  231. upload();
  232. },
  233. chkMd5(file) {
  234. let time = new Date().getTime();
  235. let fileReader = new FileReader();
  236. let spark = new SparkMD5(); //创建md5对象(基于SparkMD5)
  237. fileReader.readAsBinaryString(file.file);
  238. console.log('开始计算MD5...')
  239. //文件读取完毕之后的处理
  240. fileReader.onload = (e) => {
  241. spark.appendBinary(e.target.result);
  242. let md5 = spark.end();
  243. console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n用时:${new Date().getTime() - time} ms`);
  244. spark.destroy();
  245. };
  246. },
  247. //计算MD5
  248. computeMD5(file) {
  249. let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
  250. chunkSize = 1024*1024*64,
  251. chunks = Math.ceil(file.size / chunkSize),
  252. currentChunk = 0,
  253. spark = new SparkMD5.ArrayBuffer(),
  254. fileReader = new FileReader();
  255. let time = new Date().getTime();
  256. console.log('计算MD5...')
  257. file.cmd5 = true;
  258. file.totalChunkCounts = chunks;
  259. loadNext();
  260. fileReader.onload = (e) => {
  261. spark.append(e.target.result); // Append array buffer
  262. currentChunk++;
  263. if (currentChunk < chunks) {
  264. console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk +1}/${chunks}分片解析`);
  265. // let percent = Math.floor(currentChunk / chunks * 100);
  266. // console.log(percent);
  267. // file.cmd5progress = percent;
  268. loadNext();
  269. } else {
  270. let md5 = spark.end();
  271. console.log(`MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time)/1000} s`);
  272. spark.destroy(); //释放缓存
  273. file.uniqueIdentifier = md5; //将文件md5赋值给文件唯一标识
  274. file.cmd5 = false; //取消计算md5状态
  275. this.computeMD5Success(file);
  276. }
  277. };
  278. fileReader.onerror = () => {
  279. console.warn('oops, something went wrong.');
  280. file.cancel();
  281. };
  282. function loadNext() {
  283. let start = currentChunk * chunkSize;
  284. let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
  285. fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
  286. }
  287. },
  288. async computeMD5Success(file) {
  289. await this.getSuccessChunks(file);
  290. if (file.uploadID == "" || file.uuid == "") { //未上传过
  291. await this.newMultiUpload(file);
  292. if (file.uploadID != "" && file.uuid != "") {
  293. file.chunks = "";
  294. this.multipartUpload(file);
  295. } else {
  296. return;
  297. }
  298. } else {
  299. if (file.uploaded == "1") { //已上传成功
  300. //秒传
  301. console.log("文件已上传完成");
  302. //window.location.reload();
  303. } else {
  304. //断点续传
  305. this.multipartUpload(file);
  306. }
  307. }
  308. },
  309. // 文件进度的回调
  310. onFileProgress(rootFile, file, chunk) {
  311. console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
  312. },
  313. onFileSuccess(rootFile, file, response, chunk) {
  314. let resp = JSON.parse(response);
  315. if (resp.code === 0 && resp.merge === false) {
  316. console.log('上传成功,不需要合并');
  317. } else {
  318. axios.post('http://localhost:9999/up.php?action=merge', {
  319. filename: file.name,
  320. identifier: file.uniqueIdentifier,
  321. totalSize: file.size,
  322. totalChunks: chunk.offset + 1
  323. }).then(function(res){
  324. if (res.code === 0) {
  325. console.log('上传成功')
  326. } else {
  327. console.log(res.message);
  328. }
  329. })
  330. .catch(function(error){
  331. console.log(error);
  332. });
  333. }
  334. },
  335. onFileError(rootFile, file, response, chunk) {
  336. console.log('Error:', response)
  337. },
  338. }
  339. }
  340. </script>
  341. <style>
  342. .uploader-app {
  343. width: 850px;
  344. padding: 15px;
  345. margin: 40px auto 0;
  346. font-size: 12px;
  347. box-shadow: 0 0 10px rgba(0, 0, 0, .4);
  348. }
  349. .uploader-app .uploader-btn {
  350. margin-right: 40px;
  351. }
  352. .uploader-app .uploader-list {
  353. max-height: 440px;
  354. overflow: auto;
  355. overflow-x: hidden;
  356. overflow-y: auto;
  357. }
  358. </style>