这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战
背景
环境:vue2 ,组件:<el-upload/>
太多数组件封装的上传都是基于上传组件的action进行实现。本文主要解析自定义上传方法的实现的上传组件。
因为其中还是有两步坑是要踩的
代码
<el-upload
ref="uploadRef"
drag
multiple
action=""
:list-type="type === 'image' ? 'picture' : 'text'"
:file-list="list"
:accept="acceptEnum[type]"
:limit="limit"
:http-request="handleUpload"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleSuccess"
:on-error="handleError"
:on-exceed="handleExceed"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div v-if="type === 'image'" class="el-upload__tip" slot="tip">
只能上传jpg/png文件,且不超过2MB
</div>
</el-upload>
本文以upload的拖拽组件为例。
props
首先定义props,我定义了type属性主要区别于文件类型的上传,区别了文件的类型我们可以用不同的规则去限制上传前的校验。
props: {
type: {
type: String,
default: 'all' // all 支持任意格式 audio video image
},
limit: {
type: Number,
default: 1
},
list: {
type: Array,
default: () => []
}
},
beforeUpload
上传前的校验,对于不同类型,于是我们可以加不同的策略,我利用了一个策略模式进行上传前不同格式的校验。
beforeUpload(file) {
const strategy = this.strategies[this.type] || []
for (const rule of strategy) {
// 验证后缀的策略
if (
rule.suffix !== undefined &&
!this.checkSuffix(this.getSuffix(file.name), rule.suffix)
) {
this.$message.error(rule.message)
return false
}
// 验证大小的策略
if (rule.size !== undefined && !this.checkSize(file.size, rule.size)) {
this.$message.error(rule.message)
return false
}
}
return true
}
于是我们可以在data里面进行策略的详细配置
strategies: {
all: [],
audio: [],
video: [{ size: 500 * 1024 * 1024, message: '视频必须小于500MB' }],
image: [
{
suffix: ['.png', '.jpg'],
message: '请选择正确格式的图片'
},
{ size: 2 * 1024 * 1024, message: '图片必须小于2MB' }
]
}
自定义上传方法
上面我们进行上传前的校验。 于是我们可以进行上传了
// 自定义上传
handleUpload(ctx) {
return new Promise((resolve, reject) => {
// 这是我司实现的上传方法,保密性具体代码不公开。
upload(
ctx.file, // 源文件
(v) => {
// 上传进度
ctx.onProgress({ percent: v * 100 })
},
(src) => {
resolve(src)
},
() => {
reject()
}
)
})
},
这里是最重要的一点。
这里我也是翻阅源码才知道,自定义上传的方法怎么用的。:http-request="handleUpload"文档上面关于http-request的介绍很模糊。
首先这里。如果你去实现一个进度条。
你必须通过ctx去调用的它的onProgress方法,必须是一个对象,进度条的数值必须是一个0-100的数,key为percent,在文档上是看不到的。
我们看到源码,当我们上传时,他将文件的状态设为uploading,上传的进度条percentage,便是我们通过传进去的ev.percent赋值。
这是onProgress其实就是上传组件往外抛出的钩子函数。详情见文档使用方法。
为什么我自定义必须是一个promise呢?
先看源码
this.httpRequest就是我们的自定义方法。
看到下面的.then我只就知道,上传肯定是异步的,我们需要同的promise的回调,将上传的值进行返回。
这里的options.onSuccess, options.onError就是我们监听的上传成功和上传失败的函数了。
自此上传的过程就完毕了。
源码
<template>
<div>
<el-upload
ref="uploadRef"
drag
multiple
action=""
:list-type="type === 'image' ? 'picture' : 'text'"
:file-list="list"
:accept="acceptEnum[type]"
:limit="limit"
:http-request="handleUpload"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleSuccess"
:on-error="handleError"
:on-exceed="handleExceed"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div v-if="type === 'image'" class="el-upload__tip" slot="tip">
只能上传jpg/png文件,且不超过2MB
</div>
</el-upload>
</div>
</template>
<script>
export default {
name: 'upload',
props: {
type: {
type: String,
default: 'all' // all 支持任意格式 audio video image
},
limit: {
type: Number,
default: 1
},
list: {
type: Array,
default: () => []
}
},
data() {
return {
// 上传前校验策略配置
strategies: {
all: [],
audio: [],
video: [{ size: 500 * 1024 * 1024, message: '视频必须小于500MB' }],
image: [
{
suffix: ['.png', '.jpg'],
message: '请选择正确格式的图片'
},
{ size: 2 * 1024 * 1024, message: '图片必须小于2MB' }
]
},
typeEnum: {
all: '文件',
audio: '音频',
video: '视频',
image: '图片'
},
acceptEnum: {
all: '',
audio: 'audio/*',
video: 'video/*',
image: 'image/*'
}
}
},
methods: {
handleRemove(file) {
const fileList = this.$refs.uploadRef.fileList
const index = fileList.indexOf(file)
this.$refs.uploadRef.fileList.splice(index, 1)
this.$emit('remove', index)
},
handleError(err) {
this.$message.error(`上传失败`)
console.log('error', err)
},
handleExceed() {
this.$message.error(`最多上传${this.limit}个${this.typeEnum[this.type]}`)
},
handleSuccess(url, file, filelist) {
file.url = url
this.$emit('success', file)
},
// 图片上传验证
beforeUpload(file) {
const strategy = this.strategies[this.type] || []
for (const rule of strategy) {
if (
rule.suffix !== undefined &&
!this.checkSuffix(this.getSuffix(file.name), rule.suffix)
) {
this.$message.error(rule.message)
return false
}
if (rule.size !== undefined && !this.checkSize(file.size, rule.size)) {
this.$message.error(rule.message)
return false
}
}
return true
},
// 自定义上传 这里需自己书写上传逻辑
handleUpload(ctx) {
return new Promise((resolve, reject) => {
upload(
ctx.file,
(v) => {
// 上传进度
ctx.onProgress({ percent: v * 100 })
},
(src) => {
resolve(src)
},
() => {
reject()
}
)
})
},
getSuffix(filename) {
let suffix = ''
const lastIndex = filename.lastIndexOf('.')
if (lastIndex >= 0) {
suffix = filename.substring(lastIndex)
}
return suffix
},
checkSize(aim, totalSize) {
return aim < totalSize
},
checkSuffix(aim, suffixs) {
return suffixs.includes(aim)
}
}
}
</script>
<style lang="scss" module>
.imgBox {
border-radius: 6px;
overflow: hidden;
height: 100%;
width: 100%;
background-repeat: no-repeat;
background-position: center center;
background-size: cover;
}
</style>
注意
仅适用于vue2环境,当然大家可以参考逻辑,写出更好的上传组件。