mpvue开发小程序总结

栏目: JavaScript · 发布时间: 6年前

内容简介:原生小程序开发方式与vue有些类似,所以用过vue的前端er会很容易上手。但是原生的开发体验实在糟糕,在前端组件化的今天用原生开发组件显得很无力。对于习惯vue开发方式的前端er来说mpvue再合适不过了。mpvue可以将H5代码打包成小程序代码,目前mpvue还做不到一套代码多端运行(毕竟各个端有自己的差异性,小程序没有document和window,所以那些第三方移动端组件库并不能适用于小程序),但是已经大大减少了开发的工作量。小程序网络请求只能是https协议且不能有端口号,可以使用natapp代理

前期准备

1.框架选型

原生小程序开发方式与vue有些类似,所以用过vue的前端er会很容易上手。但是原生的开发体验实在糟糕,在前端组件化的今天用原生开发组件显得很无力。对于习惯vue开发方式的前端er来说mpvue再合适不过了。mpvue可以将H5代码打包成小程序代码,目前mpvue还做不到一套代码多端运行(毕竟各个端有自己的差异性,小程序没有document和window,所以那些第三方移动端组件库并不能适用于小程序),但是已经大大减少了开发的工作量。

2.项目的搭建

mpvue开发小程序总结

3.项目结构

project 
└───build 
└───config 
└───dist 
└───node_modules 
└───server//mock服务器 
└───src 
    └───assets
        └───sass
        |    common.scss   // 全局样式
    └───components
    └───pages
    └───plugins //存放封装的插件
    └───services
    |    Api.js   // 封装请求
    |    WxApi.js   // 对小程序api二次封装    

    └───store
        └───modules // vuex模块文件夹
        |    index.js // vuex处理文件
        |    action.js
        |    state.js
        |    mutations.js 
        |    type.js复制代码
|   App.vue
    |   config.js//配置信息,如请求地址等
    |   main.js
└───static    //静态资源
    └───images//图片
    └───font//字体图标
│   README.md
│   package.json  
│   package-lock.json  
复制代码

3.其他

小程序网络请求只能是https协议且不能有端口号,可以使用natapp代理,然后在开发中可在开发者 工具 中设置不校验域名

登录流程

1.关于OpenId和UnionId

OpenId 是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户。

UnionId 是一个用户对于同主体微信小程序/公众号/APP的标识,开发者需要在微信开放平台下绑定相同账号的主体。开发者可通过 UnionId ,实现多个小程序、公众号、甚至APP 之间的数据互通了。

同一个用户的这两个 ID 对于同一个小程序来说是永久不变的,就算用户删了小程序,下次用户进入小程序,开发者依旧可以通过后台的记录标识出来。

2.官方给出的最佳实践

新版的小程序对获取用户信息授权进行了改动,用户在小程序中需要点击组件后,才可以触发登录授权弹窗、授权自己的昵称头像等数据。因此官方给出的最佳实践是

1.调用 wx.login 获取 code ,然后从微信后端换取到 session_key ,用于解密 getUserInfo 返回的敏感数据。

2.使用 wx.getSetting 获取用户的授权情况

1) 如果用户已经授权,直接调用 API wx.getUserInfo 获取用户最新的信息;

2) 用户未授权,在界面中显示一个按钮提示用户登入,当用户点击并授权后就获取到用户的最新信息。

3.获取到用户数据后可以进行展示或者发送给自己的后端。

mpvue开发小程序总结

3.实现

  1. 制作一个登录页面
  2. 在App.vue里的onLaunch生命周期中判断Storage中是否存在,如不存在跳转到登录页并把当前页面的路由当作参数传递过去,如存在再调用wx.checkSession()检查session_key 的有效性否则跳到登录页并把当前页面的路由当作参数传递过去
  3. 在ajax请求response拦截器里判断状态码为401表示token已过期,跳转到登录页重新登陆并把当前页面的路由当作参数传递过去
  4. 登录按钮加上open-type="getUserInfo"属性,并监听getuserinfo事件,用户点击后会返回加密后的用户信息,此时执行wx.login()获取到code,将code和用户信息发送到后台换取token,并把token存储到Storage

附官方的最佳实践 mp.weixin.qq.com/s/JBdC-G9Mw…

vuex模块化

在开发时有时会遇到一些变量需要跨两三个页面传递公用,所以引入vuex是比较好的解决方案。当一个项目比较大时,所有的状态都集中在一起会得到一个比较大的对象,进而显得臃肿,难以维护。为了解决这个问题,Vuex允许我们将store分割成模块(module),每个module有自己的state,mutation,action,getter。新建以下目录

