经过一段时间的微信小程序开发,总结了一些代码片段,主要是以下几个方面:
- 小程序(授权、网络、录音、图像)
- mpvue(分包、全局变量、svg组件、组件class绑定)
小程序
授权逻辑
- 初次请求 -> 请求用户授权 -> 同意授权(-> 不同意授权 -> 结束) -> 使用对应功能
- 二次请求 -> 跳转小程序设置页面modal -> 设置页面 -> 开启scope -> 使用对应功能
const checkPermission = scope => new Promise((resolve, reject) => { wx.getSetting({ success: res => { // 是否存在认证配置 let hasAuthorized = res.authSetting.hasOwnProperty(scope) if (hasAuthorized) { // 已授权 if (res.authSetting[scope]) { resolve('已授权') return } // 未授权,提示进入小程序设置页面,wx限制:需要主动点击才能执行openSetting(),因此使用modal wx.showModal({ title: '没有权限', content: '体验该功能需要您授权功能权限,现在前往设置开启', success: res => { if (res.confirm) { reject('设置页面') wx.openSetting() } else if (res.cancel) { reject('不进入设置') } } }) } }, fail: err => { reject(err.errMsg) } }) })
网络
微信小程序不同环境下网络请求的不同之处:
- 预览及体验模式下会
校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书
,需要在后台设置允许的request,upload等域名 - 真机调试与测试环境模式下,可以在详情里勾选不校验选项跳过校验
网络请求与拦截器
可以使用fly.js作为小程序的网络请求库,在使用拦截器等功能时也较为方便。
小程序中一个特殊的地方是: content-type
为multipart/formdata
类型的POST请求不能通过自定义请求的方式发出,需要使用小程序的wx.uploadFile
方法,可以如下简单封装下:
const formDataRequest = (url, filePath, params = {}) => new Promise((resolve, reject) => { let token = wx.getStorageSync("token") wx.uploadFile({ url, filePath, name: "file", header: { token }, formData: params, success: async res => { // 一些对响应数据的处理... resolve(res.data) }, fail: err => { reject(err) } }); });
判断是否在线
使用getNetworkType
方法即可
export const isOnline = () => new Promise((resolve, reject) => { wx.getNetworkType({ success(res) { const networkType = res.networkType resolve(networkType !== 'none') }, failed(res) { reject(res) } }) })
录音处理
主要是录音时的API检测、状态控制与事件监听器的处理。
// 1\. 检测录音管理器是否可用 if (wx.getRecorderManager) { this.recorder = wx.getRecorderManager() this.addRecorderListener() } else { wx.showModal({ title: '提示', showCancel: false, content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。' }) } // 2\. 录音前检测scope.record授权情况 async startRecordHandle() { if (!this.recorder) return try { await this.checkPermission('scope.record') } catch (err) { return } this.recorder.start(this.audioOption) }, // 3\. 添加事件监听器 addRecorderListener() { if (!this.recorder) return this.recorder.onStart(() => { ... this.recording = true }) this.recorder.onStop(path => { ... this.recording = false this.audioPath = path.tempFilePath }) }
若需实现长按录音的场景,可以结合lonepress
事件与setTimeout
来实现。
<template> <g-button type="primary" ... @long-press="longPressHandle" /> </template> <script> export default { methods: { longPressHandle() { // longpress事件会在350ms后出发 this.canRecordStart = true }, touchStartHandle() { this.canRecordStart = true let delay = 400 // 设置400ms延迟 setTimeout(() => { if (this.canRecordStart) { this.startRecordHandle() } }, delay) }, touchEndHandle() { if (!this.canRecordStart) return this.canRecordStart = false this.stopRecordHandle() }, } } </script>
图像处理
获取图片信息
wx.getImageInfo
不管是CDN的图片还是本地选择的图片都需要先使用getImageInfo
获取图片的基本信息
getImageInfo(img) { return new Promise((resolve, reject) => { wx.getImageInfo({ src: img, success: res => { resolve(res) }, fail: () => { reject('获取图片信息失败') } }) }) }
选择图片
wx.chooseImage
让用户选择本地相册中或拍摄的图片,以选择单张图片为例:
const MB = 1024 * 1024 chooseSingleImage() { return new Promise((resolve, reject) => { wx.chooseImage({ count: 1, // 默认9,为1获取单张图片 sizeType: ['original', 'compressed'], // 指定是原图还是压缩图,默认二者都有 sourceType: ['album', 'camera'], // 指定来源是相册还是相机,默认二者都有 success: res => { let file = res.tempFiles[0] // 可以对所选图片尺寸或其他属性做一些限制 // let { size } = file // if (size > 20 * MB) { reject('图片大小应小于20MB') } resolve(file) }, fail: () => { reject('图片选取失败') } }) }) }
读取图片
wx.getFileSystemManager()
使用小程序的FS相关API读取文件内容
readFileInBase64(filePath) { return new Promise((resolve, reject) => { if (wx.getFileSystemManager) { // 以base64编码读取图片 wx.getFileSystemManager().readFile({ filePath: filePath, encoding: 'base64', success: res => { resolve(res) }, file: () => { reject('读取文件失败') } }) } else { // 兼容处理,若不支持则提示更新 wx.showModal({ title: '提示', showCancel: false, content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。' }) } }) }
Canvas绘制图像
小程序中使用CanvasContext API与H5的形式基本相同。需要注意的是,在小程序中绘制canvas时尺寸的单位是px
,而不是响应式的rpx
。
需要注意的是从基础库1.9.90
开始CanvasContext
的API变化了很多,在使用时需要注意兼容性,比如下面两个函数:
export const drawPoint = (ctx, x, y) => { let pointColor = '#2ba5ff' ctx.beginPath() ctx.arc(x, y, 1, 0, Math.PI * 2, true) ctx.closePath() // 兼容画布填充色方法 if (ctx.fillStyle) { // 1.9.90+ ctx.fillStyle = pointColor } else { ctx.setFillStyle(pointColor) } ctx.fill() } export const drawRect = (ctx, x, y, width, height) => { let marginColor = '#ff0000' // 兼容笔触色彩方法 if (ctx.strokeStyle) { // 1.9.90+ ctx.strokeStyle = marginColor } else { ctx.setStrokeStyle(marginColor) } ctx.lineWidth = 1 ctx.strokeRect(x, y, width, height) }
mpvue
分包及分包预加载
mpvue-loader: ^1.1.2
直接在app.json
中配置subPackages
即可:
{ ... "subPackages": [ { "root": "pages/module-bob/", "pages": ["subpage-a/main", "subpage-b/main", "subpage-c/main"] }, { "root": "pages/module-alice/", "pages": ["subpage-d/main", "subpage-e/main", "subpage-f/main"] } ], "preloadRule": { "pages/index/main": { "network": "wifi", "packages": ["pages/module-bob"] } } ... }
其中preloadRule
为预加载配置,上面的设置意为进入index页面时当为wifi网络时预加载module-bob子包。
使用globalData全局变量
在小程序中将自带的globalData
挂载到vue的原型方法上。
在src中的main.js
最后添加如下代码:
import Vue from 'vue' import App from './App' ... const app = new Vue(App) app.$mount() Vue.prototype.$globalData = getApp().globalData // 添加该行
然后就可以在其他页面使用该命令操作全局变量了
// page A this.$globalData.userInfo = {name: 'yrq110'} // page B console.log(this.$globalData.userInfo) // page C this.$globalData.userInfo.name: 'yrq110'
注意,在子页面中使用globalData时,将变量赋值的操作放在data中是无效的,如下:
export default { data() { // 无效 // isIPX: this.$globalData.isIPX }, computed: { isIPX() { // 有效 return this.$globalData.isIPX } }, }
SVG图标组件的默认尺寸与预设尺寸
在图标组件中加载svg时使用父标签上的尺寸作为默认尺寸,并在传入特定props参数时使用预设尺寸。
业务中碰到了这个问题,使用如下的方法进行了解决:在image组件的load事件处理器中将加载的原始尺寸绑定到style上。
实现了: 1. 默认使用svg标签自带尺寸 2. 当传入size属性则使用预设尺寸
<template> <image ... @load="loadHandle" :style="{ width: !size ? iconWidth + 'rpx' : '100%', height: !size ? iconHeight + 'rpx' : '100%'}" ... /> </template> <script> export default { ... data() { return { iconWidth: 0, iconHeight: 0, loaded: false // 是否加载完毕 } }, props: { ... size: String }, computed: { ... getSizeClass() { let { size } = this return size || '' }, setSizeStyle() { if (!this.loaded || this.size) return {} return { width: this.iconWidth + 'rpx', height: this.iconHeight + 'rpx' } } }, methods: { loadHandle(e) { this.loaded = true // 使用加载后的默认尺寸 const { detail } = e.mp this.iconWidth = detail.width * 2 this.iconHeight = detail.height * 2 } } ... } </script>
解决无法在组件上绑定class的trick
将keyword作为prop属性传入组件并通过Computed属性绑定到class上,这样在外部引用时就可以根据keyword设置自定义的样式了。
组件中的关键代码如下:
<template> <div :class="customClass"> <slot></slot> </div> </template> <script> export default { ... props: { type: String, ... }, computed: { customClass() { let type = this.type || '' return type } } ... } </script>
在外部引用时就可以使用自定义class来在外部使用样式了:
<template> ... <g-button type="custom-button"></g-button> ... </template> ... <style lang="scss"> ... .custom-button { .text { margin-left: 10px; } ... } </style>