[原创] 分享我们自己搭建的微信小程序开发框架——wframe及设计思想详解

wframe不是控件库,也不是UI库,她是一个微信小程序面向对象编程框架,代码只有几百行。她的主要功能是规范小程序项目的文件结构、规范应用程序初始化、规范页面加载及授权管理的框架,当然,wframe也提供了一些封装好了的函数库,方便开发者调用。

wframe目前已实现的核心功能:

1. 应用程序初始化自动从服务器获取配置,ajax成功后触发ready事件;

2. 每个页面对象可以配置是否requireLogin属性,如果需要登录,则每个页面在进入ready方法之前会自动完成授权、获取用户信息、服务器端登录;

3. 完成ajax全局封装:如果用户已经登录,则会自动在http-header添加token信息,如果session过期则会重新进入登录流程;

本文的阅读对象:想要自己搭建小程序框架的人,相信本文会给你提供一些思路。

我们为什么要开发wframe?

我们开发的小程序越来越多,小程序也越来越复杂,于是我们就想将每个小程序重复在写的那一部分代码提出来,变成一个公共的函数库,一个跟每个项目的业务逻辑完全不相关的函数库。除了在新项目中可以节省代码之外,有一些复杂的代码逻辑由于提到了公共的函数库,我们将其优化得更优雅、更健壮。

说wframe是一个函数库虽说也可以,但wframe更像一个框架。我们通常把一些静态方法、静态对象、仅处理页面内容的JS文件集称作函数库,比如jQuery;我们通常把处理了应用程序和页面生命周期,以及使用了大量的面向对象编程技术的JS文件集称作框架。因此,wframe其实是一个框架。

重要说明:wframe框架用到了大量的面向对象编程知识,比如实例、继承、覆写、扩展、抽象方法等等,因此对开发人员,特别是项目中的架构师,的面向对象编程能力有较高要求。

项目源码已上传到GitHub并会持续更新:https://github.com/leotsai/wframe

一、wframe项目结构

[原创] 分享我们自己搭建的微信小程序开发框架——wframe及设计思想详解

wframe的最核心的职责就是规范项目文件结构。

为什么需要规范呢?因为我们小程序越来越多,如果每个小程序的文件结构都不一样的话,那定是一件很难受的事。另外,wframe由于其框架的身份,其本职工作就是定义一个最好的文件结构,这样基于wframe创建的所有小程序都将自动继承wframe的优秀品质。

1. _core文件夹

wframe框架源码,与业务毫不相干,每个小程序都可以直接将_core文件夹复制到项目中,而当wframe更新版本时,所有小程序可以直接覆盖_core完成升级。用下划线“_”开头的目的有2个:

(a) 将此文件夹置顶;

(b) 标记此文件夹是一个特殊文件夹,本框架中还有其他地方也会用到下划线开头为文件夹/文件。

2. _demo文件夹

业务核心文件夹,比如定义一些扩展wframe框架的类,同时这些类又被具体的业务类继承使用,比如ViewModelBase等。

3. pages文件夹

与微信小程序官方文档定义一致:放置页面的地方。

4. app.js

程序主入口,只不过基于wframe的小程序的app.js跟官方的长得很不一样,我们定义了一个自己的Applicaiton类,然后再new的一个Application实例。稍后详解。

5. mvcApp.js

几乎每个js文件都会require引入的一个文件,因为这相当于是项目的静态入口,其包含了所有的静态函数库,比如对wx下面方法的封装、Array类扩展、Date类扩展、网络请求(ajax)封装等等。mvcApp.js几乎只定义了一个入口,其内部的很多对象、方法都是通过require其他JS引入的。因此,大多数情况下,我们只需要require引入mvcApp.js就够了。

写到这里,分享一个我们的编程思想:入口要少。小程序里有哪些入口:this、getApp()、wx、mvcApp。其实也就是每一行代码点号“.”前面的都叫代码入口。

我们还有另一个编程规范(强制):每个文件不能超过200行代码(最好不超100行)。这就是要求每个程序员必须学会拆分,拆分也是我们的另一个编程思想。通过拆分,每个JS文件职责清晰,极大的提高了代码阅读率。

