阅读 4598

mpvue开发大型体育项目及总结记

开篇

最近接到上头的指示,要做一个体育类的小程序,并且要在元旦之前上线一版,看了下时间,距离元旦只有一个多月,而且除去测试的时间和双休,最多只有三个星期,时间相当的紧迫,而且需求文档都更新到1.3了,这也就意味着安卓和ios的版本迭代已经到了1.3了,而我们小程序要在三个星期内开发完1.0-1.3版本的功能,所以我们的时间相当的紧迫,看了下需求文档和原型图,我整个人大吃一惊,有100多个页面,我顿时懵逼了,产品经理更我说;你们只需做三个模块,最后一个模块不用做,我看了一下最后一个模块,有10几个页面,除去10几个页面还有90多个呀,页面还好,最难的是需求文档的业务逻辑呀,而且这个项目的难度比中小型电商项目类的要难的多,交互相当的频繁和复杂,怎么办呢,一句话,凉拌,毕竟这是boss要求做的项目,只好硬着头皮往下做,不得不从。

技术选型

因小程序页面多,切交互频繁,如果用原生开发的话,时间来不急,而且页面的交互很多,这样原生小程序就显得那么的吃力了,最终我将技术选型放在了,wepy和mpvue这两个框架上,wepy框架是微信官方维护的,兼容性和扩展性很好,mpvue是美团旗下的,我最终选择了mpvue,原因是mpvue的语法跟Vue的语法是一样的,而且我们的前端同学都会Vue,所以选择mpvue是最好的选择,于是看了一下mpvue的文档和注意点,最终搭建了小程序的项目结构,将任务安排了下去,于是开启了加班的苦日子....

  • 技术点1-小程序,安卓,iOS三端数据信息同步,免登陆

因项目中的登录方式含有微信登录,所以三端协商,如果用户是微信登录的话,三端统一取,unionid 这个字段,这时肯定有同学要问;为什么不取openid ,如果做过小程序的人一定知道openid是唯一的标识,微信小程序有一个,那么在安卓,iOS他们也都有自已的openid标识,所以这样是不能达到三端数据信息同步,免登陆的效果的,微信官方介绍了6种获取unionid 的方法,我们项目最终采用了解密获取的方法,官方文档

import {AchieveOpenid} from '@/http/api.js';
    let that=this;
    wx.login({
        async success(resCode){
            const cache=await AchieveOpenid({    //openid,内部服务器=》腾讯获取到了openid
                code:resCode.code
            });
            that.openid=cache.result.unionid;
            wx.setStorageSync('openId',that.openid);
        }
    })
复制代码

注意:这个方法是官方中的wx.login+code2Session方法,也确实可以获取unionid,但是天有不测风云呀,测试组的人员测出了unionid不存在的情况,而且还有几个账号也出现了这种问题,那么我们就开始找原因,最终我们发现,如果用户没有关注过任何的公众号,微信是不会给他返回unionid的,我们找到原因之后,马上换了另外一种方法,那就是解码的方式,也是我们最终的方法。

    <button class='openpage-authorize' 
            open-type="getUserInfo" 
            lang="zh_CN" 
            @getuserinfo="onGotUserInfo">
    </button>
复制代码
   //注意我这里只列举解码的代码,有些代码省略了,请熟知。
   import WXBizDataCrypt from "@/utils/cryptojs/RdWXBizDataCrypt.js"   //引用解码
    methods:{
         deCode(encryptedData,iv,sessionKey){
         let wxObj=null,data=null;
             wxObj= new WXBizDataCrypt('wx3ea59bf3ff3a9bb8', sessionKey);
             data= wxObj.decryptData(encryptedData,iv);
            this.openid=data.unionId;
            wx.setStorageSync('openId',data.unionId);
        },
        onGotUserInfo(e) {   //通过按钮触发getuserinfo
            if(e.mp.detail.userInfo){
                this.deCode(e.mp.detail.encryptedData,e.mp.detail.iv,this.sessionKey);
            }else{
                toast('请再次授权');
            }
        },
    }
复制代码

最终我们可以通过上面的代码获取unionId解码地址下载,注意:解码这一步最好放在服务端解码,不要放在客户端解码,这样会造成信息泄露.....

  • 技术点2-在小程序中使用 canvas

