☞☞ 个人主页欢迎访问 ☜☜
最近在开发一个移动端项目微信公众号,前端框架我用当然是Vue,期间遇到了很多问题,在这里记录一下,希望也可以帮助到入坑vue项目的伙伴们,先说说我用到的技术栈:
vue:前端主体框架vue 2.0
vue-router:vue全家桶之一 vue的路由
axios:基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中配合vue-axios完成vue中和后台对接url发送请求
vuex:vue全家桶之一 vue的状态管理器
Webpack:webpack 3.0在nodejs下用于对项目的模块化管理方案,或者是自动化打包工具,将我们项目中的所有文件经过loader之后解析为浏览器识别的js模块
vux:UI框架
Less:less语言 打包后自动编译为css
ESNext:es写法 完美支持vue 2.0
Rem:整个项目采用rem布局来兼容各个尺寸的手机
Jquery:jquery库
Flex:flex布局更方便快捷
vue-quill-editor:基于quill插件开发的一款支持vue的富文本编辑器
vue-scroller:滚动条 上拉加载
vue-cropper:截图工具
mint-ui:IndexList首字母索引列表
为了赶项目进度这里直接安装的vue-cli脚手架,不会搭建vue项目的可以看我的两外两个文章:
Vue 2.x + Webpack 4.x的那些事---萌新必备
Vue-cli+Element+Less开发
接下来把在项目开发的过程中遇到的几个坑和几个vue开发小技巧分享给大家:
一、 rem布局
所谓rem布局就是一种弹性布局--等比缩放,就是开发之前,先考虑好移动端的适配问题,因为移动端会出现各种尺寸,不能尺寸大小都一样
//假设一个比例,然后根据这个比例和我们的UI设计图计算出我们想要的适配方案
// 首先在css全局里面写入:
html{
font-size: 20px;
}
// js全局写入:
var rem = 20; // 设 全局的html的font-size默认是20px
;(function(){
// 320: 设备大小 原比例1:1 宽度320px
// 假设1px = (20/320)rem 即1rem = 20px
// 实际根据UI设计图 如果设计图大小宽度为640 那么640/320=2 所以设计图是2倍图
// 综上所述 2倍图应该是20*2=40px 即实际1rem = 40px
// 所以在设计图量的尺寸(像素)/40就是实际要在页面写入的rem exp: 测量设计图中的某一div宽度为80px 经过计算80/40=2 那么写入样式就应该是width: 2rem;
rem = 20/320*document.documentElement.clientWidth; // 20/320 = 实际字体大小/实际设备宽度
document.documentElement.style.fontSize = rem+'px';
window.onresize = function(){
rem = 20/320*document.documentElement.clientWidth;
document.documentElement.style.fontSize = rem+'px';
};
})();
二、 代理
在和后台做交互数据的过程中,避免不了出现不同端口或者不同域名,这个时候我们就要做代理,首先vue提倡的交互工具是axios这里就不多说了,至于为什么技术栈里会涉及到vue-axios
,是为了更好地适配vue而做的
// 首先在全局引入:
import axios from 'axios';
import VueAxios from 'vue-axios';
Vue.use(VueAxios,axios);
在config文件夹下有一个index.js,该文件就是webpack的配置,里面有一个proxyTable
对象,将下面的代码放进去:
'/api': { //将映射为/api
target: 'http://192.168.0.242:8080/', // 后台的接口域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, //是否跨域
pathRewrite: { // 如果接口本身没有/api需要通过pathRewrite来重写地址
'^/api': ''
}
}
在vue中这样交互:
// 在代码里通过axios和后台进行交互:
var url = '/api/wechatRegister/sendCodeToMoile.htm' // 原地址为: http://192.168.0.242:8080/wechatRegister/sendCodeToMoile.htm
var paramsObj = {
mobile: this.phoneNumber
}
this.axios.get(url, {
params: paramsObj
}).then((res) => {
// coding...
}).catch((response) => {
console.log(response)
})
这样就可以完成代理进行交互了,但是在你之后打包项目到生产环境的时候你会发现并无法访问;这里有几个坑需要填一下:
三、 webpack配置问题(打包之后可能会出现的问题)
之前项目遇到过的一些问题:
配置vue+webpack踩过的坑
Vue中使用锚点定位跳转失效
vue中使用监听器(watch)需要注意的问题
vue项目样式中的scoped属性
vue+webpack 绑定src找不到图片报404
以下是本次项目中遇到的一些问题:
(1)路径问题
- 如果项目中采用了反向代理Nginx的话,那么config/index.js配置中的build下的
assetsPublicPath
一定要用./
(/
代表域名的根目录,./
代表当前路径) - 如果打包之后背景图片读取不到,就在
build/utils.js
下添加一句代码publicPath: '../../'
,像这样:
return ExtractTextPlugin.extract({
use: loaders,
fallback: 'vue-style-loader',
publicPath: '../../'
})
(2) 代理的交互接口请求不到
发现所有请求都失效报错了,在network下查看地址都错了,这是因为开发环境和生产环境不一样,地址需要根据环境变化的,所以我的解决方案是定义一个全局变量挂载在vue的实例上,该全局变量通过识别生产或是开发模式来给webpack的代理一个值,再将该变量写入每一个接口中:
- plugins/global.js 自定义的一个全局js文件:
//项目域名地址
const url = 'http://192.168.0.22:8083';
let ROOT;
//由于封装的axios请求中,会将ROOT打包进去,为了方便之后不再更改,判断了当前环境,而生成的不同的ROOT
if (process.env.NODE_ENV == 'development') {
//开发环境下的代理地址,解决本地跨域跨域,配置在config目录下的index.js dev.proxyTable中
ROOT = "/api"
} else {
//生产环境下的地址
ROOT = url;
}
exports.PROXYROOT = url; //代理指向地址
exports.ROOT = ROOT;
- config/index.js webpack配置文件:
const config = require('../src/plugins/global');
proxyTable: {
[config.ROOT]: {
target: config.PROXYROOT,
secure: false,
changeOrigin: true,
pathRewrite: {
['^'+config.ROOT]: ''
}
}
}
- main.js 入口文件:
const variable = require('@/plugins/global');
Vue.prototype.api_config = variable.PROXYROOT;
- vue交互代码:
var url = this.api_config + '/wechatRegister/sendCodeToMoile.htm'
var paramsObj = {
mobile: this.phoneNumber
}
this.axios.get(url, {
params: paramsObj
}).then((res) => {
// coding...
}).catch((response) => {
console.log(response)
})
(3)项目中的icon处理
打包之后的一些小icon图标vue都会以base64打包掉,而不会打包在assets下以图片形式展示的
(4)慎用eslint规范
如果没有那么了解eslint编码规范的话,就把build下的webpack.base.conf.js中的这块代码注释掉,像这样:
const createLintingRule = () => ({
// test: /\.(js|vue)$/,
// loader: 'eslint-loader',
// enforce: 'pre',
// include: [resolve('src'), resolve('test')],
// options: {
// formatter: require('eslint-friendly-formatter'),
// emitWarning: !config.dev.showEslintErrorsInOverlay
// }
})
用了这个的话,一些编码不规范,vue就会报错
(5)npm包管理
在项目中安装一些npm依赖包的时候,会被分成两组对象dependencies
和devDependencies
;
-
dependencies
是生产环境需要的包,通过npm install -S
命令进行安装 -
devDependencies
是开发环境需要的包,通过npm install -D
命令进行安装,生产环境就不需要了
所以在我们代码里面使用的一些功能性插件和UI组件一定要安装在dependencies
中,否则在生产环境功能就会失效
(6)jquery或$ is not defined
如果想在vue中引入jquery,那么它的正确使用方式是这样的:
- 安装jquery:
npm i jquery -S
- 在webpack里面配置一下 build/webpack.base.conf.js:
// 在文件最顶端如果没引入webpack的话引入一下
const webpack = require("webpack")
plugins: [
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery"
})
],
然后无需引入,在vue代码中直接使用即可 exp: $('#app')
(7)vux的webpack配置(这里特别注意不是vuex是vux UI框架)
使用了vux这个组件的朋友可以看一下,安装引入之后,官方也说了,还需要在webpack配置一下,前面的步骤官方都写的很明白,只是最后一步配置问题有很多伙伴不会,这里我也遇到了坑,所以记录了一下:
在build/webpack.base.conf.js下:
// 引入vux的loader
const vuxLoader = require('vux-loader')
module.exports = vuxLoader.merge(webpackConfig, {
plugins: [
'vux-ui'
]
})
四、 小技巧
(1)双击事件实现方式
双击事件可以通过短时间内的两个单击事件来实现,就是给单击事件加一个定时器,写一个计算点击的次数,根据点击的次数来达到双击的效果:
this.count ++; // 初始值设为0
let timer = setTimeout(() => {
if (this.count == 2) {
console.log('双击');
}
this.count = 0;
clearTimeout(timer);
}, 300);
(2)touchstart和click事件冲突
这两个事件如果同时使用的时候,可能会引发冲突,即touchstart好使,click失效,这个问题来自于touchstart下阻止了默认事件,导致click失效,需要去掉touchstart身上的取消默认事件方法或属性
(3)微信公众号更改文章标题
在配置全局路由的时候,加一个meta对象,在meta对象里面写一个title,该title就是我们微信公众号文章上的title,在配置一个全局路由守卫,将路由中的title赋值给DOM的title:
import Vue from 'vue';
import Router from 'vue-router';
// 登录
const Login = r => require.ensure([], () => r(require('@/components/login/Login.vue')), 'Login');
// 注册
const Register = r => require.ensure([], () => r(require('@/components/register/Register.vue')), 'Register');
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
redirect: '/login'
}, {
path: '/login',
name: 'Login',
component: Login,
meta: {
title: '用户登录'
}
}, {
path: '/register',
name: 'Register',
component: Register,
meta: {
title: '用户注册'
}
}
]
});
// 全局路由守卫
router.beforeEach((to, from, next) => {
document.title = to.meta.title;
next();
});
export default router;
(4)微信自定义分享
- 安装依赖包
npm install weixin-js-sdk
- 引入微信插件
import wx from 'weixin-js-sdk';
- 全局微信分享配置
我在全局引入了该方法,并且封装在了vue下:
Vue.prototype.$wxConfig = function(appId, timestamp, nonceStr, signature) {
// 微信分享功能的配置项 全局引入
wx.config({
debug: false,
appId: appId, // 和获取Ticke的必须一样------必填,公众号的唯一标识
timestamp: timestamp, // 必填,生成签名的时间戳
nonceStr: nonceStr, // 必填,生成签名的随机串
signature: signature, // 必填,签名
// 需要分享的列表项:分享给朋友/QQ好友, 分享到朋友圈/QQ空间
jsApiList: [
'onMenuShareAppMessage',
'onMenuShareTimeline',
'onMenuShareQQ',
'onMenuShareQZone',
'updateAppMessageShareData',
'updateTimelineShareData'
]
});
}
在app.vue中使用:
通过axios请求拿到了这四个属性值,然后调用传参:
this.$wxConfig(this.appId, this.timestamp, this.nonceStr, this.signature);
再封装一个可定义的分享插件:
Vue.prototype.$wechat = function(title, link, imgUrl, desc) {
// 微信分享功能分享之后的信息
// 处理验证失败的信息
wx.error(function (res) {
console.log('验证失败返回的信息:', res)
});
// 处理验证成功的信息
wx.ready(function () {
var shareData = {
title: title, // 分享标题
desc: desc, // 分享描述 这里请特别注意是要去除html
link: link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致window.location.href.split('#')[0]
imgUrl: imgUrl, // 分享图标
success: function (res) {
// 用户确认分享后执行的回调函数
console.log('用户确认分享后执行的回调函数:', res)
},
cancel: function (res) {
// 用户取消分享后执行的回调函数
console.log('取消分享到朋友圈返回的信息为:', res)
}
};
if(wx.onMenuShareAppMessage){ //微信文档中提到这两个接口即将弃用,故判断
wx.onMenuShareAppMessage(shareData); //1.0 分享到朋友
wx.onMenuShareTimeline(shareData); //1.0 分享到朋友圈
wx.onMenuShareQQ(shareData); //1.0 分享到QQ
wx.onMenuShareQZone(shareData); //1.0 分享到QQ空间
}else{
wx.updateAppMessageShareData(shareData); //1.4 分享给朋友/QQ好友
wx.updateTimelineShareData(shareData); //1.4 分享到朋友圈/QQ空间
}
})
}
然后再每个页面初始化的时候调用上面的这个方法:
created () {
this.$wechat('公众号文章标题', 'http://www.baidu.com/', '图片', '公众号文章信息')
}
五、 兼容性问题
由于Android和IOS的系统以及浏览器内核不同等,许多东西无法适配兼容,这里提供一个方案,可以判断两者将其分开来写:
- 封装好一个判断设备的插件
Vue.prototype.$is_android = function() {
// 判断当前访问的设备是安卓还是IOS
var userAgent = navigator.userAgent;
var isAndroid = userAgent.indexOf('Android') > -1 || userAgent.indexOf('Linux') > -1; // Android设备
var isIOS = !!userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); // IOS设备
if (isAndroid) {
return true;
}
if (isIOS) {
return false;
}
}
- To use
let isAndroid = this.$is_android;
if (isAndroid) {
// Android设备访问
// coding...
} else {
// IOS设备访问
// coding...
}
虽然chrome浏览器自带模拟机模拟移动端,但是毕竟是模拟机,内核什么的都是不同的,所以真实效果还是要在真机测试的,其中好多新的css3或h5的写法在IOS下不兼容,测试出以下bug:
(1)css3背景
Android写法(IOS下失效):
background: url('../images/icon/icon-right.png') center no-repeat / 100%;
IOS下需改成:
background: url('../images/icon/icon-right.png') center no-repeat;
background-size: 100%;
(2)flex布局
使用flex布局的时候需要注意一下
justify-content: space-evenly;
在IOS下失效,要么使用space-around
替代,要么换其他方式
(3)fixed属性在IOS软键盘弹出后失效
(4)安卓上点击输入框弹出软键盘后盖住一部分背景
首先全屏背景最外层盒子不可以设置overflow:hidden;
,否则点击页面底部的输入框弹出软键盘之后会出现
被键盘盖住,无法边看边输入,页面不能实时上下拖动,这是第一点;
第二点是本来整个页面高度是100%的,结果弹出软键盘之后,页面的高度就变成了100%-软键盘的高度,所以这个时候我们要事先一开始进入项目就存一个可视区高到vuex里面,然后绑定一个行内样式style,再判断之前存的可视区高和当前的可视区高给页面高度赋值来解决这个问题:
// 给最外层盒子绑定一个行内高
:style="{ height: bodyHeight }"
// 挂载的时候进行修改
mounted(){
if (document.documentElement.clientHeight < this.$store.state.clientHeight) {
this.bodyHeight = '100%';
} else {
this.bodyHeight = document.documentElement.clientHeight + 'px';
}
}
(5)Android和IOS上h5视频的一些问题
由于在一部分路由的子路由里面使用video的话,IOS全屏播放再退出全屏的时候会出现闪退的情况,网上有这么一个属性webkit-playsinline="true"
和playsinline="true"
设置上之后会禁止全屏播放,但事实并非如此,设置上之后一点效果也没有,说是下面还需要给webview设置一个``,这个是在IOS官方API中提到的,让我来说说为什么在视频上会出现这么个问题:
问题出现的原因:
虽然都是移动端微信访问视频,但是安卓应用中的采用的是QQ浏览器中播放视频,而苹果采用的是苹果自带的播放器
问题所涉及的内容:
- 安卓上的微信播放视频video:
点击页面上的视频进行播放,播放完了再回到页面中,会出现视频在文本最上层,页面中的所有内容不管是什么定位都会被盖在视频的下面,有一个属性x5-video-orientation
可以兼容android播放,让video视频不再是最上层,变成可控制的,但是会出现新的问题,点击视频之后可以听到声音但是没有图像显示,一片黑,必须点击横向播放按钮才可以正常播放,如果用了富文本编辑器插件问题会暴露的更多 - 苹果上的微信播放视频video:
正常怎么点击播放都没问题,但是在一部分路由的子路由里面使用video的话,IOS全屏播放再退出全屏的时候会出现闪退的情况
我的解决方案:
通过原生js对video进行控制,一般业务都需要全屏,那我们就不要全屏,就对一些就简单的功能入手,网上有原生video的一些事件方法,可以通过这些事件对video进行控制,像这样:
// result是从后台获取到的数据 里面包含iframe这样的视频
this.content = result.content.replace(/iframe/g, 'video');
this.$nextTick(()=>{
$('video').attr('controls', 'controls');
})
如果不是iframe的视频,而是h5的video,就是下面这样:
var aVideo = document.getElementsByTagName('video');
for (let i = 0; i < aVideo.length; i++) {
aVideo[i].onclick = function(){
if (this.paused) {
this.play();
} else {
this.pause();
}
};
}
(6)在IOS上弹出框显示域名的问题
// alert() 和 confirm()
六、 项目前后的准备
在开发项目前需要了解一下需求以及需要哪些插件,提前找好,当然UI组件之类的可以提前找个差不多,但是插件的话找到了可能最后也会因为一些原因被淘汰掉,这里我先说一下如何选择一款适合自己项目的UI;
vue的UI库非常多,但是要找准适合自己项目的:
- Cube UI:滴滴的基于 Vue.js 实现的精致移动端组件库。 stars:6k
- Muse-UI:基于 Vue2.0 开发的组件库。 stars:7k
- Element:饿了么前端开发的基于 Vue 2.0 的桌面端组件库。 stars:38k
- Mint UI:饿了么前端开发的基于 Vue 2.0 的移动端组件库。 stars:14k
- vue-element-admin:是一个后台前端解决方案,它基于 vue 和 element-ui实现。 stars:35k
- uni-app:是一个使用 Vue.js 开发跨平台应用的前端框架。 stars:8k
- SUI Mobile:由阿里巴巴国际UED前端出品的移动端UI库,是一套基于 Framework7 开发的UI库,并且能兼容到 iOS 6.0+ 和 Android 4.0+,非常适合开发跨平台Web App。 stars:6k
- WeUI:是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。 stars:22k
- MUI:最接近原生APP体验的高性能前端框架。 stars:11k
以上就是我所知道的所有vue的UI库,以及他们是干什么的详细介绍也展现给了大家,希望有帮助。
如果您想自己写更多特效或者好看的字体库,那么这里有两个推荐:
- Animate.css:一个CSS动画的跨浏览器库。 stars:60k
- Awesome-vue:一套绝佳的图标字体库和CSS框架。 stars:46k
项目中有富文本编辑器和截图的需求,因此找了几种插件:
富文本编辑器:
- Vue-Quill-Editor:基于 Quill、适用于 Vue 的富文本编辑器,这款可以用在移动端,是比较好用的一个。 stars:4k
- wangEditor:基于javascript和css开发的 Web富文本编辑器,只适用在PC端,移动端会出现很多问题。 stars:7.7k
- Vue2Editor:用于使用Vue.js和Quill.js构建的富文本编辑,这款没用过,个人感觉既然适配了vue,就应该没啥大问题。 stars:1.5k
关于Vue-Quill-Editor在vue中的详细使用方式请戳这里;期间还用过两款,不过让我pass了,bug漏洞百出,问题很多;在使用前两个编辑器的时候不会的可以问我,会的一定帮助大家
截图工具:
- vue-cropper:该插件特别灵活使用特别简单,还支持vue。
在开发过程中有很多问题需要总结,会逐渐完善代码,如果有什么问题的小伙伴们可以在这里给我留言哈,对大家有帮助的内容可以提供下,一起学习共同进步
如果喜欢本文的话单击爱心加关注谢谢O(∩_∩)O~
欢迎访问我的GitHub,喜欢的可以star,项目随意fork,支持转载但要下标注