微信小程序降(小)妖伏(小)魔篇
开发就是一个西行取经的过程,期间不是一帆风顺,不定会遇到什么鬼。
最近开发一个简单的每日答题签到小程序,期间遇到一些始料未及的坑记录一下,避免日后重复踩坑。本文针对初入小程序的小白。:)
可能会有更好的解决方案,欢迎指正。
多张gif图预警,大概20M ?
第一难: scroll-view标签对dispay:flex无效
想要实现一个横向滚动的日历,很自然的想到scroll-view组件,于是洋洋洒洒的写下几行代码:
demo.wxml
复制代码 { {item}}
demo.scss(为了写着方便,请自行转换成wxss语法,下同)
page { width: 100%; height: 100%; background: #17448E;}.scroll-view-demo { display: flex; flex-direction: row; .item { width: 80rpx; height: 80rpx; border: solid 1px #fff; display: flex; color: #fff; font-size: 36rpx; justify-content: center; align-items: center; border-radius: 40rpx; margin-left: 50rpx; }}复制代码
搓搓手,看看效果
尴尬,说好的横向滚动呢,明明flex-direction: row;
写的清清楚楚明明白白。。
既然scroll-view
对flex
不友好,那我在子元素上再包裹一层view
组件应该万事大吉了吧,于是:
demo.wxml
复制代码 { {item}}
demo.scss
.scroll-view-demo { .item-container { display: flex; flex-direction: row; .item { width: 80rpx; height: 80rpx; border: solid 1px #fff; display: flex; color: #fff; font-size: 36rpx; justify-content: center; align-items: center; border-radius: 40rpx; margin-left: 50rpx; } }}复制代码
看看效果:
然鹅。。好像不是那么回事,子元素怎么就被挤成胶囊了。。 仔细研究一番,需要给.item-container
加上固定的width
才行,甚是麻烦。
简单点,开发的方式简单点,既然scroll-view
对flex
如此高冷,那就换一种方式:
demo.wxml
复制代码 { {item}}
demo.scss
.scroll-view-demo { width: 100%; white-space: nowrap; .item { width: 80rpx; height: 80rpx; line-height: 80rpx; text-align: center; border: solid 1px #fff; display: inline-block; color: #fff; font-size: 36rpx; border-radius: 40rpx; margin-left: 50rpx; }复制代码
效果:
两个字,接近完美?
不过,在手机上浏览scroll-view
会有一个滚动条,很丑有木有
于是挑灯夜读翻阅资料找到解决方法:
app.css
/*隐藏scroll-view滚动条*/::-webkit-scrollbar{ width: 0; height: 0; color: transparent;}复制代码
这下妥妥的了,
年轻人切记,
scroll-view
尽量不用flex
第二难: scroll-view 设置scroll-left无效问题
以为scroll-view
踩完一个坑就结束了么,图森破图样。
想给scroll-view
在页面loading结束后滚动到某一个位置, 很简单,闭着眼写代码:
demo.wxml
复制代码 { {item}}
demo.js
Page({ data: { scrollLeft: 100, list: [] }, onLoad(){ setTimeout(()=>{ //假装异步获取数据 this.setData({ list: [1,2,3,4,5,6,7,8] }); },1000); }})复制代码
看效果,发现scroll-view
带在原地纹丝不动。惆怅~
let me see see 代码稍作改动:
demo.js
Page({ data: { scrollLeft: 0, list: [] }, onLoad(){ setTimeout(()=>{ //假装异步获取数据 this.setData({ list: [1,2,3,4,5,6,7,8], scrollLeft: 100 }); },1000); }})复制代码
perfect 果然达到了预期,细想原因:
- 一般在页面加载时我们会请求数据,并渲染列表, 但是我们在标签或者
data
中设置的scroll-left
值会在数据渲染前赋值. - 此时的
scroll-view
中还是空的,所以scroll-left
不会生效. 我们应该在数据渲染到scroll-view
中以后,再同步scroll-left
的值
第三难: button自定义样式,无法去掉默认样式
想写一个自定义样式的按钮,so easy,给我一秒写出来:
demo.wxml
超新脱俗的按钮 复制代码
demo.scss
button,.button { width: 500rpx; height: 88rpx; display: flex; align-items: center; justify-content: center; font-size: 32rpx; background: #FE7437; color: #fff; border-radius: 44rpx; margin: 20rpx auto;}复制代码
效果:
猛一看,感觉不能再完美了,定睛一看,怎么觉得button
组件写出来的按钮不是那么纯洁,有阴影。
很自然的再加行代码:
demo.scss
border: none;复制代码
事情并没有那么简单,阴影依然噩梦般存在。 有困难,找百度,果不其然大神们的奇技淫巧顺利解决,贴上完整代码:
demo.scss
button,.button { width: 500rpx; height: 88rpx; display: flex; align-items: center; justify-content: center; font-size: 32rpx; background: #FE7437; color: #fff; border-radius: 44rpx; margin: 20rpx auto; &::after{ border-radius: 0; border: none; }}复制代码
效果:
简直两个按钮一模一样。
第四难 让人伤神的input组件
小程序开发免不了要有登录页面,于是就离不开input
组件,代码就不贴了,常规操作,直接上图。
看图很容易发现几个问题:
- 切换
input
时 文字会闪动 - 切换下一个
input
时 需要多次才能获取到焦点,弹出键盘。 - 如果在不同的手机上看,会发现密码框的黑点大小也不一致
针对第一个问题,百度谷歌挖地三尺也木有找到好的解决办法(可能是挖的不够深 :() 第二个问题,既然不能自主获取焦点,那我们结合官方文档助它一臂之力:
demo.wxml
复制代码 登录
demo.scss
...复制代码
demo.js
Page({ data: { username: '', password: '', selectName: true, selectPwd: false, }, getUsername(e){ //获取用户名 this.setData({ username: e.detail.value, }); }, getPassword(e){ //获取密码 this.setData({ password: e.detail.value, }) }, handleName(){ this.setData({ selectName: true, selectPwd: false, }) }, handlePwd(){ this.setData({ selectName: false, selectPwd: true, }) }});复制代码
看看效果:
看着像是好多了 ? 思考: 若文本框不止两个,怎么写才更优雅,另,没有看在不同手机上的表现。
第三个关于密码黑点大小的问题 我也木有良策 +_+
以下是网上大佬们关于input
组件bug的总结:
-
placeholder
文字与input
的值重叠 暂无解决方法 -
获取焦点 和 失去焦点 时,光标和文字跳动 暂无解决方法
-
当
input
设置为居中对齐时,光标会出现在奇怪的位置 暂无解决方法 -
bindconfirm
事件在失去焦点时也会触发,类似于blur
暂无解决方法 -
对
input
做动画时,如果是获取焦点状态,会失效 暂无解决方案,因为 input 在获取焦点时是native 组件,失去焦点后改回 web 组件 -
type
为idcard
,digit
时并不是调用数字键盘 暂无解决方案,目前起作用的只有number
-
在
input
聚焦期间,不能做css
动画,否则input
中的placeholder
会错位,如果动画和聚焦都想要的话,那么可以在动画完成之后,再设置聚焦
看看微信官方社区的吐槽,如果你也被坑了,去给助个攻吧
第五难 getPhoneNumber获取用户手机号解密报错问题
小程序登录时我们可以利用微信获取手机号快捷登录,根据文档我们很容易写一套登录流程:
demo.wmxl
复制代码
demo.js
Page({ ... async getPhoneNumber(e) { if(!e.detail.iv){ //被拒绝授权 return; } let code = ''; /* * 别慌,wxPromise.login()其实就是wx的api被promise化,参考 **转转**代码 * https://github.com/zhuanzhuanfe/fancy-mini/blob/master/src/wxPromise.js */ const res = await wxPromise.login(); code = res.code; if(!code){ console.error(`调用 wx.login 失败`, err); return util.toast('微信登录失败'); } try{ //server端微信登录接口 const result = await service.passport.wxLogin({ data: { code: code, iv: e.detail.iv, encryptedData: e.detail.encryptedData } }); const res = result.data; if (res.status === 0) { // 登录成功 ... }else { ... } }catch(err){ ... } } ...});复制代码
server.js
async wxLoginAction(){ //微信登录 const {ctx} = this; const body = ctx.request.body; //请求微信接口 https://api.weixin.qq.com/sns/jscode2session const result = await this.callService('wxApplet.code2Session', { appid: ***, secret: ***, js_code: body.code, grant_type: 'authorization_code' }); if(result.status == 0){ let sessionKey = result.data.session_key; let encryptedData = body.encryptedData; let iv = body.iv; //解密获取手机号 (解密库小程序文档有对应的源码) let pc = new WXBizDataCrypt(appid, sessionKey); let mobile = pc.decryptData(encryptedData, iv).phoneNumber; //解密成功 生成登录态等操作 ... return this.json({ status: 0, message: 'ok', data: mobile }); }else { .... }}复制代码
搓搓手,看效果:
竟然首次登录的时候有报错,以后再次登录却一切正常。
看server端报错日志,如下:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt复制代码
这条error
信息是从WXBizDataCrypt
解密失败时抛出来的。
查看文档,看button的官方文档Tips中有这一条:
在bindgetphonenumber 等返回加密信息的回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。
另外,查看网上大神的解决方案,其中有一条是这样说的:
纳尼,点击按钮之前就要调用login
,于是将信将疑的再次修改代码:
demo.js
Page({ data: { code: '' }, async onLoad(options){ try { const res = await wxPromise.login(); this.setData({ code: res.code }) }catch (err){ this.setData({ code: '' }) } }, async getPhoneNumber(e) { if(!e.detail.iv){ //被拒绝授权 return; } let code = this.data.code; if(!code){ console.error(`调用 wx.login 失败`, err); return util.toast('微信登录失败'); } try{ //server端微信登录接口 const result = await service.passport.wxLogin({ data: { code: code, iv: e.detail.iv, encryptedData: e.detail.encryptedData } }); const res = result.data; if (res.status === 0) { // 登录成功 ... }else { ... } }catch(err){ ... } }});复制代码
修改完毕,经过测试,首次登录果然很顺利就成功了,但是又出现了一个幺蛾子,再次登录就又又又报错:
error: code is used复制代码
这个报错就很好理解了,稍稍修改一下代码:
demo.js
Page({ data: { code: '' }, async onLoad(options){ try { const res = await wxPromise.login(); this.setData({ code: res.code }) }catch (err){ this.setData({ code: '' }) } }, async getPhoneNumber(e) { if(!e.detail.iv){ //被拒绝授权 return; } let code = this.data.code; if (!code) { //防止code失效 try{ const res = await wxPromise.login(); code = res.code; }catch(err){ console.error(`调用 wx.login 失败`, err); return util.toast('微信登录失败'); } } try{ //server端微信登录接口 const result = await service.passport.wxLogin({ data: { code: code, iv: e.detail.iv, encryptedData: e.detail.encryptedData } }); const res = result.data; this.setData({ //用过的code需要清空,重新获取 code: '' }); if (res.status === 0) { // 登录成功 ... }else { ... } }catch(err){ ... } }});复制代码
泪流满面,自我测试,确实经得起组织考验,完美解决。(也可能测试的不够透彻,如果还有报错,或者更好的解决方案,欢迎打脸T T)
第六难 刷新页面图片加载时变形问题
image
组件开发时特别常见,要写一个宽度固定,高度自适应代码也很简单:
demo.wxml
复制代码
看着很简单,感觉稳稳地,但是刷新页面时会有中见鬼的赶脚,话不多说上图:
仔细看似乎有鬼影掠过。。。 加载时图片被拉扯变形,?
其实解决方法也很简单:
demo.scss
image { height: auto;}复制代码
对,就是这么简单,就完美解决。?
以上。
结束
开发是一个漫长的过程,前面未知的坑还很多。火来水挡水来土掩,填的坑越多,路会越平坦,再攒一波坑,等下次更新。
最后插播一条广告,经过一天多次被拒后,小程序终于审核通过了(感谢各种TV T T),欢迎体验吐槽
参考文章:
小程序里的登录态处理,主要参考以下文章(具体实现省略):