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