所谓: 一图胜千言这句话说明了图片描述事物的能力是非常强大的, 尤其当今手机拍照功能那么方便, 用户对使用拍照和相册的需求日益上升因此, 在我们的移动应用中, 可能经常会碰到这样的功能需求, 需要为用户提供在相册中选择照片或者拍照片并上传的功能
例如下图所示的应用界面, 这是一个比较典型的创建帖子或问答等内容的表单, 用户可以填写标题和正文, 并从自己的手机相册中选择 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()
- })
- }
- }
- }
这个提交保存函数的主要流程是:
将图片分别通过文件上传 APIwx.uploadFile 进行上传, 并返回上传后的图片地址备用;
接着将标题正文以及刚才的图片地址一并通过调用后端创建问题的 API, 保存到数据库中
保存完毕, 返回问题列表页
在我的这个实现代码中, 是将上传文件和创建问题分别通过 2 个后端 API 来进行的, 其实 wx.uploadFile 除了上传文件, 同时也可以携带其他表单数据, 这样一来, 就可以用单一 API 来实现具体选择哪一种方式, 主要看你们实际的后端 API 的设计了
最后, 附上比较完整的源代码供大家参考吧
点击下载本文完整示例代码 http://md-assets.oss-cn-hangzhou.aliyuncs.com/download/utils.zip
来源: http://www.jianshu.com/p/c1e0574ee63d