我们可以看到上面两个项目中的案例图片,他们是用canvas画的,第一个是采用微信官方的api,wx.createCanvasContext 不懂得同学可以自已去看微信官方文档,代码如下

 const ctx = wx.createCanvasContext('myCanvas');
            ctx.setLineCap('round')
            var gradient1=ctx.createLinearGradient(0,0,170,0);
            gradient1.addColorStop("0","#FFF956");
            gradient1.addColorStop("1.0","#FF6C00");
            var gradient2=ctx.createLinearGradient(0,0,170,0);
            gradient2.addColorStop("0","#8156FE");
            gradient2.addColorStop("1.0","#3AFFF1");
            ctx.setLineWidth(4);
            ctx.beginPath();
            ctx.arc(50, 50, 30,0.75*Math.PI,0.25*Math.PI,false);
            ctx.setStrokeStyle('#4e4f59');
            ctx.stroke()
            ctx.beginPath();
            ctx.arc(50, 50, 38,0.75*Math.PI,0.25*Math.PI,false);
            ctx.setStrokeStyle('#4e4f59');
            ctx.stroke();
            //胜
            ctx.beginPath();
            ctx.arc(50, 50, 30,0.75*Math.PI,(((winarc*1.5+0.75)%2)==0?2:((winarc*1.5+0.75)%2))*Math.PI,false);
            ctx.setStrokeStyle(gradient1)
            ctx.stroke()
            //负
            ctx.beginPath();
            ctx.arc(50, 50, 38,0.75*Math.PI,(((failarc*1.5+0.75)%2)==0?2:((failarc*1.5+0.75)%2))*Math.PI,false)
            ctx.setStrokeStyle(gradient2);
            ctx.stroke()
            ctx.setTextAlign('center');
            ctx.setFontSize(16);
            ctx.setFillStyle('#fff');
            ctx.setTextBaseline('middle');
            ctx.fillText('战绩', 50, 50);
            ctx.draw()
复制代码

注意点:如果canva的数据是异步的话,一定要在数据加载完成之后,在让它渲染到视图层中去,如果不这样做的话,canvas会数据不同步,具体的做法可以加一个开关,如下..

      <canvas canvas-id="myCanvas" class="index-header-data-circle-canvas" v-if='on'></canvas>
复制代码
   data(){
       return {
           on:false
       }
   }
   async xx(){
        try{
             const data=await xxx();
             this.on=data.code==='000'?true:false;
        }catch (error) {}
   }
   //注意:以上代码只是模拟,仅供查考。
复制代码
  • 技术点3-图片上传转化base64

