当前位置:   article > 正文

基于vue-simple-uploader封装文件分片上传组件

vue-simple-uploader


vue-simple-uploader 是基于 simple-uploader.js 和Vue结合做的一个上传组件,自带 UI,可覆盖、自定义。它支持文件、多文件、文件夹上传;支持拖拽文件、文件夹上传;可暂停、继续上传;支持秒传;上传队列管理,支持最大并发上传;分片上传;支持进度、预估剩余时间、出错自动重试、重传等操作。

文档

vue-simple-uploader文档
simple-uploader.js文档

安装

npm install vue-simple-uploader --save
  • 1

使用

因为我是单独使用的子组件,所以我只在子组件中使用,也可以放在main.js中

import uploader from 'vue-simple-uploader'
import Vue from 'vue'
Vue.use(uploader)
  • 1
  • 2
  • 3

在这里插入图片描述

html部分

<template>
	<div>
		<uploader
			ref="simUploader"
			:options="options"
			:autoStart="autoStart"
			:fileStatusText="fileStatusText"
			class="uploader"
			@file-added="onFileAdded"
			@files-added="onFilesAdded"
			@file-success="onFileSuccess"
			@file-progress="onFileProgress"
			@file-error="onFileError"
		>
			<uploader-unsupport></uploader-unsupport>
			<uploader-btn class="uploader-wrapper" :attrs="attrs">
				<uploader-drop :attrs="attrs">
					<img class="img-upload" :src="require('#/assets/images/icon-upload.png')" alt="" />
					<div class="uploader-drop-text">
						可直接点击或将文件拖拽至框内上传
					</div>
				</uploader-drop>
			</uploader-btn>
			<div class="uploader-list">
				<div v-for="(item, index) in uploaderList" :key="item.id + index" class="uploader-list-item">
					<uploader-file :file="item" hidden-complete list>
						<template #default="{ extension, file, formatedSize, progress, status }">
							<div class="uploader-list-item__inner">
								<i class="el-icon-document"></i>
								<template>
									<el-tooltip :content="file.name">
										<span class="file-info">
											{{ file.name }}
										</span>
									</el-tooltip>
									<md-el-icon
										name="el-icon-edit-outline edit-icon"
										@click="editFile(index)"
										style="font-size:16px"
									></md-el-icon>
								</template>
								<span>{{ formatedSize }}</span>
								<div v-if="file.name.split('.').pop() === 'zip' && enableUnzip">
									<el-radio-group v-model="file.enableUnzip">
										<el-radio :label="true">
											<span>解压</span>
										</el-radio>
										<el-radio :label="false">
											<span>不解压</span>
										</el-radio>
									</el-radio-group>
								</div>
								<!-- 状态分为2种,  上传,解压 -->
								<div style="width:30%">
									<div v-if="item.enableUnziping">
										<el-progress
											:percentage="item.progress"
											:status="item.status ? 'exception' : 'success'"
											:show-text="false"
											text-inside
										></el-progress>
									</div>
									<div v-else>
										<el-progress
											:percentage="progress * 100"
											:status="item.status ? 'exception' : 'success'"
											:show-text="false"
											text-inside
										></el-progress>
									</div>
								</div>
								<div style="width:50px">
									{{
										item.enableUnzipStatus
											? item.enableUnzipStatus
											: item.status
											? fileStatusText.error
											: fileStatusText[status]
									}}
								</div>
								<i
									class="el-icon-circle-close"
									@click="deleteFile(item, index)"
									style="font-size:16px"
								></i>
							</div>
						</template>
					</uploader-file>
				</div>
			</div>
		</uploader>
	</div>
</template>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

配置项

由于是对原simple-upload.js做的封装,大多数的配置可以参考原生js文档,部分配置与原生有些许不同,请注意仔细查看相关参考文档。

options: {
	target: this.uploadUrl, //目标上传地址URL,默认值为 '/'。
	singleFile: this.singleFile, // 单文件上传 默认 false
	chunkSize: 5 * 1024 * 1024, // 分片时按照该值来分。最后一个上传分片的大小是可能是大于等于1倍的这个值但是小于两倍的这个值大小
	forceChunkSize: true, // 是否强制所有的块都一定小于等于chunkSize
	fileParameterName: 'file', // 上传文件时文件的参数名,默认file
	maxChunkRetries: 5, // 最大自动失败重试上传次数,值可以是任意正整数,如果是 undefined 则代表无限次,默认 0
	testChunks: true, // 是否开启服务器分片校验
	simultaneousUploads: 6, // 并发上传数,默认 3
	chunkRetryInterval: null, // 重试间隔,值可以是任意正整数,如果是 null 则代表立即重试,默认null
	query: {}, // 其他额外的参数
	headers: this.uploadHeaders, // 额外的一些请求头,例如有时我们需要在header中向后台传递token,默认为对象: {}
	processParams: (params, file) => {
		// 处理请求参数 一般用于修改参数名字或者删除参数 这里我处理了压缩文件是否可以解压
		if (this.enableUnzip && file.enableUnzip) {
		}
		params.md5 = file.md5
		params.identifier = file.identifier
		return params
		}
},
fileStatusText: {
	success: '成功',
	error: '出错了',
	uploading: '上传中',
	paused: '暂停',
	waiting: '等待中'
},
autoStart:false, // 默认 true, 是否选择文件后自动开始上传。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