二、详解

1. app.js和Application类详解

app.js定义了程序入口。

 var mvcApp = require('mvcApp.js');
var Application = require('_core/Application.js'); function MvcApplication() {
Application.call(this);
this.initUrl = 'https://www.somdomain.com/api/client-config/get?key=wx_applet_wframe';
this.host = 'http://localhost:18007';
this.confgis = {
host: 'http://localhost:18007',
cdn: 'https://images.local-dev.cdn.somedomain.com'
};
this.mock = true;
this.accessToken = null;
this.useDefaultConfigsOnInitFailed = false;
}; MvcApplication.prototype = new Application(); MvcApplication.prototype.onInitialized = function (configs) {
if (configs != null && configs !== '') {
this.configs = JSON.parse(configs);
this.host = this.configs.host;
}
}; App(new MvcApplication());

可以看到app.js定义了一个MvcApplication类,继承自框架中的Application类,同时重写了父类的onInitialized方法。

下面是框架中的Application类:

 var WebClient = require('http/WebClient.js');
var AuthorizeManager = require('weixin/AuthorizeManager.js');
var weixin = require('weixin.js'); function Application() {
this.initUrl = '';
this.host = '';
this.session = null;
this.initialized = false;
this.mock = false;
this.useDefaultConfigsOnInitFailed = false;
this.authorizeManager = new AuthorizeManager();
this._userInfo = null;
this._readyHandlers = [];
}; Application.prototype = {
onLaunch: function () {
var me = this;
if(this.initUrl === ''){
throw 'please create YourOwnApplication class in app.js that inerits from Application class and provide initUrl in constructor';
}
var client = new WebClient();
client.post(this.initUrl, null, function(result){
if (result.success || me.useDefaultConfigsOnInitFailed){
me.initialized = true;
me.onInitialized(result.success ? result.value : null);
me.triggerReady();
}
else{
weixin.alert('小程序初始化失败', result.message);
}
}, '初始化中...');
},
onShow: function () { },
onHide: function () { },
onError: function () { },
onPageNotFound: function () { },
ready: function (callback) {
var me = this;
if (this.initialized === true) {
callback && callback();
return;
}
this._readyHandlers.push(callback);
},
triggerReady: function () {
for (var i = 0; i < this._readyHandlers.length; i++) {
var callback = this._readyHandlers[i];
callback && callback();
}
this._readyHandlers = [];
},
onInitialized: function(configs){ },
getUserInfo: function(callback){
var me = this;
if(this._userInfo != null){
callback && callback(this._userInfo.userInfo);
return;
}
this.authorizeManager.getUserInfo(function(result){
me._userInfo = result;
callback && callback(me._userInfo.userInfo);
});
},
getCurrentPage: function(){
var pages = getCurrentPages();
return pages.length > 0 ? pages[0] : null;
}
}; module.exports = Application;

Applicaiton类(及其子类)在wframe框架中的主要工作:

1. 应用程序初始化的时候从服务器获取一个配置,比如服务器域名(实现域名实时切换)、CDN域名,以及其他程序配置信息;

2. 全局存储用户的授权信息和登陆之后的会话信息;

3. 全局mock开关;

4. 其他快捷方法,比如获取当前页面等。

Application类核心执行流程:

1. 应用程序初始化时首先从服务器获取客户端配置信息;

2. 获取完成之后会触发onInitialized方法(在子类中覆写)和ready方法。

2. PageBase类详解

