前言:之前公司需求做一个上传图片/视频的功。
第一版只是简单的选择图片/或上传,倒也没什么难度。
后面第二版 增加了按顺序上传 也没什么难度 就是处理异步回调的问题。
第三版,逻辑开始复杂起来了,增加了取消上传、删除、上传的状态:上传中 上传成功 上传失败、测量图片宽高,不符合规定的宽高不让上传(我司定的规则是 widht=>400 && height>=400)、状态都会用UI体现。
做第三版的时候也没考虑太多, 在原来的基础上修修改改,也一一实现了,但是有很多问题,比如:取消的时候(这里调用取消上传的函数), 但图片依然上传成功。处理起来比较麻烦 异步回调调异步,不过后面也解决了。
最近又有一个页面需要用到 这个组件,来回复制也行,但是考虑到改一处就是改三处,而且改的时候也麻烦。于是乎,今天加班 抽取一个组件,顺便优化一下代码。
开撸
组件具备的功能:
- 选择图片/视频上传
- 上传中
- 上传成功
- 上传失败
- 删除
- 图片不符合规范
- ui展示
1.选择图片/视频上传
// 相册选择照片
onClikSelectResources: function(res) {
self.selectComponent('.uploadImage').onClikSelectResources(res.currentTarget.id)
// self.selectComponent('.uploadImage').data.uploadImageList 这种方式可以获取 组件里面的数据
self.setData({
showDialog: false
})
},
// 选择资源
onClikSelectResources: function(res) {
let self = this;
let sourceType;
switch (res) {
case 'albumImage':
console.log("相册选择图片");
sourceType = ['album'];
self.addImageResources(sourceType)
break
case 'cameraImage':
console.log("拍摄图片");
sourceType = ['camera'];
self.addImageResources(sourceType)
break
case 'albumVideo':
console.log("相册选择视频");
sourceType = ['album'];
self.addVideoResources(sourceType);
break
case 'cameraVideo':
console.log("拍摄视频");
sourceType = ['camera'];
self.addVideoResources(sourceType);
break
}
},
微信小程序 组件可以向页面传递事件,不过我在页面的点击事件,调用组件的函数,也能达到目的。 页面的事件能不能传递给组件,这个没有仔细研究。
第一个onClikSelectResources是页面的点击事件,点击选取照片/视频。
第二个onClikSelectResources是组件的函数,传递一个viewId 区分拍照/选取。
页面只提供一个事件 后续逻辑都在组件里。
2.上传中
// 添加图片资源
addImageResources: function (sourceType) {
let self = this;
console.log(self.data.deleteResNumber, self.data.isVideo,self.data.uploadImageList.length)
// 已上传多少个资源
let uploadedNumber = self.data.uploadImageList.length - self.data.deleteResNumber;
// 还可以上传几张
let maxNumber = self.data.isVideo ? 10 - uploadedNumber : 9 - uploadedNumber;
if (self.data.isVideo && uploadedNumber >= 10) {
self.releaseShowModal('只能添加9张')
return;
}
if (uploadedNumber >= 9) {
self.releaseShowModal('只能添加9张')
return;
}
wx.chooseImage({
count: maxNumber,
sizeType: ['original'],
sourceType: sourceType,
success(imageRes) {
console.log('选择图片返回信息', imageRes)
self.data.resIndex = self.data.uploadImageList.length;
for (let i = 0; i < imageRes.tempFilePaths.length; i++) {
let imageObj = {
// 本地资源地址
local_url: imageRes.tempFilePaths[i],
// 网络资源地址
url: '',
//资源类型 0 视频 1 图片
res_type: 1,
// 上传状态 上传中 upload_start 上传失败 upload_error 上传成功 upload_success 图片删除 upload_delete
upload_state: 'upload_start',
// 取消任务函数
cancel_function: '',
// 资源上传位置
res_index: self.data.resIndex + i,
// 图片宽高
image_size: '',
// 图片是否为第一个
image_first: '',
// 图片是否是最后一个
image_last: '',
// 是否是封面
image_cover: '',
}
self.data.uploadImageList.push(imageObj)
self.calculatedResOrder();
// 测量图片 获取图片宽高
wx.getImageInfo({
src: imageRes.tempFilePaths[i],
success: function (res) {
let imgwidth = res.width
let imgheight = res.height;
// 图片宽高需要大于或者等于400
if (imgwidth >= 400 && imgheight >= 400) {
// 记录每张图片的大小
self.data.uploadImageList[self.data.resIndex + i].image_size = imgwidth + ',' + imgheight;
// 上传七牛
self.uploadQiniu(imageRes.tempFilePaths[i], self.data.resIndex + i)
} else {
// 图片不符合规范
self.data.uploadImageList[self.data.resIndex + i].upload_state = 'upload_delete';
self.calculatedResOrder();
wx.showToast({
icon: 'none',
title: '上传图片太模糊,请重新上传'
});
}
},
function(res) {
// 图片测量失败
console.log('内容图片测量失败')
self.data.uploadImageList[self.data.resIndex + i].upload_state = 'upload_delete';
self.calculatedResOrder();
wx.showToast({
icon: 'none',
title: '内容图片测量失败'
});
}
})
}
}
})
},
此函数就是选择/拍摄,判断还可以选择几张图片,如果超出九张就不让上传,有一个变量 deleteResNumber 可能起来比较懵逼,后面会讲到
接着就是wx.chooseImage() 这个函数。选取的成功回调里面 遍历选择的图片数组,遍历的时候 添加图片对象,
每个图片对象都有若干个属性,其中 upload_state 这个属性比较重要 这个属性管理张图的生命周期,开始 结束,死亡... 根据这些状态显示不同的ui。
每添加一个图片对象 调用一下 calculatedResOrder 函数 这个函数是配合业务逻辑计算用的,下面会讲到。
接着就是getImageInfo 这个微信系统的函数,给一个图片地址测量图片宽高,如果该图片的宽高不符合规范 则不添加。
添加完毕之后 调用七牛上传函数,传入图片地址、这个图片的位置 进行上传。
其中有一点要说的是 变量resIndex ,每次选择图片上传 记录原本资源数组的长度,这次上传完毕, 下次再上传,图片位置就自动往后排序。
下面是UI xml文件


