封装图片/文件上传组件
element-ui
vue2
图片上传
前言
刚过端午,突然想起来这个月还没更新文章,所以发一篇😆。在后台管理系统中,图片或者文件上传用的还是蛮多的,之前的时候每次都是从element-ui中或者之前的项目中copy一大坨代码过来,但是代码的重复度还是蛮高的,无非就是改改路径之类的,所以对图片和文件上传做了封装。样式还是沿用的element-ui的照片墙和列表文件,并没有做太大更改,在功能上除了常规的增删之外还做了图片回显,点击预览,超出隐藏等功能。需要说明的一点就是==本组件图片或文件上传均是手动上传(就是选择图片或文件后不会立即上传,而是手动调用上传函数进行上传)==,因为做过的业务中大多数都是手动上传,所以是对这种类型的进行封装。
效果
图片上传的效果
文件上传的效果
1. 在模板基础上修改及常规增删处理
<template>
<div class="base-file-upload">
<el-upload
:class="{ hide: !addButtonShow }"
ref="upload"
action="#"
:list-type="uploadOptions.listType"
multiple
:auto-upload="false"
:file-list="fileLists"
:limit="limit"
:accept="uploadOptions.accept"
:on-remove="handleFileRemove"
:on-exceed="onExceed"
:on-change="handleChange">
<!-- 上传按钮 -->
<i v-if="fileType === 'image'" slot="default" class="el-icon-plus"></i>
<el-button v-else size="small" type="primary">点击上传</el-button>
<div v-if="fileType !== 'image'" slot="tip" class="upload-tip el-upload__tip">上传文件大小不超过{{ fileSize }}MB</div>
<!-- 上传后文件展示及工具,仅图片会展示 -->
<div v-if="fileType === 'image'" slot="file" slot-scope="{ file }">
<!-- 上传后图片 -->
<el-image class="upload-img" :src="file.url" fit="cover"></el-image>
<!-- 工具栏 -->
<span v-if="file.url" class="el-upload-list__item-actions">
<!-- 查看 -->
<span class="el-upload-list__item-preview" @click="handlePictureCardPreview(file)">
<i class="el-icon-zoom-in"></i>
</span>
<!-- 删除 -->
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
<i class="el-icon-delete"></i>
</span>
</span>
</div>
</el-upload>
<!-- 查看图片 -->
<el-dialog title="查看图片" :visible.sync="seeImage" append-to-body width="800">
<img width="100%" :src="seeImageUrl" alt="" />
</el-dialog>
</div>
</template>
图片上传和文件上传要区分,所以删除用了两个,文件删除是用
on-remove
的绑定的函数,图片删除则是通过点击删除icon来实现的。图片还有一个点击查看图片的icon。
删除函数
// 点击删除
handleRemove(file) {
this.removeFile(file);
},
handleFileRemove(file) {
if(this.fileType !== 'image') {
this.removeFile(file);
}
},
// 对fileList移除文件
removeFile(file) {
const uid = file.uid;
if(uid) {
const findFileIndex = this.fileLists.findIndex(item => {
return item.uid === uid;
})
this.fileLists.splice(findFileIndex, 1)
}
},
2. 上传类型、文件大小及数量超出的处理
大小限制放在on-change
绑定的函数中。
// 状态改变触发(将文件的校验放到这里进行处理)
handleChange(file, files) {
// console.log('状态改变触发file:', file);
if(file.status === 'ready') {
this.fileLists.push(file); // 先放入再删除
}
// 只做大小检验
const uploadError = {
type: "",
msg: "",
};
const fileSize = (file.size / 1024 / 1024).toFixed(1); // 获取图片大小(MB)
// 图片大小出错处理
if (Number(fileSize) > this.fileSize) {
uploadError.type = "size error";
uploadError.msg =
`图片大小超出限制!上传图片大小不能超过` + this.fileSize + "MB!";
if (this.isShowTip) {
this.$message({
message: uploadError.msg,
type: 'warning'
});
}
this.$emit("upload-error", uploadError);
this.removeFile(file);
return
}
// console.log('状态改变触发files:', files);
}
这里选择文件后先将图片/文件放入文件列表中,如果大小超出限制,再将文件从文件列表中删除。
文件超出则是在on-exceed
绑定的函数中。
// 超出个数限制
// 如果一次性上传超出数量限制,不会触发before-upload,on-change钩子
// files为本次超出上传的所有文件
onExceed(files) {
console.log('超出', files);
const uploadError = {
type: "",
msg: "",
};
uploadError.type = "limit error";
uploadError.msg = `超出数量限制,最多接受文件数为:` + this.limit;
if (this.isShowTip) {
this.$message({
message: uploadError.msg,
type: 'warning'
});
}
this.$emit("upload-error", uploadError);
}
原本是想实现超出的话,先将文件填满再将多余的部分忽略并提示,但是在测试的时候发现==文件超出只会走on-exceed绑定的函数,并不会走on-change绑定的函数,所以只做了超出提示==。
超出隐藏则是通过计算属性,比较限制文件数和文件列表的长度,如果超出就隐藏图片上传的“+”号。==文件上传模式未做超出隐藏==。
文件类型这里提供了两个props,一个是fileType
,只接受三个参数image
video
file
用来快速定义类型;一个是supportType
,跟el-upload
的accept
一样。
computed: {
// 类型限制
uploadOptions() {
const options = {};
if(this.fileType === 'image') {
options.listType = 'picture-card';
options.accept = 'image/*'
} else if(this.fileType === 'video') {
options.listType = 'text';
options.accept = 'video/*'
} else {
options.listType = 'text';
options.accept = ''
}
if(this.supportType) {
options.accept = this.supportType
}
return options
},
// 超出隐藏
addButtonShow() {
let length = this.fileLists.length;
return length < this.limit
}
},
可以同时传入fileType和supportType,一个用来决定上传样式,一个用来决定接受的文件类型。
3. 回显文件的处理
回显文件是指接受已经上传过的文件,通过props preImgs来接受一个由文件路径组成的数组。
watch: {
preImgs: {
immediate: true,
handler(newValue, oldValue) {
this.fileLists = [];
const preFileLists = newValue.map(url => {
const random = Math.floor(Math.random () * 900) + 100;
const time = Date.now();
const uid = Number(random + '' + time);
const fileName = url.slice(url.lastIndexOf('/') + 1);
return {
uid: uid,
name: fileName,
url,
}
})
this.fileLists = this.fileLists.concat(preFileLists);
},
},
},
uid采用时间戳加随机数的组合,文件名则是截取路径最后一个斜杠后的内容。
4. 手动上传处理
这里只说明一下我这里是怎么处理的:循环文件列表,将文件对象通过FormData包装后进行上传,最后返回上传后返回的文件路径。因为每个公司在图片处理方面的业务也不一样,所以仅供参考。
// 上传
// 需要填写上传的url及响应后的地址字段
// 返回值为url组成的数组
async mySubmit(httpUrl, httpRes) {
console.log('上传');
// console.log(this.fileLists);
let result = [];
for(let file of this.fileLists) {
const rawFile = file.raw; // file对象
if(rawFile) { // 本地图片
const form = new FormData();
form.append("file", rawFile);
let timeout = parseFloat((file.size / 1024 / 1024).toFixed(2)) * 5000;
if (timeout < 10000) {
// 默认时长10000
timeout = 10000;
}
try {
let res = await this.myRequestApi(httpUrl, form, timeout);
result.push(res[httpRes])
} catch (error) {
break
}
} else { // 网络图片
result.push(file.url)
}
}
return result
},
myRequestApi(url, form, timeout) {},
使用示例
<template>
<div class="upload-form">
<el-form ref="form" :model="form" label-width="80px">
<el-form-item label="上传图片">
<ImgUpload ref="imgUpload" :pre-imgs="pres" />
</el-form-item>
</el-form>
<el-button type="primary" @click="getUploadRes">提交表单</el-button>
</div>
</template>
<script>
import ImgUpload from "@/components/BaseFileUpload";
export default {
name: "uploadForm",
components: { ImgUpload },
data() {
return {
form: {},
pres: []
};
},
mounted() {
// 图片回显
this.pres = ['https://element.eleme.cn/static/guide.0a8462c.png', 'https://element.eleme.cn/static/guide.0a8462c.png']
},
methods: {
async getUploadRes() {
// 校验
const files = this.$refs.imgUpload.getFileList();
if (files.length < 1) {
console.log("请至少上传一张图片");
return;
}
// 上传
try {
const files1 = await this.$refs.imgUpload.mySubmit("url", "fileName");
this.form.frontPic = files1.join(",");
} catch (error) {
console.log("图片上传失败");
}
},
},
};
</script>
<style lang='scss' scoped>
.upload-form {
width: 500px;
}
</style>
组件文档及源码地址
- 组件文档
Attribute
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|---|---|---|---|
limit | 最大允许上传个数 | Number | — | 3 |
fileSize | 文件大小限制(单位MB) | Number | — | 10 |
fileType | 上传文件类型 | String | image/video/file | image |
supportType | 上传具体类型 | String | — | — |
isShowTip | 是否采用消息提示 | Boolean | — | true |
preImgs | 预览图片数组 | Array | — | — |
- fileType有三个可选值,image是照片墙的形式,其他为默认形式,image上传文件类型为
image/*
,video的上传文件类型为video/*
,file没有上传形式限制。- supportType用来明确具体的文件类型,值的形式参考
el-upload
的accept
参数。- isShowTip会默认在图片超出大小,数量超出限制的时候进行提示,安装了
element ui
即可使用。- preImgs类型为由单个字符串组成的数组,例如:
[' https://element.eleme.cn/static/guide.0a8462c.png',' https://element.eleme.cn/static/guide.0a8462c.png']
。
Methods
方法名 | 说明 | 参数 |
---|---|---|
getFileList | 获取当前文件列表 | — |
mySubmit | 手动上传(async/await函数) | (httpUrl:上传的地址,httpRes:返回后的字段) |
reset | 清空上传列表 |
mySubmit返回值为由返回字段内容(上传后的url)组成的数组。
Events
事件名称 | 说明 | 回调参数 |
---|---|---|
upload-error | 上传失败时触发的回调 | 提示内容 |
提示内容为对象格式,格式为
{type: "", msg: ""}
,其中type
为类型,msg
为出错说明。
- 示例源码
源码地址:
相关链接: