微信小程序之图片选择、预览与上传

所谓:一图胜千言。这话说明了图片描述事物的能力是非常强大的(怪不得我们可以用表情包聊一整天),尤其现在的手机拍照功能那么方便,用户对使用拍照和相册的需求日益上升。因此,在我们的移动应用中,可能经常会碰到这样的功能需求,需要为用户提供在相册中选择照片或者拍照片并上传的功能。

例如下图所示的应用界面,这是一个比较典型的创建帖子或问答等内容的表单,用户可以填写标题和正文,并从自己的手机相册中选择3张图片(或直接通过摄像头拍摄),且当点击缩略图时,可以全屏预览查看这些图片:

像这样一个带图片上传和预览功能的表单,在移动app中是比较常见的。那么在微信小程序中该如何来实现呢?且看我们一步步来构建这样的功能。

标题和正文输入框

对于这个表单,我们首先来创建上部的2个输入区域:标题和正文输入区。我们使用了一个单行输入框组件<input>来接收标题的输入,而使用一个多行输入组件<textarea>来接收正文的输入,并且为它们分别设置了maxlength属性来作最大输入字符数的限制。然后,为了更加直观,我们还为这2个输入区域分别放置了一个展示当前已输入字符数统计状态的标签。

界面的WXML代码大致如下所示:

<view class="question-input-area">

  <!-- 问题标题区域  -->
  <view class="question-title-wrap">
    <!-- 标题输入框 -->
    <input class="question-title" placeholder="请输入标题" maxlength="40" placeholder-style="color:#b3b3b3;font-size:18px;" bindinput="handleTitleInput"></input>
    <!-- 标题输入字数统计 -->
    <view class="title-input-counter">{{titleCount}}/40</view>
  </view>

  <!-- 问题正文区域  -->
  <view class="weui-cells weui-cells_after-title">
    <view class="weui-cell">
      <view class="weui-cell__bd">
        <!-- 多行输入框 -->
        <textarea class="weui-textarea" placeholder="请输入问题的正文内容。" maxlength="500" placeholder-style="color:#b3b3b3;font-size:14px;" style="height: 12rem" bindinput="handleContentInput" />
        <!-- 正文输入字数统计 -->
        <view class="weui-textarea-counter">{{contentCount}}/500</view>
      </view>
    </view>
  </view>

</view>

而与之对应的Page代码如下:

import { $init, $digest } from '../../utils/common.util'

