经过一段时间的微信小程序开发,总结了一些代码片段,主要是以下几个方面:
- 小程序(授权、网络、录音、图像)
- 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>