Vue如何实现封装一个切片上传组件
组件效果
单文件切片上传

多文件切片上传

组件使用案例
使用文档
Attribute
标红色部分为二次封装处理过的功能,其他为el-upload自带属性
| 参数 | 说明 | 类型 | 可选值 | 默认值 | 备注 |
|---|---|---|---|---|---|
| action | 必选参数,分片上传的地址,预请求和合并请求在组件外操作 | String | - | - | |
| headers | 设置上传的请求头部 | String | - | - | |
| multiple | 是否支持多选文件 | boolean | - | ||
| accept | 可上传文件类型,多种类型用","分隔 (格式不符合自动提示) | String | - | - | |
| on-remove | 文件列表移除文件时的钩子 | function(file, fileList) | — | — | |
| on-success | 文件上传成功时的钩子 | function(response, file, fileList) | — | — | |
| on-error | 文件上传失败时的钩子 | function(err, file, fileList) | — | — | |
| on-progress | 文件上传时的钩子 | function(event, file, fileList) | — | — | |
| on-change | 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 | function(file, fileList) | — | — | |
| on-exceed | 文件超出个数限制时的钩子 | function(files, fileList) | — | — | |
| list-type | 文件列表的类型 | string | text/picture/picture-card | text | |
| show-file-list | 是否显示已上传文件列表(文件分片上传时建议设置false,否则会有两个进度条) | boolean | — | true | |
| file-list | 上传的文件列表, 例如: [{name: 'food.jpg', url: 'xxx.cdn.com/xxx.jpg'}] | array | — | [] | |
| disabled | 是否禁用 | boolean | — | false | |
| cancelable | 是否支持取消 | boolean | — | false | |
| limit | 最多允许上传个数(超出个数自动提示) | number | — | — | |
| size | 限制大小 | String | — | — | |
| hideBtn | 是否在上传过程中隐藏上传按钮 | boolean | — | — | false |
Slot
| 插槽名 | 说明 |
|---|---|
| trigger | 触发文件选择框的内容 |
| tip | 提示说明文字 |
| more-tips | 在默认提示后补充说明 |
封装过程
切片上传组件是基于el-upload进行的二次封装,文章开头组件效果演示可以看到上传一个文件会发送三个请求:prepare,chunk, merge,也就是整个上传过程,主要分为三步:1.预请求 2.分片请求 3.合并请求,预请求和合并请求就是我们正常的http请求,主要处理的是分片请求,分片请求主要的步骤是:
将文件切片
构造切片请求参数
控制分片请求的并发
1. 文件切片
在el-upload上传后, 在on-change属性的回调里可以获取文件file,通过file.raw.slice对文件进行切片,目前的切片规则是:1.小于10M 固定一片 2.小于50M 文件10%为一片 3.大于50M 固定5M 一片(可以根据自己的需求进行修改)
genFileChunks(file) {
const chunks = []
let cur = 0
// 小于10M 固定一片
if (file.size < (10 * 1024 * 1024)) {
chunks.push({
index: cur,
file: file.raw.slice(cur, file.size),
originFilename: file.name
})
return chunks
}
// 小于50M 文件10%为一片
if (file.size < (50 * 1024 * 1024)) {
const chunkSize = parseInt(file.size * 0.1)
while (cur < file.size) {
chunks.push({
index: cur,
file: file.raw.slice(cur, cur + chunkSize),
originFilename: file.name
})
cur += chunkSize
}
return chunks
}
// 大于50M 固定5M 一片
const chunkSize = parseInt(5 * 1024 * 1024)
while (cur < file.size) {
chunks.push({
index: cur,
file: file.raw.slice(cur, cur + chunkSize),
originFilename: file.name
})
cur += chunkSize
}
return chunks
},一个32M的文件按照10%切一片,构造好的切片数据是这样的

2. 构造切片请求参数
切片请求不同业务的参数是变化的,所以参数部分可以抛出给父组件处理,增加组件的复用性
父组件
子组件
3. 控制分片请求的并发
切片上传如果不控制并发,在分片很多时,就会同时发送很多个http请求,导致线程阻塞,影响页面其他请求的操作,所以控制并发是需要的。我设置的是最多允许3个并发请求。
sendRequest(requests, limit = 3) {
return new Promise((resolve, reject) => {
const len = requests.length
let counter = 0
let isTips = false // 只提示一次失败
let isStop = false // 如果一个片段失败超过三次 认为当前网洛有问题 停止全部上传
const startRequest = async() => {
if (isStop) return
const task = requests.shift()
if (task && task.file.status !== 'cancel') {
// 利用try...catch捕获错误
try {
// 具体的接口 抽离出去了
await ajax(task)
if (counter === len - 1) { // 最后一个任务
resolve()
} else { // 否则接着执行
counter++
startRequest() // 启动下一个任务
}
} catch (error) {
// 网络异常
if (error === 'NETWORK_ERROR' && !isTips) {
Message.error('网络异常,文件上传失败')
this.upLoading = false
this.preLoading = false
isTips = true
this.handleRemove('', [])
}
// 接口报错重试,限制为3次
if (task.error < 3) {
task.error++
requests.unshift(task)
startRequest()
} else {
isStop = true
reject(error)
}
}
}
}
// 启动任务
while (limit > 0) {
// 模拟不同大小启动
setTimeout(() => {
startRequest()
}, Math.random() * 2000)
limit--
}
})
}
}