专栏首页大前端开发微信小程序之图片选择、预览与上传

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

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

例如下图所示的应用界面,这是一个比较典型的创建帖子或问答等内容的表单,用户可以填写标题和正文,并从自己的手机相册中选择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 条评论
登录 后参与评论

相关文章

  • 微信小程序中实现瀑布流布局和无限加载

    瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。

    一斤代码
  • 微信小程序之上拉加载与下拉刷新

    在移动端,随着手指不断向上滑动,当内容将要到达屏幕底部的时候,页面会随之不断的加载后续内容,直到没有新内容为止(我们是有底线的-o-),我们称之为上拉加载,从技...

    一斤代码
  • 让Wordpress成为你微信小程序的文章管理利器

    Wordpress,相信很多很多人都用过,是一款大名鼎鼎的内容管理系统(CMS),因其社区的强大,插件的丰富,用它来建网站和博客真的是非常方便好用。它拥有功能强...

    一斤代码
  • Go 命令行解析 flag 包之通过子命令实现看 go 命令源码

    正式介绍子命令的实现之前,先了解下 flag 包中的一个类型,FlagSet,它表示了一个命令。

    波罗学
  • 1.2.1 、Google Analytics布署的几种方法

    这一节主要介绍如何部署GA跟踪代码和如何对跟踪代码做个性化的定制,提高数据准确度以及检验代码是否部署正确。

    GA小站
  • django 渲染模板与 vue 的 {{ }} 冲突解决方法

    如果不可避免的在同一个页面里既有 django 渲染又有 vue 渲染的部分,可有 2 种方式解决

    kirin
  • ABAP和Java里关于DEFAULT(默认)机制的一些语言特性

    由此可见新语法比较简洁,能少写3行代码。但是这样有一个缺陷,在新语法下如果it_data内表内不存在object_ext的值为cl_crm_prodil_bo_...

    Jerry Wang
  • Linux巩固记录(2) java项目的编译和执行

    以前只在linux上配置J2EE项目执行环境,无非配置下jdk,部署tomcat,再通过docker或者jenkins自动部署上去

    肖哥哥
  • 使用 mono 编译 .NET Standard 应用

    微软发布 .NET Standard 2.0 已经有一段时间了, 根据 .NET Standard 2.0 支持版本的文档, Mono 5.4 是支持 .NET...

    beginor
  • Spring 中的自动装配

    自动装配指使用 Spring 满足 Bean 依赖的一种方法,Spring 会在应用上下文中为某个 Bean 寻找其依赖的 Bean,主要有三种装配机制:

    村雨遥

扫码关注云+社区

领取腾讯云代金券