先来看一波效果图css
初始化项目html
一、在安装了node.js的前提下,使用如下命令vue
npm install --g vue-clinode
二、在将要构建项目的目录下android
vue init webpack myproject(项目目录名称)webpack
一路回车以下ios
中间会让选择ESLint进行项目代码风格检查,为了美观和效率,能够开起来,vue-router用起来,红框框中的两个测试,不要也罢,后面是问要使用哪一个进行install依赖包,默认npm好了;而后回车,等待下载依赖;慢的话能够用镜像。nginx
下载完成以后会看到以下提示:git
按照步骤往下走就行了github
接下来在浏览器里输入localhost:8080,就能够进入到vue的世界了
只有这些还不够,这距离一个响应式的app框架还差好多,接下来就正式搭建一个移动端的项目吧。
首先咱们来看一下刚构建好的vue的项目结构
能够发现项目中有assets和static两个文件夹可存放静态文件,那岂不是冲突了?其实否则,assets中存放的静态文件是会通过webpack处理的,通常放一些图片之类的静态资源,而static则不会收到webpack的影响,调用的时候也是经过绝对路径调用的,一般用来存放一些第三方的静态资源库。
此项目将基于vue-cli的项目目录进行改造,使其集成vue-router、vuex、axios,并且能够自动适配移动端大小。
在开始写代码以前,先说一下ESlint警告和报错,能够选择性修改校验规则,点击参考修改,也可使用 /* eslint-disable */ 选择性忽略校验
路由(vue-router)
一个项目的路由是一个项目的基础,咱们先从路由开始,在刚一开始初始化项目的时候,vue-router就被引进项目里来了,上面的图片里在src\router里面放的就是路由配置文件,按照我的习惯我将对上面的目录结构进行调整,以下
新建page目录存放主逻辑页面,components存放公共组件,router统一管理路由
如图引入新的页面,路由跳转可经过this.$router.push('/Home')
路由vue-cli都给封装的差很少了,倒也没什么要大改的地方,接下来来看下vuex
vuex(状态管理模块)
关于vuex的介绍官网也给了比较详细的介绍,对其做用不太了解的话能够参考什么是vuex,这里只说怎么集成在项目里面,而且简单介绍其用法
(1)安装vuex
npm install vuex --save
(2)配置vuex
根据我的开发习惯,项目中vuex的配置也不相同,不过大致都差很少,也有差异大的地方,比方说官网推荐在actions里写异步操做改变state状态,可是我仍是比较喜欢将请求数据等异步操做放在store外面操做,在经过commit去改变状态,具体将会在下面的数据请求模块的封装里提到
废话很少说了,看下面图片
首先建立了一个状态的文件夹,用于管理整个状态;在modules里面分开来写各个模块的状态,以下
1 /** 2 * home.js 3 * 用于home模块的状态管理 4 */ 5 import * as types from '../mutation-type' // 引入定义的方法 6 const home = { 7 state: { 8 number: 1 9 }, 10 mutations: { 11 [types.SET_NUM](state, num) { // 修改state 可经过mapMutations调用 12 state.number = num 13 } 14 }, 15 actions: {}, 16 getters: { // 定义getters,能够经过mapGetters拓展函数调用 17 number: state => { 18 return state.number 19 } 20 } 21 } 22 export default home // 输出home模块
mutation-type定义了一些修改state的方法,以下
在index.js统一输出,以下
1 import Vue from 'vue' 2 import Vuex from 'vuex' 3 import home from './modules/home' 4 import createLogger from 'vuex/dist/logger' 5 6 Vue.use(Vuex) 7 const debug = true 8 9 export default new Vuex.Store({ 10 modules: { 11 home 12 }, 13 plugins: debug ? [createLogger()] : [] // 是否开启vuex的debug模式 14 })
这里用到了一个vuex的内置插件,如上图,开启以后状态的每次改变均可以在console里面查看修改信息以下图
这里的index配置好以后就是要在main.js里注册一下
经过以上几步设置,就能够在项目里面使用状态了,这里以home.vue为例,看下面代码
1 import {mapMutations, mapGetters, mapState} from 'vuex' // 引入map方法 2 export default { 3 data () { 4 return { 5 num: 0 6 } 7 }, 8 methods: { 9 ...mapMutations({ // 调用setNum方法 10 setNum: 'SET_NUM' 11 }), 12 increase() { 13 this.num++ 14 this.setNum(this.num) // 将this.num转入setNum 15 } 16 }, 17 computed: { 18 // ...mapGetters([ // 经过getters获取state数据 19 // 'number' 20 // ]), 21 ...mapState({ // 经过state获取state数据 22 number: state => state.home.number 23 }) 24 }
到这里vuex的引入就结束了,下面来继续看数据请求模块(axios)
axios(数据请求模块)
以前vue数据请求模块用的是vue-resource,官方不推荐,弃之;说下axios的集成步骤,以及须要注意的一些地方
(1)安装axios和js-cookie
npm install axios --save
(2)配置axios
在src目录下面新建apiconfig文件夹,用来封装请求和定义一些关于请求的全局变量;同时建立api文件夹,用来分别声明各个模块的请求方法,以下图
先来看apiconfig里的公共封装部分;这里会对请求作如下处理
下面看代码
1 /* eslint-disable */ 2 import axios from 'axios' 3 4 /** 5 * 定义请求常量 6 * TIME_OUT、ERR_OK 7 */ 8 export const TIME_OUT = 1000; // 请求超时时间 9 export const ERR_OK = true; // 请求成功返回状态,字段和后台统一 10 export const baseUrl = process.env.BASE_URL // 引入全局url,定义在全局变量process.env中,开发环境为了方便转发,值为空字符串 11 12 // 请求超时时间 13 axios.defaults.timeout = TIME_OUT 14 15 // 封装请求拦截 16 axios.interceptors.request.use( 17 config => { 18 let token = localStorage.getItem('token') // 获取token 19 config.headers['Content-Type'] = 'application/json;charset=UTF-8' 20 config.headers['Authorization'] = '' 21 if(token != null){ // 若是token不为null,不然传token给后台 22 config.headers['Authorization'] = token 23 } 24 return config 25 }, 26 error => { 27 return Promise.reject(error) 28 } 29 ) 30 // 封装响应拦截,判断token是否过时 31 axios.interceptors.response.use( 32 response => { 33 let {data} = response 34 if (data.message === 'token failure!') { // 若是后台返回的错误标识为token过时,则从新登陆 35 localStorage.removeItem('token') // token过时,移除token 36 // 进行从新登陆操做 37 } else { 38 return Promise.resolve(response) 39 } 40 }, 41 error => { 42 return Promise.reject(error) 43 } 44 ) 45 // 封装post请求 46 export function fetch(requestUrl, params = '') { 47 return axios({ 48 url: requestUrl, 49 method: 'post', 50 data: { 51 'body': params 52 } 53 }) 54 }
以上代码以post请求为例,对请求进行公共封装,而且定义了一些常量以供请求使用,另外分别对请求和响应进行了拦截,方便在请求或者数据返回时,对数据进行统一处理,具体在代码的注释里均可以看到,下面就以登陆为例,对封装的请求方法进行调用。
下面来看api模块部分,以home-api为例,看代码
1 /** 2 * 引入fetch、baseUrl 3 * @param params 4 * @returns {*} 5 */ 6 import {fetch, baseUrl} from 'config/index' 7 // 登陆接口 8 export function loginUserNo(params) { 9 return fetch(`${baseUrl}/root/login/checkMemberLogin`, params) 10 }
在文件里引入fetch方法和baseUrl,这里为何能够简写成'config/index'呢,须要在'build/webpack.base.conf.js'里添加如下代码,后面引入api同理
这里export登陆方法loginUserNo以后,就能够在组件里面使用这个登陆方法了,以下代码
1 import * as homeApi from 'api/home-api' // 引入api 2 import { ERR_OK } from 'config/index' // 引入请求成功状态 3 // 请求方法 4 login() { 5 let params = { 6 password: '*******', 7 storeNo: '', 8 userName: '*********' 9 } 10 homeApi.loginUserNo(params).then((res) => { 11 let {data} = res 12 if (data.success === ERR_OK) { 13 // 请求成功操做,存储token 14 localStorage.setItem('token', data.value.token) 15 } else { 16 } 17 }).catch(() => { 18 }) 19 } 20 }
在点击登陆以后执行登陆方法,就能够调用请求方法了,可是这里还有一个问题
关于数据请求,避不开的一个老生常谈的问题就是跨域,一样的上面点击登陆也会涉及到跨域没法请求的问题,不过好在vue-cli里面已经配置了解决跨域问题的模块,咱们能够在config/index.js里面配置如下要代理的地址,以下图
将以root开头的api转发出去,将地址指向接口地址,这样就解决了跨域的问题。
到此,vue全家桶的引入及应用就基本完成了,可是到目前为止这个项目还只能进行简单的路由跳转、状态存储以及数据请求,而咱们的目标是一个移动端应用框架,接下来咱们还要解决以下几个问题
下面咱们就先从移动端适配问题入手
项目的适配
由于移动端设备屏幕大小,屏幕比例什么的差异比较大,因此移动端项目的适配问题就显得尤其重要,这里咱们主要使用flexible.js进行适配,关于flexible.js,不懂得话能够点这里,这里咱们以最经常使用的750*1334的尺寸为例
引入flexible.js,在main.js里引入flexible.js文件,可将flexible.js做为静态文件放在最外层static文件夹里引入,以下图
使用less做为css预处理器,首先安装less
(1)安装less和less-loader
npm install less less-loader --save-dev
(2)配置less
在build/webpack.base.conf.js 的module.exports.module.rules 里面添加
1 { 2 test: /\.less$/, 3 loader: 'style-loader!css-loader!less-loader' 4 },
而后在组件里面使用的时候,在style标签上加上 lang="less",就能够正常的使用less了,这里咱们来引入几个初始化项目的less文件,在src下面建立styles文件夹,放入如下文件
在每一个组件里的style标签里引入index.less和variable.less
1 <style scoped lang="less"> 2 @import "~styles/index.less"; 3 @import "~styles/variable.less"; 4 .hello{ 5 h1{ 6 color: red; 7 .fs(38); // mixin里数字大小函数 8 } 9 } 10 </style>
而后上面写关于像素的样式的时候,都在mixin.less定义下,就能够实现对全部移动端的适配问题。
移动端页面切换及切换动画
此处将切换动画单独拿出来讲如下,做为移动端通常要实现的需求是,第一级菜单切换不须要转场动画,第一级菜单向第二级菜单转场时须要过渡动画;针对这一需求提供如下解决方案。
须要用到动画的话确定会用到vue的transition,不熟悉的话能够看这里,这里实现动画的解决方案是判断要执行路由的方向,以下代码,在路由配置文件里定义路由的方法
1 // 须要左方向动画的路由用this.$router.to('****') 2 Router.prototype.togo = function (path) { 3 this.isleft = true 4 this.isright = false 5 this.push(path) 6 } 7 // 须要右方向动画的路由用this.$router.goRight('****') 8 Router.prototype.goRight = function (path) { 9 this.isright = true 10 this.isleft = false 11 this.push(path) 12 } 13 // 须要返回按钮动画的路由用this.$router.goBack(),返回上一个路由 14 Router.prototype.goBack = function () { 15 this.isright = true 16 this.isleft = false 17 this.go(-1) 18 } 19 // 点击浏览器返回按钮执行,此时不须要路由回退 20 Router.prototype.togoback = function () { 21 this.isright = true 22 this.isleft = false 23 }
上面在执行路由跳转的时候,在App.vue里面判断滑动的方向,来指定动画的方向,不须要动画的话,能够直接使用this.$router.push('****'),下面是App.vue里处理的动画代码
<template> <div id="app"> <transition :name="transitionName"> <router-view class="Router"></router-view> </transition> </div> </template> <script> export default { name: 'App', data() { return { transitionName: 'slideleft' } }, watch: { $route() { // 监听路由变化从新赋值 if (this.$router.isleft) { this.transitionName = 'slideleft' } if (this.$router.isright) { this.transitionName = 'slideright' } } } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; } .Router { position: absolute; top: 0; left: 0; right: 0; width: 100%; height: 100%; transition: all .5s ease; -webkit-transition: all .5s ease; -moz-transition: all .5s ease; } .slideleft-enter, .slideright-leave-active { opacity: 0; -webkit-transform: translate(100%, 0); transform: translate(100%, 0); } .slideleft-leave-active, .slideright-enter { opacity: 0; -webkit-transform: translate(-100%, 0); transform: translate(-100%, 0); } </style>
在组件中使用的话则使用
1 this.$router.goBack() // 返回 2 this.$router.to('****') // 进入到详情
还有一步,就是监听点击浏览器返回按钮,在main.js里写以下代码
1 window.addEventListener('popstate', function(e) { 2 router.togoback() // router已经在上面import进来 3 }, false)
移动端UI框架选择
做为移动端项目,上面步骤其实已经算完善了,可是每每会遇到项目工期紧,或者缺乏人手的时候,这个时候引入一个移动端的UI就如虎添翼了,不用本身去封装一些ui组件了,这里使用mint-ui,优势可自行搜索,这里讲一下对mint-ui的引入。
(1)安装mint-ui
npm install mint-ui --save
(2)引入mint-ui
在main.js里引入mint-ui
1 import Mint from 'mint-ui' 2 import 'mint-ui/lib/style.css' // 引入css 3 Vue.use(Mint) // 全局使用
这样就能够在整个vue项目里面使用mint-ui的组件了。
打包
打包遇到的一些问题
(1)打包以后在ios上点击元素会闪出来一个半透明的灰色框,这里须要加一句css作下兼容-webkit-tap-highlight-color:rgba(0,0,0,0); 放入#app的css里
(2)点击事件右300ms的延迟,可采用fastclick.js解决,参考如下代码
1 npm install fastclick --save 2 3 // 在main.js引入 4 import FastClick from 'fastclick' 5 FastClick.attach(document.body)
打包注意事项
若是将项目打包用于移动端浏览器,则直接打包,不须要更改其它的东西,在包以后上传至服务器,使用nginx作下接口转发便可
若是想将打包的静态文件进一步打包成移动端应用,则须要修改如下config/index.js
在config/prod.env.js新增baseUrl
打包成app以后,移动端不会存在跨域问题。
写在最后
上面项目纯属我的搭建,适用于移动端项目,包括浏览器端,微信公众号以及打包以后的android,ios应用,目前还存在一些不足的地方,不过基本功能能够正常使用,具体的代码,若有须要可在个人github中下载使用,若是以为对你有用,请给我点赞,若有修改建议,请提出。
项目地址:https://github.com/MrKaKaluote/vue-mobile.git
项目新增了mock功能,具体看这里:vue项目配置Mock.js