2.上传成功/上传失败
// 上传七牛
uploadQiniu: function (imagePath, index) {
let self = this;
console.log('上传七牛 uploadQiniu', imagePath)
util.uploadQiniu(
imagePath,
0,
'content_image' + index,
false,
false,
function (imageUrl) {
// 如果已经取消 但是取消函数需要执行时间 由于上传的太快 依然会走成功回调 但此时状态为删除 则不添加
if (self.data.uploadImageList[index].upload_state == 'upload_delete') {
return;
}
// 为图片资源url 赋值
self.data.uploadImageList[index].url = imageUrl + '?imageslim';
// 更新图片资源状态 为成功
self.data.uploadImageList[index].upload_state = 'upload_success'
console.log('图片上传成功 此时资源数组:',self.data.uploadImageList)
// 上传完毕之后 计算一下当前位置
self.calculatedResOrder();
},
function (res) {
// 如果已经取消 但是取消函数需要执行时间 由于上传的太快 依然会走失败回调 但此时状态为删除 则不添加
if (self.data.uploadImageList[index].upload_state == 'upload_delete') {
return;
}
// 更新图片资源状态 为删除
self.data.uploadImageList[index].upload_state = 'upload_error';
self.calculatedResOrder();
wx.showToast({
icon: 'none',
title: '上传图片失败,请重新上传'
});
console.log('上传图片失败', res)
},
function (res) {
// 如果这张图片已经取消 但是函数还未添加 而正要添加函数 可以直接执行
if (self.data.uploadImageList[index].upload_state == 'upload_delete') {
res();
}
self.data.uploadImageList[index].cancel_function = res;
},
)
},
uploadQiniu 这个函数是上传七牛的,自己封装的 里面就是传入一些参数 三个回调
- 上传成功
第一个成功回调 返回一个图片网络地址,判断这张图有没有删除,删除了则直接返回
如果没有删除 存下这张图片的网络地址,状态变成 upload_success,然后调用计算函数 计算一下。
判断这个 upload_delete 也是为了预防,如果我删除的时候调用取消函数 但是执行取消函数也需要时间,在取消的过程中,图片已经上传成功了,所以判断与一下图片的状态 - 上传失败
第二个失败回调,也是判断下是否删除 没有删除 状态变成 upload_error - 取消函数
第三个回调 当你调用七牛上传的时候,这个函数会返回你一个取消函数
一般来说 这个回调比上面的两个回调执行的早 但是 我这边做个预防 刚上传一张图片 我立马点击取消,此时这个函数刚刚执行,我就判断 这这张图片状态是否删除 如果删除 那么就直接调用,取消上传
3.删除资源
// 删除资源
onClickDeleteRes:function (res) {
let self = this;
let index = res.currentTarget.dataset.alphaBeta;
if (self.data.uploadImageList[index].upload_state == 'upload_start') {
// 此时删除 但是资源正在上传 那么进行取消
self.data.uploadImageList[index].cancel_function();
}
// 删除视频 那就可以继续上传
if (!self.data.uploadImageList[index].res_type) {
self.data.isVideo = false;
}
self.data.uploadImageList[index].upload_state = 'upload_delete';
// 计算图片顺序
self.calculatedResOrder();
console.log("删除资源", self.data.uploadImageList);
},
这个注释说的很清楚了 如果该图片正在上传 则调用取消上传,删除视频 isVideo 复制false,因为需求规定 只能上传一个视频,删除完了 调用一下 calculatedResOrder。值得说一点的是 这个资源对象没有从数组移除 只是状态变成了delete 为什么不直接移除呢 因为删除数组 顺序就乱了 每个图片对的位置也乱了 取消函数也和当前的图片对应不上了,全乱了 问题很多。
4.查看资源
// 查看资源
onCllickLookRes: function (res) {
let self = this;
let index = res.currentTarget.dataset.alphaBeta
console.log("查看资源", index)
// 查看资源 确保该资源已经上传成功
if (self.data.uploadImageList[index].upload_state == 'upload_success') {
if (!self.data.uploadImageList[index].res_type) {
wx.navigateTo({
url: '../video/video?url=' + self.data.uploadImageList[index].url,
})
return;
} else {
let newImgList = [];
// 遍历资源数组
for (let i = 0; i < self.data.uploadImageList.length; i++) {
// 此资源 是图片 并且上传成功
if (self.data.uploadImageList[i].upload_state == 'upload_success' && self.data.uploadImageList[i].res_type) {
newImgList.push(self.data.uploadImageList[i].url)
}
}
wx.previewImage({
current: self.data.uploadImageList[index].url,
urls: newImgList // 需要预览的图片http链接列表
})
}
}
},
这个逻辑也很简单 判断点击的这张资源状态是否 upload_success 判断这个资源是视频还是图片,视频跳往播放页 图片则调用wx的浏览图片函数 遍历这个数组的意义 就是 把有效的资源的资源放到新数组里 进行浏览 原本的资源数组 有些资源对象是没有url地址的。
下面说一下 calculatedResOrder 函数
calculatedResOrder:function () {
let self = this;
// 正序遍历一遍
for (let i = 0;i< self.data.uploadImageList.length;i++) {
// 找出第一个 资源 然后终止循环 第一个也有可能是错误的
if (self.data.uploadImageList[i].upload_state != 'upload_delete') {
self.data.uploadImageList[i].image_first = true;
break;
}
}
// 正序遍历一遍
for (let i = 0; i < self.data.uploadImageList.length; i++) {
// 找出是第一张 不是错误的 也没有被删除资源
if (self.data.uploadImageList[i].res_type && self.data.uploadImageList[i].upload_state != 'upload_delete' && self.data.uploadImageList[i].upload_state != 'upload_error') {
self.data.uploadImageList[i].image_cover = true;
break;
}
}
// 计算删除了多少资源
let deleteResNumber = 0;
// 是否有视频
// 正序遍历一遍
for (let i = 0; i < self.data.uploadImageList.length; i++) {
// 找出没有被删除
if (!self.data.uploadImageList[i].res_type && self.data.uploadImageList[i].upload_state !='upload_delete') {
self.data.isVideo = true;
}
// 计算删除了多少资源
if (self.data.uploadImageList[i].upload_state == 'upload_delete') {
deleteResNumber++;
}
}
// 找出最后一张有效资源
for (var i = self.data.uploadImageList.length - 1; i >= 0; i--) {
if (self.data.uploadImageList[i].upload_state != 'upload_delete') {
self.data.uploadImageList[i].image_last = true;
break
}
}
self.setData({
deleteResNumber: deleteResNumber,
uploadImageList: self.data.uploadImageList,
})
console.log('更新布局UI',self.data.uploadImageList)
},
这个是根据公司业务逻辑 写的一个函数
每次每次改变uploadImageList资源数组里面的对象属性时 都要调用一边 因为需求是 需要第一张图的封面的宽高 需要将第一张图片设置为封面 需要将视频设置特定的标识 需要视频封面 ui上来说 第一张图 和最后一张图的左右间距也不一样 这些都需要找出来。
前面说到 deleteResNumber 这个函数,刚刚删除资源的时候也说了 图片并没有从资源数组中移除 因为怕顺序乱了,用 deleteResNumber 记录一下删除了几张图片 选择的图片的时候 uploadImageList.length - uploadImageList 得出已经上传几张有效资源。我们需求是 最多九张图片,1个视频,所以有视频的情况下,10 - 有效资源数量 = 还可以上传多少,如果没有视频 那么就是 9 - 有效资源数量 = 还可以上传多少。
中间写的可能不是很清楚,有不明白的,或者觉得有什么说的不足的 请多多指教!
附上git地址:github.com/SuperHanMr/… 欢迎star!