之前一直使用的element的上传组件,后面发现大于5G的文件上传不了,然后通过各种查询浏览,才发现有插件可以帮助实现。因为没写过所以实现过程还是挺复杂的,需要前后端配合,具体也是参考夏大师博客完成,公司需求不复杂,功能实现就行,所以写的很简易。
插件:vue-simple-uploader
<template>
<div id="global-uploader">
<!-- 上传 -->
<uploader
ref="uploader"
:options="options"
:auto-start="false"
class="uploader-example"
:file-status-text="statusText"
@file-added="onFileAdded"
@file-success="onFileSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
>
<uploader-unsupport />
<uploader-drop
style="display:flex;padding:10px;align-items: center;justify-content: center;"
>
<p>拖动文件到此 或</p>
<uploader-btn id="global-uploader-btn" ref="uploadBtn" :attrs="attrs">
<i class="el-icon-upload" />
<span style="margin-left:5px;">选择文件</span></uploader-btn>
</uploader-drop>
<uploader-list v-show="panelShow">
<div slot-scope="props" class="file-panel">
<!-- <div class="file-title">
<h4>文件列表</h4>
</div> -->
<ul class="file-list">
<li v-for="file in props.fileList" :key="file.id">
<uploader-file
ref="files"
:class="'file_' + file.id"
:file="file"
:list="true"
/>
</li>
<div v-if="!props.fileList.length" class="no-file">
暂无待上传文件
</div>
</ul>
</div>
</uploader-list>
</uploader>
</div>
</template>
<script>
import seversConfig from '../../../vue.config'
const SparkMD5 = require('spark-md5')
export default {
name: 'GlobalUploader',
props: {
params: {
type: Object,
default: function() {
return {}
}
}
},
data() {
return {
serverAddress: seversConfig.devServer.proxy['/api'].target.split(
'/api'
)[0],
uploadIng: false, // 上传状态
panelShow: false, // 选择文件后,展示上传panel
attrs: {
// 接受的文件类型,形如['.png', '.jpg', '.jpeg', '.gif', '.bmp'...]
// accept: ACCEPT_CONFIG.getAll()
// accept: [
// '.png',
// '.jpg',
// '.jpeg',
// '.gif',
// '.bmp',
// '.tar',
// '.txt',
// '.exe',
// '.rar',
// '.7z',
// '.wim',
// '.zip',
// '.doc',
// '.xls',
// '.xlsx',
// '.pdf'
// ]
},
statusText: {
success: '上传成功',
error: '上传失败',
uploading: '正在上传',
paused: '暂停上传',
waiting: '等待上传'
},
options: {
target: `/api/addfilebig`, // 目标上传 URL
chunkSize: 10 * 1024 * 1024, // 分块大小
fileParameterName: 'file', // 上传文件时文件的参数名,默认file
simultaneousUploads: 1, // 并发数
maxChunkRetries: 3, // 最大自动失败重试上传次数
testChunks: true, // 是否开启服务器分片校验 -> 即上传前判断块是否已经存在
// 服务器分片校验函数,秒传及断点续传基础
/*
服务器分片校验函数,判断秒传及断点续传,传入的参数是Uploader.Chunk实例以及请求响应信息
reponse码是successStatuses码时,才会进入该方法
reponse码如果返回的是permanentErrors 中的状态码,不会进入该方法,直接进入onFileError函数 ,并显示上传失败
reponse码是其他状态码,不会进入该方法,正常走标准上传
checkChunkUploadedByResponse函数直接return true的话,不再调用上传接口
*/
checkChunkUploadedByResponse: function(chunk, message) {
// console.log(message, 'msg')
const objMessage = JSON.parse(message)
if (parseInt(objMessage.code) === 2) {
return true
}
return (objMessage.data || []).indexOf(chunk.offset + 1) >= 0
},
headers: {
// 在header中添加的验证,请根据实际业务来
// Authorization: 'Bearer ' + Ticket.get().access_token
},
// 额外的自定义查询参数
query: (file, chunk) => {
// console.log(file, 'fileQuery')
file.params = this.params
return {
...file.params
}
}
}
}
},
methods: {
// 一个文件选中
onFileAdded(file, event) {
// console.log(this.$refs.uploader, 'uploader')
this.panelShow = true
// 调用校验MD5方法
this.computeMD5(file)
// 将额外的参数赋值到每个文件上,解决了不同文件使用不同params的需求
file.params = this.params
this.uploadIng = true
this.$emit('getLoading', this.uploadIng)
},
computeMD5(file) {
const loading = this.$loading({
lock: true,
text: `正在计算MD5`,
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
})
const fileReader = new FileReader()
const time = new Date().getTime()
const blobSlice =
File.prototype.slice ||
File.prototype.mozSlice ||
File.prototype.webkitSlice
let currentChunk = 0
const chunkSize = this.options.chunkSize
const chunks = Math.ceil(file.size / chunkSize)
const spark = new SparkMD5.ArrayBuffer()
// 文件状态设为"计算MD5"
// this.statusSet(file.id, 'md5')
file.pause()
loadNext()
fileReader.onload = e => {
spark.append(e.target.result)
if (currentChunk < chunks) {
currentChunk++
loadNext()
// 实时展示MD5的计算进度
this.$nextTick(() => {
console.log(
'校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%'
)
})
} else {
const md5 = spark.end()
loading.close()
this.computeMD5Success(md5, file)
console.log(
`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
file.size
} 用时:${new Date().getTime() - time} ms`
)
}
}
fileReader.onerror = function() {
this.error(`文件${file.name}读取出错,请检查该文件`)
loading.close()
file.cancel()
}
function loadNext() {
const start = currentChunk * chunkSize
const end =
start + chunkSize >= file.size ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
},
// MD5校验成功
computeMD5Success(md5, file) {
file.uniqueIdentifier = md5
file.resume() // 开始上传
// this.statusRemove(file.id)
},
// 文件上传进度
onFileProgress(rootFile, file, chunk) {
this.uploadIng = true
this.$emit('getLoading', this.uploadIng)
console.log(
`上传中 ${file.name},chunk:${chunk.startByte /
1024 /
1024} ~ ${chunk.endByte / 1024 / 1024}`
)
},
onFileSuccess(rootFile, file, response, chunk) {
const res = JSON.parse(response)
// console.log(res, 'res')
if (parseInt(res.code) === 1) {
// file.cancel()
}
// 服务器自定义的错误,这种错误是Uploader无法拦截的
if (parseInt(res.code) === 0) {
return
} else {
file.cancel()
this.$emit('getUrl', res.url)
}
this.uploadIng = false
this.$emit('getLoading', this.uploadIng)
// 如果服务端返回需要合并
// if (res.needMerge) {
// // 合并接口调用
// addfilebig({
// tempName: res.tempName,
// fileName: file.name,
// ...file.params
// })
// .then(data => {
// // 文件合并成功
// Bus.$emit('fileSuccess', data)
// })
// .catch(e => {})
// // 不需要合并
// } else {
// Bus.$emit('fileSuccess', res)
// console.log('上传成功')
// }
},
onFileError(rootFile, file, response, chunk) {
console.log(response, 'fileError')
}
}
}
</script>
<style lang="scss" scoped>
.uploader-example {
width: 100%;
// padding: 15px;
// margin: 40px auto 40px;
font-size: 12px;
// border: 1px solid #000 !important;
}
.uploader-example .uploader-btn {
width: 80px;
height: 24px;
// line-height: 1;
display: flex;
justify-content: center;
align-items: center;
white-space: nowrap;
cursor: pointer;
background-color: #1890ff;
border: 1px solid #1890ff;
border-color: #1890ff;
color: #fff;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
margin-left: 10px;
border-radius: 3px;
transition: 0.1s;
font-weight: 400;
-webkit-user-select: none;
padding: 7px 15px;
font-size: 12px;
:hover {
background-color: #1890ff;
}
}
.uploader-example .uploader-list {
max-height: 440px;
overflow: auto;
overflow-x: hidden;
overflow-y: auto;
ul {
margin: 0 !important;
padding-left: 0 !important;
li {
::marker {
unicode-bidi: isolate;
font-variant-numeric: tabular-nums;
text-transform: none;
text-indent: 0px !important;
text-align: start !important;
text-align-last: start !important;
}
list-style-type: none !important;
}
}
.uploader-file {
// border-bottom: 0;
}
}
.uploader-unsupport {
:hover {
background-color: #1890ff;
}
}
.uploader-btn {
display: inline-block;
}
.no-file {
padding: 20px;
text-align: center;
border-left: 1px solid #cccccc;
border-bottom: 1px solid #cccccc;
border-right: 1px solid #cccccc;
}
</style>
<template>
<uploadFile
:params="bigFileParams"
@getUrl="getUrl"
@getLoading="getLoading"
/>
</template>
<script>
export default {
data(){
return{
bigFileParams: {
// 大文件参数
},
uploadIng: false
}
},
methods:{
// 大文件上传相关
getUrl(url) {
// 获取上传完后的url
this.postObj.url = url
// console.log(url, 'url2222222222')
},
// 上传loading
getLoading(uploadIng) {
this.uploadIng = uploadIng
}
}
}
</script>