PageBase类是所有页面都会继承的一个基类。先看代码:

 console.log("PageBae.js entered");

 const app = getApp();

 function PageBase(title) {
this.vm = null;
this.title = title;
this.requireLogin = true;
}; PageBase.prototype = {
onLoad: function (options) {
var me = this;
if (this.title != null) {
this.setTitle(this.title);
}
this.onPreload(options);
app.ready(function () {
if (me.requireLogin && app.session == null) {
app.getUserInfo(function (info) {
me.login(info, function (session) {
app.session = session;
me.ready(options);
});
});
}
else {
me.ready(options);
}
});
},
ready: function (options) { },
onPreload: function(options){ },
render: function () {
var data = {};
for (var p in this.vm) {
var value = this.vm[p];
if (!this.vm.hasOwnProperty(p)) {
continue;
}
if (value == null || typeof (value) === 'function') {
continue;
}
if (value.__route__ != null) {
continue;
}
data[p] = this.vm[p];
}
this.setData(data);
},
go: function (url, addToHistory) {
if (addToHistory === false) {
wx.redirectTo({ url: url });
}
else {
wx.navigateTo({ url: url });
}
},
goBack: function () {
wx.navigateBack({});
},
setTitle: function (title) {
this.title = title;
wx.setNavigationBarTitle({ title: this.title });
},
login: function (userInfo, callback) {
throw 'please implement PageBase.login method.';
},
getFullUrl: function () {
var url = this.route.indexOf('/') === 0 ? this.route : '/' + this.route;
var parts = [];
for (var p in this.options) {
if (this.options.hasOwnProperty(p)) {
parts.push(p + "=" + this.options[p]);
}
}
if (parts.length > 0) {
url += "?" + parts.join('&');
}
return url;
},
isCurrentPage: function(){
return this === getApp().getCurrentPage();
}
}; PageBase.extend = function (prototypeObject) {
var fn = new PageBase();
for (var p in prototypeObject) {
fn[p] = prototypeObject[p];
}
return fn;
}; module.exports = PageBase;

由于微信小程序Application类的onLaunch不支持回调,也就是说,在wframe框架中,虽然我们在onLaunch时发起了ajax调用,但是程序并不会等待ajax返回就会立即进入Page对象的onLoad方法。这是一个非常重要的开发小程序的知识前提,但是官方文档并没有重要说明。

PageBase类的三个实例属性:

1. vm:即ViewModel实例,可以理解为官方文档中的Page实例的data属性;

2. title:页面标题

3. requireLogin:是否需要登录,如果设置为true,则页面onLoad执行后自动进入登录流程,登录完成后才会触发页面的ready方法;

PageBase类的实例方法:

1. onLoad:对应官方文档中的onLoad事件。wframe框架自动会处理requireLogin属性,处理完成后才触发ready方法;

2. ready:每个业务级页面的主入口,每个业务级页面都应该实现ready方法,而不一定实现onLoad方法;

3. onPreload:在执行onLoad之前执行的方法,不支持异步;

4. render:非常常用的方法,功能是将ViewModel(即data)呈现到页面上,在业务页面中直接使用this.render()即可将更新的数据呈现出来;

5. go:页面跳转,相比官方的wx.navigateTo简化了很多;

6. goBack:等于wx.navigateBack;

7. setTitle:直接设置页面标题;

8. login:可以理解成抽象方法,必须由子类实现,在我们demo中由业务级框架中的DemoPageBase实现;

9. getFullUrl:获取页面完整地址,包括路径和参数,便于直接跳转;

10. isCurrentPage:判断该页面实例是否在应用程序页面栈中处于当前页面,主要用于setInterval函数中判断用户是否已离开了页面;

3. DemoPageBase类详解

这是业务层级的框架内容。我们建议每个页面都继承自该类,这个类可以封装跟业务相关的很多逻辑,方便子类(业务页面)直接通过this调用相关方法。

在wframe的demo框架中,我们实现了PageBase类的抽象方法login。

这里请注意同目录的api.js文件。在我们的编码规范中,所有ajax访问都需要提到专门的api.js文件,通常与页面类处于同一目录,这是为了方便mock API。请看示例代码:

 var mvcApp = require('../mvcApp.js');

 var api = {
login: function (userInfo, code, callback) {
var data = mvcApp.serializeToKeyValues(userInfo) + "&code=" + code;
mvcApp.ajax.busyPost('/demo/api/login', data, function(result){
callback(result.value);
}, '登陆中...', true);
}
};
if (getApp().mock) {
var api = {
login: function (userInfo, code, callback) {
setTimeout(function(){
callback({
token: '98c2f1bd7beb3bef3b796a5ebf32940498cb5586ddb4a5aa8e'
});
}, 2000);
}
};
} module.exports = api;

