效果展示
包含技术点
1、分片上传。
2、文件秒传。
3、文件夹上传
4、文件续传。
5、文件拖拽上传。
组件目录
实现分析
分片上传
通过H5 FileUpload对象可以实现文件上传, mutiple属性可以支持文件多选。拿到文件对象后,调用完整的分片上传流程:计算MD5-添加文件-获取鉴权信息-递归上传分片-上传完成。
如何切割文件分片?
方法介绍:blob.slice(); 属于Blob对象的一个方法,而File对象是继承Blob对象的,因此File对象也含有slice方法。
如何计算进度?
通过文件大小及分片计算算法,算出分片大小和分片数目以后,通过当前上传的分片数目除以总共分片数目得到进度信息。
如何计算上传速度?
每上传一个分片,记录分片上传请求的时间,通过分片的大小除以请求时间得到速度信息。
上传分片的代码:
// 上传分片 const uploadPart = (resolve, reject) => { let blob = file.slice((params.PartNumber - 1) * chunkSize, params.PartNumber * chunkSize); let begin = new Date().getTime(); params['Body'] = blob; s3.uploadPart(params, function (err, data) { if (err || item.status !== 'uploading') { reject(err || item.status); } else { item.percentage = Math.round(params.PartNumber * 100 / frameNum); // 进度 let spend = (new Date().getTime() - begin) / 1000; // 消耗时间 item.speed = Math.round(blob.size / spend) * 1.3; // 速度 log_content.frameSeq = params.PartNumber; l_params.logContent = JSON.stringify(log_content); // debugger; log(l_params).then(() => { if (params.PartNumber < frameNum) { params.PartNumber += 1; uploadPart(resolve, reject); } else { item.status = 'success'; resolve(); } }).catch((err) => { reject(err); }); } }); };
文件秒传
秒传是将上传的文件与服务器中的文件进行比对,若云端存在相同文件,则将直接把文件秒速保存到你的网盘。调用“添加文件”接口,若服务端返回文件ID,表明云端已存储相同文件,新的文件会存储为源文件的一份索引。
文件夹上传
chrome的私有属性webkitdiretory可以支持文件夹上传。分片上传流程需要增加递归创建文件夹:递归创建文件夹-计算MD5-添加文件-获取鉴权信息-递归上传分片-上传完成。
判断文件为文件夹上传?
file.webkitRelativePath属性。
判断文件是否一次上传?
这里要处理的场景是,一个文件夹上传两次,第二次需要对文件夹重命名。如何判断一批文件是一次上传的,还是同名文件夹上传多次。可以通过上传框change的时候,构造文件对象增加时间戳属性,同一批文件的时间戳相同。
HTML:
<input ref="folder" @change='handleChange' name="folderInput" multiple="" webkitdirectory="" accept="*/*" type="file">
递归创建文件夹可参考Node算法:
const mkdirs = (dirname, callback, errback) => { fs.stat(dirname, (err, stats) => { if (err) { mkdirs(path.dirname(dirname), () => { fs.mkdir(dirname, callback) }, errback) } else { if (stats.isDirectory()) { callback() } else { errback() } } }) }
文件续传
大文件在上传过程中进行中断网络或刷新浏览器等操作,重新登录可断点上传。后台记录文件上传的分片信息,当上传过程被终止以后,重新登录查询当前用户未上传成功的续传列表。存储那边会保留已上传的分片,断点上传从未上传的分片开始,可大大减少大文件上传终止需重新上传的时间。
文件拖拽上传
H5新特性可实现文件拖拽上传。其中,与拖拽文件相关的事件有dragover(文件拖拽在悬浮)
、dragleave(文件拖拽离开)
、drop(文件拖拽放下)。在事件对象中,一个
e.dataTransfer
这样的属性,它是一个DataTransfer
类型的数据,有如下的属性
属性 | 类型 | 说明 |
files | FileList | 拖拽的文件列表 |
items | DataTransferItemList | 拖拽的数据(有可能是字符串) |
types | Array | 拖拽的数据类型 该属性在Safari下比较混乱 |
完整的组件代码:
<template> <div class="uploadMask" style="position: fixed" :class="{ 'is-dragover': dragover }" v-show="dragover" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false" > <slot></slot> </div> </template> <script> export default { name: 'ElUploadDrag', props: { disabled: Boolean }, data() { return { dragover: false }; }, methods: { onDragover() { if (!this.disabled) { this.dragover = true; } }, /*eslint-disable*/ onDrop(e) { let _this = this; if (!this.disabled) { let event = e || window.event; this.dragover = false; let df = event.dataTransfer; // 拖曳操作的过程中,我们可以用过dataTransfer对象来传输数据,以便在拖曳操作结束的时候对数据进行其他的操作; let len = df.files.length; let dealFileCnt = 0; // 读取文件是个异步的过程,需要记录处理了多少个文件了 let files = []; function callback (files) { // 抛出文件数组 _this.$emit('file', files); } // 检测是否已经把所有的文件都遍历过了 function checkDropFinish () { if ( dealFileCnt === len - 1 ) { console.log('ie'); callback(files); // 所有的文件都遍历过了emit 出去 } dealFileCnt++; } if (df.items) { // 有dataTransfer项目列表时 for (let i = 0; i < len; i++) { let entry = df.items[i].webkitGetAsEntry(); // 读取拖拽元素信息 if (entry.isFile && !entry.isDirectory) { // isDirectory是否是文件夹 files.push(df.files[i]); } } callback(files); } else { // ie浏览器 for (let i = 0; i < len; i++) { let dropFile = df.files[i]; if (dropFile.type) { files.push(dropFile); checkDropFinish() } else { try { var fileReader = new FileReader(); fileReader.readAsDataURL(dropFile.slice(0, 3)); fileReader.addEventListener('load', function (e) { console.log(e, 'load'); files.push(dropFile); checkDropFinish(); }, false); fileReader.addEventListener('error', function (e) { console.log(e, 'error,不可以上传文件夹'); checkDropFinish(); }, false); } catch (e) { console.log(e, 'catch error,不可以上传文件夹'); checkDropFinish(); } } } } } } }, mounted() { let app = document.getElementsByClassName('app')[0]; app.ondragstart = function (e) { // 拖拽开始 e.preventDefault();//取消默认的链接元素和图片元素拖拽会触发拖拽上传 }; app.addEventListener('dragover', (e) => { // 拖拽到另一个容器是促发 e.preventDefault(); this.onDragover(); }); } }; </script>