序言
小程序: 相亲小红圈+
tool: wepy
传送门:
[ wepy ] https://tencent.github.io/wepy/document.html#/
[ Vue ] https://cn.vuejs.org/
[ ES6 ] http://es6.ruanyifeng.com/
[ git ] https://www.liaoxuefeng.com/
七月份结束,项目上线,回过头来整理一下项目,项目为wepy1.7.0后版本开发的小程序,配套的后台前端使用ant-design-vue开发,上传测试服工具使用Xshell6。
内容
- wepy构建工程具体可在wepy官网中查看,此处不多介绍。
1 | $ wepy init standard my-project /**创建项目*/ |
wepy属于类Vue写法,要在wepy中使用异步操作(async/await)需要在工程的app.way入口文件中constructor函数中注册:
1 | constructor () { |
生命周期:
应用生命周期
属性 | type | 描述 | 触发时机 |
---|---|---|---|
onLaunch | Function | 生命周期函数–监听小程序初始化 | 用户首次打开小程序,触发 onLaunch(全局只触发一次) |
onShow | Function | 生命周期函数–监听小程序显示 | 当小程序启动,或从后台进入前台显示,会触发 onShow |
onHide | Function | 生命周期函数–监听小程序隐藏 | 当小程序从前台进入后台,会触发 onHide |
页面生命周期
属性 | type | 描述 | 触发时机 |
---|---|---|---|
onLoad | Function | 监听页面加载,一个页面只会调用一次 | 小程序注册完成后,加载页面,触发onLoad方法,参数可以获取wx.navigateTo和wx.redirectTo及 |
onReady | Function | 监听页面初次渲染完成,代表页面已经准备妥当,可以和视图层进行交互 | 首次显示页面,会触发onReady方法,渲染页面元素和样式,一个页面只会调用一次 |
onShow | Function | 监听页面显示,当redirectTo或navigateBack的时候调用 | 当小程序有后台进入到前台运行或重新进入页面时,触发onShow方法。 |
onHide | Function | 监听页面隐藏,当navigateTo或底部tab切换时调用 | 当小程序后台运行或跳转到其他页面时,触发onHide方法 |
onUnload | Function | 监听页面卸载 | 当使用重定向方法wx.redirectTo(OBJECT)或关闭当前页返回上一页wx.navigateBack(),触发onUnload。 |
版本更新
版本更新代码,一般较为固定,直接复制在 onLaunch 生命周期内
1 | if(wx.canIUse('getUpdateManager')){ |
组件
组件复用
说明: 组件复用,在 同一个页面,一个组件多次复用且每次传入不同的数据源,但是表现出来的数据源全部一致,其他的数据并没有渲染上
原因: 组件名一致导致wepy认为是一模一样的组件,如此数据源也不会变动
解决: 组件异名化
1 | import CompA from 'path' |
这样,既可以重复运用组件
文字换行
小程序文字换行:
1 | <text>第一行\n第二行\n第三行\n</text> |
定时器
页面业务逻辑有需要用到倒计时功能,如下图。
在页面中有需要用到倒计时或者其他定时器任务时,新建的定时器在卸载页面时一定要清除掉,有时候页面可能不止一个定时器需求,在卸载页面(onUnload钩子函数)的时候一定要清除掉当前不用的定时器
定时器用来做倒计时效果也不错,初始时间后台获取,前端处理,后台直接在数据库查询拿到的标准时间(数据库原始时间,T分割),前端需要正则处理一下这个时间:
1 | let overTimeStr = data.over_time.split('T') |
最终把时间分割为[年,月, 日, 时, 分, 秒]的数组,(如果后端已经把时间处理过了那就更好了),然后把该数组传递给倒计时函数
1 | countDownCtrl( time, group ) { |
至此,倒计时效果处理完毕,PS:终止时间一定要大于currentDate,否则显示会出现异常(包括但不限于倒计时闪烁、乱码等)
最后,退出该页面去其他页面时,一定要在页码卸载钩子中清除倒计时!!!
1 | onUnload() { |
组件传值
组件传值和Vue有点细微区别,Vue强调父组件的数组和对象不要直接传到子组件使用,应为子组件可能会修改这个data,如图:
但是,wepy中,有时候确实需要把一个对象传递到子组件使用,单个传递对象属性过于繁琐,而且!!!如果单个传递对象的属性到子组件,如果该属性是一个数组,则子组件永远会接收到 undefined 。此时最好用整个对象传值替代单个对象属性逐个传值的方法,且一定要在传值时加入 .sync 修饰符,双向传值绑定。确保从接口拿到的数据也能实时传递到子组件,而非 undefined
:circleMembersList.sync="circleMembersList"
阻止组件的点击事件传播
解决: 添加函数 catchtap=”funcName” 即可,funcName可为空函数,也可以直接不写
token判断
小程序调试时,有时候会出现首次打开无内容(拿不到数据)的状态,需要“杀死”小程序再打开才能看到数据内容,其中可能的原因之一便是 token 的失效。在与后台交互的时候,token必不可少。尤其是在小程序分享出去的链接,由其他用户点开分享链接进入小程序内部,此时更是要判断token,token的判断一般选在 onShow()钩子执行而不在 onLoad()钩子内执行。若不存在token,则应该执行登录去拿取token,再进行业务逻辑
1 | onShow() { |
formid
微信提供了服务通知,即在你支付、快递等行为时,微信会直接给你发一个服务通知(模板消息)来提醒,每次提醒都会消耗该用户存储的formID,formID为消耗品,用一个少一个,只有通过用户的表单提交行为才可以积攒formID
1
2
3
4
5
6
7
8<form @submit="submitForm" report-submit="true">
<button form-type="submit" class="editCard" @tap = "goModifiPage('editFormTab')">修改</button>
</form>
//js方法
submitForm(e) {
this.postFormId( e.detail.formId ) // 向后端传输formid
}
支付
准备: crypto-js.js && md5.js
微信支付流程为: 前端点击支付按钮拉起支付 ==》 准备加密数据 ==》 调用后端接口,传入需要的加密数据 ==》 后端验证加密数据,再返回加密数据 ==》 前端拿到后端加密数据(时间戳、内容、签名),对时间戳和内容进行本地签名,再判断本地签名和后端签名是否一致,若不一致,直接返回,退出支付,支付失败!若一致,对刚刚后台返回的content(内容)进行解析,拿到所需订单数据,前端拉起微信支付,参数传入刚刚解析数据 ===》 得到支付结果 success or fail !结束
1 | /** |
前端点击支付按钮:
1 | // 单独支付接口 |
支付函数:
1 | // 支付函数,我前端有五种支付情况(单独、自己发起拼团、拼别人团、ios、免费五种支付),所以单独抽出支付,每次调用支付函数 |
图片上传(七牛云)
更多图床网站请见我博客: https://www.cnblogs.com/fanghl/p/11419914.html
图片上传服务器采用七牛云服务,在app.wpy内小程序触发的时候,请求七牛云拿到token存为全局变量。
1 | //app.wpy |
导入七牛云文件import qiniuyun from '@/utils/qiniuUploader'
base.js代码:
1 | // 上传图片 base.js |
页面结构
1 | <!-- 上传生活照 --> |
上传图片业务:
1 | // 从相册选择照片上传 |
微信消息聊天布局
微信聊天框整体布局特点有: 接收方和发送方消息分别位于屏幕的左右两侧、最新的消息一定是在屏幕最底部、进入消息dialog页面一定是显示的最新消息,即页面滑动在最底部。这三个基本特征构成了微信聊天页面的布局原则。
先看效果图 (非最终效果):↓
布局思路:flex反向布局
1 | <!-- 格式化代码 --> |
1 | .msgBox{ |
1 | .msgItem{ /*消息item样式*/ |
保持页面始终滑动在最底部函数
1 | pageScrollToBottom( msgLength ) { //在页面需要进行变化时调用 |
自己发送的消息数据可以直接压入本地数组 talkContent 内,Unshift()进入,得到“负负得正”效果,即数据反,布局反即可得到从底部排列的布局。对方的消息从服务器拉下来的时候,放入 talkContent 内前 reverse() 一下即可
聊天页面input顶起页面相关
聊天input点击后,默认为顶起页面,也可以关闭默认选择不顶起。但是不顶起页面其实是input脱离当前page,会出现键盘上方没有我们的输入框!因为键盘不顶起页面,故不会影响之前的布局,输入框一般都在页面最底部。
解决: wx.onKeyboardHeightChange 监听键盘高度,严重不推荐input自身函数bindkeyboardheightchange,因为bindkeyboardheightchange 在手势上划隐藏键盘时Android是不会被触发的!!!
思路: adjust-position = "false"
设置不顶起页面,在手动把内容展示view 的高度减少键盘的高度!在键盘拉起时,内容高度减少键盘的高度,在键盘隐藏式,回复原高度。最后的效果和微信原生聊天一样!
效果:
优化:在减少高度的同时,把内容页面滑到最底部,以展示最新消息!
1 | <input class="inputContent" |
1 | onInpueChange() { |
CSS注意点
CSS持续补充中……word-break: break-all; //换行文字,英文溢出
-webkit-overflow-scrolling: touch; //ios端启用硬件加速,解决ios端滑动粘手
catchtouchmove='true' //模态框中添加,禁止页面滑动
circleDynamic:last-of-type //特定类circleDynamic中最后一个元素
:nth-of-type(1/odd/even) //选择特定元素下第几个元素
1 | /* CSS 吸顶 */ |
async/await
异步编程的终极解决方案,在小程序内拿取code或者login时会用到,await可理解为求值!async可理解为搭配await的语法,如果异步函数去掉await,返回的一般是 promise 对象,需要手动去reject 和 resolve 。
1 | if( !wepy.getStorageSync('token') ) { |
ios/android机型区别
由于微信小程序的运行规范限制等,一些在 Android 上可以存在的业务需求并不能原封不动在 ios 端运行,否则小心 封号警告 (此处手动滑稽.jpg),所以一般采取两个系统的用户进入某一个页面,展现不同的内容。
判断机型:在 app.wpy 入口文件中,onlaunch 生命周期内判断机型并保存到全局变量即可
1 | getSystemInfo() { |
分包
微信小程序官方限制小程序代码大小不得超过 2M ,在业务逻辑较多的情况下,查过2M后,我们可以采用分包加载。
1 | //app.wpy |
canvas生成海报
小程序分享至朋友圈的海报制作过程,海报内容为动态获取,内容根据每个用户生成不同的海报
如图,除了背景使用本地图片,其他的所有内容均为动态获取,且每次获取的不尽相同。
图片先从网络图片下载到本地才可以渲染,如果开发工具可以正常显示,而真机无法绘制,那么请先检查你的 downloadFile 域名!!!
绘制圆角-直角图片
图片保存模糊
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88//绘制文字
drawTxt(fontSize, color, content, x, y, bold=false) {
this.ctx.save()
this.ctx.setFontSize(fontSize)
this.ctx.setFillStyle(color)
this.ctx.fillText(content, x, y)
if (bold) {
this.ctx.fillText(content, x, y + 0.3)
this.ctx.fillText(content, x + 0.3, y)
}
this.ctx.setTextBaseline('middle')
this.ctx.restore()
}
//绘制圆角图片(默认)-矩形图片
getRectWithRadius(ctx, x, y, w, h, r, c, borderArgs = []) {
// r > 0 则默认绘制圆角图片, r = 0 ,则绘制矩形
//绘制圆形 则 r = 1/2 w || 1/2h
//绘制任意直角 则 borderArgs = [leftTop, rightTop, rightBottom, leftBottom]控制
let rate = this.rate
let b = borderArgs
ctx.beginPath()
ctx.moveTo(x / rate, y / rate)
b && b[0] ? '' : ctx.arc((x + r) / rate, (y + r) / rate, r / rate, Math.PI, 1.5 * Math.PI)
b && b[1] ? ctx.lineTo((x + w) / rate, y / rate) : ctx.lineTo((x + w - r) / rate, y / rate)
b && b[1] ? '' : ctx.arc((x + w - r) / rate, (y + r) / rate, r / rate, 1.5 * Math.PI, 0)
b && b[2] ? ctx.lineTo((x + w) / rate, (y + h) / rate) : ctx.lineTo((x + w) / rate, (y + h - r) / rate)
b && b[2] ? '' : ctx.arc((x + w - r) / rate, (y + h - r) / rate, r / rate, 0, 0.5 * Math.PI)
b && b[3] ? ctx.lineTo((x) / rate, (y + h) / rate) : ctx.lineTo((x + r) / rate, (y + h) / rate)
b && b[3] ? '' : ctx.arc((x + r) / rate, (y + h - r) / rate, r / rate, 0.5 * Math.PI, Math.PI)
ctx.closePath()
if (c) {
ctx.fillStyle = c
ctx.fill()
}
}
// 保存海报
savePosterToLocal() {
const that = this
// 获取用户是否开启用户授权相册
wx.getSetting({
success(res) {
// 如果没有则获取授权
if (!res.authSetting['scope.writePhotosAlbum']) {
wx.authorize({
scope: 'scope.writePhotosAlbum',
success() {
that.saveCanvas()
},
fail() {
that.toast('右上角开启授权', 'none')
wx.openSetting()
}
})
} else {
that.saveCanvas()
}
}
})
}
//canvas保存至相册
saveCanvas() {
// 1-把画布转化成临时文件
const that = this
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: 560, // 画布的宽
height: 996, // 画布的高
destWidth: 1080 * 750 / wx.getSystemInfoSync().windowWidth,
destHeight: 1920 * 750 / wx.getSystemInfoSync().windowWidth,
canvasId: 'poster',
success(res) {
wepy.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success(res2) {
that.toast('保存至相册成功', 'none')
},
fail() {
that.toast('保存失败,稍后再试', 'none')
}
})
},
fail() {
that.toast('保存失败,稍后再试', 'none')
}
})
}多张图片下载
网络图片下载成本地图片,且全部下载完全后才可以绘制canvas
1 | let promise1 = new Promise(function(resolve,reject){ |
- 异常显示
有时候Android手机会显示不完canvas,宽度异常,ios却没有该异常现象。其中一种可能就是设置canvas标签的宽高单位不一致,canvas内单位同一位 px ,把常用的 rpx 替换为 px
<canvas canvas-id="poster" style="width: 280px; height: 498px;"></canvas>
touchmove/onPageScroll动画效果
如图,右下角的按钮,一般会做这样的效果,当用户滑动列表时,该按钮向下滑动并隐藏,当用户停止滑动且页面亦停止滑动(非用户手指脱离屏幕)时,该按钮再从页面底部滑出。
- touchmove、 onPageScroll
思路1: 第一种想法是touchstart时, 触发下滑动画,touchend时触发上划动画
缺点: tap点击事件也会先触发start 和 end 事件,故点击屏幕也会触发动画,且屏幕抖动,pass
思路2: touchmove 时触发动画,touchend时上划动画,
缺点:虽然避免了点击就触发动画,但效果不佳,手指离开屏幕,页面还在滑动,动画已触发。
最终解: 只用 touchmove 来判断用户滑动列表,再用 onPageScroll 配合 超时器 来处理页面停止滑动。
代码:
1 | //template |
页面一直处于滑动时,超时器不会生效,只有在页面停止滑动后,超时器才生效,程序执行
录音
录音没有难度,上传语音采用七牛云服务,拿到临时路径经过七牛云拿到网络路径,在传给自己服务器。这里实现了一个圆环进度条(canvas),在录音时配合录音时长展示
1 | //绘制进度条圆环 |
播音
createInnerAudioContext
用来播放各个用户语音,可以随时切换不同用户的语音播放,安卓规规矩矩没问题
ios播放异常
ios用户切换语音时会播放第一个音频,但随后的语音却不会播放,实例已经销毁,但貌似对ios无效。解决:单例模式创建实例,在销毁实例后,在将变量手动清空,可以解决ios新建播音实例无效问题。
1 | //data |
不管是录音还是播音,在页面卸载(onUnload)的时候清除掉实例或者初始化,有canvas也要清除画布
华为-textarea-层级异常
华为部分机型,对小程序的textarea标签支出并不友好,其textarea的内容以及 placeholder 内容恨天高,无法通过程序控制,甚至小程序官方说 canvas 的层级是最高的,但也没高过textarea!
问题: 盖在textarea上面的弹框会被textarea的内容穿掉盖不住,并且点击事件直接穿透
解决:在拉起盖在textarea上面的弹框(或组件)时,用 view 标签重写模仿 textarea 样式,并把 textarea 关闭掉。
持续更新…….