vue大文件分片上传vue-simple-uploader使用
自定义组件的封装,默认样式修改
vue 插槽的使用
关于小文件上传类似图片之类的,我们可以直接用很多ui框架封装的上传组件,或者自己写一个input 上传,利用FormData 对象提交文件数据,
但是对于比较大的文件,比如我的这个项目就要求在客户端上传2G左右的文件(难受,http那种),就需要将文件分片上传(file.slice()),否则中间http长时间连接可能会断掉。
我这里使用的是vue-simple-uploader,一个基于simple-uploader封装的上传插件,因此你需要先了解simple-uploader及vue-simple-uploader相关API
地址:
simple-uploader
vue-simple-uploader
样式:https://xukaishan.github.io/
在使用之前需要了解:
我们需要修改默认样式(我这里结合element-ui),请先下载vue-uploader源码,查看源码方便我们使用插槽替换默认样式及使用它的相关方法
chunkNumber: 当前块的次序,第一个块是 1,注意不是从 0 开始的。
totalChunks: 文件被分成块的总数。
chunkSize: 分块大小,根据 totalSize 和这个值你就可以计算出总共的块数。注意最后一块的大小可能会比这个要大。
currentChunkSize: 当前块的大小,实际大小。
totalSize: 文件总大小。
identifier: 这个就是每个文件的唯一标示。
filename: 文件名
.upload() 开始或者继续上传。
.pause() 暂停上传。
.resume() 继续上传。
.cancel() 取消所有上传文件,文件会被移除掉。
.progress() 返回一个0-1的浮点数,当前上传进度。
.isUploading() 返回一个布尔值标示是否还有文件正在上传中。
.addFile(file) 添加一个原生的文件对象到上传列表中。
.removeFile(file) 从上传列表中移除一个指定的 Uploader.File 实例对象。
更多查看官方api文档 https://github.com/simple-uploader/Uploader/blob/develop/README_zh-CN.md#
安装
npm install vue-simple-uploader --save
import Vue from ‘vue’
import uploader from ‘vue-simple-uploader’
Vue.use(uploader)
这里先贴出封装的代码:
<template>
<el-dialog
custom-class="myUpLoad"
title="资源上传"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="600px"
:before-close="handleClose"
@open="openDialog"
>
<div class="list_content" v-loading="isDisable" element-loading-text="资源解析中...">
<uploader
:options="options" //配置项
:autoStart="false"
ref="uploader"
class="uploader-example"
@file-added="onFileAdded" //绑定文件上传事件回调,下面会介绍
@file-success="onFileSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
>
<uploader-unsupport></uploader-unsupport> //浏览器不支持将显示此内容
<uploader-list>
<template slot-scope="props">
<ul>
<li v-for="file in props.fileList" :key="file.id" class="liItem">
<uploader-file :file="file" :list="true" ref="uploaderFile" :pauseFlag="pauseFlag">
<template slot-scope="props">//使用插槽替换组件默认样式
<div class="filebox">
<p class="fileNameBox">
<span class="fileIcon"></span>
{{file.name}}
</p>
<p class="fileProgressBox">
<el-progress
class="progressLength"
:percentage="parseInt((props.progress.toFixed(2))*100-1<0?0:(props.progress.toFixed(2))*100-1)"
></el-progress>
<span
:class="{statusBtn:!pauseFlag,resumeBtn:pauseFlag}"
class="progressBtn"
@click="pause(file,file.resourceId)"
></span>
<span class="cancelBtn progressBtn" @click="remove(file,file.resourceId)"></span>
</p>
<p class="fileInfoBox">
<span class="fileInfoItem">速度:{{pauseFlag? 0: props.formatedAverageSpeed}}</span>
<span
class="fileInfoItem"
>已上传:{{(parseFloat(props.formatedSize)*props.progress).toFixed(1)}}/{{props.formatedSize}}</span>
<span
class="fileInfoItem"
>剩余时间:{{pauseFlag? 0: props.formatedTimeRemaining}}</span>
</p>
</div>
</template>
</uploader-file>
</li>
</ul>
</template>
</uploader-list>
//开始上传按钮
<uploader-btn ref="uploadBtn" id="global-uploader-btn" :attrs="attrs">添加</uploader-btn>
</uploader>
</div>
<div slot="footer" class="dialog-footer upLoadFooter">
<el-button plain size="medium" @click="cancelAddRes" class="el-button cancel_btn">取 消</el-button>
<el-button
:class="{none:isUploading}"
size="medium"
@click="continueAddRes"
class="el-button continueAddRes_btn"
id="continueAddRes_btn"
>添加</el-button>
</div>
</el-dialog>
</template>
options配置及processParams参数
<script>
var resourceId = "";//保存资源id
import SparkMD5 from "spark-md5";
export default {
data() {
return {
//上传组件配置
options: {
target: UPLOAD,
testChunks: false, //是否开启服务器分片校验
maxChunkRetries: 5, //最大自动失败重试上传次数
chunkSize: 3 * 1024 * 1024,//分片大小
singleFile: true,//单文件上传
simultaneousUploads: 1,
processParams(params) {//每一次分片传给后台的参数,params是该方法返回的形参,包含分片信息
return {//返回一个对象,会添加到每一个分片的请求参数里面
name: params.filename,
code: params.identifier,
total: params.totalChunks,
index: params.chunkNumber,
resourceId,
};
}
},
attrs: {
accept: ".zip"
}
};
},
组件通信我采用的是bus管理,在挂载是监听,销毁时注销监听事件,当然你如果是父子通信就没有这么复杂
mounted() {
//组件通信,需要监听开始上传,具体按自己业务决定
this.$root.Bus.$on("addResourceUpload", (params) => {
this.dialogVisible = true;
setTimeout(() => {
//隐藏原来的上传按钮,模拟点击开始上传
document.querySelector("#global-uploader-btn").click();
}, 50);
});
},
destroyed() {
this.$root.Bus.$off("addResourceUpload")
},
文件上传各个阶段回调,下面几个回调一般都会用到
//文件添加时回调,这里可以做格式校验,md5计算
onFileAdded(file) {
let typeName = file.file.name;
let reg = /\.zip$/;
if (!reg.test(typeName)) {
file.ignored = true;
this.$alert("上传文件格式不正确,请检查", "上传错误", {
confirmButtonText: "确定",
callback: action => {
}
});
} else {
// 计算MD5;
this.computeMD5(file);
}
},
//所有分片上传完成的回调,需要注意的是,由于异步上传,每一次分片上传不会等上一次响应回来才发送,
//因此该上传成功的回调时,返回的分片响应可能不是最后一片,因此后续会发送合并请求告诉后台进行合并文件
onFileSuccess(rootFile, file, message, chunk) {
//所有分片上传成功,发送合并分片请求,具体参数与后台确定,后台合并文件
},
//文件上传的过程中会频繁调用该函数
onFileProgress(rootFile, file, chunk) {},
//文件上传出错的回调
onFileError(rootFile, file, msg, chunk) {
}
计算Md5,生成唯一标识uniqueIdentifier
computeMD5(file) {
let that = this;
let fileReader = new FileReader();
let md5 = "";
file.pause();//先暂停,后续生成uniqueIdentifier
fileReader.readAsArrayBuffer(file.file);
fileReader.onload = e => {
if (file.size != e.target.result.byteLength) {
this.error("文件读取失败");
return;
}
md5 = SparkMD5.hash(file.name);//业务需求以文件名作为加密
file.uniqueIdentifier = md5;
if (md5 != "") {
file.resume();//继续上传文件
}
};
fileReader.onerror = function() {
this.error("文件读取失败");
};
},
uploader-list组件可以上传多个文件,多个file对象放在该组件里面上传,之前我是想做多个文件上传,发现浏览器较卡并且后台也不好处理,
于是就做单个大文件上传,单文件的话你可以不用uploader-list,直接使用uploader-file,我这里就不做修改了,uploader-file组件就是存放file对象的相关信息
vue-simple-uploader源码 的uploader-file组件部分代码:
<template>
<div class="uploader-file" :status="status">
<slot
:file="file"
:list="list"
:status="status"
:paused="paused"
:error="error"
:response="response"
:average-speed="averageSpeed"
:formated-average-speed="formatedAverageSpeed"
:current-speed="currentSpeed"
:is-complete="isComplete"
:is-uploading="isUploading"
:size="size"
:formated-size="formatedSize"
:uploaded-size="uploadedSize"
:progress="progress"
:progress-style="progressStyle"
:progressing-class="progressingClass"
:time-remaining="timeRemaining"
:formated-time-remaining="formatedTimeRemaining"
:type="type"
:extension="extension"
:file-category="fileCategory"
>
<div class="uploader-file-progress" :class="progressingClass" :style="progressStyle"></div>
<div class="uploader-file-info">
<div class="uploader-file-name"><i class="uploader-file-icon" :icon="fileCategory"></i>{{file.name}}</div>
<div class="uploader-file-size">{{formatedSize}}</div>
<div class="uploader-file-meta"></div>
<div class="uploader-file-status">
<span v-show="status !== 'uploading'">{{statusText}}</span>
<span v-show="status === 'uploading'">
<span>{{progressStyle.progress}}</span>
<em>{{formatedAverageSpeed}}</em>
<i>{{formatedTimeRemaining}}</i>
</span>
</div>
<div class="uploader-file-actions">
<span class="uploader-file-pause" @click="pause"></span>
<span class="uploader-file-resume" @click="resume">️</span>
<span class="uploader-file-retry" @click="retry"></span>
<span class="uploader-file-remove" @click="remove"></span>
</div>
</div>
</slot>
</div>
</template>
<script>
import Uploader from 'simple-uploader.js'
import events from '../common/file-events'
import { secondsToStr } from '../common/utils'
const COMPONENT_NAME = 'uploader-file'
export default {
data () {
return {
response: null,
paused: false,
error: false,
averageSpeed: 0,
currentSpeed: 0,
isComplete: false,
isUploading: false,
size: 0,
formatedSize: '',
uploadedSize: 0,
progress: 0,
timeRemaining: 0,
type: '',
extension: '',
progressingClass: ''
}
}
}
</script>
修改默认样式之前推荐看一下vue-simple-uploader源码,方便后续修改操作,在这里我们需要使用插槽,替换掉uploader-file默认元素,这里使用slot,代码如下:
<uploader-file :file="file" :list="true" ref="uploaderFile" :pauseFlag="pauseFlag">
<template slot-scope="props">
<div class="filebox">
<p class="fileNameBox">
<span class="fileIcon"></span>
{{file.name}}
</p>
<p class="fileProgressBox">
<el-progress
class="progressLength"
:percentage="parseInt((props.progress.toFixed(2))*100-1<0?0:(props.progress.toFixed(2))*100-1)"
></el-progress>
<span
:class="{statusBtn:!pauseFlag,resumeBtn:pauseFlag}"
class="progressBtn"
@click="pause(file,file.resourceId)"
></span>
<span class="cancelBtn progressBtn" @click="remove(file,file.resourceId)"></span>
</p>
<p class="fileInfoBox">
<span class="fileInfoItem">速度:{{pauseFlag? 0: props.formatedAverageSpeed}}</span>
<span
class="fileInfoItem"
>已上传:{{(parseFloat(props.formatedSize)*props.progress).toFixed(1)}}/{{props.formatedSize}}</span>
<span
class="fileInfoItem"
>剩余时间:{{pauseFlag? 0: props.formatedTimeRemaining}}</span>
</p>
</div>
</template>
</uploader-file>
<template slot-scope="props">
props可以获取到组件数据,比如props.formatedAverageSpeed获取格式化后的上传平均速度,
props.formatedTimeRemaining获取剩余时间等
具体样式代码我就不贴出来了,可以自行修改。