文件上传与读取实时进度展示 | by Vue

4,898 阅读3分钟

监听进程的方法onprogress

1 )文件读取的progress事件属于FileReader对象。

2 )下载的progress事件属于XMLHttpRequest对象。

3 )上传的progress事件属于XMLHttpRequest.upload对象。

本案例主要实现了文件读取文件上传进度的可视化显示

demo功能

  • 可添加并上传多个文件,每添加一个文件,会进行读取与上传,上传后存储文件与文件信息缓存于files数组中;
  • 文件添加后,先显示读取进度条,读取100%后开始上传并显示上传进度条;
  • 读取文件时,文件标题会显示文件读取中...,进度条为文件读取进度;
  • 上传文件时,文件标题更新为具体文件名称与已上传的实时数据量,动态更新上传量,进度条为上传进度;
  • 上传完毕,自动隐藏进度条;
  • 注意点1:页面上只有一个input[type=file]框,但为了存储多个文件的二进制数据,这里将二进制数据缓存到files数组中,input[type=file]框只作为实时数据的处理使用;
  • 注意点2:读取进度在实际应用中是可以省略的,本demo中读取后会将文件二进制数据缓存如files数组中,如果省略这步,可以不用读取文件。在这里主要用于体验文件读取进度的显示和缓存数据;

页面与样式

<ul>
    <li v-for="(file, n) in files" v-show="file.readPercent" class="file_item">
        <span class="file_name" v-if="!file.fileName">
            文件读取中...
        </span>
        <span class="file_name" v-else>
            {{ file.fileName }}({{ file.uploadSize}})
        </span>
        <div class="progress_bar" v-show="file.readPercent !== '100%'">
            <div :class="['percent', 'bar' + n]" :style="'width:' + file.readPercent"></div>
        </div>
        <div class="progress_bar" v-show="file.uploadPercent !== '100%'">
            <div :class="['upload', 'bar' + n]" :style="'width:' + file.uploadPercent"></div>
        </div>
        <button @click="del(n)" class="del iconfont icon-delete" v-show="file.uploadPercent === '100%'"></button>  
    </li>
    <li>
        <input type="file" name="file" id="file" ref="file" @change="handleFileSelect" v-show="false"/>
        <label for="file" style="color: #4C84FF;font-size: 14px;line-height: 32px;cursor: pointer;">Add attachment</label>
    </li>
</ul>
<style>
        ul {
            background-color: #f2f2f2;
            border-radius: 4px;
            overflow: hidden;
            border: 1px solid #ddd;
            padding-left: 0;
        }
        ul::before {
            content: 'Attachment';
            height: 32px;
            line-height: 32px;
            display: block;
            padding-left: 11px;
        }
        ul li.file_item {
            margin-bottom: 1px;
        }
        ul li {
            list-style: none;
            position: relative;
            background-color: #fff;
            height: 32px;
            padding-left: 11px;
            overflow: hidden; /* 解决margin塌陷 */
        }
        ul li .file_name {
            font-size: 14px;
            color: #111;
            font-weight: bolder;
            line-height: 32px;
            position: absolute;
        }
        ul li .del {
            position: absolute;
            right: 8px;
            top: 6px;
            font-size: 19.8px;
            width: 19.8px;
            height: 19.8px;
            cursor: pointer;
            padding: 0;
            border: 0;
            color: #666;
        }
        .progress_bar {
            background-color: #fff;
            border-radius: 100px;
            overflow: hidden;
          margin: 12px 166px 12px 206px;
          border: 1px solid #ddd;
          line-height: 8px;
          height: 8px;
          /* font-size: 14px; */
          clear: both;
          opacity: 1;
          -moz-transition: opacity 1s linear;
          -o-transition: opacity 1s linear;
          -webkit-transition: opacity 1s linear;
        }
        .progress_bar.loading {
          opacity: 1.0;
        }
        .progress_bar .percent, .progress_bar .upload {
          background-color: #aaa;
          height: 100%;
          width: 0;
        }
      </style>

方法实现

每选择一次文件,files数组中添加一项占位,视图更新显示该条数据处理情况;

handleFileSelect(e) {
    // 加入this.files
    this.files.push({})
    this.$nextTick(tick=>{
        this.getFile(e.target)
    })
},

读取文件后显示文件名

