???? 1.需求说明
???? 2.实现思路
???? 3.el-upload组件主要属性说明
???? 4.前端传递MultipartFile数组与服务端接收说明
???? 5.完整代码
????动态模块新增添加动态功能,支持多张图片上传.实现过程中对el-upload
组件不是很熟悉,踩了很多坑,当然也参考过别的文章,发现处理很复杂.这里记录最终实现结果,方便有同样问题的同学查看,避免浪费多余时间.下面是发布动态的页面,点击上传图片打开本地文件选择,点击确定完成动态发布功能.
????多张图片上传逻辑(兼容单张图片上传,只需要修改最大上传图片数量为1):
????1.需要页面选择好所有图片之后调用服务端的图片上传接口,这样处理的原因是图片上传过程中会出现几种场景:
选择好图片之后要删除之前选择的某一张或某几张;
选择好图片之后要追加几张图片
如果按照选择一张就调用一次图片上传接口,不但会增加服务端调用次数,而且还会增加无效图片的存储成本.
????2.多张图片一次上传接口调用成功后返回多张图片的图片地址,再调用服务端动态发布接口,完成动态发布功能
1.禁用选择图片自动上传功能
????action属性:图片上传服务端请求地址,在组件中属于必传,默认选择一张图片就要调用一次.按照上面梳理的逻辑需要禁用调用该功能,auto-upload设置为false即可.action由于是必传,所以此处设置为#
2.开启多选
????设置multiple设置为true,默认false,否则在出现的文件选择窗口中只能选择一个文件.
3.设置选择文件最大数量
????使用limit属性,超过最大数量的处理逻辑可以在on-exceed中实现,其中处理的逻辑是页面提示已超过最大数请重新选择.
4.显示已选择的图片列表
????设置file-list实现
5.选择好图片之后追加几张图片问题处理
????on-change可以监听选中的图片,一次性选择多个图片会执行多次,但是为保证业务处理逻辑执行成功,只需要最后一次on-change中添加业务处理,所以通过判断监听返回的fileList集合长度是否是最大来处理.自定义的fileList就是on-change中最后一次on-change监听返回的fileList集合信息.下文中自定义的imgUrlList为调用文件上传服务端组装的图片参数集合.服务端的file对象对应on-change监听file中的file.raw.
handleChange(file, fileList){
let length = fileList.length
this.maxLength = Math.max(length, this.maxLength)
setTimeout(() => {
if(length !== this.maxLength) {
return
} else {
this.fileList=fileList
}
})
}
6.选择好图片之后删除已选中图片问题处理
????before-remove可以监听要进行删除的图片信息.每个图片中有一个唯一标识uid,通过唯一标识删除自定义fileList中的图片
handleRemove(file, fileList){
this.fileList=this.fileList.filter(imgFile=>imgFile.uid != file.uid)
},
????服务端接口为post请求,请求方式为post表单提交.具体如下
@PostMapping("/uploadImg")
public ResultVo uploadImg(@RequestParam(name = "multipartFiles") MultipartFile[] multipartFiles,
@RequestParam(name = "fileType") Integer fileType) {
String url = adminDriftService.adminUploadImg(multipartFiles,fileType);
return ResultVoUtil.success(url);
}
前端页面需要按照FormData类型进行传递,注意一下参数拼接:
let formData = new FormData();
this.imgUrlList.map(img=>{
formData.append("multipartFiles", img)
})
formData.append("fileType", 2)
????前端:
dynamic.js:
export function uploadImg(formData) {
return axios({
url: 'uploadImg',
method: 'POST',
data: formData
})
}
新增动态弹窗:
<el-dialog title="新增动态" :visible.sync="addDynamicVisible">
<el-form :model="addDynamicForm">
<el-form-item label="用户昵称" :label-width="formLabelWidth">
<el-select v-model="addDynamicForm.userId" filterable placeholder="请选择">
<el-option
v-for="systemUser in systemUserList"
:key="systemUser.userId"
:label="systemUser.userName"
:value="systemUser.userId">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="动态文本内容" :label-width="formLabelWidth">
<el-input v-model="addDynamicForm.contentText" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="动态图片" :label-width="formLabelWidth">
<el-upload
action="#"
:file-list="fileList"
:before-remove="handleRemove"
:auto-upload="false"
:multiple="true"
:on-change="handleChange"
:limit="9"
:on-exceed="handleExceed"
list-type="picture">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="closeAddDynamic()">取 消</el-button>
<el-button type="primary" @click="addDynamicInfo()">确 定</el-button>
</div>
</el-dialog>
<script>
import {findSystemUserList,addDynamic,uploadImg} from "@/api/dynamic";
export default {
data() {
return {
addDynamicVisible:false, // 是否显示新增动态弹窗
addDynamicForm:{}, // 新增动态内容
systemUserList:[], // 系统用户列表信息
formLabelWidth: '120px',
imgUrlList:[], // 上传文件地址集合
fileList:[], // 上传文件本地显示图片集合(非服务器图片地址)
maxLength:0 // 上传文件最大值
}
}
methods:{
// 图片列表删除之后处理操作
handleRemove(file, fileList){
this.fileList=this.fileList.filter(imgFile=>imgFile.uid != file.uid)
},
// 每次打开本地文件选择窗口选择图片后处理操作
handleChange(file, fileList){
let length = fileList.length
this.maxLength = Math.max(length, this.maxLength)
setTimeout(() => {
if(length !== this.maxLength) {
return
} else {
this.fileList=fileList
}
})
},
// 选择图片超过最大限制9张之后处理操作
handleExceed(files, fileList){
this.msgError("最多选取9张,请重新选择!")
// 清空之前选择内容
this.fileList=[]
},
// 添加动态操作
addDynamicInfo(){
// 参数校验
if(!this.addDynamicForm.userId){
this.msgError("请选择用户!")
return
}
this.imgUrlList=this.fileList.map(file=>file.raw)
this.serverUploadImg()
},
// 上传图片处理逻辑
serverUploadImg() {
let formData = new FormData();
this.imgUrlList.map(img=>{
formData.append("multipartFiles", img)
})
formData.append("fileType", 2)
uploadImg(formData).then(res => {
if (res.code === 200) {
// 图片上传成功之后触发动态发布逻辑
this.addDynamicForm.contentImg=res.data
this.serverAddDynamic()
}else {
this.msgError(res.msg)
}
}).catch(() => {
this.msgError("请求失败")
})
},
// 动态发布逻辑
serverAddDynamic() {
addDynamic(this.addDynamicForm).then(res => {
if (res.code === 200) {
this.msgSuccess('添加成功!');
this.addDynamicVisible=false;
this.addDynamicForm={};
this.imgUrlList=[];
this.fileList=[];
this.serverFindDynamicInfoList(this.queryInfo)
}else {
this.msgError(res.msg)
}
}).catch(() => {
this.msgError("请求失败")
})
},
}
</script>
服务端文件批量上传:
@ApiImplicitParams({@ApiImplicitParam(name = "multipartFiles",value = "图片集合",required = true, dataType = "MultipartFile[]"),
@ApiImplicitParam(name = "fileType",value = "文件类型:1.用户图片:头像以及背景图;2.动态图",required = true,
dataType = "Integer",paramType = "insert",example = "1")})
@ApiOperation("上传图片")
@PostMapping("/uploadImg")
public ResultVo uploadImg(@NotNull(message = "文件对象不允许为空!") @RequestParam(name = "multipartFiles") MultipartFile[] multipartFiles,
@NotNull(message = "文件类型不允许为空!") @RequestParam(name = "fileType") Integer fileType) {
String url = service.adminUploadImg(multipartFiles,fileType);
return ResultVoUtil.success(url);
}
多张文件上传实现逻辑:
public String adminUploadImg(MultipartFile[] multipartFiles, Integer fileType) {
String imgUrlStr ="";
for (int i = 0; i < multipartFiles.length; i++) {
String url = aliYunService.uploadImg(multipartFiles[i], fileType,"");
imgUrlStr=imgUrlStr+url;
if(multipartFiles.length > 1 && i < multipartFiles.length-1){
imgUrlStr=imgUrlStr+",";
}
}
return imgUrlStr;
}
图片上传具体实现逻辑:
public String uploadImg(MultipartFile multipartFile,Integer fileType,String fileNameParam) {
// 返回文件地址
String fileUrl = "";
// 文件校验
checkFile(multipartFile,fileType);
// 文件上传路径
// modify by txm 2023/3/24 图片类型修改为枚举
String filePath = FilePathEnum.getOSSFilePath(fileType);
String fileName = StrUtil.isBlank(fileNameParam) ? multipartFile.getOriginalFilename():fileNameParam;
String pathKey = StrUtil.concat(true,filePath,"/",fileName);
// meta设置请求头
OSS ossClient = new OSSClientBuilder().build(driftConfig.getEndpoint(), driftConfig.getOssAccessKeyId(), driftConfig.getOssAccessKeySecret());
try {
// 上传至阿里OSS
ossClient.putObject(driftConfig.getBucketName(), pathKey, new ByteArrayInputStream(multipartFile.getBytes()));
fileUrl = driftConfig.getUrlPrefix() + pathKey;
} catch (Exception e) {
// modify by txm 2023/10/16 修改描述
log.error("文件上传失败:{}", e.getMessage());
throw new BusinessException("文件上传失败:请重试!");
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
// 上传成功
return fileUrl;
}