最近刚接触小程序,需要做多图上传,于是找了很多资料,遇到很多困难,所以记录下以便反复琢磨。
界面写的比较简单,仅供学习,大致效果和功能如下:
整体项目文件结构如下
upload.wxml如下,有添加图片,图片预览,删除图片,上传图片功能
上传图片
upload.wxss如下
.multipleImg {
width: 100%;
display: flex;
flex-flow: row wrap;
}
.upload {
position: relative;
}
.addImg {
width: 200rpx;
height: 200rpx;
margin-left: 40rpx;
}
.deleteImg {
width: 30rpx;
height: 30rpx;
position: absolute;
bottom: 20rpx;
right: 10rpx;
opacity: 1.5;
}
.text{
margin-left: 40rpx;
}
.img-box{
border-bottom:solid 2rpx #eee;
}
然后是最重要的JS部分,首先放上不考虑异步同步问题,直接一顺写下来的版本
支持最多9张图片上传,执行wx.chooseImage后,回调success,拿到本地图片URL,开始判断选择的图片是否符合要求,图片大小,图片格式都有判断,其中格式判断挺有意思,拿到的图片信息是个xxx.xxx.xxx.jpg的形式,需要切分一下拿末尾的格式和允许的格式对比,满足才行。
然后是网络上传部分,一开始没有考虑异步同步问题,想着把拿来的本地图片URL一起上传完事儿了,于是就有了第一个版本,这个版本里最后只拿了返回图片的URL后缀部分放到本地data的upload数组里,下面第二版本完善了URL拼接的代码:
data: {
upload: [], // 上传的图的URL
chooseImgs: [], // 用户选择上传的图片存到UI界面上的数组
},
uploadImgs:function() {
const that = this;
const {
chooseImgs
} = this.data;
wx.chooseImage({
count: 9 - chooseImgs.length, // 默认9
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
success: function (res) {
console.log(res)
const newChooseImgs = res.tempFilePaths; //拿到图片本地URL(URL,长度)
const imgInfo = res.tempFiles; //拿到图片的信息(URL,大小,长度)
// 判断选择的图片是否符合要求
for (let i = 0; i < imgInfo.length; i++) {
console.log("尺寸", imgInfo[i].size);
// 判断图片尺寸,大于10M不行
if (imgInfo[i].size / 1024 / 1024 >= 10) {
wx.showModal({
title: '提示', // 标题
content: "图片超过10MB啦~", // 内容
showCancel: false, // 是否显示取消按钮
confirmText: '确定', // 确认按钮的文字
});
return
}
// 判断图片格式
const imgSplit = imgInfo[i].path.split(".");
const imgSLen = imgSplit.length;
console.log("格式", imgSplit, imgSLen, imgSLen - 1);
//分割图片URL后有四段,但是下标是0,1,2,3,所以需要总数减1才是后缀的下标
if (["jpg", "jpeg", "png"].includes(imgSplit[imgSLen - 1])) {
console.log("格式正确");
} else {
console.log("格式错误");
utils.showModalInfo({
content: "请选择正确的图片格式上传",
});
return
}
}
console.log("选择图片之前", res, chooseImgs, newChooseImgs);
//此时chooseImgs的池子里是空,需要把选择到newChooseImgs里的图片传入本地chooseImgs池子里
newChooseImgs.forEach(item => {
chooseImgs.push(item);
});
console.log("选择图片后", chooseImgs, newChooseImgs); //此时池子里有图片
// 限制上传数量
if (chooseImgs.length > 9) {
wx.showModal({
title: '提示',
content: "请选择正确的图片格式上传",
showCancel: false,
confirmText: '确定',
});
}
// 判断是否显示添加图片
console.log("显示添加图片", chooseImgs.length);
if (chooseImgs.length > 0) {
//图如果满了9张,不显示加图
if (chooseImgs.length >= 9) {
that.setData({
//隐藏加号
hideAdd: true
})
} else {
that.setData({
hideAdd: false
})
}
// 显示预览图
that.setData({
chooseImgs
});
// 网络请求 上传图片
const requestMsg = [];
newChooseImgs.forEach(item => {
wx.uploadFile({
url: 'xxxxx', // xxxxx为上传图片的接口
filePath: item,
header: {
'content-type': 'multipart/form-data'
},
name: 'file',
formData: {
'dir': "goods",
'tokenId': 'xxxxxxxxxxx'
},
success: function (e) {
console.log("访问上传接口成功", e);
const data = JSON.parse(e.data);
const imgHouZhui = data.name
//把每次选择的图push进数组
const upload = that.data.upload;
console.log("上传之前的图数组", upload, imgHouZhui);
upload.push({
imgHouZhui: imgHouZhui,
isShow: false,
requestMsg, // 上传返回信息代号
});
that.setData({
upload,
});
console.log("上传之后的图数组", upload, data);
},
fail: function (e) {
console.log("访问接口失败", e);
wx.showToast({
title: "网络出错,上传失败",
icon: 'none',
duration: 1000
});
},
});
});
}
}
})
},
/***预览图片***/
previewImg: function (e) {
console.log(e)
const contentImg = e.currentTarget.dataset.item; //item就是图片url
//console.log("点击图片放大预览", contentImg);
wx.previewImage({
current: contentImg, //当前图片地址
urls: [contentImg], //所有要预览的图片的地址集合 数组形式
success: function (res) {},
fail: function (res) {},
complete: function (res) {},
})
},
/***删除图片***/
deleteImg: function (e) {
const index = e.currentTarget.dataset.index; //index是图片的索引,从0开始
const {
upload,
chooseImgs
} = this.data;
console.log("删除前的图片", upload)
upload.splice(index, 1);
chooseImgs.splice(index, 1);
//console.log("点击图片删除", index);
this.setData({
upload,
chooseImgs,
hideAdd: chooseImgs.length === 9, // 是否隐藏添加图片的图标
})
console.log("删除后的图片", upload)
},
后来老师小哥说怎么保证我是一张图片接着一张图片传,也就是一张传成功才传下一张,因为上面使用的forEach是异步操作,因此建议我改写成异步操作同步执行的形式。然后就开始各种查资料,过程中发现了一些值得记下来的句子,promise,async,await就是解决这个问题的
1.promise是解决forEach循环调用的异步方法的同步实现过程
2.为什么要用await?使我们的异步代码更像同步
3.await如果它等到的不是一个promise对象,那await表达式的运算结果就是它等的东西,
如果它等到的是一个promise对象,比如async返回的就是一个promise对象,那么它就会阻塞后面的代码(只阻塞当前路径,不阻塞别的路径),等待promise对象进入resolve状态,然后得到resolve的值,作为await表达式的运算结果。
那么问题来了,根据async,await的写法,我得把上传函数wx.upload封装起来模块化,这样比较好写一点,于是开始封装
首先在utils文件里新建config.js保存下接口
const UPLOAD_URL = 'xxxxxxxxxx' // 此处为服务器地址
module.exports = {
UPLOAD_URL
};
然后找到utils.js导入
import {
UPLOAD_URL
} from './config'
然后编写封装代码,最后exports
const uploadFile = (uploadFile) => {
return new Promise((resolve, reject) => {
wx.uploadFile({
url: UPLOAD_URL, // 上传的服务器接口地址
filePath: uploadFile,
header: {
"Content-Type": "multipart/form-data",
},
name: 'file', //上传的所需字段,后端提供
formData: {
'dir': "goods",
'tokenId': 'xxxxxxxxxx'
},
success: (res) => {
// 上传完成操作
const data = JSON.parse(res.data)
resolve({
data: data
})
},
fail: (err) => {
//上传失败:修改pedding为reject
console.log("访问接口失败", err);
wx.showToast({
title: "网络出错,上传失败",
icon: 'none',
duration: 1000
});
reject(err)
}
});
})
}
module.exports = {
formatTime: formatTime,
uploadFile: uploadFile
}
然后应用到页面上,在upload.js上面import
import {
uploadFile
} from '../../utils/util'
然后修改上面的上传图片方法,首先把
uploadImgs:function() {} 改成 async uploadImgs() {}
success: function (res) {} 改成 success: async function (res) {}
最后只需要修改//网络请求 上传图片 部分的代码如下,resName就是上传后接口返回的图片URL后缀,因此和预先给的接口前缀拼起来就是这张图片完整的URL,然后存到本地的data里的upload数组里供后续任务使用,完整URL可以放到浏览器里看看能不能显示,能显示就代表上传成功。
for (let item of newChooseImgs) {
console.log(item)
let resImage = await uploadFile(newChooseImgs[0])
console.log("await返回的内容resImage:",resImage)
const upload = that.data.upload;//把每次选择的图push进数组
const resStatus = resImage.data.status
const resName = resImage.data.name
console.log("11111",resName)
upload.push({
imgURL:'xxxxxxxx'+ resName,
});
if (resStatus == 1) {
that.setData({
"upload": upload
})
console.log("本地存储的图片URL后缀:",that.data.upload)
//
}
}
至此完成了多图上传的功能~
后续会整理更新图片的代码
对了,本地运行记得在原生编辑器右上角的详情里将下面的打勾哟~