store
        └───modules // vuex模块文件夹
               └───  kx //一级模块
                 └───  jkdbb//二级模块
                    | index.js  
                    ...
        |    index.js // vuex处理文件
        |    action.js
        |    state.js
        |    mutations.js 
        |    type.js复制代码

action,state,mutations,type作为公共。

在index.js中设置namespaced为true

export default {
    namespaced: true,
    state: {
        ywmkid: '1',
        formData:{}
    },
    mutations:{
        SET_YWMKID (state,ywmkid) {
            state.ywmkid = ywmkid
        },
        SET_FORMDATA (state,formData) {
            state.formData = formData
        }
    }
    
}复制代码

在store下的index.js

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import jkdbb from './module/kx/jkdbb/index'

Vue.use(Vuex)

const isDebug = process.env.NODE_ENV !== 'production'

export default new Vuex.Store({
    state,
    mutations,
    actions,
    modules:{
        jkdbb
    },
    strict: isDebug
})
复制代码

访问模块里的state

this.$store.state.jkdbb.ywmkid复制代码

修改模块里的state

this.$store.commit('jkdbb/SET_YWMKID',this.$route.query.id)复制代码

mpvue-router-patch

vue-router不兼容小程序,所以为了和h5代码保持一致选用mpvue-router-patch,其和vue-router类似的使用方式,默认对应小程序的navigateTo。附小程序的三种跳转方式的区别

wx.navigateTo:跳转到某个页面。会产生一个新的页面

wx.redirectTo:重定向到某个页面。替换掉当前页面

wx.switchTab:切换tab

wx.reLaunch:重新启动。清空之前的所有页面

在小程序中有层级的限制,只能打开十个页面,因此要合理利用跳转的方式。

在需要重启应用时只需要设置reLaunch为true即可

this.$router.push({path:'/pages/index/main',reLaunch: true})
复制代码

在需要切换tab时只需要设置switchTab为true即可

this.$router.push({path:'/pages/index/main',switchTab: true})
复制代码

附完整api地址 github.com/F-loat/mpvu…

flyio

axios不兼容小程序,flyio兼容Node.js 、 微信小程序WeexReact Native 、Quick App 和浏览器。以下的代码是对flyio的使用进行封装

import Config from '@/config'
import {CacheUtil} from '@/services/WxApi' 
let Fly=require("flyio/dist/npm/wx") 
let fly=new Fly;

class Response {

    constructor (res) {
        this.rawData = res
        this.code = res.code
        this.messages = res.messages
        this.data = res.data
        this.wwRequestEntity = res.wwRequestEntity
    }

    resolve () {
        if (this.isSuccess()) {
            return Promise.resolve(this.rawData)
        }
        if (this.isError()) {
            let message = Config.defErrorMessage
        }
        return Promise.reject(this.messages)
    }

    isSuccess () {
        return this.code === 1
    }

    isError () {
        return this.code === 0
    }
}


class ApiManager {

  constructor (apiPrefix) {
      fly.config.baseURL = apiPrefix || Config.apiPrefix;
      fly.config.timeout = 120000
      fly.interceptors.request.use(request => {
          const token = CacheUtil.getStorage('token');
          if (token) {  // 判断是否存在token,如果存在的话,则每个http header都加上token
              request.headers.Authorization = `Bearer ${token}`;
          }
          return request
      })


      fly.interceptors.response.use(res => {
          if (res.status >= 200 && res.status < 300) {
              let response = new Response(res.data)
              return response.resolve()
          }
          return Promise.reject(res)
      }, error => {
          const { response } = error;
          if (!response) return Promise.reject(error)
          if (response.status === 401) {
              //没有权限跳转到授权页面
              return Promise.reject(null)
          }
          return Promise.reject(error)
      })
  }
  post(uri, data, config){
    return fly.post(uri, data, config);
  }

  get(uri, data){
    return fly.get(uri, data);
  }

  put (uri, data) {
      return fly.put(uri, data)
  }

  delete (uri, data) {
      return fly.delete(uri, data)
  }
}

export function httpManager(baseURL){
    return new ApiManager(baseURL)
}

export let apiManager = httpManager()复制代码

在main.js引用并挂载

Vue.prototype.apiManager = apiManager;
复制代码

之后在页面中就可以这样使用

this.apiManager.post复制代码

