mpvue开发小程序总结

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

内容简介:原生小程序开发方式与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超多),开发体验说实话目前为止感觉不太好。但是什么技术都不可能一出来就做得很完美,作为前端只能跟着潮流掌握这些新技术了。


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

查看所有标签

猜你喜欢:

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

编程算法新手自学手册

编程算法新手自学手册

管西京 / 机械工业 / 2012-1 / 69.80元

《编程算法新手自学手册》主要内容简介:算法是指在有限步骤内求解某一问题所使用的一组定义明确的规则。程序员都会看重数据结构和算法的作用,水平越高,就越能理解算法的重要性。算法不仅是运算工具,更是程序的灵魂。《编程算法新手自学手册》循序渐进、由浅入深地详细讲解了基于C语言算法的核心技术,并通过具体实例的实现过程演练了各个知识点的具体使用流程。全书共11章,分为4篇。1~2章是基础篇,介绍算法开发所必需......一起来看看 《编程算法新手自学手册》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具