4. 页面类的实现

请看pages/index目录下的文件列表:

1. IndexViewModel:该页面的ViewModel;

2. api.js:该页面所有ajax的封装;

3. index.js:页面入口;

4. index.wxml:HTML;

5. index.wxss:样式;

先看入口index.js,代码如下:

 var mvcApp = require('../../mvcApp.js');
var DemoPageBase = require('../DemoPageBase.js');
var IndexViewModel = require('IndexViewModel.js'); function IndexPage() {
DemoPageBase.call(this, 'index');
}; IndexPage.prototype = new DemoPageBase(); IndexPage.prototype.onPreload = function(options){
this.vm = new IndexViewModel(this);
this.render();
}; IndexPage.prototype.ready = function () {
var me = this;
this.vm.load();
}; IndexPage.prototype.goDetails = function (e) {
var item = e.target.dataset.item;
wx.navigateTo({
url: '/pages/details/details?id=' + item.id
});
}; Page(new IndexPage());

index.js核心逻辑:继承自DemoPageBase,onPreload时设置了ViewModel,ready时(自动登录完成后)调用ViewModel的数据加载方法,完成。

5. ViewModel的实现

在微信小程序官方文档中,并没有提ViewModel的概念,这会导致一些稍微有点复杂的页面的data对象的处理变得很凌乱,更别说复杂页面的data处理,那根本无从维护。ViewModel的设计思想是专门用来封装视图数据的一层代码,不管是MVC,还是MVVM,ViewModel都是拆分数据层代码的最佳实践。因此,wframe框架强烈建议每个页面都建一个对应的ViewModel,封装数据结构,以及获取、处理数据。

在我们的编程思想中,ViewModel不仅仅是放数据的地方,更是封装业务逻辑的最佳位置之一。所以我们的ViewModel会很肥(fat model),会包含相关的很多业务逻辑处理。

如果项目需要,还可以封装一个DemoViewModelBase类,将其他页面ViewModel常用的方法封装进来,比如this.getUserName()等方法。

请看示例代码:

 var api = require('api.js');
var mvcApp = require('../../mvcApp.js'); function IndexViewModel(page){
this.users = [];
this.showLoading = true;
this.males = 0;
this.females = 0;
this.page = page;
}; IndexViewModel.prototype.load = function(){
var me = this;
api.getUsers(function(users){
me.showLoading = false;
me.females = users._count(function(x){
return x.gender === 'female';
});
me.males = users._count(function (x) {
return x.gender === 'male';
});
me.users = users._orderByDescending(null, function(first, second){
if(first.gender === 'male'){
if(second.gender === 'male'){
return first.birthYear > second.birthYear;
}
return true;
}
if(second.gender === 'female'){
return first.birthYear > second.birthYear;
}
return false;
});
me.page.render();
});
}; module.exports = IndexViewModel;

api.js就不贴代码了,跟上一小节中的api.js一样的。html和css部分也忽略不讲。

至此,页面级实现就完成了。

下面,笔者再对wframe框架中的其他特殊部分进行特殊说明。继续。

6. pages/_authorize文件夹

这个文件夹定义了一个授权页面,这是因为新版小程序API强制要求用户自己点授权按钮才能弹出授权。这个虽然集成在wframe框架中,但是每个项目应该自行修改此页面的样式以符合项目UI设计。

这个目录下面只有一个_authorize.js值得贴一下代码,其实都非常简单:

 var DemoPageBase = require('../DemoPageBase.js');

 function AuthPage() {
DemoPageBase.call(this, 'auth');
this.requireLogin = false;
}; AuthPage.prototype = new DemoPageBase(); AuthPage.prototype.onPreload = function (options) {
this.returnUrl = decodeURIComponent(options.returnUrl);
}; AuthPage.prototype.onGotUserInfo = function (event) {
var me = this;
if (event.detail.userInfo == null) {
return;
}
var app = getApp();
app._userInfo = event.detail;
DemoPageBase.prototype.login.call(this, app._userInfo.userInfo, function () {
me.go(me.returnUrl, false);
})
} Page(new AuthPage())