二次封装Storage

小程序的Storage没有设置过期时间的参数,我们可以对他进行二次封装

export const CacheUtil = {
  getStorage(key){
    let data = wx.getStorageSync(key);
    if (!data) {
      return null;
    }else{
      let now = new Date().getTime()
      if (now >= parseInt(data.expired)) {
        wx.removeStorageSync(key)
        return null;
      }else{
        return data.data;
      }
    }
    return 
  },
  setStorage(key,data,expired){
    let time = new Date().getTime() + expired;
    wx.setStorageSync(key,{data:data,expired:time})
  },
  removeStorage(key){
    wx.removeStorageSync(key)
  }
}
复制代码

表单验证

小程序的表单验证似乎不多,常用的就是 WxValidate 。但是我觉得不是那么好用,提示判断还要自己写。所以采用 we-validator ,它支持微信小程序、支付宝小程序、浏览器以及Nodejs端使用。我们可以在plugins文件夹中新建一个js用于添加自定义的校验规则

import WeValidator from 'we-validator'
WeValidator.addRule('Mobile', function(value, param){
     const reg = 11 && /^(((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\d{8})$/;
    return reg.test(value) 
})

export default{
    WeValidator
}
复制代码

然后再main.js中引用并挂载

Vue.prototype.$WeValidator = WeValidator.WeValidator;
复制代码

在页面中

mounted(){
      this.oValidator = new this.$WeValidator({
        rules: {
          sbmc: {
            required: true
          },
          sbxh: {
            required: true
          },
          fx: {
            required: true
          },
          yxqk: {
            required: true
          },
          azrq: {
            required: true
          },
          ssqx: {
            required: true
          },
          azdz: {
            required: true
          }
        },
        messages: {
          sbmc: {
            required: '请选择设备名称'
          },
          fx: {
            required: '请选择方向'
          },
          sbmc: {
            required: '请选择设备名称'
          },
          yxqk: {
            required: '请选择运行情况'
          },
          azrq: {
            required: '请选择安装日期'
          },
          ssqx: {
            required: '请选择所属区县'
          },
          azdz: {
            required: '请输入安装地址'
          }
        }
      })
    },
    methods:{
     submit(){ 
        if(this.oValidator.checkData(this.form)){
          
        } 
      }
    }复制代码

获取当前位置

小程序的api获取当前位置返回的信息比较少,如有需要获取到地址需要引入地图的地址解析。我用的是腾讯地图,在引用的时候会报错,是因为它不是用export导出的,所以可以在最后修改为export default QQMapWX。

import QQMapWX from '../plugins/qqmap-wx-jssdk.js'
let qqmapsdk = new QQMapWX({
                  key: config.txMapKey
              });
export function getPosition(){
  return new Promise((resolve,reject)=>{
    wx.showLoading({
      title:'正在定位',
      mask:true
    })
    wx.getLocation({
      success:function(res){
        qqmapsdk.reverseGeocoder({
            coord_type:0,
            location: {
                latitude: res.latitude,
                longitude: res.longitude
            },
            success: function(data) {
                resolve({
                  latitude: res.latitude,
                  longitude: res.longitude,
                  adcode: data.result.ad_info.adcode,
                  addr:data.result.address
                })
            },
            fail: function(err) {
                reject(err);
            },
            complete: function(err) {
              wx.hideLoading()
            }
        });
      },
      fail:function(err){
        reject(err)
      }
    })
  }) 
}复制代码

封装input组件

我们可以将input做成一个组件,可以去配置输入框的标题,清空输入框等功能。isShowArea如下一条所说的作用。在向父页面传递值时需要一个settimeout,如果不添加会有闪烁的bug。

<template>
  <div class="yg-input">
    <label :placeholder="placeholder"  :style="{width:labelWidth}">{{label}}</label>
    <div class="yg-input-control yg-textarea-control" v-if="type=='textarea'">
      <textarea v-if="isShowArea" :maxlength="maxlength" v-bind="$attrs" :cursor-spacing="100" :placeholder="placeholder" :disabled="disabled||readonly" :value="currentVal" @input="updateVal($event.target.value)"></textarea>
      <div v-if="!isShowArea">{{currentVal}}</div>
    </div>
    <div class="yg-input-control" v-else>
      <input :placeholder="placeholder" :maxlength="maxlength" :cursor-spacing="100" :disabled="disabled||readonly" :value="currentVal" v-bind="$attrs" @input="updateVal($event.target.value)" :type="type" />
      <i v-if="value && $attrs.readonly!==!!'true'" @click="clear" class="iconfont iconfont-close"></i> 
    </div>
  </div>
</template>
<script>
  export default {
    inheritAttrs: false,
    data() {
      return {
        currentVal:this.value
      }
    },
    computed:{
      isShowArea(){
        return this.$store.state.mask;
      }
    },
    methods:{
      clear(){
        this.currentVal = '';
        this.$emit('input', '');
      },
      updateVal(val) {
        this.currentVal = val;
        setTimeout(()=>{
          this.$emit('input', val);
        },0)  
      }
    },
    watch:{
      value: {
    handler(newValue, oldValue){
          this.currentVal = newValue;   
    }
   }
    },
    props: {
      label:{
        default:'',
        type:String
      },
      labelWidth:{
        default:'75px',
        type:String
      },
      type:{
        default:'text',
        type:String
      },
      value:{
        default:'',
        type:String
      },
      placeholder:{
        default:'',
        type:String
      },
      maxlength:{
        default:'-1',
        type:String
      },
      disabled:{
        default:false,
        type:Boolean
      },
      readonly:{
        default:false,
        type:Boolean
      }
    }
  }
</script>
<style lang="scss">
  
</style>
复制代码

可以将它定义为全局组件,在main.js中

Vue.component('yg-input',YgInput)
复制代码

在父页面使用

<yg-input :readonly="status==3" v-model="form.sbxh" label="设备型号" placeholder="请输入设备型号"></yg-input>
复制代码

textarea的bug

textarea是原生组件因此层级最高,因此在页面上自定义的弹窗会被textarea的内容覆盖。因此我们可以在弹窗是将它隐藏,在关闭弹窗时显示。可以在vuex中定义一个变量控制,然后在弹窗组件中对这个变量赋值。

<textarea v-if="isShowArea"></textarea>
<div v-if="!isShowArea">{{currentVal}}</div>//在textarea隐藏时显示textarea填写的内容复制代码

还有一个问题是textarea在ios真机上会有一个默认的padding导致和其他表单元素不对齐。这个官方还是没修复。

封装select组件

原生的picker不能自定义样式,而却选项的文字多了会显示...,项目中还要显示中英文所以自己写了一个

<template>
  <div class="yg-input yg-select">
    <label :style="{width:labelWidth}">{{label}}</label>
    <div class="yg-input-control" @click="showPicker">
      <input :value="selectVal" :placeholder="placeholder" v-bind="$attrs" :disabled="true"/>
      <i class="iconfont iconfont-arrow-r"></i>
    </div>
    <div class="popup-mark" v-if="toggle">
      <ul class="yg-popup">
        <li class="yg-popup-head">
          <span @click.stop="cancel">取消</span>
          <span @click.stop="confirm">确定</span>
        </li>
        <li v-for="(item,$index) in selectOption" :class="{'active':item.active}" :key="index" @click.stop="itemClick(item)">{{item.text}}</li>
      </ul>
    </div>
  </div>
</template>
<script>
  export default {
    inheritAttrs: false,
    data() {
      return {
        selectVal:'',
        toggle:false,
        cache_selectVal:'',
        cache_selectTxt:'',
        isFirstEnter:true,
        selectOption:[]
      }
    },
    methods:{
      cancel(){
        this.selectOption = [...this.setActive(this.value,this.option)];
        this.hide();
      },
      confirm(){
        if(this.selectVal != this.cache_selectTxt&&this.cache_selectTxt){
          this.selectVal = this.cache_selectTxt;
          this.$emit('input', this.cache_selectVal);
          this.$emit('change', this.cache_selectVal,this.cache_selectTxt);
        }
        this.hide();
      },
      itemClick(item){
        this.cache_selectVal = item.value;
        this.cache_selectTxt = item.text;
        this.selectOption = [...this.setActive(item.value,this.option)];
      },
      setActive(value,arr){
        for(let item of arr){
          if(value==item.value){
            item.active=true;
          }else{
            item.active=false;
          }
        }
        return arr;
      },
      hide(){
        this.$store.commit('SET_MARK',true);
        this.toggle = false;
      },
      showPicker(){
        if(this.disabled||this.readonly){
          return false;
        }

        if(!this.option.length){
          this.$message('没有相关选项');
          return false;
        }
        this.$store.commit('SET_MARK',false);
        this.toggle = true;
      }
    },
    props: {
      label:{
        default:'',
        type:String
      },
      disabled:{
        default:false,
        type:Boolean
      },
      readonly:{
        default:false,
        type:Boolean
      },
      labelWidth:{
        default:'75px',
        type:String
      },
      value:{
        default:'',
        type:String
      },
      placeholder:{
        default:'',
        type:String
      },
      option:{//
        default:function(){
          return []
        },
        type:Array
      },
      defaultFirst:{
        default:false,
        type:Boolean
      }
    },
    watch:{
      value: {
    handler(newValue, oldValue){
          this.selectOption = this.selectOption.map((item)=>{
            if(this.value==item.value){
              item.active=true;
              this.selectVal=item.text;
            }else{
              item.active=false;
            }
            return item;
          })
    },
        immediate: true
    },
      option: {
    handler(newValue, oldValue){
          this.selectOption = [...newValue];

          let _value = '';
          for(let item of newValue){
            if(this.value==item.value){
              _value = item.value;
              break;
            }
          }

          if(_value){
            this.$emit('input', _value);
          }

          if(this.option.length!==0&&!_value&&this.defaultFirst){
            this.$emit('input', this.option[0].value);
          }
          
          if(this.option.length==0&&!_value&&!this.isFirstEnter){
            this.$emit('input', '');
            this.isFirstEnter = false;
          }          
    },
    deep: true,
        immediate: true
    }
    }
  }
</script>
<style lang="scss">
  @keyframes popup{
    0%{
      transform:translate(0,100%);
    }
    100%{
      transform:translate(0,0)
    }
  }
  .popup-mark{
    position:fixed;
    z-index:9999;
    background:rgba(0,0,0,0.4);
    top:0;
    left:0;
    right:0;
    bottom:0;
    display:flex;
    align-items:flex-end;
    .yg-popup{
      animation:popup .3s;
      flex:1;
      max-height:800px;
      min-height:500px;
      overflow:auto;
      background:#fff;
      li{
        &.yg-popup-head{
          background:#fbf9fe;
          color:#999;
          line-height: 1;
          padding:0;
          overflow:hidden;
          padding:30px;
          span:nth-of-type(1){
            float: left;
          }
          span:nth-of-type(2){
            float: right;
            color:#2b79fb;
          }
        }
        background:#fff;
        border-bottom:1px solid #eee;
        line-height: 1;
        padding:30px 50px 30px 30px;
        font-size:32px;/*no*/
        color:#666;
        position:relative;
        &.active{
          color:#2b79fb;
          &:before{
            content:'';
            position:absolute;
            width:8PX;
            height:8PX;
            border-radius:50%;
            top:50%;
            background:#2b79fb;
            margin-top: -4PX;
            right: 40px;
          }
        }
      }
    }
  }
</style>复制代码

下拉刷新上拉加载

1.上拉加载更多

在列表的下面添加以下节点,loadTip默认为空,用于加载时的提示

<div class="page-list-loading">{{loadTip}}</div>
复制代码

定义maxPage表示最后一页

在onReachBottom钩子里加载下一页

onReachBottom(){
      this.loadTip = '正在加载中';
      this.form.page++;
      this.loadData();
    },
复制代码

在loadData的回调里

this.list = this.list.concat(res.data);
this.loadTip = '';
this.maxPage = Math.ceil(res.total/this.form.size);//算出最后一页
复制代码

2.下拉刷新

在需要下拉刷新的页面同级新建main.json

{
  "enablePullDownRefresh": true
}
复制代码

在onPullDownRefresh钩子里加载数据

this.maxPage = null;
this.form.page = 1;
this.list = [];
this.loadTip = '';
wx.showNavigationBarLoading();//显示标题栏的加载动画

//请求成功后
wx.hideNavigationBarLoading();
wx.stopPullDownRefresh();//停止下拉复制代码

截至2018-10-15,下拉刷新还存在bug,ios上头部head设置position为fixed时会挡住加载动画,在安卓上头部head又会跟随下滑

粘性导航

在h5中可以方便地利用window对象获取滚动距离,元素距离顶部距离等。但是在小程序中并没有window对象只能使用小程序的api获取相关数据。

在export default上面添加以下方法,该方法返回导航的第几个为高亮状态

function getNavActiveIndex(num,arr){
  if(num===0){
    return 0;
  }
  for(var i=0;i<arr.length;i++){
    if(num>=arr[i]){
      if (!arr[i + 1]) {
        return arr.length - 1;
      } else if (num < arr[i + 1]) {
        return i;
      }
    }else if (num < arr[i]) {
      if (!arr[i - 1]) {
        return 0;
      } else if (num >= arr[arr - 1]) {
        return i-1;
      }
    }
  }
}
复制代码

在onPageScroll钩子里,affix为导航的最外层,isfixed变量控制是否添加position:fixed属性,navActive控制导航的第几个为高亮状态,boundingClientRect获取affix相对于显示区域的距离

onPageScroll(e){
    const query = wx.createSelectorQuery();
    query.select('.affix').boundingClientRect((res)=>{
            if(res){
              if(res.top<0){
                this.isfixed = true;
              }else{
                this.isfixed = false;
                this.navActive = 0;
              }
            }
    }).exec();
    this.navActive = getNavActiveIndex(e.scrollTop,this.offsetTopArr);
  }复制代码

在请求完数据后获取每一个距离页面顶部的距离,45是减去导航本身的高度

mounted () {
    this.$showLoading();
    Promise.all([this.apiManager.post('/home/indexLists'),this.apiManager.post('/home/frequentlyUsed'),this.apiManager.post('/gaywtz/wrapNotice')]).then(res=>{
      this.affixArr = res[0].data[0].indexLabelLists
      this.indexDataLists=res[0].data[0].indexDataLists;
      this.hotAppList = res[1].data;
      this.imgUrls = res[2].data[0];
      this.newsList = res[2].data[1];
      this.colorCtrl();
      setTimeout(()=>{
        const query = wx.createSelectorQuery();
        query.selectAll('.affix-pane').boundingClientRect((res)=>{
            res.map((item)=>{
              this.offsetTopArr.push(item.top-45)
            })
        }).exec();
        this.$hideLoading();
      }, 200)
    })
  }复制代码

点击导航时

changeSt(index){
      this.navActive = index;
      wx.pageScrollTo({
        scrollTop: this.offsetTopArr[index]
      })
    },
复制代码

px2rpx

rpx 是微信小程序解决自适应屏幕尺寸的尺寸单位。以iPhone6为例,屏幕宽度为375px,把它分为750rpx后, 1rpx = 0.5px。px2rpx可以很方便地把px转成rpx。文档中说和px2rem使用一样,但是当某个属性并不需要将px转成rpx时,设置/*no*/并没有用,解决办法是把px改为PX。

mpvue-wxparse

后台返回的富文本编辑器中的内容是html节点,这在小程序中是解析不出来的。因此需要mpvue-wxparse去转换

js
import wxParse from 'mpvue-wxparse'
components:{
  wxParse
}

html
<wxParse :content="content" @preview="preview" @navigate="navigate" />
复制代码

所有页面的created钩子函数在小程序打开时就全部执行

改用mouted钩子

重新进入页面会保留之前的数据

假设有三个页面:列表页A,表单页一B,表单页二C。当填写完表单页C后跳转到列表页,这时如果再从列表页进入表单页B会发现B还是之前填写的数据。这是因为mpvue在离开页面时并没有调用destroyed钩子,因此目前的解决方案是在小程序的onUnload中重置data函数里的数据。可以将它封装成一个插件

const resetPageData = {
  install: function (Vue) {
    Vue.mixin({
      onUnload() {
        if (this.$options.data) {
            Object.assign(this.$data, this.$options.data())
        }
      }
    })
  }
}

export default resetPageData;
复制代码

然后在main.js中使用

Vue.use(resetPageData);
复制代码

其他需要注意的

1.background不支持本地路径

2.不能使用v-show需替换成v-if

3.在map中使用cover-view需要直接使用cover-view,如使用div会有问题,文档中写到目前cover-view支持动画,开发者工具中有效实际在真机无效,且不支持单边border,rotate等

4.solt不支持动态渲染,在封装业务组件时很是蛋疼

5.不支持自定义指令

最后吐槽

小程序还很年轻,还存在很多不足(bug超多),开发体验说实话目前为止感觉不太好。但是什么技术都不可能一出来就做得很完美,作为前端只能跟着潮流掌握这些新技术了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Numerical Methods and Methods of Approximation in Science and En

Numerical Methods and Methods of Approximation in Science and En

Karan Surana / CRC Press / 2018-10-31

ABOUT THIS BOOK Numerical Methods and Methods of Approximation in Science and Engineering prepares students and other readers for advanced studies involving applied numerical and computational anal......一起来看看 《Numerical Methods and Methods of Approximation in Science and En》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试