背景:
我这里是进行了一个正常的表单多文件上传。
踩坑:
踩坑1:
关于表单校验rules的问题。
我首先要想说一个uniapp这个rules,这个真恶心呀,也是我傻了,它里面每个属性都多了一个rules。
/**
* 表单校验规则
*/
const rules = {
name: {
rules: [{ required: true, errorMessage: '请输入姓名' },]
},
avator: {
rules: [{ required: true, errorMessage: '请上传头像' }]
}
}
这里有两个rules,我写了一个直接不生效了。写element写多了,都没想过是这里的错,而且我这还是用gpt生成的,没想过是这里的错,我真的会谢,以后要细心一点。
还有,官方文档说这里这里如果是自定义规则,需要onReady调用,可以搜搜其他的情况。
![](https://img-blog.csdnimg.cn/direct/caf8c5a2ea734402afd1c457b586595f.png)
踩坑2:
关于文件上传组件的uni-file-picker这个问题
首先说明一下:
1. 我这个上传是一个跟着表单一起上传的,
2. 而且我这个上传有点设计的不好,因为我后端的实体类中图片这个属性是一个string类型,所以说上传很麻烦。
如果使用 uni-file-picker进行上传的话,通过官方文档uni-app官网 (dcloud.net.cn)这个组件,其实它给你整好了都,但是比较糟心的是,它对于表单上传文件及其不友好,具体请看官方截图:
![](https://img-blog.csdnimg.cn/direct/4fbac2d5e3954d6eaff1ab3da7dbbd20.png)
这个什么意思呢,就是你上传完之后文件的值不会绑定到你定义的变量中,你必须用其他方法进行传值。
前端:
下面有个博客可以看看他的两个方法,看完可以回来。
uniapp uni-file-picker 上传踩坑 - 掘金 (juejin.cn) https://juejin.cn/post/7211020974024081467
第一个方法老哥的思想是没有问题的。第二个思想也没有问题,但是你需要在后端多写两个东西,单独的提交图片,单独的删除图片,我比较懒,就想在前端让图片随着表单一起提交,而不是再写俩后端,这样太麻烦了。
老哥也是个老前端了,写的代码质量都不一样,不过它的代码需要修改一下:
/**
* 选择图片
*/
const selectFileV = (res: any) => {
let { tempFiles } = res;
tempFiles.forEach((element: any) => {
let { name, url, uuid, file } = element;
volunCertifyFileLists.value.push({
name, url, uuid, file,
})
});
console.log("志愿者图片的值:", volunCertifyFileLists.value)
}
// 删除图片
const deleteFileV = (res: any) => {
let { tempFile: { uuid } } = res
let tar = volunCertifyFileLists.value.findIndex((element: {
name: '',
url: '',
uuid: '',
file: ''
}) => element.uuid === uuid)
if (typeof tar === 'number') {
volunCertifyFileLists.value.splice(tar, 1)
}
console.log("志愿者图片的值:", volunCertifyFileLists.value)
}
改成这个样子,具体改的也就一个删除图片的函数,这里是用findIndex进行查找。测试了没啥bug,其他就没有啥了。
但是不得不说,看老哥的写的代码真好,高质量代码真的会改变人。
后端:
如果你没有我的2上传设计问题其实也就结束了,如果你也是后端实体类设计的图片属性的类型是string,你可以看看我的想法,不是说多好,但是够用。
前端弄完之后就是如何将文件和表单上传到后端了,这里因为后端属性的类型是string所以说正常一次性上传是不行的,正常思路就是点击submit的时候图片上传一次,表单上传一次。但是这样子需要的考虑太多了,我这里思路是:前端新建一个数组接收文件,表单中的图片属性就不要了,后端用实体类和一个文件数组接收,如下:
![](https://img-blog.csdnimg.cn/direct/80f49098f43c455099a381f00fda68f5.png)
这样子就可以一起上传了,到service层的时候处理一下就行了。
最新状况,写出来了,但是踩坑2的想法不行了,因为微信小程序不能多文件上传,得遍历上传,这样子就不好了,还不如直接用老哥得方法2,这样还不麻烦
我是参考下面老哥得方法得
uni-app 同时上传多个文件_uni.uploadfile上传多个文件-CSDN博客 https://blog.csdn.net/wk198786/article/details/131595601怎么说呢,如果想多文件上传,小程序端只能轮询,uniapp官方说了小程序不支持上传数组,所以说,综合考虑,还是自动上传好,做个总结就是你如果上传得文件多,那就自动上传,不费事,费内存,不过可以写个删除得,就比如如果前端这边修改图片删除了,后端也跟着删除。因为我的项目是需要两个字段都多张上传,虽然可以一件上传,但是太麻烦了,而且进行前端回显得话,自动上传也方便。以前想着省点流量,变成手动上传。还是自动上传好点。
这里我找了几个平台,抖音网页版是自动上传,咸鱼,今日头条,美团,都是自动上传。
不过代码写完了都,得留一下做个备注。
这是又找到得好文章:
uni-app 微信小程序 实现图片视频多文件上传功能_uniapp上传图片或视频-CSDN博客 https://blog.csdn.net/qq_36410795/article/details/120552848
具体代码:
前端:
学生信息
Submit
import { ref, reactive } from 'vue'
interface StudentInfo {
avator: string; // 头像地址
volunCertify: string | []; // 志愿者证明图片地址
professCertify: string | []; // 专业人才证明图片地址
}
const formData = reactive({
avator: '',
volunCertify: [],
professCertify: [],
})
// 头像列表
const avatar = ref('https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0');
const onChooseAvatar = (e: any) => {
// console.log("e.detail的值: ", e)
const { avatarUrl } = e.detail
avatar.value = avatarUrl
// console.log("新的头像值:", avatar.value)
}
/**
* 两个图片列表
*/
const volunCertifyFileLists = ref([]);
const professCertifyFileLists = ref([]);
/**
* 选择图片
*/
const selectFileV = (res: any) => {
let { tempFiles } = res;
tempFiles.forEach((element: any) => {
let { name, url, uuid, file } = element;
volunCertifyFileLists.value.push({
name, url, uuid, file,
})
});
console.log("志愿者图片的值:", volunCertifyFileLists.value)
}
const selectFileP = (res: any) => {
let { tempFiles } = res;
tempFiles.forEach((element: any) => {
let { name, url, uuid, file } = element;
professCertifyFileLists.value.push({
name, url, uuid, file,
})
});
console.log("专业技术人才图片的值:", professCertifyFileLists.value)
}
// 删除图片
const deleteFileV = (res: any) => {
let { tempFile: { uuid } } = res
let tar = volunCertifyFileLists.value.findIndex((element: {
name: '',
url: '',
uuid: '',
file: ''
}) => element.uuid === uuid)
if (typeof tar === 'number') {
volunCertifyFileLists.value.splice(tar, 1)
}
console.log("志愿者图片的值:", volunCertifyFileLists.value)
}
const deleteFileP = (res: any) => {
let { tempFile: { uuid } } = res
let tar = professCertifyFileLists.value.findIndex((element: {
name: '',
url: '',
uuid: '',
file: ''
}) => element.uuid === uuid)
if (typeof tar === 'number') {
professCertifyFileLists.value.splice(tar, 1)
}
console.log("专业技术人才图片的值:", professCertifyFileLists.value)
}
/**
* 表单校验规则
*/
const rules = {
}
// 上传的图片数组
const imgFiles = ref([])
/**
* 拼接函数
*/
const concat = () => {
imgFiles.value = [];
// 拼接头像
if (avatar.value != 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0') {
formData.avator = avatar.value;
}
// 拼接志愿者图片
if (volunCertifyFileLists.value.length > 0) {
volunCertifyFileLists.value.forEach((item, index) => {
imgFiles.value.push({
name: `volunteer${index + 1}`,
uri: item.url
})
})
}
// 拼接专业人才证明图片
if (professCertifyFileLists.value.length > 0) {
professCertifyFileLists.value.forEach((item, index) => {
imgFiles.value.push({
name: `profess${index + 1}`,
uri: item.url
})
})
}
}
// 表单校验
const formRules = ref();
/**
* 进行轮询提交图片
*/
const uploadFile = (imgs: any[]) => {
const uploadTasks = imgs.map((file: any, index: number) => {
return new Promise((resolve, reject) => {
const uploadTask = uni.uploadFile({
url: '',
filePath: file.uri,
name: 'imgFile',
formData: formData,
header: {
'Content-Type': 'multipart/form-data'
},
success: function (res) {
resolve(res.data)
},
fail: function (err) {
reject(err)
},
});
// 这里可以根据需要显示进度条
uploadTask.onProgressUpdate((res) => {
console.log('上传进度', res.progress)
console.log('已经上传的数据长度', res.totalBytesSent)
console.log('预期需要上传的数据总长度', res.totalBytesExpectedToSend)
})
})
})
Promise.all(uploadTasks)
.then((res) => {
console.log('上传成功', res)
// 上传成功后的操作
})
.catch((err) => {
console.log('上传失败', err)
// 上传失败后的操作
})
}
/**
* 表单提交
*/
const submit = async (formRules: any) => {
formRules.validate(async (valid: any, errors: any) => {
if (!valid) {
uni.showLoading({
title: '提交中...',
mask: true
})
// 拼接图片
concat()
console.log("imgFiles.value的值:", imgFiles.value);
console.log("选择之后的数据: ", formData);
uploadFile(imgFiles.value)
uni.hideLoading()
} else { // 当表单验证失败时
console.log('表单校验失败', errors); // 输出错误详情
}
})
}
.container {
height: 100vh;
background-color: #fafafa;
}
.header {
font-size: 20px;
text-align: center;
}
.main {
text-align: center;
padding-top: 1px;
}
.avatar-wrapper {
width: 64px;
height: 64px;
border-radius: 50%;
}
.avatar {
width: 64px;
height: 64px;
border-radius: 50%;
margin-left: -14px;
}
唯一值得说的是两个代码,一个是轮询上传,一个是选择图片删除图片,写法都挺好得。
后端:
public Result updateDto(@RequestParam("imgFile") MultipartFile imgFile) throws IOException {
String originalFilename = imgFile.getOriginalFilename();
imgFile.transferTo(new File("D:\\images\\"+originalFilename));
return Result.success("修改成功");
}
这里如果遇见有文件报错500,估计是文件限制大小得原因,改一下就行了
这里后端删除图片我没有写,大概给一下思路,你提交删除就要先判断一下你的图片字段是否有值,如果有值,那就通过链接先把服务器上的图片删除了,然后再添加新的图片。如果没值,那说明本就没有图片,直接赋值即可,这个比较方便,因为是点击提交一件上传的。
更新一下,关于第一个老哥的点击上传就上传,点击删除就删除
如果你选择第一个链接老哥的做法,给你个忠告,uniapp它的文件删除很麻烦,如果你先上传一个图片,然后再删除是没法将服务器图片属性传入删除函数,如果是本身就有值,那就没啥问题可以传的到删除函数。因为他的删除函数的形参是绑定内存中虚拟的图片的属性,就是你提交一个图片后,它组件里面会先在内存中生成一个虚拟的图片,虽然你是提交了服务器,并且用v-model进行绑定了,但是组件内只有虚拟图片属性。你如果点击删除,对于删除函数中的形参他是内存中的图片,而不是v-model所绑定的值。。所以说你如果想删除只能用组件给的uuid进行删除。思路是第一个老哥的做法,传的话带个uuid作为图片的唯一属性即可。后端根据uuid作为图片的名字,然后截取字符串,相同的话直接删除即可,我跟老哥一样感觉这是一个大坑。
还有一件事是如果在表单中用户反悔了,删除完图片之后就退出了,从新进,这个情况下是挺麻烦的,此时图片已经被删除了,或者说,它添加了一张图片之后就退出了。
对于第一个删除图片返回,我的想法是删除图片的时候不要调用后端,就在前端删除,可以拿个数组存着,到时候一并传回后端,然后一下子删完。添加一张图片之后退出的话也是这样,给他加一个按钮,如果点击退出,那就直接把新增的图片给后端,让后端从服务器删了就行。其实我感觉这个情况还是看架构,如果公司架构师好的话,这个情况完全可以避免。
|