从零开始 搭建自己的vue 移动项目

因为公司项目需要,需要用vue做一个移动端版本;现在从0开始搭建,顺便记下搭建过程,方便日后回顾;欢迎 大家指出不足。


先看设计稿:

《从零开始 搭建自己的vue 移动项目》

布局页面

把头部抽离出来,作为全局组件;

目录位置

《从零开始 搭建自己的vue 移动项目》

定义为全局组件

在main.js 中写下

//全局组件
import topBar from '@/components/mobileTop.vue'
Vue.component('topBar', topBar);

布局 topBar 组件

1,去定义阿里图标,并引入

《从零开始 搭建自己的vue 移动项目》

《从零开始 搭建自己的vue 移动项目》

2,设置 icon 组件,并写html

《从零开始 搭建自己的vue 移动项目》

3,因为vue 模板没有引入 scss ,所以要自己安装sass-loader,node-loader

cnpm i node-sass sass-loader -D

4,写less,此布局icon 使用 position 定位布局

《从零开始 搭建自己的vue 移动项目》

因為後面加上 flexible-js 自適應 ,所以我們用 rem 佈局,參看下面的代碼,我們直接用實際尺寸除以100就可以;

.content{
    position: relative;
    padding:0 1.2rem 0 1.2rem;
  }

头部 ,main,bottom 三个部分应该怎么布局?

top,bottom main 都用absolute

这个布局在调试的时候完全没有问题;但是在真机调试的时候回存在问题,中间的 main 如果设置了移动端滚动 -webkit-overflow-scrolling:touch;会导致 滑动不了;但是如果不设置,就只能用插件去模拟滚动,非常不好;
并且 如果采用此方式,到了移动端会不时的拉起底部的bottom,让人感觉就是一个网页

top,bottom 用 absolute,main两头padding:

.box{
    position: relative;
    height: 100%;
  }
  .top{
    height: 1rem;
    background: red;
    position: absolute;
    top:0;
    left: 0;
    right: 0;
    z-index: 2;
  }
  .bot{
    height: 1rem;
    background: red;
    position: absolute;
    bottom:0;
    left: 0;
    right: 0;
    z-index: 2;
  }
  .mid{
    height: 100%;
    padding:1rem 0;
    width: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    background: blue;
    box-sizing: border-box;
  }

这个结构可以满足几乎所有要求,但是还是会有拉起底部的问题;

top,bottom 用fixed,main 用padding:1.2rem 0;

采用此方式可能会有一些忽隐忽现的问题,但是最大的好处就是 页面看起来完全像移动页面,底部也不好拉起来;滚动条相当于原生的滚动条,所以基本没有bug;有个存在的问题在这个文章中提到 https://www.cnblogs.com/xiahj…
目前我遇到的bug 在文章最后排除了。

这个算是我比较看中的方式,因为效果基本跟原生一样;

我试过用 better-scroll 处理滚动问题,但是因为微信端会有卡顿感,所以最后还是放弃了;有需求的朋友,可以根据自己情况加入better-scroll方案;

移动端自适应的基础设置

在 app.vue 中 引入 flexible-js 代码

<script>
  document.head.appendChild(meta);
  (function(doc, win){
    var docE1 = doc.documentElement,
      resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
      recalc = function(){
        var clientWidth = docE1.clientWidth;
        if(!clientWidth) return;
        //docE1.style.fontSize = clientWidth / 375  + 'px'; 这里希望设置 1rem = 1px,实验证明,这样做 会导致 html 的 fontsize小于 12px
        docE1.style.fontSize = (clientWidth / 750)*100  + 'px'; //乘以100的意义是,1为了不受fontsize小于12的影响,2为了计算方便;
      };
    if (!doc.addEventListener) return;
    win.addEventListener(resizeEvt,recalc,false);
    doc.addEventListener('DOMContentLoaded',recalc,false);
  })(document,window);
</script>

在main.js 引入全局公共样式 和 一些 模块

//引入公共样式
import '@/common/reset.css'
import '@/common/common.css'

封装 axios 生产出 http.js

/*
 * @Author lizhenhua
 * @version 2018/5/17
 * @description
 */