请注意onPreload方法中对returnUrl的获取,以及获取用户授权信息后对DemoPageBase.login方法的调用。

7. _core文件夹其他文件详解

_core文件夹之前已经讲了Application和PageBase类。继续。

1. weixin.js

主要封装了toast、busy(增加延时功能)、alert、confirm方法,后期可能会增加更多常用方法的封装。代码如下:

 var weixin = {
_busyTimer: null,
_busyDelay: 1500,
toast: function (message, icon) {
wx.showToast({
title: message,
icon: icon == null || icon == '' ? 'none' : icon
});
},
toastSuccess: function (message) {
this.toast(message, 'success');
},
busy: function (option, delay) {
clearTimeout(this._busyTimer);
if (option === false) {
wx.hideLoading();
return;
}
if (delay === 0) {
wx.showLoading({
title: option,
mask: true
});
}
else {
this._busyTimer = setTimeout(function () {
wx.showLoading({
title: option,
mask: true
});
}, delay == null ? this._busyDelay : delay);
}
},
alert: function (title, content, callback) {
content = content == undefined ? '' : content;
wx.showModal({
title: title,
content: content,
showCancel: false,
confirmText: "确定",
success: res => {
callback && callback();
}
});
},
confirm: function (title, content, buttons) {
var buttonList = [];
for (var p in buttons) {
if (buttons.hasOwnProperty(p)) {
buttonList.push({
text: p,
handler: buttons[p]
})
}
}
content = content == undefined ? '' : content;
wx.showModal({
title: title,
content: content,
showCancel: true,
cancelText: buttonList[0].text,
confirmText: buttonList[1].text,
success: res => {
if (res.confirm) {
buttonList[1].handler && buttonList[1].handler();
} else if (res.cancel) {
buttonList[0].handler && buttonList[0].handler();
}
}
});
}
}; module.exports = weixin;

2. extensions/ArrayExtensions.js

