基于vue的h5文件切片上传(获取文件md5,实现秒传、进度条实现)

原创
2018/07/20 11:45
阅读数 8K

template

<button @click="file"></button>
<label ref="upload"
       style="position: relative;">
  <input type="file"
         @change="selectFile"
         style="position: absolute; width: 1px; height: 1px; opacity: 0; z-index: -1;">
</label>

script

methods: {
    file() {
        // 模拟点击file input触发选择文件,注意:不能在任何方式的回调里面执行此语句
        this.$refs.upload.click()
    }
    selectFile(event) {
        // 调用上传方法,传入选择的文件对象
        this.uploadFile(event.target.files[0], () => {
            // upload-success
        })
        // 重置file input控件的值
        event.target.value = ''
    }
}

核心js文件

// uploadFile.js
import Vue from 'vue'
import SparkMD5 from 'spark-md5'

export default function() {

    // 将上传文件的方法挂载到vue的原型链上面
    Vue.prototype.uploadFile = uploadFile

    function uploadFile(file, backtopage) {

        // 得到md5码
        getFileMD5(file, md5 => {
            // 存储文件的md5码
            file.md5 = md5
            // 拿md5码查询后台数据库是否存在此md5码,如果存在则无需上传
            // handleAjax为封装好的ajax方法
            this.handleAjax('api/doc/file/getFileByMd5', {
                md5,
                name: file.name
            }, res => {
                if (!res.data) { // 不存在
                    // 开始上传文件
                    uploadChunk(this, file, 0, backtopage)
                } else { // 秒传
                    // 文件上传成功
                    backtopage && backtopage()
                }
            })
        })
    }

    // currentChunk为上传文件块的索引
    function uploadChunk(that, file, currentChunk, backtopage) {
        var fileReader = new FileReader(),
            // 上传文件块的大小,可自定义
            chunkSize = 2097152,
            // 计算改文件的可分为多少块
            chunks = Math.ceil(file.size / chunkSize)

        // 文件切割后的回调,this.result为切割的文件块
        fileReader.onload = function(e) {
            // 用FormData传输文件对象
            let fd = new FormData()
            // 设置文件上传接口的需要的参数
            fd.append('md5', file.md5)
            fd.append('chunks', chunks)
            fd.append('chunksize', chunksize)
            fd.append('currentChunk', currentChunk)
            // 设置上传的当前的文件块
            fd.append('fileObj', new Blob([this.result]))

            let xhr = new XMLHttpRequest()
            xhr.open('post', 'api/common/file/upload', true)
            xhr.onreadystatechange = function() {
                // 上传成功
                if (xhr.readyState == 4 && xhr.status == 200) {
                    currentChunk++
                    if (currentChunk < chunks) {
                        loadNext() // 继续切割下一块文件
                    } else {
                        // 文件上传成功
                        backtopage && backtopage()
                    }
                    xhr = null
                    return
                }
                if (xhr.readyState == 4 && xhr.status == 500) {
                    // 文件上传出错
                }
            }
            // 文件上传进度条
            xhr.upload.onprogress = function(e) {
                // 计算上传的进度
                const progress = parseInt((e.loaded + currentChunk * chunkSize) / file.size * 100)
                // 更新ui
            }
            xhr.onerror = xhr.upload.onerror = function() {
                // 文件上传出错
            }
            //开始发送
            xhr.send(fd)
            fd = null
        }

        //处理单片文件的上传
        function loadNext() {
            // 查询后台判断当前块文件有没有上传,如果已经上传则不需要上传,继续读取下一块
            that.handleAjax('api/common/file/checkChunk', { md5: upload.md5, chunk: currentChunk }, res => {
                // 后台返回没有上传过
                if (res.data === false) {
                    // 计算切割文件的开始索引和结束索引
                    var start = currentChunk * chunkSize,
                        end = Math.min(start + chunkSize, file.size)
                    // 切割文件并触发fileReader.onload
                    fileReader.readAsArrayBuffer(file.slice(start, end))
                    // 后台返回此块已经上传过,递归调用loadNext,继续判断下一块文件块
                } else {
                    currentChunk++
                    if (currentChunk < chunks) {
                        loadNext()
                    }
                }
            }, err => {
                // 文件上传出错
            })
        }
        // 触发文件第一块上传
        loadNext()
    }

    // 获得文件md5
    function getFileMD5(file, callback) {
        //声明必要的变量
        var fileReader = new FileReader(),

            //文件每块分割2M,计算分割详情
            chunkSize = 2097152,
            chunks = Math.ceil(file.size / chunkSize),
            currentChunk = 0,

            //创建md5对象(基于SparkMD5)
            spark = new SparkMD5()

        //每块文件读取完毕之后的处理
        fileReader.onload = function(e) {
            //每块交由sparkMD5进行计算
            spark.appendBinary(e.target.result)
            currentChunk++

            //如果文件处理完成计算MD5,如果还有分片继续处理
            if (currentChunk < chunks) {
                loadNext()
            } else {
                callback(spark.end())
            }
        }

        //处理单片文件的上传
        function loadNext() {
            var start = currentChunk * chunkSize,
                end = start + chunkSize >= file.size ? file.size : start + chunkSize

            fileReader.readAsBinaryString(file.slice(start, end))
        }

        loadNext()

    }

}

将uploadFile.js挂载带vue上面

// main.js

// Import Vue
import Vue from 'vue'

// Import upload
import uploadFile from './assets/js/uploadFile'

// 把封装好的文件下载挂载到vue上
Vue.use(downloadFile)
展开阅读全文
加载中
点击加入讨论🔥(11) 发布并加入讨论🔥
打赏
11 评论
14 收藏
2
分享
返回顶部
顶部