import axios from 'axios'
import store from '../store'
import {Message} from 'element-ui'
import {getToken,removeToken} from '@/util/cookie'
import tools from '@/util/tools'
import qs from 'qs'
// 创建axios实例
const service = axios.create({
  baseURL: process.env.BASE_API, // api的base_url
  timeout: 1000 // 请求超时时间
})

// request拦截器
service.interceptors.request.use(config => {

  //如果是开发环境,为非模拟接口加上“跨域”前缀 'ln'
  if(process.env.NODE_ENV=='development'&& config.url.indexOf('api')==-1){
    config.url = 'ln/'+ config.url;
  }

  let token = getToken();

  //如果data参数为 对象或者数组,就把参数 封装到key为data 属性中;
  if(typeof config.data == "object" ||typeof config.data == "Object" ||typeof config.data == "OBJECT"){
    let data = tools.cloneObj(config.data);
    config.data = {};
    config.data['data'] = JSON.stringify(data);
  }else {
    config.data = {};
  }

  //统一为所有请求加上 这两个参数
  if (token) {
    config.data['LE_AUTH_TOKEN'] = token
    config.data['token'] = token
  }

  //设置头部token
  if (store.getters.token) {
    config.headers['X-Token'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
  }

  //不设置 这样,后台拿不到数据
  config.headers['Content-Type'] = 'application/x-www-form-urlencoded'

  //get请求,只能放在 params 中,转为url传参的方式
  //所以统一使用post请求,只有post存在 paramBody,我们可以吧参数放在 data 中
  config.method = "POST"

  //把所有参数处理为 form 表单提交的方式,并且转义,如果不这样,后端(会直接得到字符串,不是正常对象)解析不出来;
  //前端发送:data=%7B%22loginName%22%3A%22lzh%22%2C%22loginPassword%22%3A%22123456%22%2C%22appId%22%3A%22lext79987422-5180-40%22%2C%22platType%22%3A1%7D
  //后端收到:{data={"loginName":"lzh","loginPassword":"123456","appId":"lext79987422-5180-40","platType":1}}
  config.data = qs.stringify(config.data)

  return config
}, error => {
  // Do something with request error
  console.log(error) // for debug
  Promise.reject(error)
})

// respone拦截器
service.interceptors.response.use(
  response => {
    let res = response.data;
    if (res.status == 1) {
      return res.data
    }if(res.status ==-1208){
      Message({
        message: res.errorMsg,
        type: 'error',
        duration: 5 * 1000
      })
      removeToken();
    } else {  //这里处理 所有数据错误
      Message({
        message: res.errorMsg,
        type: 'error',
        duration: 5 * 1000
      })
      return Promise.reject(res.errorMsg)
    }
  },
  error => {  //这里处理的是 所有网络请求错误
    console.log('err' + error)// for debug
    let err = error + '', info = '';
    if (err.indexOf('timeout') != -1) {
      info = "请求超时";
    } else {
      info = err
    }
    Message({
      message: info,
      type: 'error',
      duration: 5 * 1000
    })
    return Promise.reject(error)
  }
)


export default service

可以根据自己情况,拦截 ajax 后作统一处理;
把http.js 在main.js 中 引入

//引入 axios 实例
import $http from '@/util/$http' 
Vue.prototype.$http = $http;

在vue 中直接使用 $http 模块

<script>
  export default {
    data: function () {
      return {
        document: {}
      }
    },
    created() {
      this.$http({
        url: this.ajaxApi.test.list,
        data: {
          id: "docid:6CFE06297BBA4E1FBAA00BDE2809198F"
        },
      }).then(res => {
        if(res){
          this.document = this.tools.cloneObj(res.document)
        }
      })
    }
  }
</script>

配置 ajaxApi.js 为全局,统一管理接口

// api 表
export default {
  test: {
    list:"/api/data/document"
  }
}

//引入 api 表
import ajaxApi from "@/util/ajaxApi"
Vue.prototype.ajaxApi  = ajaxApi

配置 tools.js 为全局,提供常用工具函数

// tools.js
export default {
  cloneObj (obj){
    var str, newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== 'object') {
      return;
    } else if (window.JSON) {
      str = JSON.stringify(obj), //序列化对象
        newobj = JSON.parse(str); //还原
    } else {
      for (var i in obj) {
        newobj[i] = typeof obj[i] === 'object' ? cloneObj(obj[i]) : obj[i];
      }
    }
    return newobj;
  }
}

