lijh
2022-01-12 bf6fbec0b5593eb28dc195a58a4208d29b79dfee
commit | author | age
bf6fbe 1 <template>
L 2     <div id="global-uploader">
3         <!-- 上传 -->
4         <uploader ref="uploaderId" :options="options" :autoStart="false" @file-added="onFileAdded"
5             @file-success="onFileSuccess" @file-progress="onFileProgress" @file-error="onFileError"
6             @file-complete="onFileComplete" class="uploader-app">
7             <uploader-unsupport></uploader-unsupport>
8
9             <uploader-btn id="global-uploader-btn" :directory="false" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>
10
11             <uploader-list v-show="panelShow">
12                 <div class="file-panel" slot-scope="props" :class="{'collapse': collapse}">
13                     <div class="file-title">
14                         <h2>文件列表</h2>
15                         <div class="operate">
16                             <el-button @click="fileListShow" type="text" :title="collapse ? '展开':'折叠' ">
17                                 <i class="iconfont" :class="collapse ? 'inuc-fullscreen': 'inuc-minus-round'"></i>
18                             </el-button>
19                             <el-button @click="close" type="text" title="关闭">
20                                 <i class="iconfont icon-close"></i>
21                             </el-button>
22                         </div>
23                     </div>
24
25                     <ul class="file-list">
26                         <li v-for="file in props.fileList" :key="file.id">
27                             <uploader-file :class="'file_' + file.id" ref="files" :file="file" :list="true">
28                             </uploader-file>
29                         </li>
30                         <div class="no-file" v-if="!props.fileList.length"><i class="iconfont icon-empty-file"></i>
31                             暂无待上传文件</div>
32                     </ul>
33                 </div>
34             </uploader-list>
35
36         </uploader>
37
38     </div>
39 </template>
40
41 <script>
42     /**
43      *   全局上传插件
44      *   调用方法:Bus.$emit('openUploader', {}) 打开文件选择框,参数为需要传递的额外参数
45      *   监听函数:Bus.$on('fileAdded', fn); 文件选择后的回调
46      *            Bus.$on('fileSuccess', fn); 文件上传成功的回调
47      */
48
49     import {
50         ACCEPT_CONFIG
51     } from '@/js/config';
52     import Bus from '@/js/bus';
53     import SparkMD5 from 'spark-md5';
54     import $ from 'jquery';
55     import Element from 'element-ui';
56
57     // 这两个是我自己项目中用的,请忽略
58     // import {Ticket} from '@/assets/js/utils';
59     // import api from '@/api';
60
61     export default {
62         data() {
63             return {
64                 options: {
65                     //target: "http://192.168.21.184:8089/aa",
66                     chunkSize: 6 * 1024 * 1024,
67                     fileParameterName: 'upFile',
68                     forceChunkSize: true,
69                     maxChunkRetries: 3,
70                     testChunks: true, //是否开启服务器分片校验
71                     // 服务器分片校验函数,秒传及断点续传基础
72                     checkChunkUploadedByResponse: function(chunk, message) {
73                         let resp = JSON.parse(message);
74                         if (!resp.success) {
75                             console.error("校验分片异常")
76                             return false;
77                         }
78
79                         //已经秒传
80                         if (resp.result.chunkIndex != null && resp.result.chunkIndex[chunk.offset] > 0) {
81                             console.log(chunk.offset + 1 + "已经秒传..")
82                             return true;
83                         }
84                         console.log(chunk.offset + 1 + "正常上传..")
85
86                         return false;
87                     },
88                     headers: {
89                         // Authorization: Ticket.get() && "Bearer " + Ticket.get().access_token
90                     },
91                     query() {
92                         //aa: "bbb";
93                     }
94                 },
95                 attrs: {
96                     accept: ACCEPT_CONFIG.getAll()
97                 },
98                 panelShow: true, //选择文件后,展示上传panel
99                 collapse: true,
100             }
101         },
102         mounted() {
103             Bus.$on('openUploader', query => {
104                 this.params = query || {};
105
106                 if (this.$refs.uploadBtn) {
107                     $('#global-uploader-btn').click();
108                 }
109             });
110         },
111         computed: {
112             //Uploader实例
113             uploader() {
114                 return this.$refs.uploaderId.uploader;
115             }
116         },
117         methods: {
118             onFileAdded(file) {
119                 
120                 this.panelShow = true;
121                 this.computeMD5(file);
122
123                 Bus.$emit('fileAdded');
124             },
125
126             onFileComplete(rootFile) {
127                 console.log(11111)
128                 console.log(rootFile)
129             },
130             onFileProgress(rootFile, file, chunk) {
131                 console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
132             },
133             onFileSuccess(rootFile, file, response, chunk) {
134                 console.log("上传完成回调", response);
135                 let res;
136                 try {
137                     if (typeof response =="string") {
138                         res = JSON.parse(response);
139                     } else {
140                         res = response;
141                     }
142                     // 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
143                     if (!res.success) {
144                         this.$message({
145                             message: res.errmsg,
146                             type: 'error'
147                         });
148                         // 文件状态设为“失败”
149                         this.statusSet(file.id, 'failed');
150                         return
151                     }
152
153                 } catch (ex) {
154                     console.log(ex)
155                 }
156
157                 var $this = this;
158
159
160                 // 文件状态设为“合并中”
161                 $this.statusSet(file.id, 'merging');
162
163                 //请求合并
164                 $this.$axios.get('http://192.168.21.184:8001/commonFileUpload/doMerge?mainMD5=' + file.uniqueIdentifier +
165                         "&mediaId=" + localStorage.getItem("mediaId"))
166                     .then(function(response) {
167
168                         if (response.data.success) {
169                             // 文件合并成功
170                             Bus.$emit('fileSuccess');
171                             $this.statusRemove(file.id);
172
173                             console.log('文件合并成功');
174                             console.log('上传成功');
175                             console.log("文件上传结束" + new Date().getTime())
176                         } else {
177                             //请求合并异常 
178                             $this.statusRemove(file.id);
179                             $this.statusSet(file.id, 'mergingExcetion');
180                         }
181
182                     })
183                     .catch(function(error) {
184                         console.error(error);
185                         $this.$message({
186                             message: "合并异常",
187                             type: 'error'
188                         });
189                         $this.statusRemove(file.id);
190                         $this.statusSet(file.id, 'mergingExcetion');
191                     });
192
193
194             },
195
196             onFileError(rootFile, file, response, chunk) {
197                 this.$message({
198                     message: response,
199                     type: 'error'
200                 })
201             },
202
203             /**
204              * 计算md5,实现断点续传及秒传
205              * @param file
206              */
207             computeMD5(file) {
208                 let fileReader = new FileReader();
209                 let time = new Date().getTime();
210                 console.log("文件上传开始" + new Date().getTime())
211                 let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
212
213                 let currentChunk = 0;
214                 const chunkSize = 6 * 1024 * 1024; //md5计算时切片大小
215                 let chunks = Math.ceil(file.size / chunkSize);
216
217                 let spark = new SparkMD5.ArrayBuffer();
218
219                 // 文件状态设为"计算MD5"
220                 this.statusSet(file.id, 'md5');
221                 file.pause();
222
223                 loadNext();
224
225                 fileReader.onload = (e => {
226                     spark.append(e.target.result);
227
228                     if (currentChunk < chunks) {
229                         currentChunk++;
230                         loadNext();
231
232                         // 实时展示MD5的计算进度
233                         this.$nextTick(() => {
234                             $(`.myStatus_${file.id}`).text('校验MD5 ' + ((currentChunk / chunks) * 100)
235                                 .toFixed(0) + '%')
236                         })
237                     } else {
238                         let md5 = spark.end();
239                         this.computeMD5Success(md5, file);
240                         console.log(
241                             `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${new Date().getTime() - time} ms`
242                         );
243                     }
244                 });
245
246                 fileReader.onerror = function() {
247                     this.error(`文件${file.name}读取出错,请检查该文件`)
248                     file.cancel();
249                 };
250
251                 function loadNext() {
252                     let start = currentChunk * chunkSize;
253                     let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
254
255                     fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
256                 }
257             },
258
259             computeMD5Success(md5, file) {
260                 //1.初始化分片上传请求,获取分片参数
261
262                 //计算内网ip是否联通,选择上传接口url,如果是
263
264                 let $this = this;
265                 //this.$axios.post('http://192.168.21.184:8001/commonFileUpload/createShardPost', {
266                 this.$axios.post('http://192.168.21.184:8001/sfcMedia/addMedia', {
267                         storeType: "0",
268                         title: file.name,
269                         typeId: "4",
270                         dirId: "7",
271                         uploadDto: {
272                             internalHostList:["192.168.40.149:9000"],
273                             mainMD5: md5,
274                             size: file.size,
275                             fileName: file.name,
276                             ext: file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length), //文件名没有点号会异常
277                             type: file.type
278                         }
279                     }, {
280                         headers: {
281                             'Sys-Access-Token': "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbktleU5hbWUiOiJTeXMtQWNjZXNzLVRva2VuIiwidXNlck5hbWUiOiJhZG1pbiIsImV4cCI6MTY0MTM3OTQ0NX0.FQXRgkLXlmgpLDcP2cbZZBNwCEDZJoeoHen6OZz5Wbk",
282                         }
283                     }).then(function(response) {
284                         console.log("文件上传请求初始化");
285                         console.log(response);
286                         if (response.data.success) {
287                             let uploadOptions = response.data.result;
288
289                             localStorage.setItem("mediaId",uploadOptions.mediaId)
290                             let uploadURLArray = uploadOptions.uploadUrlList;
291                             
292                             
293                             console.log("文件上传url", uploadURLArray)
294                             // 将自定义参数直接加载uploader实例的opts上
295                             let opts = $this.uploader.opts;
296
297                             //opts.target = "http://192.168.21.184:8001/commonFileUpload/doUpload"; //上传接口
298                             opts.target = function(file, chunk, isTest, obj) {
299                                 //定义分片检测url
300                                 if (isTest) {
301                                     return "http://192.168.21.184:8001/commonFileUpload/checkShardStatus"
302                                 }
303                                 let chunkUploadUrl = uploadURLArray[chunk.offset]
304                                 if (chunkUploadUrl == undefined || chunkUploadUrl == null || chunkUploadUrl ==
305                                     "") {
306                                     console.error("获取分片上传链接异常 chunkNumber:{}", (chunk.offset + 1))
307                                     return "";
308                                 }
309                                 return chunkUploadUrl;
310                             }
311
312                             //分片请求成功后处理
313                             opts.processResponse = function(response, cb, file, chunk) {
314                                 console.log("分片" + (chunk.offset + 1) + "上传成功", response)
315
316                                 //通知服务器分片上传状态
317                                 $this.$axios.get(
318                                         'http://192.168.21.184:8001/commonFileUpload/updateProgress?mainMD5=' +
319                                         file.uniqueIdentifier +
320                                         "&chunk=" + (chunk.offset + 1) +
321                                         "&chunkSize=" + (chunk.loaded)
322                                     )
323                                     .then(function(re) {
324                                         console.log("分片更新上传进度成功", re)
325                                         cb(null, re.data)
326                                     })
327                                     .catch(function(error) {
328                                         console.error("分片更新上传进度异常", error);
329                                         cb(null, error)
330                                     });
331
332                             }
333
334                             opts.processParams = function(params, file, chunk, isTest) {
335                                 if (isTest) {
336                                     return params;
337                                 }
338                                 return {};
339                             }
340
341                             opts.method = "octet";
342                             opts.testMethod = "GET"; //调用分片上传验证请求类型
343                             opts.uploadMethod = "PUT"; //调用分片上传时使用的http方法请求类型
344
345                             //opts.chunkSize = uploadOptions.chunkSize; //切片大小
346                             $this.params.mainMD5 = md5; //mainMD5参数
347                             $this.params.chunkNumber = opts.chunkNumber; //分片索引参数
348                             Object.assign(opts, {
349                                 query: {
350                                     ...$this.params,
351                                 }
352                             })
353
354                             file.uniqueIdentifier = md5;
355                             file.resume();
356                             $this.statusRemove(file.id);
357                         } else {
358                             //中断上传
359                             file.cancel();
360                         }
361                     })
362                     .catch(function(error) {
363                         console.error(error);
364                         //中断上传
365                         file.cancel();
366                         return;
367                     });
368
369
370             },
371
372             fileListShow() {
373                 let $list = $('#global-uploader .file-list');
374
375                 if ($list.is(':visible')) {
376                     $list.slideUp();
377                     this.collapse = true;
378                 } else {
379                     $list.slideDown();
380                     this.collapse = false;
381                 }
382             },
383             close() {
384                 this.uploader.cancel();
385
386                 this.panelShow = false;
387             },
388
389             /**
390              * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
391              * @param id
392              * @param status
393              */
394             statusSet(id, status) {
395                 let statusMap = {
396                     md5: {
397                         text: '校验MD5',
398                         bgc: '#fff'
399                     },
400                     merging: {
401                         text: '合并中',
402                         bgc: '#e2eeff'
403                     },
404                     mergingExcetion: {
405                         text: '合并异常',
406                         bgc: '#e2eeff'
407                     },
408                     transcoding: {
409                         text: '转码中',
410                         bgc: '#e2eeff'
411                     },
412                     failed: {
413                         text: '上传失败',
414                         bgc: '#e2eeff'
415                     }
416                 }
417
418                 this.$nextTick(() => {
419                     $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
420                         'position': 'absolute',
421                         'top': '0',
422                         'left': '0',
423                         'right': '0',
424                         'bottom': '0',
425                         'zIndex': '1',
426                         'backgroundColor': statusMap[status].bgc
427                     }).text(statusMap[status].text);
428                 })
429             },
430             statusRemove(id) {
431                 this.$nextTick(() => {
432                     $(`.myStatus_${id}`).remove();
433                 })
434             },
435
436             error(msg) {
437                 this.$notify({
438                     title: '错误',
439                     message: msg,
440                     type: 'error',
441                     duration: 2000
442                 })
443             }
444         },
445         watch: {},
446         destroyed() {
447             Bus.$off('openUploader');
448         },
449         components: {}
450     }
451 </script>
452
453 <style>
454     #global-uploader {
455         position: fixed;
456         z-index: 20;
457         right: 15px;
458         bottom: 15px;
459     }
460
461     #global-uploader .uploader-app {
462         width: 1820px;
463     }
464
465     #global-uploader .file-panel {
466         background-color: #fff;
467         border: 1px solid #e2e2e2;
468         border-radius: 7px 7px 0 0;
469         box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
470     }
471
472     #global-uploader .file-panel .file-title {
473         display: flex;
474         height: 40px;
475         line-height: 40px;
476         padding: 0 15px;
477         border-bottom: 1px solid #ddd;
478     }
479
480     #global-uploader .file-panel .file-title .operate {
481         flex: 1;
482         text-align: right;
483     }
484
485     #global-uploader .file-panel .file-list {
486         position: relative;
487         height: 240px;
488         overflow-x: hidden;
489         overflow-y: auto;
490         background-color: #fff;
491     }
492
493     #global-uploader .file-panel .file-list>li {
494         background-color: #fff;
495     }
496
497     #global-uploader .file-panel.collapse .file-title {
498         background-color: #E7ECF2;
499     }
500
501     #global-uploader .no-file {
502         position: absolute;
503         top: 50%;
504         left: 50%;
505         transform: translate(-50%, -50%);
506         font-size: 16px;
507     }
508
509     #global-uploader .uploader-file-icon:before {
510         content: "" !important;
511     }
512
513     #global-uploader .uploader-file-actions>span {
514         margin-right: 6px;
515     }
516
517     #global-uploader .uploader-file-icon[icon=image] {
518         background: url(../images/image-icon.png);
519     }
520
521     #global-uploader .uploader-file-icon[icon=video] {
522         background: url(../images/video-icon.png);
523     }
524
525     #global-uploader .uploader-file-icon[icon=document] {
526         background: url(../images/text-icon.png);
527     }
528
529     /* 隐藏上传按钮 */
530     #global-uploader-btn {
531         position: absolute;
532         clip: rect(0, 0, 0, 0);
533     }
534 </style>