微信小程序踩坑指南

作者: 熊猫猫超人 | 来源:发表于2019-05-30 18:30 被阅读167次

    微信小程序降(小)妖伏(小)魔篇

    开发就是一个西行取经的过程,期间不是一帆风顺,不定会遇到什么鬼。

    最近开发一个简单的每日答题签到小程序,期间遇到一些始料未及的坑记录一下,避免日后重复踩坑。本文针对初入小程序的小白。:)

    可能会有更好的解决方案,欢迎指正。

    多张gif图预警,大概20M 😱

    第一难: scroll-view标签对dispay:flex无效

    想要实现一个横向滚动的日历,很自然的想到scroll-view组件,于是洋洋洒洒的写下几行代码:

    demo.wxml

    <scroll-view
        scroll-x="true"
        class="scroll-view-demo"
    >
        <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
            {{item}}
        </view>
    </scroll-view>
    

    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;
        }
    }
    

    搓搓手,看看效果

    scroll_view.png

    尴尬,说好的横向滚动呢,明明flex-direction: row;写的清清楚楚明明白白。。

    既然scroll-viewflex不友好,那我在子元素上再包裹一层view组件应该万事大吉了吧,于是:

    demo.wxml

    <scroll-view
        scroll-x="true"
        class="scroll-view-demo"
    >
        <view class="item-container">
            <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
                {{item}}
            </view>
        </view>
    </scroll-view>
    

    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;
        }
      }
    }
    

    看看效果:

    scroll_view_flex.gif

    然鹅。。好像不是那么回事,子元素怎么就被挤成胶囊了。。 仔细研究一番,需要给.item-container加上固定的width才行,甚是麻烦。

    简单点,开发的方式简单点,既然scroll-viewflex如此高冷,那就换一种方式:

    demo.wxml

    <scroll-view
            scroll-x="true"
            class="scroll-view-demo"
    >
        <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
            {{item}}
        </view>
    </scroll-view>
    

    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_ok.gif

    两个字,接近完美😆

    不过,在手机上浏览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

    <scroll-view
                scroll-x="true"
                class="daily-check-calendar-scroll"
                scroll-left="{{scrollLeft}}"
            >
            <view class="item" wx:for="{{list}}" wx:key="{{index}}">
                {{item}}
            </view>
    </scroll-view>
    

    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

    <view class="button">超新脱俗的按钮</view>
    <button>超新脱俗的按钮,too</button>
    

    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.png

    猛一看,感觉不能再完美了,定睛一看,怎么觉得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;
      }
    }
    

    效果:

    button-ok.png

    简直两个按钮一模一样。

    第四难 让人伤神的input组件

    小程序开发免不了要有登录页面,于是就离不开input组件,代码就不贴了,常规操作,直接上图。

    input.gif

    看图很容易发现几个问题:

    1. 切换input时 文字会闪动
    2. 切换下一个input时 需要多次才能获取到焦点,弹出键盘。
    3. 如果在不同的手机上看,会发现密码框的黑点大小也不一致

    针对第一个问题,百度谷歌挖地三尺也木有找到好的解决办法(可能是挖的不够深 :()
    第二个问题,既然不能自主获取焦点,那我们结合官方文档助它一臂之力:

    demo.wxml

    <view class="account-main">
        <text class="account-title">登录</text>
        <form class="login-form" bindsubmit="formSubmit">
            <view class="form-item {{selectName ? 'selected' : ''}}">
                <input type="text" value="{{username}}" focus="{{selectName}}" bindtap="handleName" bindinput="getUsername" placeholder-class="login-ipt-place" class="login-ipt" placeholder="手机号/邮箱" />
            </view>
            <view class="form-item {{selectPwd ? 'selected' : ''}}">
                <input type="text" value="{{password}}" focus="{{selectPwd}}" bindtap="handlePwd" bindinput='getPassword' placeholder-class="login-ipt-place"  maxlength="48" class="login-ipt pwd-ipt" placeholder="登录密码" password="true" />
            </view>
            <button class="form-submit" form-type="submit">
                登录
            </button>
        </form>
    </view>
    

    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_ok.gif

    看着像是好多了 😆
    思考: 若文本框不止两个,怎么写才更优雅,另,没有看在不同手机上的表现。

    第三个关于密码黑点大小的问题 我也木有良策 +_+

    以下是网上大佬们关于input组件bug的总结:

    • placeholder 文字与 input 的值重叠 暂无解决方法

    • 获取焦点 和 失去焦点 时,光标和文字跳动 暂无解决方法

    • input 设置为居中对齐时,光标会出现在奇怪的位置 暂无解决方法

    • bindconfirm 事件在失去焦点时也会触发,类似于 blur 暂无解决方法

    • input 做动画时,如果是获取焦点状态,会失效 暂无解决方案,因为 input 在获取焦点时是native 组件,失去焦点后改回 web 组件

    • typeidcard, digit 时并不是调用数字键盘 暂无解决方案,目前起作用的只有 number

    • input聚焦期间,不能做css动画,否则input中的placeholder会错位,如果动画和聚焦都想要的话,那么可以在动画完成之后,再设置聚焦

    看看微信官方社区的吐槽,如果你也被坑了,去给助个攻吧

    input.png

    传送门

    第五难 getPhoneNumber获取用户手机号解密报错问题

    小程序登录时我们可以利用微信获取手机号快捷登录,根据文档我们很容易写一套登录流程:

    demo.wmxl

    <button class="login-by-wx" open-type="getPhoneNumber"  bindgetphonenumber="getPhoneNumber">
        微信登录
    </button>
    

    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 {
                ....
            }
    }
    

    搓搓手,看效果:

    login.gif

    竟然首次登录的时候有报错,以后再次登录却一切正常。

    看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_error.png

    纳尼,点击按钮之前就要调用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

    <image
        mode = "widthFix"
        src="你的图片地址"
    >
    </image>
    

    看着很简单,感觉稳稳地,但是刷新页面时会有中见鬼的赶脚,话不多说上图:

    image.gif

    仔细看似乎有鬼影掠过。。。 加载时图片被拉扯变形,😱

    其实解决方法也很简单:

    demo.scss

    image {
        height: auto;
    }
    

    对,就是这么简单,就完美解决。 😆

    以上。

    结束

    开发是一个漫长的过程,前面未知的坑还很多。火来水挡水来土掩,填的坑越多,路会越平坦,再攒一波坑,等下次更新。

    最后插播一条广告,经过一天多次被拒后,小程序终于审核通过了(感谢各种TV T T),欢迎体验吐槽

    rrd.jpg

    参考文章:

    小程序里的登录态处理,主要参考以下文章(具体实现省略):

    相关文章

      网友评论

        本文标题:微信小程序踩坑指南

        本文链接:https://www.haomeiwen.com/subject/uyxbtctx.html