支持IE11的文件分片逐一上传的方法(vue)

介绍

实现一个大文件分片上传的功能,仅支持单个文件的分片逐一上传,对上传的文件没有大小限制,但上传速度没有过互联网的测验,本地测验,上传速度约等于本地文件拷贝速度,有很多弊端,但可满足基本的大文件分片上传功能。

本文仅为前端vue,如需后台java,请移步我的另一篇文章:大文件分片上传的后台方法(Java)

1 HTML部分

没啥可说的,使用Element-ui的<el-upload>标签,上代码

1.1 实现文件的选择与上传(使用Element-ui的<el-upload>标签)

<template>
	<div>
		<el-upload
			:accept="accept"
			:auto-upload="auotUpload"
			action=""
			:multiple="multiple"
			:http-request="prepareUpload"
		>	
		</el-upload>
	</div>
</template>

1.2 实现文件上传的一个进度条(使用Element-ui的<el-progress>标签)

<template>
	<div>
		<el-card style="margin-top:2px;margin-lift:2%;margin-right:2%;height:40px;" >
			<div style="margin-top:4px;margin-lift:2%">
				<el-row>
					<el-col :span="8">
						<div style="margin-top:8px;margin-lift:2%">{{fileInfo.fileName}}</div>
					</el-col>
					<el-col :span="8">
						<div style="margin-top:8px;margin-lift:2%">
							<el-progress :percentage="percentage" type="line" :text-inside="true" :stroke-width="strokeWidth" :status="status"/>
						</div>
					</el-col>
					<el-col :span="3">
						<div style="margin-top:8px;margin-lift:2%">{{fileInfo.fileName}}/{{fileInfo.allNum}}Mb</div>
					</el-col>
					<el-col :span="5">
						<el-button type="text" @click="usable=1">暂停</div>
						<el-button type="text" @click="prepareUpload(0)">继续</div>
					</el-col>
				</el-row>
			</div>
		</el-card>
	</div>
</template>

2 JavaScript部分

2.1 参数部分

data(){
	return:{
		//文件参数
		fileInfo:{
			md:"",	//文件唯一标识码,类似MD5,如果拥有MD5技术可以替换为MD5
			allNum:"",	//文件分片后的全部分片数量
			successNum:"",	//上传成功的分片文件数量
			fileName:"",	//文件名
			detail:"",	//文件描述
			path:"",	//文件存储路径
			type:0,		//文件状态,0为未上传完成,1为上传完成,用于查询时是否展示
			parentId:"",	//文件类型的父类,用于查询时是否展示
			fileTypeId:""	//文件类型,用于查询时是否展示
		}SIZE:1024*1024,	//控制单个分片文件的大小

		//以下为`el-upload`标签使用的参数
		accept:"",	//控制默认选择的文件类型
		autoUpload:true,	//是否在选择文件后自动开始上传文件
		uploadURL:"",	//文件上传地址这里为空,因为不需要使用这个参数,但是必须有
		baseURL:"",		//文件上传地址的根地址,后边方法中会拼接完成完整的地址,根地址一般为:http://127.0.0.1:8080/demo/......
		limit:1,	//支持同时上传的文件数量
		multiple:false,	//是否支持文件多选
		showFileList:false,	//是否显示文件列表
		file:"",	//上传文件列表
		
		//以下为进度条组件所用参数
		strokeEidth:12,	//进度条的高度
		percentage:0,	//进度条百分比
		sataus:"exception",	//进度条状态
		
		//以下用于控制文件上传功能是否开启
		usable=1
	}
}

2.2 方法部分

2.2.1 开始上传之前的验证方法

