请注意,项目使用vue3 + Element-Plus 2.3.7 + springboot 2.7.8!
思路说明
element-plus提供了el-upload组件实现头像上传,具体的属性说明请参考官方文档
组件拿到用户上传的图片之后,首先需要展示,用户确认后再传输给后端,后端接收图片资源后进行保存,并将保存状态返回给前端。
代码实现
前端代码
- 前端代码-template
<el-dialog class="avatarDialog" :showClose="false" center v-model="avatarDialogVisible" title="更新头像">
<el-upload ref="upload" class="avatar-uploader" :before-upload="beforeAvatarUpload" action="#"
:http-request="uploadFile" :auto-upload="false" multiple list-type="picture-card" :file-list="fileList"
:on-change="fileChange">
<el-icon>
<Plus />
</el-icon>
<template #file="{ file }">
<img :src="file.url" alt="" />
</template>
</el-upload>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="handleUpdateAvatar">
确认更新
</el-button>
<el-button @click="cancel">取消</el-button>
</span>
</template>
</el-dialog>
这里对用到的属性进行简单解释:
1、before-upload:是el-upload组件提供的钩子函数,在图片上传之前执行,一般用于检查图片的格式和大小是否满足条件
2、action属性:图片上传到后端的路径,我这里不使用该属性,用“#”占位(你可以用其它字符串),后面使用自定义的上传方法。这里有个小坑,使用该属性会出现跨域问题
3、http-request:自定义的上传方法,会覆盖默认的xhr上传行为,如果需要使用它,你得关闭自动上传,即auto-upload=“false”
4、list-type:选中文件后以什么样的形式展示,可选值有:text、picture、picture-card
5、file-list:一般和data中的数据绑定,表示上传的文件列表
6、multiple:是否支持多选文件
7、on-change:文件状态改变时的钩子函数,在添加文件、上传成功和上传失败都会执行该函数
然后使用插槽,对图片选中后进行展示,也就是代码中间的template部分
- 前端代码-data部分
data() {
return {
fileList: [], //上传的文件列表
avatarDialogVisible: false, //头像框显示控制
}
},
- 前端代码-methods部分
methods: {
// 自定义上传图片方法,参数必须为params
uploadFile(params) {
const file = params.file;
var forms = new FormData();
// 我这里需要将更新时间和uid同时传给后端,后端使用对象接收,所以没有设置content-type
// 如果你只传file,要设置axios中headers的content-type为:multipart/form-data
forms.append("file", file);
forms.append("updatedTime", this.$utils.getNowFormatTime());
forms.append("uid", this.user.uid);
this.$axios({
method: "post",
url: "/avatar/upload",
data: forms,
}).then(res => {
if (res.data.code == "200") {
this.$message({
message: '上传成功',
type: 'success'
})
this.avatarDialogVisible = false;
} else {
this.$message({
message: '上传失败',
type: 'error'
})
}
}).catch(err => {
console.log("上传文件时发生错误: " + err);
})
},
// 上传图片时检查
beforeAvatarUpload(file) {
//允许的图片类型
const imgType = (file.type === 'image/jpeg') || (file.type === 'image/png') || (file.type === 'image/gif');
//文件大小3M
const imgLimit= (file.size / 1024 / 1024) < 3;
if (!imgType) {
this.$message.error("只允许jpg、png、gif格式的图片");
return false;
}
if (!imgLimit) {
this.$message.error("上传的图片大小不能超过3MB");
return false;
}
console.log("检查通过");
return true;
},
//文件改变时的钩子函数,只能上传一张,选中后会覆盖掉之前的图片,
fileChange(file, fileList) {
//this.file表示上一张被选中的图片,file.raw表示当前选中的图片
this.file = file.raw;
if (fileList.length > 0) {
this.fileList = [fileList[fileList.length - 1]];
}
},
// 确认修改头像
handleUpdateAvatar() {
this.$refs['upload'].submit();
this.avatarDialogVisible = false;
this.$refs['upload'].clearFiles();
},
// 取消上传头像
cancel() {
this.avatarDialogVisible = false;
this.$refs['upload'].clearFiles();
},
}
后端代码
- 实体类Upload-接收前端参数
@Data
public class Upload {
private MultipartFile file;
private String uid;
private String updatedTime;
}
- controller层
@PostMapping("/upload")
public Result avatar(Upload upload) throws FileNotFoundException {
MultipartFile avatarFile = upload.getFile();
String uid = upload.getUid();
String updatedTime = upload.getUpdatedTime();
if(avatarFile.isEmpty()){
return Result.fail("文件上传失败");
}
// 设置文件名:uid+原文件名
String fileName = uid + "_" + avatarFile.getOriginalFilename();
//获取静态资源目录所在的位置路径:/项目名称/target/classes
//注意,如果你的项目要部署到云服务器,这个方法会失效
String path = ResourceUtils.getURL("classpath:").getPath();
//拼接保存头像的目录路径
File avatarDir = new File(path + "/static/upload/avatar");
//如果文件路径不存在,就新建一个,使用mkdirs()函数,可以创建多级目录
if(!avatarDir.exists()){
System.out.println("正在创建/upload/avatar目录");
avatarDir.mkdirs();
}
//文件保存的路径,这里的System.getProperty("file.separator")表示当前系统下的分隔符,如:"/"
File dest = new File(avatarDir + System.getProperty("file.separator") + fileName);
//存储在数据库中的路径
String storePath = "/static/upload/avatar/" + fileName;
try{
//保存图片,transferTo()函数根据指定路径保存图片
avatarFile.transferTo(dest);
//更新数据库
Avatar avatar = new Avatar();
avatar.setUid(Integer.valueOf(uid));
avatar.setAvatar(storePath);
avatar.setUpdateTime(updatedTime);
//成功返回图片的路径,用于前端图片展示
return avatarService.saveOrUpdate(avatar)?Result.success(1, storePath):Result.fail("文件上传失败");
}catch(IOException e){
e.printStackTrace();
return Result.fail("文件上传失败");
}
}