[原创] 分享我们自己搭建的微信小程序开发框架——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的优秀品质。
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.
[原创] 分享我们自己搭建的微信小程序开发框架——wframe及设计思想详解的更多相关文章
- 微信小程序 使用腾讯地图SDK详解及实现步骤
信小程序 使用腾讯地图SDK详解及实现步骤 微信小程序JavaScript SDK: 官方文档:http://lbs.qq.com/qqmap_wx_jssdk/index.html 步骤: 1 ...
- 分享一下我的个人微信小程序
分享一下我的个人微信小程序 1.有我平时整理的一些小程序相关的技术,供大家参考. 2.有几个好玩的例子 有问题可以一起参考
- 微信小程序答题,怎么设计页面渲染,答完一题,跳到下一题
想要的效果 1.第一页只显示第一道题的内容,如图红框2.答题后,点击下一题,内容显示第二道题的内容 代码 answer.wxml <!--pages/answer/answer.wxml--&g ...
- 微信小程序开发框架技术选型
目前微信小程序开发有三种方式,原生微信小程序,使用mpVue或wepy微信小程序开发框架. 三种开发方式横向对比资料如下:
- 【猿分享第10期】微信小程序Meetup扫盲专场回顾(转载)
首先感谢答疑师:子慕 前端工程师,目前就职于医联,偶尔写点博客,吐槽总结,偶尔吟“湿”作对,润滑万物,江湖人称子慕大诗人. 直播间语音回放收听,请微信扫描下图二维码授权进入即可. 以下为本次直播的全部 ...
- 原创:新手布局福音!微信小程序使用flex的一些基础样式属性
来源:新手布局福音!微信小程序使用flex的一些基础样式属性 作者:Nazi Flex布局相对于以前我们经常所用到的布局方式要好的很多,在做微信小程序的时候要既能符合微信小程序的文档开发要求,又能 ...
- 微信公众号菜单添加小程序,miniprogram,pagepath参数详解,php开发公众号
随着微信小程序功能的开发, 已经可以跟公众号打通了, 主要有两种方式: 1) 在公众号文章中插入小程序 2) 在公众号菜单中添加小程序 第一种方式, 子恒老师在前面的课程已经详细介绍过, 今天来讲第二 ...
- 微信小程序开发框架整理
目前除了原生的微信小程序开发外,各大厂商陆续造了自己的开发框架,现整理如下: WePY 腾讯官方开源的小程序组件化开发框架,目前有15K+Star ,一直在更新着,社区活跃,掉坑能快速的找到方法爬出来 ...
- 【原创】1、简单理解微信小程序
先看下网站的运行方式: 而小程序是这样: what?就这样?是的,就这样.那小程序官方提供的Wafer,还有Wafer2...想太多了,抛弃它们吧.不应当为了解决一个简单的旧问题而去整一个复杂的新问题 ...
随机推荐
- SVN汉化教程2017.10.6
https://jingyan.baidu.com/album/b87fe19e95f5925219356853.html?picindex=4
- js中函数的写法
js提供了灵活的函数写法,我们常见的函数写法和调用可能是: function ask(){ console.log(1); } ask(); 这样就完成了函数的定义和调用,司空见惯. 还有js里面的匿 ...
- Android 自定义ViewGroup手把手教你实现ArcMenu
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这 ...
- MyEclipse代码提示设置
(一)普通代码提示 1. 打开MyEclipse ,然后"window"→"Preferences" 2. 选择"java",展开,&quo ...
- python之12306自动查票
一.导读 本篇文章所采用的技术仅用于学习.研究,任何其他用途请自行承担后果. 12306自动查票使用到的python库主要是splinter,同时也涉及到查票的城市编码,具体的城市编码请在网络上搜 ...
- 【读书笔记】《Maven实战》第6章 仓库
6.1什么是Maven仓库? Maven仓库:存储所有Maven项目共享的构件的统一位置. Maven仓库的作用:Maven项目仅需声明依赖坐标,即可在需要的时候自动根据坐标找到仓库中的构件. 6.2 ...
- Dubbo中暴露服务的过程解析
dubbo暴露服务有两种情况,一种是设置了延迟暴露(比如delay="5000"),另外一种是没有设置延迟暴露或者延迟设置为-1(delay="-1"): 设置 ...
- [POI2007]洪水pow bfs
发现:只在所有自己的城市建水泵一定是最优解. 所以对自己的城市按高度排序,该城市不用建的前提是从他出发经过一条高度都小于等于他的路径能到达一个已经修建水泵的 sort+bfs...... #inclu ...
- java中“==”和equals方法的区别,再加上特殊的String引用类型
==和equals的区别: 1.==是运算符,而equals是基类Object定义的一个方法,并且equals使用==定义的 2.进行比较时,分为 基本数据类型 的比较和 引用数据类型 的比较 ...
- JDBC知识详解
一.相关概念 1.什么是JDBC JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它 ...