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

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

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

demo源码: one图书管理系统

组件:图表控件

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

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

组件化

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

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

如下图:

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

<!-- 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模块冲突,代码如下:


<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 中可以写成这样:


<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中关键代码如下:


<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

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即可让两个组件同时拥有该功能,参考代码如下:

<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 用于存储全局变量。

export default {
    session: ''
}

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

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 并且发送请求。

// 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 中获取列表并渲染:

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 文件改动如下:

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 . 安装组件

npm install wepy-com-toast --save

2 .无缓存编译

wepy build --no-cache

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

<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 .调用

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 中添加初始化代码。

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 事件完成页面的上报。

export default class Index extends wepy.page {
    onLoad () {
        mta.Page.init();
    };
}

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

export default class extends wepy.app {
    onError () {
        mta.Event.stat("error",{});
    };
}

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

mta.Event.stat("payed",{});

结束语

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

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网杂技

20个为前端开发者准备的文档和指南2

1.CSS Vocabulary(CSS词汇表) (需自备墙梯才可以访问) 点击该应用,将会使你了解到CSS语法所有不同的部分,和它们对应的属性名是什么。 ? ...

33710
来自专栏知晓程序

如何开发电商类小程序 Vol.3:数据加载和图文排版

1014
来自专栏葡萄城控件技术团队

纯前端表格控件SpreadJS与Java结合,实现模板上传和下载等功能

您可直接解压到 eclipse 的 workspace 下,导入 Existing Projects into Workspace 工程即可。

1082
来自专栏jeremy的技术点滴

使用hexo写博文

5514
来自专栏大数据钻研

前端面试那些坑之HTML篇

HTML 1、Doctype作用?标准模式与兼容模式各有什么区别? (1)、<!DOCTYPE>声明位于位于HTML文档中的第一行,处于<html> 标签之前...

3429
来自专栏PHP在线

最新HTML5学习路线整合

HTML5是万维网的核心语言,标准通用标记语言下的一个应用超文本标记语言(HTML)的第五次重大修改,一方面提升了用户体验,另一方面HTML5技术跨平台,适配多...

1013
来自专栏我和未来有约会

从Flash到Silverlight进阶教程-用代码来创建动画

从Flash到Silverlight进阶教程 用代码来创建动画 这节里将要讲述一个自定义用户控件最基本的操作,就好象Flash中的MovieClips一样,动态...

3535
来自专栏守候书阁

webpack+vue项目实战(二,开发管理系统主页面)

上篇文章(webpack+vue项目实战(一,搭建运行环境和相关配置))搭建了好了基本的一个项目目录,安好好了一些要用到的依赖,以及把项目跑了起来。接下来,我们...

591
来自专栏Material Design组件

Human Interface Guidelines —— Popovers

34211
来自专栏HTML5学堂

readonly 和 disable的区别

readonly和disabled它们都能够做到使用户不能够更改表单域中的内容。但是它们之间有着微小的差别,总结如下: Readonly只针对inpu...

3354

扫码关注云+社区