图片上传微信小程序给我们提供了api,wx.chooseImage ,上传简单,关键是如何转化base64位呢,我们的舒同学用了如下的写法,看着确实没什么问题,用临时路径作为一个请求的url,把数据返回格式设置成arraybuffer,这个也确实是个办法,在微信开发工具里面也是ok的,但是天有不测风云呀,在真机上请求报错了,那么这种方法pass掉。

   wx.chooseImage({
      success:res=>{
            wx.request({
                  url:url,
                  responseType: 'arraybuffer', //最关键的参数,设置返回的数据格式为arraybuffer
                  success:res=>{
                        let base64 = wx.arrayBufferToBase64(res.data); 
                      }
           })
})
复制代码

针对上面的问题,仔细的看了下微信官方文档,最终找到了一个代码少,简单的方法,wx.getFileSystemManager()这个api可以解决我们上面的问题,微信官方文档,代码如下

              wx.chooseImage({
                count: 1,
                sizeType: ['original', 'compressed'],
                sourceType: ['album', 'camera'],
                success:(res)=>{
                     wx.getFileSystemManager().readFile({
                                filePath:res.tempFilePaths[0],  //选择图片返回的相对路径
                                encoding: 'base64', 
                                success: res => {  
                                     console.log(res.data)
                                }
                    })
                }
              })
复制代码

其实除了,wx.getFileSystemManager()可以解决我们的问题外,还有一种方法,那就是更html5一样的处理方法,通过canvas来画,然后在用canvas的api来转base64,注意:如果通过canvas来转base64的话,有个bug,那就是在iOS手机上图片会出现旋转90度的问题 小程序可以借鉴这个同学的方法来解决,如果是html5的话可以通过exif.js这个库来解决问题。

  • 技术点4-对picker的封装

小程序中有个picker组件,他支持5中类型,虽然有5中类型但是每个项目的不同,所以对picker的用途就不同,因此我们将对picker进行封装,来达到满足我们项目的需求,我们封装省市,时间日期等组件,我这里就只介绍省市组件的封装,其他的组件封装原理同省市组件原理一样的,我这里就不多说了,代码如下。

    <picker  class="pickes"  mode="multiSelector" 
           @change="PickerChange"
           @columnchange="PickerColumnChange" 
           :range="allList" 
           range-key='provinceName'  
           :value='multiIndex' v-if='show'>
          <div class="slot"></div>
    </picker>
复制代码
/**
 * @describe 省市选择器
 * @rerurn  省,市,省id,市id
 */
  import {allCity} from "@/http/api.js";
  export default {
      data() {
        return {
            list:[],
            multiIndex: [0, 0], //显示化动的列数
            allList:[],    //存储二维数据
            singleList:[], //存储一维数组
            show:false,    //防止数据没有加载出来
            cityInfo:{},   //存储省,市,省id,市id
        }
      },
      mounted(){
        this.init();
      },
      methods:{
        async init(){   
            try {
                 let child=[],data=null;
                  data=await allCity();  //获取后台返回的城市
                  this.singleList=data.result;
                 child.push(data.result[0]);
                 this.allList.push(data.result,child);
                this.show=true;
            } catch (error) {}
        },
          PickerChange(e) {
             this.cityInfo.provinceId=this.singleList[e.mp.detail.value[0]].provinceId; //省id
             this.cityInfo.provinceName=this.singleList[e.mp.detail.value[0]].provinceName; //省名
             this.cityInfo.cityId=this.singleList[e.mp.detail.value[0]].cityList[e.mp.detail.value[1]].cityId; //城市id
             this.cityInfo.cityName=this.singleList[e.mp.detail.value[0]].cityList[e.mp.detail.value[1]].cityName; //城市名
             this.$emit('cityInfo',this.cityInfo); //将值传给父组件
          },
          PickerColumnChange(e) {
            switch (e.mp.detail.column) {
              case 0:
                this.list = [];
                this.singleList.forEach(item => {
                       if (item.provinceId ==this.singleList[e.mp.detail.value].provinceId) {
                         item.cityList.forEach(item=>{
                           //注意这一步最为重要,给数组添加一个和父对象一样的键值名,这样picker组件可以找的到
                             item.provinceName=item.cityName;  
                         })
                      }
                   });
                this.allList[1]=this.list;
                this.multiIndex[0]=e.mp.detail.value;
                this.multiIndex[1]=0;  //注意这个表示的时选择中省切换的时候,要将省的第一个城市放在第一位
                break;
            }
          },
      }
  }
复制代码

  • 技术点5-在小程序使用高德定位

因项目中要用到定位功能,而小程序中的的api并不适用项目,所以就选择了高德定位,高德小程序版文档,代码如下

   //注意我这里只列举定位的代码,有些代码省略了,请熟知。
   
    let _this = this,myAmapFun=null;
     myAmapFun = new amapFile.AMapWX({
        key: "xxxxx"   //高德的密钥
    });
    myAmapFun.getRegeo({
        success(data){
            _this.$store.dispatch('cityLocal',data[0].regeocodeData.addressComponent.city);
        },
        fail(err) {
               wx.showModal({
                    title: '提示',
                    content: '定位失败,请手动定位',
                    success (res) {
                        if (res.confirm) {
                        path({url:'/pages/city/main'});
                        }else if (res.cancel) {
                           _this.$store.dispatch('cityLocal','定位失败');
                        }
                    }
                })
        }
    });
复制代码
  • 技术点6-对微信小程序节点的运用

由于项目用到了城市索引选择功能,所以就采用 wx.createSelectorQuery()这个api来实现这个功能,代码如下

   <ul  class="slide">
        <li v-for="(item,index) in cityJson.leter" 
            :key="index" 
            @tap='touStart(item.letid,item.lettext)'>
            {{item.lettext}}
        </li>
     </ul>
复制代码
   //注意我这里只列举城市索引选择的代码,有些代码省略了,请熟知。
   onPageScroll(e){
        this.scollTop=e.scrollTop    //同步
    },
    methods:{
         touStart(flag,text){
                 try {
                    wx.createSelectorQuery().select(flag).fields({   //运用微信节点api
                    dataset: true,
                        size: true,
                        rect: true,
                        computedStyle: ['margin', 'backgroundColor']
                        }, (res)=> {
                            wx.pageScrollTo({
                                    scrollTop: this.scollTop+res.top,
                                    duration: 0
                            });
                            this.on=true;
                            this.modalText=text;
                            setTimeout(()=>{
                                  this.on=false;
                            },2000)
                        }).exec() 
                 }catch (error) {}
            },
    }
复制代码

当然实现上面这个功能也可以用其他的方法,如scroll-view,我这里就不多说了。

  • 技术点7-返回上一层页面,刷新页面数据

我们可以通过微信中的wx.navigateBack()这个api就可以返回上一层页面,但是怎样返回上一层页面并且刷新呢,其实可以通过onShow这个生命周期函数来刷新页面,如果那个页面含有参数的话,最好代码这么写

onShow(){
//之所以用try,是因为mpvue官方说,如果要获取地址参数的话,最好在mounted周期里面获取,我们用try可以避免代码终止和报错
     try {   
         let id=this.$root.$mp.query.Id;
         this.init(id);
     }catch (error) {}
}
复制代码

结语

由于时间的原因,我暂时先介绍这几个在小程序中常见的问题和功能,后面我会陆续介绍,如下技术栈

  • vue的三种ssr方法,以及在项目中的使用
  • react+redux在项目中的使用
  • 打造自已的webpack,gulp开发环境
  • koa框架的介绍和使用

最后,祝所有人,新年快乐,在2019年中实现自已的梦想,放飞自我。

关注下面的标签,发现更多相似文章
评论