getFile(target) {
    if (!target.files[0]) return;
    let filevalue = target.value;
    let index = filevalue.lastIndexOf('.');
    let file = {}
    file.fileName = target.files[0].name
    file.filesExtension = filevalue.substring(index)
    let self = this
    this.readFile(target.files[0]).then(res => {
        file.fileData = res // 缓存读取的二进制数据
        Object.assign(self.files[self.files.length - 1], {
            fileName: file.fileName, // 文件名
            filesExtension: file.filesExtension, // 扩展名
            fileData: file.fileData,
            readPercent: '100%'
        })
        self.upload(target.files[0])
        self.$forceUpdate()
    }).catch(e => {
        console.log(e)
    })
},
readFile(file) {
    let self = this;
    return new Promise(function(resolve, reject) {
        let fileCard = self.files[self.files.length - 1];
        
        // 在选择新文件后重置进度指示器
        self.$set(fileCard, 'readPercent', '0%');
    
        let reader = new FileReader();

        reader.onprogress = self.updateProgress; // 更新进度条
        reader.onerror = function (e) {
            switch(e.target.error.code) {
                case e.target.error.NOT_FOUND_ERR:
                    reject('文件没找到');
                    break;
                case e.target.error.NOT_READABLE_ERR:
                    reject('文件不可读');
                    break;
                default:
                    reject('读取文件时出错');
            };
        }
        reader.onloadend = function (res) {
            // 确保进度条最后显示100%
            self.$set(fileCard, 'readPercent', '100%');
            resolve(res.target.result)
        }
    
        // 将文件作为二进制数组读入
        reader.readAsArrayBuffer(file);
    })
},

监听读取文件进度条

updateProgress(e) {
    // e 是一个 ProgressEvent.
    if (e.lengthComputable) {
        let percentLoaded = ((e.loaded / e.total) * 100).toFixed(2);
        let fileCard = this.files[this.files.length - 1];
        this.$set(fileCard, 'fileSize', this.renderSize(e.loaded))
        // 更新进度条长度
        if (percentLoaded < 100) {
        this.$set(fileCard, 'readPercent', percentLoaded+ '%');
        }
    }
},

上传文件并监听上传进度

upload(fileObj) {
    let formFile = new FormData();
    formFile.append("attachment", fileObj); //加入文件对象

    let fileCard = this.files[this.files.length - 1];
    $.ajax({
        url: window.location.origin + '/upload',
        data: formFile,
        type: "post",
        dataType: "json",
        cache: false,//上传文件无需缓存
        processData: false,//用于对data参数进行序列化处理 这里必须false
        contentType: false, //必须
        xhr: () => {
            let xhr = $.ajaxSettings.xhr();
            if (xhr.upload) {
                xhr.upload.onprogress = (e) => {
                    let percent=e.loaded/e.total;//文件上传百分比
                    if (percent <= 1) {
                        this.$set(fileCard, 'uploadPercent', (percent * 100).toFixed(2) + '%')
                        this.$set(fileCard, 'uploadSize', this.renderSize(e.loaded))
                    }
                };
            }
            return xhr;
        },
        error: (error) => {
            console.log(error)
        },
        success: (result) => {
            this.$set(fileCard, 'uploadPercent', '100%')
            this.$set(fileCard, 'filePath', result.file.filepath) // 添加接口返回的文件信息,如果没有可以忽略
        },
        complete: ()=> {
            this.clearFile() // 上传完毕清空input[type=file]
        }
    })
},

清空input[type=file]

clearFile() {
    this.$refs.file.value = ''
},

文件大小的“可视化”,把比特值换算成常用单位显示

renderSize(value){
   if(null==value||value==''){
       return "0 Bytes";
   }
    let unitArr = new Array("Bytes","KiB","MiB","GiB","TiB","PiB","EiB","ZiB","YiB");
    let index=0;
    let srcsize = parseFloat(value);
    index=Math.floor(Math.log(srcsize)/Math.log(1024));
    let size =srcsize/Math.pow(1024,index);
    size=size.toFixed(2);//保留的小数位数
    return size+unitArr[index];
},

删除文件

del(n) {
    this.files.splice(n, 1)
},

demo下载

github.com/bambibren/u…

体验要点

由于我的demo是本地服务器,上传速度会比较快。而且真实的上传和文件读取速度可能比你想象的要快,所以小文件的进度可能会一闪而过。

【建议:可以将网络调成slot3G,这样多数文件上传进度就都可以看到了。至于文件读取进度,需要上传较大的文件才可以看到。】

另外,建议测试完毕后,清空upload文件件里上传的文件,并且清空废纸篓/回收站。避免给电脑存储增加负担。

欢迎指教

即学即分享,还请各位大佬多多指出优化与问题,我才能进步!