prepareUpload(params){		//准备开始上传
	//这里检查是否有文件正在上传,如果上传开始,则usable会被修改为0
	if(this.usable===0){return this.$message({message:"当前正在执行文件上传任务,请在当前任务结束后再启动次功能",type="warning"})}
	let file=params.file
	//这里判断该方法是通过哪里调用的,
	//如果是通过选择文件调用,则params!==0,则将文件保存到this.file
	//如果是通过继续按钮调用,则params===0,则取用this.file继续执行文件上传
	if(params===0){
		//这里判断一下是否真的有文件在上传,如果没有,则退出任务
		if(this.file===""){return this.$message({message:"当前暂无任务,无法继续",type="warning"})}
		file=this.file;
		this.$message({message:"文件开始继续上传",type="success"})
	}else{
		file=params.file;
		this.file=params.file
	}
	//这里验证文件类型,可以根据自己需要进行验证,这里验证是否为zip或rar文件,如果不是则不允许上传
	if(!(file.name.substring(file.name.length-4)===".zip"||!(file.name.substring(file.name.length-4)===".rar"){
		return this.$message({message:"请选择zip或rar文件",type="warning"})
	}
	//这里验证文件大小,可自行修改限制
	if(file.size>1024*1024*1024*3){return this.$message({message:"您无法选择一个大于3GB的文件",type="warning"})}
	if(file.size<=0){return this.$message({message:"您无法选择一个大于大小为0的文件",type="warning"})}
	//开始执行文件上传的前置操作,将usable修改为0
	this.usable=0
	//修改文件名,用于文件下载时的为文件重命名后带有文件后缀,避免文件无法打开问题
	if(this.fileInfo.fileName===""){
		this.fileInfo.fileName=file.name
	}else{
		const a=this.fileInfo.fileName.substring(this.fileInfo.fileName.length-3)
		if(!(this.fileInfo.fileName.substring(this.fileInfo.fileName.length-3)===".7z"||
		this.fileInfo.fileName.substring(this.fileInfo.fileName.length-3)==="zip"||
		this.fileInfo.fileName.substring(this.fileInfo.fileName.length-3)==="rar"||)
		){
			if(fileName.substring(file.name.length-3)===".7z"){
				this.fileInfo.fileName+=file.name.substring(file.name.length-3)
			}else{
				this.fileInfo.fileName+=file.name.substring(file.name.length-4)
			}
		}
	this.getMD5(file)	//自己写的一个简易版计算文件唯一标识码,具体介绍在方法中有
	this.getFileChunk(file)		//获取文件分片集合,用于逐一上传
	//这里写一个请求,用于判断文件是否已经上传,判断文件的状态是否已经上传完成
	//这里请求方法请使用自己的,我是自己封装的请求方法
	el.post("请求地址"this.fileInfo).then((result)=>result.json()).then((result)=>{
		this.getResult(result)	//因为很多地方需要写同样的内容,就封装了一个方法来将返回值存储起来
		//判断一下文件状态,如果为1则表示服务器中存在与该文件相同的文件,则无需再次上传,执行秒传,否则继续执行
		if(result.result.type===1){
			//执行秒传时需要对数据库进行写入新文件名的操作,所以还需要一个请求来完成
			el.post("请求地址"this.fileInfo).then((result)=>result.json()).then((result)=>{
				if(result.result.type===1){return this.$message({message:"秒传成功",type:"success"})
			}
		}else{
			this.beforeUpload()		//如果文件没有秒传则调用上传之前的方法
		}
	}
}

2.2.2 开始上传文件

开始上传之前

beforeUpload(){
	//循环调用该方法,实现对fileList的逐一上传,该方法主要为避免vue的异步执行导致逻辑错误
	//实现方式为通过成功上传次数与总分片数量比较判断何时return
	//成功上传次数为后台返回,所以js无需做次数递增,判断依据为this.fileInfo.successNum
	this.beginUpload()	//调用开始文件上传方法
}

开始上传文件

beginUpload(){
	//判断用户是否操作了暂停按钮,如果是则终止任务
	if(this.usable===1){return this.$message({message:"任务已暂停",type:"success"})}
	//创建请求头
	const headers=new Headers({'Csrf-Token':window,localStorage.getItem('csrfToken')})
	//创建请求体
	const body=this.buildBody()
	//判断文件为首次上传还是继续上传,这里主要是后台对不同状态有不同操作,需要分开操作,不能使用同一个接口
	//首次上传文件与继续上传文件两个方法的区别只有url不同,(参数也不同,但是是通过其他方法封装的,无需考虑)
	if(this.fileInfo.type===0){
		const url=this.baseURL+""	//这里完成完整URL的拼接
		//如果文件状态为0则执行继续上传
		this.uploadFile(url,headers,body)
	}else if(this.fileInfo.type!==0||this.fileInfo.type!==1){
		const url=this.baseURL+""	//这里完成完整URL的拼接
		//如果文件状态不为0或1则执行首次上传
		this.uploadFile(url,headers,body)
}

2.2.3 上传文件

uploadFile(url,headers,body){
	//这里上传文件的方法我使用的fetch,如果不支持可以考虑使用ajax等,需自行修改请求方式
	fetch(url,{"method":"POST","headers":headers,"body":body}).then(response=>response.json()).then(result=>{
		this.getResult(result)	//因为很多地方需要写同样的内容,就封装了一个方法来将返回值存储起来
		//判断一下分片文件列表是否已经全部上传,如果是则提示上传成功
		if((this.fileInfo.successNum===1&&this.fileInfo.allNum===1)&&this.fileInfo.type===1){
			this.$message({message:"上传成功",type:"success"})
			this.usable=1
			this.findFileListInfo()//调用查询文件方法(该文章中没有这个文件查询方法)
			return this.resetFileInfo()	//调用重置数据方法并结束程序执行
		}
		this.beginUpload()	//如果判断没有完成则再次执行文件开始上传(此处开始循环调用,直到return为止)
	}
}

2.2.4 获取文件的MD5

getMD5(file){
	//获取文件的最后一次修改时间并用空格拆分为数组(因为好固定内容,对这个时间做一个处理,减少MD5值的长度)
	const dateList=file.lastModifiedDate.toString().split(/ /)	//注意:两个/之间有空格
	let date=""
	for(let i=0;i<5,i++){date+=dateList[i]}
	//MD是将文件名、最后一次修改时间通过一个自定义的简易HASH方法处理后用-拼接,最后在拼接-文件长度获得
	this.fileInfo.md=this.HASH(file.name)+"-"+this.HASH(date)+"-"+file.size.toString()
}
HASH(info){
	//定义一个编码字符集
	const I64Bit_TABLE='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghizklmnopqrstuvwxyz0123456789+_`~·()()“”"\''
	let hash=5381
	let i=info.length-1
	//判断info是String类型还是Array类型
	if(typeof info=="String"){
		for(li>-1;i--){
			hash+=(hash<<5)+info.charCodeAt(i)
		}
	}else{
		for(;i>-1;i--){
			hash+=(hash<<5)+info[i]
		}
	}
	let value=hsah&0x7FFFFFFF	//7个F
	let resultValue=""
	do{resultValue+=I64Bit_TABLE[value&0x3F]}
	while(value>>=6)
	return resultValue
}

2.2.5 获取文件分片数量/文件分片List

getFileChunk(file){
	//判断文件是否为空
	if(file.size===0){return this.$message({message:"文件解析失败",type="warning"})}
	//判断文件大小是否大于一个分片的大小
	if(file.size>this.SIZE){
		//如果文件大小大于一个分片的大小,则循环拆分文件并将每一个分片存入this.fileList
		for(let i=0;i*this.SIZE<file.size;i++){
			this.fileList.push(file.slice(i*this.SIZE,(i+1)*this.SIZE))
			this.fileInfo.allNum=i+1
		}
	}else{
		this.fileInfo.allNum=1
		this.fileList.push(file)
	}
}

2.2.6 构建请求体

buildBody(){
	//创建请求体
	const formData=new FormData()
	//将本次请求需要用到的文件分片临时存储在const file中
	const file=this.fileList[this.fileInfo.successNum]
	//开始封装请求体
	formData.append("md",this.fileInfo.md)
	formData.append("allNum",this.fileInfo.allNum)
	formData.append("successNum",this.fileInfo.successNum)
	formData.append("path",this.fileInfo.path)
	formData.append("type",this.fileInfo.type)
	formData.append("fileName",this.fileInfo.fileName)
	formData.append("detail",this.fileInfo.detail)
	formData.append("size",this.fileInfo.size)
	formData.append("parentId",this.fileInfo.parentId)
	formData.append("fileTypeId",this.fileInfo.fileTypeId)
	formData.append("file",file)
	return formData
}

2.2.7 获取请求的返回值

//因为不是全部的返回值都需要用到,所以只需要获取需要的值即可,写多了还可能出现获取不到而报错,导致终止运行
getResult(result){
	this.fileInfo.allNum=result.result.allNum
	this.fileInfo.successNum=result.result.successNum
	this.fileInfo.path=result.result.path
	this.fileInfo.type=result.result.type
	//下边是计算当前上传进度条的百分比,用于进度条进度显示,Math.round()实现对括号中数字的向上取整
	this.percentage=Math.round(this.fileInfo.successNum/this.fileInfo.allNum*100)
}

2.2.8 重置请求参数

resultFileInfo(){
	this.fileInfo.md=""
	this.fileInfo.successNum=""
	this.fileInfo.allNum=""
	this.fileInfo.fileName=""
	this.fileInfo.detail=""
	this.fileInfo.path=""
	this.fileInfo.type=""
	this.fileInfo.parentId=""
	this.fileInfo.fileTypeId=""
	this.fileList=[]
	this.file=""
	this.percentage=0
}

本文全部更新已完成

如果觉得不错请点赞收藏支持一下,有问题请评论

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值