微信小程序开发框架搭建

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hss01248/article/details/53405308

使用开发工具的正确姿势

微信提供的开发工具的编辑功能不是一般的水,写代码肯定不能用它,否则就是浪费生命.不说别的,连自动保存都没有,第一次写时写了一个多小时,后面下班直接关掉,也不弹出提示说没保存.然后第二天过来,写的代码全没了!!! 顿时感到巨坑无比.这些工具开发人员吃干饭的么???
(后来的版本已经修复不能自动保存的问题了,当然编辑功能还是不好用.)

它的正确用法是作为运行和调试工具.

那么适合作为编辑工具的是: webStorm.基于IntelJ内核,开启Dracula主题,跟Android studio的使用习惯非常接近,so cool!各种方法提示,自动保存,快速查找…应有尽有.闭源的微信开发工具就不要用来写代码了,珍惜生命.
webStorm要识别wxml和wxss,还需要配置一下文件类型:(看下面别人截的图)
记住html和css里都要加上微信小程序对应的类型

1478137328137_4.png

综上,开发时,用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: 没有找到对应的内容 等等.

我们实际使用中需要的:

如果是大多数情况的请求时,只需要指定:

  1. url的尾部
  2. 该请求的非一般的参数
  3. 该请求由什么动作引起的(第一次进入,刷新,加载更多)

对于响应,我们只需要:

  1. 成功时:拿到data里面的数据
  2. 失败时,拿到失败的信息(细分一下,包括可以直接显示给用户的和不能让用户看到的),以及失败状态码
  3. 数据为空的回调:(常见于列表数据的加载)

我们期望的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();

},

页面状态管理

对于大多数网络请求后显示的页面,有这么几种页面状态:

  1. 第一次进入时,网络请求过程中:显示”加载中”的状态
  2. 加载的内容为空,显示”空白页面”
  3. 加载发生错误,显示”错误页面”,此页面一般有一个点击重试的按钮.该按钮一般的逻辑是:如果没有网络则点击后去打开网络设置,如果有网络,则重新发送网络请求.
  4. 加载成功,就显示内容页.

对于已经加载成功了,显示了内容页的”下拉拉刷新”:

  1. 页面上方会有”刷新中”的ui显示,这个微信已经原生集成,无需处理.
  2. 刷新成功,最好是弹出toast提示数据刷新成功
  3. 刷新失败,可以不提示,也可以提示,看具体选择.

对于一些分批加载的列表数据,一般还有上拉”加载更多”的功能:

参考微信文档中ui设计规范,上拉加载更多的ui提示应该放在页面最下部占一行,而不应该在页面中间显示一个大大的loading的效果.

  1. scrollview拉到最底部,触发加载事件,显示”加载中”的ui
  2. 加载成功,直接就将数据添加到原list上,这时也看不到最底部那行ui,所以不用处理
  3. 加载失败,则在那一行显示”加载失败”的字样,同时提示用户”上拉重试”,或者在那一行放置一个按钮,点击按钮重试.

封装

通过上面的分析,可以确定大部分页面的通用状态管理逻辑,那么就可以设计通用的状态管理模板了.

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
        });
    }

最终的效果:

black.jpg

1478250101201_4.png

常用页面模板的封装

整个页面就是只有一个简单的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;  //页面id
var 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>

效果

1478249563128_2.png

代码

wxapp-devFrame

没有更多推荐了,返回首页