微信小程序 选择图片/视频组件

2,689 阅读9分钟

前言:之前公司需求做一个上传图片/视频的功。
第一版只是简单的选择图片/或上传,倒也没什么难度。
后面第二版 增加了按顺序上传 也没什么难度 就是处理异步回调的问题。
第三版,逻辑开始复杂起来了,增加了取消上传、删除、上传的状态:上传中 上传成功 上传失败、测量图片宽高,不符合规定的宽高不让上传(我司定的规则是 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文件

根据状态显示UI。
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!

完结散花