Page({

  data: {
    titleCount: 0, //标题字数
    contentCount: 0, //正文字数
    title: '', //标题内容
    content: '' //正文内容
  },

  onLoad(options) {
    $init(this)
  },

  handleTitleInput(e) {
    const value = e.detail.value
    this.data.title = value
    this.data.titleCount = value.length  //计算已输入的标题字数
    $digest(this)
  },

  handleContentInput(e) {
    const value = e.detail.value
    this.data.content = value
    this.data.contentCount = value.length  //计算已输入的正文字数
    $digest(this)
  }
}

【注意】有人可能会对这里的一些代码觉得奇怪,这段JavaScript代码中出现的$init和$digest是什么?其实它是一个通过对象深层比较,将Page的data对象中的数据进行批量、按需更新到视图层WXML中的一个功能。对初学者来说,你暂且可以认为是在每个调用$digest(this)的地方调用了一次this.setData()的操作吧,方便理解。

通过上面的两段代码,我们就已经把表单的输入框部分创建出来了。下面,我们要进入本文的关键功能部分。

选择和预览图片、以及上传图片

微信小程序提供的众多API中,wx.chooseImage函数就是用来访问手机相册或摄像头的。调用该函数后,界面下方会呼出一个菜单,可以分别选择进入相册挑选已有照片或是打开摄像头进行拍照:

二话不说继续上代码!我们往WXML里新添一个按钮,点击该按钮就会触发wx.chooseImage的调用:

<button
    type="default" size="mini" bindtap="chooseImage" 
    wx:if="{{images.length < 3}}"
>添加图片</button>
import { $init, $digest } from '../../utils/common.util'

Page({

  data: {
    images: []
  },

  onLoad(options) {
    $init(this)
  },

  chooseImage(e) {
    wx.chooseImage({
      sizeType: ['original', 'compressed'],  //可选择原图或压缩后的图片
      sourceType: ['album', 'camera'], //可选择性开放访问相册、相机
      success: res => {
        const images = this.data.images.concat(res.tempFilePaths)
        // 限制最多只能留下3张照片
        this.data.images = images.length <= 3 ? images : images.slice(0, 3) 
        $digest(this)
      }
    })
  }
}

通过以上代码,我们就可以开始把玩起手机相册和摄像头了。但是目前选择了照片或拍了照之后,在表单界面上并不能看到。下面我们就要继续做选择图片后的展示工作。

我们通过wx:for语法,将我们之前存在images数组中的照片展示到界面上来:

<view class="question-images">
  <block wx:for="{{images}}" wx:key="*this">
    <view class="q-image-wrap">
      <!-- 图片缩略图  -->
      <image class="q-image" src="{{item}}" mode="aspectFill" data-idx="{{index}}" bindtap="handleImagePreview"></image>
      <!-- 移除图片的按钮  -->
      <view class="q-image-remover" data-idx="{{index}}" bindtap="removeImage">删除</view>
    </view>
  </block>
</view>

我们在每个缩略图元素上绑定了一个点击事件,当点击缩略图的时候,会调用微信小程序提供的预览图片的方法wx.previewImage进行全屏预览,用户可以左右滑动查看选中图片列表中的大图。另外,在每个缩略图的下方,还有一个删除按钮,用于移除所选的图片,方便重新选图。下面是对应的JS代码:

import { $init, $digest } from '../../utils/common.util'

Page({

  data: {
    images: []
  },

  onLoad(options) {
    $init(this)
  },

  removeImage(e) {
    const idx = e.target.dataset.idx
    this.data.images.splice(idx, 1)
    $digest(this)
  },

  handleImagePreview(e) {
    const idx = e.target.dataset.idx
    const images = this.data.images
    wx.previewImage({
      current: images[idx],  //当前预览的图片
      urls: images,  //所有要预览的图片
    })
  }
}

终于,只剩下最后一件事,就是提交表单数据及上传图片到后端,将的这些数据组成一个完整的问题,保存进数据库。

对于我们的WXML,还缺最后这个提交按钮呢!立马补上吧:

<!-- 提交表单按钮  -->
<button class="weui-btn" type="primary" bindtap="submitForm">提交</button>

然后就是这Page中的集大成者(大杂烩吧,哈哈)submitForm函数:

import { $init, $digest } from '../../utils/common.util'

Page({

  data: {
    images: []
  },

  onLoad(options) {
    $init(this)
  },

  submitForm(e) {
    const title = this.data.title
    const content = this.data.content

    if (title && content) {
      const arr = []

      //将选择的图片组成一个Promise数组,准备进行并行上传
      for (let path of this.data.images) {
        arr.push(wxUploadFile({
          url: config.urls.question + '/image/upload',
          filePath: path,
          name: 'qimg',
        }))
      }

      wx.showLoading({
        title: '正在创建...',
        mask: true
      })

      // 开始并行上传图片
      Promise.all(arr).then(res => {
        // 上传成功,获取这些图片在服务器上的地址,组成一个数组
        return res.map(item => JSON.parse(item.data).url)
      }).catch(err => {
        console.log(">>>> upload images error:", err)
      }).then(urls => {
        // 调用保存问题的后端接口
        return createQuestion({
          title: title,
          content: content,
          images: urls
        })
      }).then(res => {
        // 保存问题成功,返回上一页(通常是一个问题列表页)
        const pages = getCurrentPages();
        const currPage = pages[pages.length - 1];
        const prevPage = pages[pages.length - 2];

        // 将新创建的问题,添加到前一页(问题列表页)第一行
        prevPage.data.questions.unshift(res)
        $digest(prevPage)

        wx.navigateBack()
      }).catch(err => {
        console.log(">>>> create question error:", err)
      }).then(() => {
        wx.hideLoading()
      })
    }
  }
}

这个提交保存函数的主要流程是:

  1. 将图片分别通过文件上传APIwx.uploadFile进行上传,并返回上传后的图片地址备用;
  2. 接着将标题、正文、以及刚才的图片地址一并通过调用后端创建问题的API,保存到数据库中。
  3. 保存完毕,返回问题列表页

在我的这个实现代码中,是将上传文件和创建问题分别通过2个后端API来进行的,其实wx.uploadFile除了上传文件,同时也可以携带其他表单数据,这样一来,就可以用单一API来实现。具体选择哪一种方式,主要看你们实际的后端API的设计了。

最后,附上比较完整的源代码供大家参考吧。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏静默虚空的博客

详谈如何定制自己的博客园皮肤

如果你仅仅想原封不动的使用本人的定制皮肤,而不想了解实现细节。那么你只需要完成以下几个步骤即可,后面的章节可以忽略。

4420
来自专栏闻道于事

HTML表格表单综合——用户注册表

今天学习了表格和表单知识,我综合了他们添加了一些拓展知识做了一个用户注册表,以下面代码来整理表格和表单知识: <!DOCTYPE html PUBLIC "-/...

1.5K6
来自专栏前端人人

React第三方组件1(路由管理之Router的使用②多层级跳转及重定向)

本教程总共6篇,每日更新一篇,请关注我们!你可以进入历史消息查看以往文章,也敬请期待我们的新文章! 1、React第三方组件1(路由管理之Router的使用①...

3354
来自专栏coding for love

CSS入门1-认识html之标签

(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)

1112
来自专栏极客慕白的成长之路

图标字体应用实践

使用的时候,通过background-position调整显示的位置,如下图所示:

1752
来自专栏疯狂的小程序

关于微信小程序内置组件swiper,circular使用分享

swiper,关于滑块的一些效果无缝,断点,视差等等...我想这里就不用做太多的赘述,这里给大家分享一下实战项目中使用circular(衔接)的一点小特性、小技...

67910
来自专栏calmound

基于iframe的移动端嵌套

需求描述 上上周接到了新的项目,移动端需要做一个底部有五个导航,点击不同的导航页面主体显示不同的页面,其中两个页面是自己做,而另外三个页面是引用另外三个网址,其...

6006
来自专栏coding

vue.js组件间通信

组件间需要能相互通信才价值,通信包括数据的传递,方法的调用。这样才能将不同组件结合起来搭建页面

941
来自专栏惶心 - 技术博客

Grouper.html: 分享群组的最佳方式

之前看到 狗子 的 https://getrbq.com ,是给 DIYgod 的群组做的一个加群页面,发现他是用 折影轻梦 的模板修改了一下做好的。虽然说这个...

1726
来自专栏小白课代表

更新|PC截图工具的最佳选择。

说到截图,最常用的可能就是QQ自带的截图功能了,简单流畅能满足我们的日常需求,依附于QQ,没有特殊的需求甚至不需要其他的截图软件。

1360

扫码关注云+社区

领取腾讯云代金券