因为公司项目需要,需要用vue做一个移动端版本;现在从0开始搭建,顺便记下搭建过程,方便日后回顾;欢迎 大家指出不足。
先看设计稿:
布局页面
把头部抽离出来,作为全局组件;
目录位置
定义为全局组件
在main.js 中写下
//全局组件
import topBar from '@/components/mobileTop.vue'
Vue.component('topBar', topBar);
布局 topBar 组件
1,去定义阿里图标,并引入
2,设置 icon 组件,并写html
3,因为vue 模板没有引入 scss ,所以要自己安装sass-loader,node-loader
cnpm i node-sass sass-loader -D
4,写less,此布局icon 使用 position 定位布局
因為後面加上 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 代码
在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 模块
配置 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 是不具有 局域网 访问性的,所以我们要改一下 项目的配置
在 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: 使用具有回弹效果的滚动,
当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。
常见流程列表布局与样式
我们通常会在移动端布局中接触到 流程进度列表 结构的设计图;例如淘宝的:物流进度图;这次拿到的设计稿是这样:
这两个结构可以看做一样的;整体分为左(标签)中(长线和点)右(内容)三部分,其中,最左边考虑到它位置直接贴在边上,必定是用绝对定位实现;最右边是普通的布局,难点在于怎么处理中间这根贯穿整个列表的线,和上面的圆点。
目前我知道的有两种方法:
一个是每个li 中用div画出自己的线和点,然后每个线拼接起来组成长线,这个方法的缺点是比较难定线的长度,想用height:100%,但是不生效,很可能要用到js;
第二种是本次我采用的方法,用li的伪元素before画线,after 画圆点,通过z-index设置覆盖层级;这样的好处是,线的高度可以用height:100%;难点是圆点在不同分辨率下,可能会出现偏移的情况(线没有穿过圆心);这里直接给圆点做了css3的居中定位,并用magin-right的负值微调了一下,初步测试在不同分辨率下表现都比较好;在这里给出实现代码:
html
-
拟稿意见
公伟杰信息技术部
2018-07-12
这个提议不错,试试看
//以下重复这个li
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;这个时候,这个时候就出现了光标在中间的情况;
解决方案是设置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
最后的解决方案是通过定位 把输入部分上提
详细的讨论在下面做了笔记
https://segmentfault.com/n/13...
移动端,安卓键盘弹起,顶起底部的bug
移步 https://segmentfault.com/n/13...