WePY 在手机充值小程序中的应用与实践

2017-06-19 18:59:21 浏览数 (1)

wepyjs 发布了两个月了,中间经历了很多版本更新,也慢慢开始有一些用户选择 wepyjs 作为开发框架来开发小程序,比如一些线上小程序。

以及一些来自网上的 wepyjs 的相关资源:

demo源码: one,图书管理系统

组件:图表控件

因此我也将手机充值小程序在开发过程中 wepyjs 的应用心得分享出来,可以参照对比与传统小程序开发上的差异。

说明:本文不涉及到 wepyjs 的使用与说明,如果需要请参看我的另一篇文章 ”打造小程序组件化开发框架” 或直接参看wepyjs 项目地址

组件化

开发时期望逻辑代码按照业务模块划分,从视觉图上来看,首页可以分为五个模块,分别是:

  • 输入框:Input
  • 下拉历史记录:History
  • 充话费:Mobile
  • 充流量:Traffic
  • 右下角菜单:Menu

如下图:

在原生小程序中,可以使用小程序的模板特性来达到模块化区别的目地,如下:

代码语言:javascript复制
<!-- index.wxml -->
<import src="components/input"/>
<import src="components/history" />
<import src="components/mobile" />
<import src="components/traffic" />
<import src="components/menu" />

<view class="pageIndex">
    <template is="comInput" data="{{number}}" />
    <template is="comMobile" data="{{mobileList}}" />
    <template is="comTraffic" data="{{trafficList}}" />
    <template is="comMenu"/>
</view>
// index.js
var Input = require('./components/input');
var History = require('./components/history');
var Mobile = require('./components/mobile');
var Traffic = require('./components/traffic');
var Menu = require('./components/menu');

var MyApp = {
    Input: Input,
    History: History,
    Mobile: Mobile,
    Traffic: Traffic
    Menu: Menu
};
// ....
Page(MyApp);

如此,便可以业务模块去组织自己的代码。

小程序的js模块与wxml模块并无直接关联,在数据或是事件的命名上需要使用前缀或者是命名空间的方式区分,以防冲突。

比如在Mobile模块中有一个商品列表list,并且每个商品都有一个点击下单事件submit。因此在开发时需要使用mobileList,mobileSubmit或者Mobile.list,Mobile.submit以防止与Traffic模块冲突,代码如下:

代码语言:javascript复制

<block wx:for-items="{{mobileList}}">
    <view class="goods mobile" bindtap="mobileSubmit" data-id="{{item.id}}" data-amount="{{item.amount}}" data-type="{{item.type}}">
        {{item.price}}
    </view>
</block>

使用 wepyjs 直接让小程序能够支持组件化开发。让小程序开发能够像 Vue,React 一样使用自定义组件开发。因此首页index.wpy 中可以写成这样:

代码语言:javascript复制

<template>
    <view class="pageIndex">
        <cinput :number.sync="number" />
        <mobile />
        <traffic />
        <menu />
    </view>
</template>
<script>
    import wepy from 'wepy';
    import Input from '../components/input';
    import Menu from '../components/menu';
    import Mobile from '../components/mobile';
    import Traffic from '../components/traffic';

    export default class Index extends wepy.page {

        components = {
            menu: Menu,
            mobile: Mobile,
            traffic: Traffic,
            cinput: Input
        };

        data = {
            number: ''
        };
    }
</script>

在充话费组件components/mobile.wpy中关键代码如下:

代码语言:javascript复制

<template>
    ....
    <block wx:for-items="{{list}}">
        <view class="goods mobile" bindtap="submit({{item.id}}, {{item.amount}}, {{item.type}})">
            {{item.price}}
        </view>
    </block>
    ....
</template>
<script>
    import wepy from 'wepy';

    export default class Mobile extends wepy.component {
        data = {
            list: []
        };
        methods = {
            submit (id, amount, type) {

            }
        };
        onLoad () {
            // load list;
        }
    }
</script>

对比于之间的代码,我们不用再关心是mobileList还是trafficList。无论是Mobile组件还是Traffic组件,都有自己的listsubmit方法。保证了组件与组件之间数据的隔离。

Mixin 混合

混合是对组件的复用性的一种补充,使用Mixin可以很灵活的复用不同组件中的相同部分。

比如,为了做好用户体验细节的优化,在面额列表的滚动时加入了阴影控制。当滚到最左边时,左边无阴影,滚动到最右边时,右边无阴影,滚动到中间时两边都出现阴影。如下图:

阴影由两个透明渐变效果的样式决定:left-shadow,right-shadow。

对于Mobile组件和Traffic组件来说,这一功能是两者共有特性,因此可以使用Mixin来实现。

创建Mixin文件mixin/scroll.js

代码语言:javascript复制
import wepy from 'wepy';

export default class ScrollMixin extends wepy.mixin {

    data = {
        shadow: 'left-shadow'
    };
    methods = {
        scroll: function (e) {
            this.shadow = 'left-shadow right-shadow';
        },
        scrollLeft: function (e) {
            this.shadow = 'right-shadow';
        },
        scrollRight: function (e) {
            this.shadow = 'left-shadow';
        }
    };
}

然后在Mobile和Traffic中分别引用当前Mixin即可让两个组件同时拥有该功能,参考代码如下:

代码语言:javascript复制
<template>
    ....
    <scroll-view scroll-x class="{{shadow}}" bindscrolltoupper="scrollLeft" bindscrolltolower="scrollRight" bindscroll="scroll">
        <block wx:for-items="{{list}}">
            <view class="goods mobile" bindtap="submit({{item.id}}, {{item.amount}}, {{item.type}})">
                {{item.price}}
            </view>
        </block>
    </scroll-view>
    ....
</template>
<script>
    import wepy from 'wepy';
    import ScrollMixin from '../mixin/scroll';

    export default class Mobile extends wepy.component {
        mixins = [ScrollMixin];
        ...
    }
</script>

登录态维护

小程序提供 wx.login 接口可以方便的获取到用户的 code,通过 code 置换出 session 作为应用态。session 可以储存在 storage 中或者是内存当中,详情可参照官方文档。

参照官方文档整理出我们小程序获取登录态的步骤以及应当具备的能力:

  1. 服务器提供一个使用 code 转换登录态 session 的接口。
  2. 进入应用时,调用 wx.login() 获取 code。
  3. 调用接口将 code 转换为 session,并且储存到内存或者storage中。
  4. 发请 request 请求时自动带上 session 字段。
  5. 因为某些原因导致 session 失效时,可以自动再次获取新的 session 并且发送请求。

画出流程图如下:

实现代码如下:

创建公用模块 common/global.js 用于存储全局变量。

代码语言:javascript复制
export default {
    session: ''
}

在应用启动时登录,并且置换 session,并且利用 wepyjs 的 intercept 功能让每个 request 都带上 session。

代码语言:javascript复制
import wepy from 'wepy';
import api from './common/api';
import G from './common/global';

import 'babel-polyfill';

export default class extends wepy.app {
    onLaunch() {
        wepy.login()
            .then(res => api.getSession(res.code))
            .then(res => {
                G.session = res.session;

                this.intercept('request', { // request 的拦截器,在每次发送request请求时都会加上session
                    config (p) {
                        p.session = G.session;
                        return p;
                    }
                });
            });
    }
}

定义 api 模块,封装 request 方法,使其在 session 失效时能再次更新 session 并且发送请求。

代码语言:javascript复制
// common/api.js
import wepy from 'wepy';
import G from './global';

export default {
    /**
     * code 置换登录态 session 接口
     */
    getSession (code) {
        return wepy.request({
            url: 'https://yourserver/session',
            data: {
                code: code
            }
        });
    },
    /**
     * 封装 request 方法,在第一次登陆态失效后自动登录并转换 session 后重发请求
     */
    request (data, tryagain) {
        return new Promise ((resolve, reject) => {
            wepy.request(data).then(res = > {
                if (res.data.retCode === 'xxxxx') { // 登录态验证失败
                    if (tryagain) {
                        reject('Something is wrong'); // code 置换 session 后依然返回登录态验证失败
                        return;
                    }
                    return wepy.login() // 可能是session过期等原因,获取最新 code
                        .then(loginRes => this.getSession(loginRes.code)) // 使用最新code置换 session
                        .then(sessionData => {
                            G.session = sessionData.session;
                            return this.request(data, true); // 重发请求
                        }).catch(reject);
                } else {
                    resolve(res);
                }
            }).catch(reject);;
        });
    },
    getMobileList () {
        let data = {url: 'https://yourserver/api'};
        return this.request(data);
    }
};

因此,在开发时,就不用去关心何时应该登录的问题,直接调用接口既可。比如在 mobile.wpy 中获取列表并渲染:

代码语言:javascript复制
export default class Mobile extends wepy.app {
    async onLoad () {
        this.list = await api.getMobileList();
    }
}