methods

onFileAdded(file) {
	//文件添加成功后可以进行文件格式、文件名称、文件大小、是否可重名等限制
	// 计算MD5,完成后开始上传操作
    this.computeMD5(file, () => {
		this.$emit('addFile', file)
	})
}
onFilesAdded(files, fileList) {
	console.log('on-files-added', files, fileList)
},
// 文件上传成功后,在“上传完成”的回调中,通过服务端合并文件
onFileSuccess(rootFile, file, message, chunk) {
	// 如果 需要合并
    if (this.needMerge) {
        request.mergerFile({
            tempName: res.tempName,
            fileName: file.name,
            ...file.params,
        }).then(data => {
            // 文件合并成功
            this.$emit('fileSuccess', data);
        }).catch(e => {});
    // 不需要合并    
    } else {
        this.$emit('fileSuccess', res);
        console.log('上传成功');
    }
}onFileProgress(rootFile, file, chunk) {
	// 文件进度的回调
	// console.log('on-file-progress', rootFile, file, chunk)
},
onFileError(rootFile, file, message, chunk) {
	console.log('on-file-error', rootFile, file, message, chunk)
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

MD5计算

断点续传及秒传的基础是要计算文件的MD5,这是文件的唯一标识,然后服务器根据MD5进行判断,是进行秒传还是断点续传。在file-added事件之后,就计算MD5,我们最终的目的是将计算出来的MD5加到参数里传给后台,然后继续文件上传的操作,详细的思路步骤是:
1)把uploader组件的autoStart设为false,即选择文件后不会自动开始上传
2)先通过 file.pause()暂停文件,然后通过H5的FileReader接口读取文件
3)将异步读取文件的结果进行MD5,这里我用的加密工具是spark-md5,你可以通过npm install spark-md5 --save来安装,也可以使用其他MD5加密工具。
4)file有个属性是uniqueIdentifier,代表文件唯一标示,我们把计算出来的MD5赋值给这个属性 file.uniqueIdentifier = md5,这就实现了我们最终的目的。
5)通过file.resume()开始/继续文件上传。

/**
 * 计算md5,实现断点续传及秒传
 * @param file
 * @param callback  计算完成的回调
 */
computeMD5(file, callback) {
	let fileReader = new FileReader()
	let time = new Date().getTime()
	let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
	let currentChunk = 0
	const chunkSize = this.options.chunkSize // 10 * 1024 * 1000;
	let chunks = Math.ceil(file.size / chunkSize) // 总块数
	let spark = new SparkMD5.ArrayBuffer()
	file.pause()
	file.md5Status = '解析中'
	
	file.temp_identifier = Math.random() // 先添加一个临时的, 前端自己用
	loadNext()

	fileReader.onload = e => {
		spark.append(e.target.result)
		if (currentChunk < chunks) {
			currentChunk++
			loadNext()
		} else {
			let md5 = spark.end()
			file.identifier = this.folderId + '/' + md5
			this.computeMD5Success(md5, file)
			file.md5Status = '解析完成'
			console.log(
				`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
					file.size
				} 用时:${new Date().getTime() - time} ms`
			)
			callback && callback()
		}
	}
	fileReader.onerror = function() {
		this.$notify({
			title: '错误',
			message: `文件${file.name}读取出错,请检查该文件`,
			type: 'error',
			duration: 2000
		})
		file.cancel()
	}
	function loadNext() {
		let start = currentChunk * chunkSize
		let end = start + chunkSize >= file.size ? file.size : start + chunkSize
		fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
	}
},
computeMD5Success(md5, file) {
	// 将自定义参数直接加载uploader实例的opts上
	file.md5UniqueIdentifier = md5
	file.isMergeComplete = false
	if (this.autoStart) {
		// 自动上传
		file.resume()
	}
},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

给file的uniqueIdentifier 属性赋值后,请求中的identifier即是我们计算出来的MD5

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/995765
推荐阅读
相关标签
  

闽ICP备14008679号