//引入工具库
import tools from "@/util/tools"
Vue.prototype.tools = tools;

再配置一个 cookie 方法文件,方便操作cookie

import Cookies from 'js-cookie'

const TokenKey = 'LtpaToken2'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token,time) {
  return Cookies.set(TokenKey, token,{expiry:time})
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

因为是移动端开发,所以你需要真机调试

因为localhost 是不具有 局域网 访问性的,所以我们要改一下 项目的配置
《从零开始 搭建自己的vue 移动项目》

在 config/index.js 找到如下代码:

 // host: 'localhost', // can be overwritten by process.env.HOST
    host: '10.20.139.118', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined

改为你的ip 以后,重启项目

在手机浏览器输入 http://10.20.139.118:8080/#/ 就可以访问到你的页面了

一个非常重要的 css 属性

刚刚开始 开发的时候,开发出来的页面 在webview上 滑动总是卡卡的感觉,人家的页面是丝质顺滑,跟原生的一样;后来想到了 用一个插件
模拟这种效果,iscroll.js;

其实 只需要一句css 就能解决这个缓动的效果
在 app.vue 下写

html, body {
  -webkit-overflow-scrolling: touch;
}

-webkit-overflow-scrolling:touch是什么?

MDN上是这样定义的:

-webkit-overflow-scrolling 属性控制元素在移动设备上是否使用滚动回弹效果. auto: 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。 touch: 使用具有回弹效果的滚动,

当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。

常见流程列表布局与样式

我们通常会在移动端布局中接触到 流程进度列表 结构的设计图;例如淘宝的:物流进度图;这次拿到的设计稿是这样:

《从零开始 搭建自己的vue 移动项目》

《从零开始 搭建自己的vue 移动项目》

这两个结构可以看做一样的;整体分为左(标签)中(长线和点)右(内容)三部分,其中,最左边考虑到它位置直接贴在边上,必定是用绝对定位实现;最右边是普通的布局,难点在于怎么处理中间这根贯穿整个列表的线,和上面的圆点。
目前我知道的有两种方法:
一个是每个li 中用div画出自己的线和点,然后每个线拼接起来组成长线,这个方法的缺点是比较难定线的长度,想用height:100%,但是不生效,很可能要用到js;
第二种是本次我采用的方法,用li的伪元素before画线,after 画圆点,通过z-index设置覆盖层级;这样的好处是,线的高度可以用height:100%;难点是圆点在不同分辨率下,可能会出现偏移的情况(线没有穿过圆心);这里直接给圆点做了css3的居中定位,并用magin-right的负值微调了一下,初步测试在不同分辨率下表现都比较好;在这里给出实现代码:

html

<ul class="item-ul">
  <li class="flex-bet">
    <div class="list-left">拟稿意见</div>
    <div class="list-right">
      <div class="top flex-bet">
        <div class="top-left"><span class="yl">公伟杰</span>信息技术部</div>
        <div class="date">2018-07-12</div>
      </div>
      <p>这个提议不错,试试看</p>
    </div>
  </li>
    //以下重复这个li
</ul>

scss

/*流程表*/
    .flex-bet{
      display: flex;
      justify-content: space-between;
      align-items: center;
    }

  .item-ul {
    padding: 0.2rem 0;
    li {
      position: relative;
      padding-left: 1.2rem;
      padding-bottom: 0.2rem;
      &:before {
        content: "";
        position: absolute;
        left: 1rem;
        top: 8px;
        width: 1px;
        background: #58a8ff;
        height: 100%;
      }
      &:after {
        content: "";
        position: absolute;
        left: 0.945rem;
        top: 8px;
        width: 0.1rem;
        height: 0.1rem;
        border: 1px solid #57a9ff;
        border-radius: 50%;
        background: #fff;
        margin-right: -2px;
      }
      .list-left {
        position: absolute;
        width: 1.4rem;
        height: 0.57rem;
        line-height: 0.57rem;
        font-size: 12px;
        text-align: center;
        color: #fff;
        background: #58a8ff;
        border-radius: 0 0.7rem 0.7rem 0;
        left: 0;
        top: 0;
        z-index: 5;
      }
      .list-mid {
        align-self: start;
        z-index: 4;
        height: 100%;
        text-align: center;
        width: 20px;
        margin-top: -4px;
        .icon {
          display: inline-block;
          width: 0.1rem;
          height: 0.1rem;
          border: 1px solid #57a9ff;
          border-radius: 50%;
          background: #fff;
          margin-right: -2px;
        }
      }
      .list-right {
        width: 6.1rem;
        padding: 0.05rem 0.3rem 0 0.5rem;
        font-size: 12px;
        .top-left {
          color: #8c8c8c;
          span {
            display: inline-block;
            padding: 0.05rem 0.2rem;
            color: #484848;
            border-radius: 15px;
            margin-right: 0.2rem;
            background: transparent;
            font-size: 15px;
          }
          .yl {
            background: #ffedd9;
          }
          .bl {
            background: #daecff;
          }
          .pin {
            background: #ffe0eb;
          }
          .zl {
            background: #e3e0ff;
          }
        }
        .date {
          color: #8c8c8c;
        }
        p {
          text-align: left;
          font-size: 12px;
          color: #484848;
          line-height: 0.57rem;
          padding-left: 4px;
        }
        .keyword {
          text-align: left;
          margin-top: 0.1rem;
          span {
            display: inline-block;
            width: 1rem;
            height: 0.35rem;
            line-height: 0.35rem;
            text-align: center;
            font-size: 12px;
            border: 1px solid #b3b3b3;
            margin-right: 0.15rem;
            &:first-child {
              border: 1px solid #57a9fb;
              color: #57a9fb;
            }
          }
        }
      }
    }
    li:last-child:before {
      height: 0.1rem;
    }
  }

一些容易遇到的坑

js focus textarea 光标定位在中间

需求常见: 点击某个input,划开一个新页面,里面可以填300字意见;我watch show/hide 变量,如果显示状态的话,就让子组件的textarea focus;这个时候,这个时候就出现了光标在中间的情况;

《从零开始 搭建自己的vue 移动项目》

解决方案是设置100 毫秒的延迟

watch:{
      control(val){
        if(val){
          /*延迟100毫秒,fix 光标定位在中间的bug*/
          setTimeout(()=>{
            this.$refs.textBox.focus();
          },100)
        }
      }
    }

弹窗后,页面依然可以滚动页面的bug,滚动穿透

解决方案是当弹窗的时候,给发生滚动的盒子 加上 overflow:hide;关闭弹窗的时候移除;
你还可以open的时候记住滚动条的位置,close的时候复原;
这里最大的坑可能是,要找清楚真实发生了滚动的盒子;很可能不是body;

.oh{
    overflow:hidden !important;
}

 openPop(value){
        this[value] = true;
        document.getElementById('app').className ='oh'
      },
      closePop(value){
        this[value] = false;
        document.getElementById('app').className=' ';
      }
  • html,body标签非弹窗千万不能有overflow设置,会有苹果设备 fixed 布局下滚动条不出来,同时还会出现盖住 top,bottom的情况
html, body{
  position: relative;
  height: 100%;
  -webkit-overflow-scrolling: touch;    //可能是跟这个属性冲突了
  /*overflow-y: auto;*/
  /*overflow-x: hidden;*/ /*这里不能加overflow所有属性,在苹果下会有上下拉盖住顶部底部的bug */
}

scrllow 组件要求父元素定高,但是父元素又不能确定高度。

传统的做法是用 百分比定高,但是要一层层设置 父元素的高度。不然百分比获取不到值。所以这里建议用 vh 代替 %

输入板遮挡textarea 或者input

最后的解决方案是通过定位 把输入部分上提

《从零开始 搭建自己的vue 移动项目》

详细的讨论在下面做了笔记
https://segmentfault.com/n/13…

移动端,安卓键盘弹起,顶起底部的bug

移步 https://segmentfault.com/n/13…

    原文作者:lidog
    原文地址: https://segmentfault.com/a/1190000015711062
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