一大堆数组扩展方法,非常常用,非常好用。引入mvcApp的业务层代码均可直接使用。代码如下:

 var ArrayExtensions = {};

 Array.prototype._each = function (func) {
for (var i = 0; i < this.length; i++) {
var item = this[i];
var result = func(i, item);
if (result === false) {
return;
}
}
}; Array.prototype._sum = function (propertyOrFunc) {
var total = 0;
var isFunc = typeof (propertyOrFunc) == "function";
this._each(function (i, item) {
if (isFunc) {
total += propertyOrFunc(item);
} else {
var value = item[propertyOrFunc];
if (value != undefined) {
value = value * 1;
if (!isNaN(value)) {
total += value;
}
}
}
});
return total;
}; Array.prototype._where = function (predicateFunction) {
var results = new Array();
this._each(function (i, item) {
if (predicateFunction(item)) {
results.push(item);
}
});
return results;
}; Array.prototype._orderBy = function (property, isFirstGreaterThanSecondFunction) {
var items = this;
for (var i = 0; i < items.length - 1; i++) {
for (var j = 0; j < items.length - 1 - i; j++) {
if (isFirstGreaterThanSecond(items[j], items[j + 1])) {
var temp = items[j + 1];
items[j + 1] = items[j];
items[j] = temp;
}
}
}
function isFirstGreaterThanSecond(first, second) {
if (isFirstGreaterThanSecondFunction != undefined) {
return isFirstGreaterThanSecondFunction(first, second);
}
else if (property == undefined || property == null) {
return first > second;
}
else {
return first[property] > second[property];
}
} return items;
}; Array.prototype._orderByDescending = function (property, isFirstGreaterThanSecondFunction) {
var items = this;
for (var i = 0; i < items.length - 1; i++) {
for (var j = 0; j < items.length - 1 - i; j++) {
if (!isFirstGreaterThanSecond(items[j], items[j + 1])) {
var temp = items[j + 1];
items[j + 1] = items[j];
items[j] = temp;
}
}
}
function isFirstGreaterThanSecond(first, second) {
if (isFirstGreaterThanSecondFunction != undefined) {
return isFirstGreaterThanSecondFunction(first, second);
}
else if (property == undefined || property == null) {
return first > second;
}
else {
return first[property] > second[property];
}
} return items;
}; Array.prototype._groupBy = function (property) {
var results = [];
var items = this; var keys = {}, index = 0;
for (var i = 0; i < items.length; i++) {
var selector;
if (typeof property === "string") {
selector = items[i][property];
} else {
selector = property(items[i]);
}
if (keys[selector] === undefined) {
keys[selector] = index++;
results.push({ key: selector, value: [items[i]] });
} else {
results[keys[selector]].value.push(items[i]);
}
}
return results;
}; Array.prototype._skip = function (count) {
var items = new Array();
for (var i = count; i < this.length; i++) {
items.push(this[i]);
}
return items;
}; Array.prototype._take = function (count) {
var items = new Array();
for (var i = 0; i < this.length && i < count; i++) {
items.push(this[i]);
}
return items;
}; Array.prototype._firstOrDefault = function (predicateFunction) {
if (this.length == 0) {
return null;
}
if (predicateFunction == undefined) {
return this[0];
}
var foundItem = null;
this._each(function (i, item) {
if (predicateFunction(item)) {
foundItem = item;
return false;
}
return true;
});
return foundItem;
}; Array.prototype._any = function (predicateFunction) {
if (predicateFunction == undefined) {
return this.length > 0;
}
var hasAny = false;
this._each(function (i, item) {
if (predicateFunction(item)) {
hasAny = true;
return false;
}
return true;
});
return hasAny;
}; Array.prototype._select = function (newObjectFunction) {
if (newObjectFunction == undefined) {
throw "parameter newObjectFunction cannot be null or undefined";
}
var items = [];
this._each(function (i, item) {
items.push(newObjectFunction(item));
});
return items;
}; Array.prototype._insert = function (index, item) {
this.splice(index, 0, item);
}; Array.prototype._insertMany = function (index, items) {
if (items == null) {
return;
}
for (var i = 0; i < items.length; i++) {
this._insert(index + i, items[i]);
}
}; Array.prototype._add = function (item) {
this.push(item);
}; Array.prototype._addMany = function (items) {
if (items == null) {
return;
}
for (var i = 0; i < items.length; i++) {
this.push(items[i]);
}
}; Array.prototype._clear = function () {
this.splice(0, this.length);
}; Array.prototype._count = function (predicateFunction) {
var count = 0;
this._each(function (i, item) {
if (predicateFunction(item)) {
count++;
}
});
return count;
}; /************************************** */
module.exports = ArrayExtensions;

3. http/WebClient.js