上面解释的是原始的登录态维护的一种方式,在手机充值小程序里,每个后端接口都有封装 code 置换 session 的能力,后端接口会优先判断请求中是否有 session,如果有 session 优先使用 session,如果没有,使用请求参数中的 code 去置换 session,然后处理请求,再将 session 返回到 response 当中。因此前端流程有些许改变,如下图:

common/api.js 文件改动如下:

代码语言:javascript复制
import wepy from 'wepy';
import G from './global';

export default {
    request (data, tryagain) {
        return new Promise((resolve, reject) => {
            if (G.session) {
                wepy.request(data).then(res => {
                    if (res.data.retCode === 'xxxxx') { // 登录态验证失败
                        if (tryagain) {
                            reject('Something is wrong'); // code 置换 session 后依然返回登录态验证失败
                            return;
                        }
                        G.session = '';
                        return this.request(data, true);
                    } else {
                        resolve(res);
                    }
                }).catch(reject);
            } else {
                wepy.login() // 可能是session过期等原因,获取最新 code
                    .then(loginRes => {
                        data.data.code = loginRes.code;
                        return wepy.request(data); // 使用 code 发送 request 请求
                    }) 
                    .then(res => {
                        G.session = res.session; // 返回结果中 设置 session
                        resolve(res);
                    }).catch(reject);
            }
        });
    }
};

第三方组件

小程序中并不能像H5一样直接使用alert弹出消息提示,因此可以选择使用 wx.showToast 的API进行消息提示,但是官方只支持success 和 loading 两种样式。重新写一个 toast 组件成本略高,因此考虑直接使用现成的 wepy-com-toast 组件。使用方法如下:

1 . 安装组件

代码语言:javascript复制
npm install wepy-com-toast --save

2 .无缓存编译

代码语言:javascript复制
wepy build --no-cache

3 .需要的组件中引入 toast 组件

代码语言:javascript复制
<template>
 <toast />
</template>
<script>
 import wepy from 'wepy';
 import Toast from 'wepy-com-toast';

 export default class Index extends wepy.page {
     components = {
         toast: Toast
     };
 }
</script>

4 .调用

代码语言:javascript复制
this.$invoke('toast', 'show', {
 title: '系统繁忙',
 img: 'https://yourpicture.com/sad.png',
});

实现效果如下图:

数据上报

[MTA是腾讯自家的数据分析平台,在小程序发布后MTA平台很快的就支持了小程序的数据上报。因此手机充值选择MTA做为数据上报平台,具体步骤如下:

1 .在MTA官网注册应用。

2 .在mp平台,小程序开发设置中,将https://pingtas.qq.com 添加为可信域名。

3 .安装 mta-analysis 模块:npm install mta-analysis --save

4 .在 app.wpy 中添加初始化代码。

代码语言:javascript复制
import wepy from 'wepy';
import mta from 'mta-analysis';

export default class extends wepy.app {
    onLaunch() {
        mta.App.init({
           "appID":"xxxx", // 注册后得到的appID
           "eventID":"xxxx", // 注册后得到的eventID
           "statPullDownFresh":true, // 使用分析-下来刷新次数/人数,必须先开通自定义事件,并配置了合法的eventID
           "statShareApp":true, // 使用分析-分享次数/人数,必须先开通自定义事件,并配置了合法的eventID
           "statReachBottom":true // 使用分析-页面触底次数/人数,必须先开通自定义事件,并配置了合法的eventID
        });
    };
}

这样就完成了MTA的初始化工作,在每个页面的 onLoad 事件中加入 init 事件完成页面的上报。

代码语言:javascript复制
export default class Index extends wepy.page {
    onLoad () {
        mta.Page.init();
    };
}

在 app.wpy 中加入报错上报。

代码语言:javascript复制
export default class extends wepy.app {
    onError () {
        mta.Event.stat("error",{});
    };
}

以及在其它业务逻辑代码上加入一些自定义事件上报,比如下单上报,支持上报等等。

代码语言:javascript复制
mta.Event.stat("payed",{});

结束语

至此,基本介绍完了 wepyjs 在手机充值项目的应用了,剩下的就是业务代码的开发了。wepyjs 通过不停的版本更新迭代去吸收一些传统框架优秀特性融入其中,比如:组件通讯、props传值、Mixin、Slot、拦截器等等。也希望在以后能有更多的小程序开发者使用 wepyjs 进行开发。

0 人点赞