[原创] 分享我们自己搭建的微信小程序开发框架——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...想太多了,抛弃它们吧.不应当为了解决一个简单的旧问题而去整一个复杂的新问题 ...
随机推荐
- Windows上使用Thunderbird与GPG发送和解密公钥加密的电子邮件
作者:荒原之梦 原文链接:http://zhaokaifeng.com/?p=552 非对称加密的原理: 最先出现的加密方法是对称加密.在对称加密算法中是不区分公钥和私钥的,加密与解密使用的都是同一个 ...
- ROC曲线的计算
1.ROC曲线简介 在评价分类模型时,会用到ROC(receiver operating characteristic)曲线.ROC曲线可用来评价二元分类器( binary classifier)的优 ...
- FPGA学习笔记(四)——Verilog基本语法
###### [该随笔部分内容转载自小梅哥] ######### 组合逻辑: 多路选择器.加法器.译码器.乘法器 时序逻辑: 计数器.分频器.定时器.移位寄存器 一.Verilog文件的基 ...
- Python读取文件时输入文件绝对路径报错
问题描述 读取本地文件,输入绝对路径时出错,显示数据被截断. 最开始以为是转译错误,因为eclipse会自动把\变成\\, pycharm这里没有,就手动改一下.结果还是不行. 根本原因 文件名出错了 ...
- JFree图表
************************************************************************************* 使用JFree生成统计图片, ...
- Python + Appium 【已解决】driver(session)在多个class之间复用,执行完一个类的用例,再次执行下个类的用例时不需要初始化
实现效果:打开App进行自动化测试,只需打开APP一次,按先后顺序执行n个py文件中的相应操作,实现自动化测试. 示例:如截图示例,一个App,根据此APP内不同的模块,写成了不同的py文件, 预期结 ...
- 计算机17-3,4作业C
C.Class Degisn Description 定义一个Circle类,有成员变量(或称之为域)x,y(圆心坐标)r(圆半径),成员方法intersect()两个圆是否相交的判断方法,和所需要的 ...
- java IO流全面总结
流的概念和作用 流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象.即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作. Ja ...
- Redis+Restful 构造序列号和压力测试【后续】
大家还记上篇博文https://www.cnblogs.com/itshare/p/8643508.html,测试redis构造流水号的tps是600多/1s. 这个速度显然不能体现redis 集群在 ...
- Android 7.0 启动篇 — init原理(二)(转 Android 9.0 分析)
======================================================== ================================== ...