封装网络请求,我们叫ajax。增加busy、header、自动异常处理等逻辑。非常常用,非常好用。代码如下:

 var weixin = require('../weixin.js');

 function WebClient() {
this.busyText = null;
this.busyDelay = 1500;
this.url = '';
this.data = null;
this.method = 'GET';
this.contentType = 'application/x-www-form-urlencoded';
this.dataType = 'json';
this.onlyCallbackOnSuccess = false;
this._request = null;
this._callback = null;
this._header = {};
}; WebClient.prototype = {
setHeader: function(key, value){
this._header[key] = value;
},
removeHeader: function(key){
delete this.header[key];
},
get: function (url, callback, busyText, onlyCallbackOnSuccess){
this.method = 'GET';
this.url = url;
this.data = null;
this._callback = callback;
this.busyText = busyText;
this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess;
this.execute();
},
post: function (url, data, callback, busyText, onlyCallbackOnSuccess) {
this.method = 'POST';
this.url = url;
this.data = data;
this._callback = callback;
this.busyText = busyText;
this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess;
this.execute();
},
execute: function () {
var me = this;
if (this.busyText != null && this.busyText !== '') {
weixin.busy(me.busyText, me.busyDelay);
}
this._request = wx.request({
url: me.url,
data: me.data,
method: me.method,
header: me._getRequestHeader(),
success: (response) => {
if (me.busyText != null) {
weixin.busy(false);
}
me._onResponse(response);
},
fail: res => {
if (me.busyText != null) {
weixin.busy(false);
}
me._handleError({ statusCode: 500 });
}
});
},
_getRequestHeader: function(){
var header = {};
if(this.contentType != null && this.contentType !== ''){
header['content-type'] = this.contentType;
}
for(var p in this._header){
if(this._header.hasOwnProperty(p)){
header[p] = this._header[p];
}
}
return header;
},
_onResponse: function (response) {
if (response.statusCode === 200) {
if (this.onlyCallbackOnSuccess === false) {
this._callback && this._callback(response.data);
} else {
if (response.data.success === true) {
this._callback && this._callback(response.data);
} else {
weixin.alert("提示", response.data.message);
}
}
}
else {
this._handleError(response);
}
},
_handleError: function (response) {
if (response.statusCode === 0 && err.statusText === "abort") {
return;
}
if (this.onlyCallbackOnSuccess) {
weixin.alert("网络错误", "错误码:" + response.statusCode);
} else {
this._callback && this._callback({
success: false,
message: "网络错误:" + response.statusCode,
code: response.statusCode
});
}
}
}; module.exports = WebClient;

4. weixin/AuthorizeManager.js

wframe框架自带的授权管理器,在Application初始化时已赋值到Application.authorizeManager实例属性上面,因此,如果想要自定义实现AuthorizeManager,那么可以继承框架中的默认AuthorizeManager,然后再重写部分方法,然后在初始化Applicaiton的时候注入不同的实现类即可。

这个类的实例已经添加到Application实例,所以可以通过 getApp().authorizeManager.authorize('your-scope-name', callback)  弹出授权。

 var weixin = require('../weixin.js');

 function AuthorizeManager() {
this.pageUrl = '/pages/_authorize/_authorize';
}; AuthorizeManager.scopes = {
userInfo: 'scope.userInfo'
}; AuthorizeManager.prototype = {
authorize: function (scope, callback) {
var me = this;
me._isAuthorized(scope, function (authorized) {
if (authorized) {
callback();
}
else {
me._showAuthorize(scope, callback);
}
});
},
getUserInfo: function (callback) {
var me = this;
var scope = AuthorizeManager.scopes.userInfo;
function handleAuthorized() {
wx.getUserInfo({
success: function (res) {
callback && callback(res);
},
fail: function (res) {
var url = getApp().getCurrentPage().getFullUrl();
wx.redirectTo({
url: me.pageUrl + "?returnUrl=" + encodeURIComponent(url)
});
}
})
};
me.authorize(scope, handleAuthorized);
},
_isAuthorized: function (scope, callback) {
wx.getSetting({
success: function (res) {
callback(res.authSetting[scope] === true);
}
});
},
_showAuthorize: function (scope, callback) {
var me = this;
wx.authorize({
scope: scope,
success: function () {
callback();
},
fail: function (res) {
if (scope === AuthorizeManager.scopes.userInfo) {
callback();
}
else {
me._openAuthorizeSetting(scope, callback);
}
}
})
},
_openAuthorizeSetting: function (scope, calback) {
var me = this;
weixin.alert('提示', '您需要授权才能继续操作', function () {
wx.openSetting({
success: function (res) {
if (!res.authSetting[scope]) {
me._openAuthorizeSetting(scope, callback);
} else {
callback && callback();
}
}
})
});
}
}; module.exports = AuthorizeManager;

三、结语

wframe会持续更新,我们会持续将项目中的最佳实践、框架优化等添加进来。

使用wframe框架开发小程序,那才能真正的体会JS面向对象的编程体验,这种体验是相当的美妙。希望小程序官方可以尽早引入wframe的设计思想,让小程序开发体验变成完完全全的面向对象开发体验。

THE END.

上一篇:PHP die与exit的区别


下一篇:Spark-RPC理解