微信小程序开发框架搭建
来源:互联网 发布:js 高精度计算 编辑:程序博客网 时间:2024/05/29 11:44
使用开发工具的正确姿势
微信提供的开发工具的编辑功能不是一般的水,写代码肯定不能用它,否则就是浪费生命.不说别的,连自动保存都没有,第一次写时写了一个多小时,后面下班直接关掉,也不弹出提示说没保存.然后第二天过来,写的代码全没了!!! 顿时感到巨坑无比.这些工具开发人员吃干饭的么???
(后来的版本已经修复不能自动保存的问题了,当然编辑功能还是不好用.)
它的正确用法是作为运行和调试工具.
那么适合作为编辑工具的是: webStorm.基于IntelJ内核,开启Dracula主题,跟Android studio的使用习惯非常接近,so cool!各种方法提示,自动保存,快速查找…应有尽有.闭源的微信开发工具就不要用来写代码了,珍惜生命.
webStorm要识别wxml和wxss,还需要配置一下文件类型:(看下面别人截的图)
记住html和css里都要加上微信小程序对应的类型
综上,开发时,用webstorm来写代码,用微信开发工具来运行和调试,速度飕飕的!
网络请求的封装
微信提供了底层网络驱动以及成功和失败的回调.但对于一个项目中的实际使用而言,仍然还是显得繁琐,还有很多封装和简化的空间.
wx.request({ url: 'test.php',//请求的url data: {//请求的参数 x: '' , y: '' }, header: {//请求头 'Content-Type': 'application/json' }, method:"POST", success: function(res) {//成功的回调 console.log(res.data) }})
网络框架二次封装的一般姿势
对于一个网络访问来说,请求一般是get和post,拼上各种参数以及请求头,然后拿到回来的响应,解析并得到最终需要的数据.
对于具体项目来说,请求时会有每个(或大多数)请求都要带的参数,都要带的请求头,返回的数据格式可能都是一致的,那么基于此,对微信的网络请求api进行二次封装:
在我目前的项目中,
请求:
大多数请求是post,基本上每个请求都需要携带sessionId来与服务器验证登录状态,还有很多请求是基于分页的,需要带上pageSize和pageIndex.
再跟页面逻辑关联起来,请求可能是因为第一次进入页面,或者刷新,或者上拉加载更多.
响应:
大多数拿到的数据格式是标准json格式,如下
{ "code":1, "data":xxx,//可能是String,也可能是JsonObject,或JsonArray,也可能是null,或undefined "msg":yyy//可能为空}
通过请求的状态码code来判断这个请求是否真正成功.我们的项目中还有常见的是code=5:登录过期或未登录,code=2: 没有找到对应的内容 等等.
我们实际使用中需要的:
如果是大多数情况的请求时,只需要指定:
- url的尾部
- 该请求的非一般的参数
- 该请求由什么动作引起的(第一次进入,刷新,加载更多)
对于响应,我们只需要:
- 成功时:拿到data里面的数据
- 失败时,拿到失败的信息(细分一下,包括可以直接显示给用户的和不能让用户看到的),以及失败状态码
- 数据为空的回调:(常见于列表数据的加载)
我们期望的api是:
netUtil.buildRequest(page,urlTail,params,callback)//必须的参数和回调.setXxx(xxx)//额外的设置,链式调用...send();//最终发出请求的动作
基于上面的分析,封装如下:
定义好携带构建请求的对象:
//这两个错误码是项目接口文档统一定义好的const code_unlogin = 5;const code_unfound = 2;function requestConfig(){ this.page; //页面对象 this.isNewApi = true; this.urlTail=''; this.params={ pageIndex:0, pageSize:getApp().globalData.defaultPageSize, session_id:getApp().globalData.session_id }; this.netMethod='POST'; this.callback={ onPre: function(){}, onEnd: function(){ }, onSuccess:function (data){}, onEmpty : function(){}, onError : function(msgCanShow,code,hiddenMsg){}, onUnlogin: function(){ this.onError("您还没有登录或登录已过期,请登录",5,'') }, onUnFound: function(){ this.onError("您要的内容没有找到",2,'') } }; this.setMethodGet = function(){ this.netMethod = 'GET'; return this; } this.setApiOld = function(){ this.isNewApi = false; return this; } this.send = function(){ request(this); }}
请求的封装
供我们调用的顶层api:
//todo 拷贝这段代码去用--buildRequest里的callback/* onPre: function(){}, onEnd: function(){ hideLoadingDialog(page); }, onSuccess:function (data){}, onEmpty : function(){}, onError : function(msgCanShow,code,hiddenMsg){}, onUnlogin: function(){ this.onError("您还没有登录或登录已过期,请登录",5,'') }, onUnFound: function(){ this.onError("您要的内容没有找到",2,'') }* *//** * 注意,此方法调用后还要调用.send()才是发送出去. * @param page * @param urlTail * @param params * @param callback 拷贝上方注释区的代码使用 * @returns {requestConfig} */function buildRequest(page,urlTail,params,callback){ var config = new requestConfig(); config.page = page; config.urlTail = urlTail; if (getApp().globalData.session_id == null || getApp().globalData.session_id == ''){ params.session_id='' }else { params.session_id = getApp().globalData.session_id; } if (params.pageIndex == undefined || params.pageIndex <=0 || params.pageSize == 0){ params.pageSize=0 }else { if (params.pageSize == undefined){ params.pageSize = getApp().globalData.defaultPageSize; } } log(params) config.params = params; log(config.params) //config.callback = callback; if(isFunction(callback.onPre)){ config.callback.onPre=callback.onPre; } if(isFunction(callback.onEnd)){ config.callback.onEnd=callback.onEnd; } if(isFunction(callback.onEmpty)){ config.callback.onEmpty=callback.onEmpty; } if(isFunction(callback.onSuccess)){ config.callback.onSuccess=callback.onSuccess; } if(isFunction(callback.onError)){ config.callback.onError=callback.onError; } if(isFunction(callback.onUnlogin)){ config.callback.onUnlogin=callback.onUnlogin; } if(isFunction(callback.onUnFound)){ config.callback.onUnFound=callback.onUnFound; } return config;}
最终请求的发送:
function request(requestConfig){ //检验三个公有参数并处理.这里与上面有所重复,是为了兼容之前写的几个api,不想改了. requestConfig.params.sessionId= getApp().globalData.sessionId; if (requestConfig.params.sessionId ==null || requestConfig.params.sessionId == ''){ delete requestConfig.params.sessionId; } if (requestConfig.params.pageIndex ==0 || requestConfig.params.pageSize == 0){ delete requestConfig.params.pageIndex ; delete requestConfig.params.pageSize; } //var body = getStr("&", requestConfig.params);//拼接请求参数 requestConfig.onPre();//请求发出前 wx.request({ // url: getApp().globalData.apiHeadUrl+requestConfig.urlTail+"?"+body,貌似这样写,同时不给data赋值,post请求也是可以成功的 url: getApp().globalData.apiHeadUrl+requestConfig.urlTail, method:requestConfig.netMethod, data:requestConfig.params, header: {'Content-Type':'application/json'}, success: function(res) { console.log(res); if(res.statusCode = 200){ var responseData = res.data var code = responseData.code; var msg = responseData.message; if(code == 0){ var data = responseData.data; var isDataNull = isOptStrNull(data); if(isDataNull){ requestConfig.onEmpty(); }else{ requestConfig.onSuccess(data); } }else if(code == 2){ requestConfig.onUnFound(); }else if(code == 5){ requestConfig.onUnlogin(); }else{ var isMsgNull = isOptStrNull(msg); if(isMsgNull){ var isCodeNull = isOptStrNull(code); if (isCodeNull){ requestConfig.onError("数据异常!,请核查",code,''); }else { requestConfig.onError("数据异常!,错误码为"+code,code,''); } }else{ requestConfig.onError(msg,code,''); } } }else if(res.statusCode >= 500){ requestConfig.onError("服务器异常!",res.statusCode,''); }else if(res.statusCode >= 400 && res.statusCode < 500){ requestConfig.onError("没有找到内容",res.statusCode,''); }else{ requestConfig.onError("网络请求异常!",res.statusCode,''); } }, fail:function(res){ console.log("fail",res) requestConfig.onError("网络请求异常!",res.statusCode,''); }, complete:function(res){ // that.setData({hidden:true,toast:true}); } })}
将方法暴露,并在需要时引用:
方法写在netUtil.js下,在该js文件最下方暴露方法:
module.exports = { buildRequest:buildRequest}
实际引用:
var netUtil=require("../../utils/netUtil.js");
实际使用时:
小技巧: js无法像java一样定义好了接口,然后IDE自动生成代码.可以这样: 将callback的空方法写到netUtil的buildRequest方法上方的注释区,每次用时,点击方法名跳到那边去拷贝即可.
var params = {};params.id = id;netUtil.buildRequest(that,API.Album.DETAIL,params,{ onPre: function(){ netUtil.showLoadingDialog(that); }, onEnd:function(){ }, onSuccess:function (data){ netUtil.showContent(that); .... }, onEmpty : function(){ }, onError : function(msgCanShow,code,hiddenMsg){ netUtil.showErrorPage(that,msgCanShow); }, onUnlogin: function(){ this.onError("您还没有登录或登录已过期,请登录",5,'') }, onUnFound: function(){ this.onError("您要的内容没有找到",2,'') }}).send();
},
页面状态管理
对于大多数网络请求后显示的页面,有这么几种页面状态:
- 第一次进入时,网络请求过程中:显示”加载中”的状态
- 加载的内容为空,显示”空白页面”
- 加载发生错误,显示”错误页面”,此页面一般有一个点击重试的按钮.该按钮一般的逻辑是:如果没有网络则点击后去打开网络设置,如果有网络,则重新发送网络请求.
- 加载成功,就显示内容页.
对于已经加载成功了,显示了内容页的”下拉拉刷新”:
- 页面上方会有”刷新中”的ui显示,这个微信已经原生集成,无需处理.
- 刷新成功,最好是弹出toast提示数据刷新成功
- 刷新失败,可以不提示,也可以提示,看具体选择.
对于一些分批加载的列表数据,一般还有上拉”加载更多”的功能:
参考微信文档中ui设计规范,上拉加载更多的ui提示应该放在页面最下部占一行,而不应该在页面中间显示一个大大的loading的效果.
- scrollview拉到最底部,触发加载事件,显示”加载中”的ui
- 加载成功,直接就将数据添加到原list上,这时也看不到最底部那行ui,所以不用处理
- 加载失败,则在那一行显示”加载失败”的字样,同时提示用户”上拉重试”,或者在那一行放置一个按钮,点击按钮重试.
封装
通过上面的分析,可以确定大部分页面的通用状态管理逻辑,那么就可以设计通用的状态管理模板了.
ui的显示是通过Page里的data中的数据来控制的,并通过page.setData({xxx})来刷新的,原先每个页面都拷贝同样的js属性和wxml代码去实现封装,后来进行了封装,js属性用方法来封装,通过微信提供的template封装共同的wxml代码,通过import或include导入到wxml中(但是不知什么bug,template一直无法起作用).
控制ui显示与否的属性的封装
function netStateBean(){//toast的是老api,工具升级后无需设置了 this.toastHidden=true, this.toastMsg='', this.loadingHidden=false, this.emptyHidden = true, this.emptyMsg='暂时没有内容,去别处逛逛吧', this.errorMsg='', this.errorHidden=true, this.loadmoreMsg='加载中...', this.loadmoreHidden=true,}
页面js里的使用:
Page( data: { title:'名师',//todo 设置标题栏 emptyMsg:'暂时没有内容,去别处逛逛吧',//todo 空白页面的显示内容 netStateBean: new netUtil.netStateBean(), ... }, ... )
wxml里:
模板
<template name="pagestate" > <view class ="empty_view" wx:if="{{!emptyHidden}}" > <view class="center_wrapper" > <view class="center_child" > <icon type="info" size="45"/> <view class="msg"> {{emptyMsg}}</view> </view> </view> </view> <view class ="error_view" wx:if="{{!errorHidden}}" > <view class="center_wrapper"> <view class="center_child" > <icon type="warn" size="45" /> <view class="msg"> {{errorMsg}}</view> <button class = "retrybtn" type="warn" loading="{{btnLoading}}" disabled="{{btnDisabled}}" catchtap="onRetry" hover-class="other-button-hover"> 点击重试 </button> </view> </view> </view> </template>
使用
<!--状态管理模板--><import src="../../template/pagestate.wxml"/><view > <template is="pagestate" data="{{...netStateBean}}"/></view>
js中提供API来控制页面状态:
module.exports = { showContent:showContent, showErrorPage:showErrorPage, showEmptyPage:showEmptyPage, loadMoreNoData:loadMoreNoData, loadMoreStart:loadMoreStart, loadMoreError:loadMoreError, hideLoadingDialog:hideLoadingDialog, showLoadingDialog:showLoadingDialog, showSuccessToast:showSuccessToast, dismissToast:dismissToast, showFailToast:showFailToast, .... } //具体的实现就是,拿到page对象中的data.netStateBean,修改部分数值,然后刷新ui. function showEmptyPage(that){ hideLoadingDialog(that); var bean = that.data.netStateBean; bean.emptyHidden = false; bean.loadingHidden = true; var empty = that.data.emptyMsg; if (isOptStrNull(empty)){//如果那个页面没有自定义空白说明文字,就使用默认的. empty = "没有内容,去别的页面逛逛吧" } bean.emptyMsg= empty; bean.contentHidden=true;//隐藏content页面 bean.errorHidden = true;//隐藏error页面 //刷新UI that.setData({ netStateBean: bean }); }
最终的效果:
常用页面模板的封装
整个页面就是只有一个简单的listview或gridview,数据从网络拉取,带上拉刷新和下拉加载更多的功能
分析
对于这种简单的页面来说,分析各页面不同的地方:
1.上一个页面传入的参数:
2.网络请求的url
3.网络请求的部分参数(其中分批加载的每批大小和第几批这两个参数的key在整个项目中都是一样的,每批大小的value可能不一样)
4.response数据回来后,从哪个字段中取出列表对应的数据,可能会不一样
5.对列表数据datas的每一条,有些字段的数据需要处理,处理的方式会不一样.
6.UI中:item内容和样式每个页面会不一样
7.标题栏文字
8.列表数据为空时的说明文字
9.非第一进入时调用onShow(相当于安卓中的onResume)时,是否自动刷新页面数据
js-页面加载逻辑全部封装在方法中:
netUtil.requestSimpleList(that,pageIndex,action);
js–page里:需要设置的都加上了todo注释,高亮显示,便于直接填内容
var utils=require("../../utils/util.js");var netUtil=require("../../utils/netUtil.js");var viewBeans=require("../../beans/viewBeans.js");var infoBeans=require("../../beans/infoBeans.js");var API=require("../../utils/API.js");const request_firstIn = 1;const request_refresh = 2;const request_loadmore = 3;const request_none = 0;var app = getApp();var that;var id =0; //页面idvar intentDatas;var isFristIn = true;var needRefreshOnResume = false;//todo 页面需要自己决定Page({ data: { title:'我的收藏',//todo 设置标题栏 emptyMsg:'暂时没有内容,去别处逛逛吧',//todo 空白显示内容 requestMethod:"POST",//todo 如果不是post,则在这里改 urlTail:API.User.MYCOLLECTION,//todo 需补全的,页面请求的url netStateBean: new netUtil.netStateBean(), currentPageIndex:1, currentAction : 0, infos:[],//列表数据 },//以下四个方法是生命周期方法,已封装好,无需改动 onLoad: function(options) { that = this; intentDatas = options; if (that.data.emptyMsg != null && that.data.emptyMsg != '' ){ that.data.netStateBean.emptyMsg = that.data.emptyMsg; } that.parseIntent(options); this.requestList(1,request_firstIn) }, onReady: function () { wx.setNavigationBarTitle({ title: this.data.title }); }, onHide:function(){ }, onShow:function(){ if (isFristIn){ isFristIn = false; }else { if (needRefreshOnResume){ if (that.data.currentAction ==request_none){ this.requestList(1,request_refresh);//刷新 } } } }, //上拉加载更多 onLoadMore: function(e) { console.log(e); console.log(that.data.currentAction +"---"+request_none); if (that.data.currentAction ==request_none){ this.requestList(that.data.currentPageIndex+1,request_loadmore) } }, //下拉刷新,通过onPullDownRefresh来实现,这里不做动作 onRefesh: function(e) { console.log(e); }, onPullDownRefresh:function(e){ this.requestList(1,request_refresh);}, //针对纯listview,网络请求直接一行代码调用 requestList:function(pageIndex, action){ netUtil.requestSimpleList(that,pageIndex,action) }, //todo 滑动监听,各页面自己回调 scroll: function(e) { console.log(e) }, //todo 将intent传递过来的数据解析出来 parseIntent:function(options){ }, //todo 设置网络参数 /** * 设置网络参数 * @param params config.params * @param id 就是请求时传入的id,也是成员变量-id */ setNetparams: function (params) { }, //todo 如果list数据是netData里一个字段,则更改此处 getListFromNetData:function(netData){ return netData; }, //todo 数据的一些处理并刷新数据 handldItemInfo:function(info){ }})
wxml中,写好空白页面,错误页面,加载更多栏
<view class="section" style="width: 100% ;height: 100%"> <scroll-view wx:if="{{!netStateBean.contentHidden}}" scroll-y="true" style="height:1300rpx;position:relative; z-index:0;" lower-threshold="50" bindscrolltolower="onLoadMore" bindscrolltoupper="onRefesh" ><!--todo listview--> <view class="list_data"> <block wx:for-items="{{infos}}" wx:for-item="info" wx:for-index="index"> <navigator url="/pages/lession/{{info.classname}}?id={{info.id}}&title={{info.title}}" hover-class=""> <!--todo---listview: 这里写item的具体内容 --> </navigator> </block> </view><!--todo gridview 同时js中getListFromNetData()方法返回utils.to2DimensionArr(netData,columnNum);--> <block wx:for-items="{{infos}}" wx:for-item="info" > <view class="row-container"> <block wx:for-items="{{info}}" wx:for-item="item"> <navigator url="/pages/lession/album?id={{item.id}}" hover-class=""> <!--todo gridview 这里写item具体的内容--> </navigator> </block> </view> </block> <!--加载更多的条栏--> <view class="loadmore_view" wx:if="{{!netStateBean.loadmoreHidden}}" > {{netStateBean.loadmoreMsg}} </view> </scroll-view> <!--空白页面--> <view class ="empty_view" wx:if="{{!netStateBean.emptyHidden}}" > <view class="center_wrapper" > <view class="center_child" > <icon type="info" size="45"/> <view class="msg"> {{netStateBean.emptyMsg}}</view> </view> </view> </view> <!--错误页面--> <view class ="error_view" wx:if="{{!netStateBean.errorHidden}}" > <view class="center_wrapper"> <view class="center_child" > <icon type="warn" size="45" /> <view class="msg"> {{netStateBean.errorMsg}}</view> <button class = "retrybtn" type="warn" loading="{{loading}}" disabled="{{disabled}}" bindtap="onRetry" hover-class="other-button-hover"> 点击重试 </button> </view> </view> </view></view>
日志打印控制
function log(msg){var isDebug = getApp().globalData.isDebug;if (isDebug){ console.log(msg);}
}
android中的gridview在小程序里的实现
小程序文档中只提供了一维的列表渲染示例,相对应的就是安卓中的列表listview.一维的数组,一维的UI.
如果是实现gridview这种XY轴两个方向的延伸,数据方面需要一个二维数组,UI方法,需要两层的嵌套渲染.
数据转换:一维数组转换成二维数组
/** * * @param arr 原始数据,是一个一维数组 * @param num 变成二维数组后,每个小一维数组的元素个数,也就是gridview中每行的个数 */function to2DimensionArr(arr,num){ var newArr = new Array();//二维数组 if (arr == undefined){ return newArr; } var subArr=null; for(var i =0;i<arr.length;i++){ var item = arr[i]; if((i%num==0) ||subArr==null){ subArr = new Array();//内部的一维数组 newArr.push(subArr); } subArr.push(item); } return newArr;}
UI上嵌套渲染:
<block wx:for-items="{{infos}}" wx:for-item="info" > <view class="row-container"> <block wx:for-items="{{info}}" wx:for-item="albumItem"> <view class="row-album-item"> <navigator url="/pages/lession/album?id={{albumItem.id}}" hover-class=""> <image mode="aspectFill" src="{{albumItem.coverUrl}}" class="album-cover-img"/> <text class="album-name">{{albumItem.name}}</text> </navigator> </view> </block> </view> </block>
效果
代码
wxapp-devFrame
- 微信小程序开发框架搭建
- SSH程序开发框架搭建过程
- 微信小程序开发--框架
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- 微信小程序开发环境搭建
- # 锁类Lock 和 Synchronized#
- ARCGIS中Python实现按属性字段批量掩膜提取
- 用 Docker 构建、运行、发布来一个 Spring Boot 应用
- java中用Io流实现文本复制(按行读按行写)
- nodejs NPM教程(yiibai)
- 微信小程序开发框架搭建
- 同步辅助类CyclicBarrier
- Mongodb的应用场景
- oracle11g broker主库异常断电后的测试
- 遍历删除非空文件夹
- iOS截屏功能
- LevelDB源码分析8-db key
- WMD:基于词向量的文档相似度计算
- 十年工龄的程序员为你揭示最危害程序员职业生涯的三大观念