vue-cli 安装及 实战项目
项目地址: https://github.com/maxiangsai/vue-demo
1、vue-cli 安装(默认你已有node.js环境)
npm install vue-cli -g
2、创建一个以webpack为模板的项目(一直yes就会有单元测试,eslint代码检查等,我的是默认vue init webpack后,一直enter)--我的根目录为m86xq
vue init webpack
npm install
npm install vuex vue-resource mint-ui --save (本项目用到的依赖,vuex管理应用程序的状态;vue-resource为vue的ajax库,现在推荐使用axios;mint-ui为饿了么的移动端ui框架)
npm install sass-loader node-sass --save-dev (vue文件用到sass,需要的依赖)
3、运行项目
npm run dev
4、浏览器自动打开后可以看到vue.js的界面,则运行成功
5、目录结构(由于项目是我做好了一部分的,所以有些文件是你们没有的,后面会说到)
src/store目录下的是vuex的文件;
src/sass目录为各个组件的sass样式以及定义的变量,mixins等
router目录为项目路由设置
static/fonts为移动端的字体图标
static/css为页面初始化css(reset.css),我这是通过index.html直接写在head里
data.json是自定义的假数据
6、main.js
import Vue from 'vue'
import App from './App'
import VueResource from 'vue-resource'
import router from './router'
import store from './store'
import MintUi from 'mint-ui'
import 'mint-ui/lib/style.css'
import '../static/css/font-awesome.min.css'
Vue.use(MintUi)
Vue.use(VueResource)
/* eslint-disable no-new */
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
a、import为es6的语法,将模块导入进来,在main.js导入的,在全局都能使用
b、外部的一些库,例如mint-ui, vue-resource导入进来后,都需要通过Vue.use()使用
c、创建一个vue实例,挂载路由,数据启动(还不能运行,还要App.vue)
7、router/index.js
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import Index from 'components/index'
import Home from 'components/home'
import Order from 'components/order'
export default new Router({
routes: [
{
path: '/index',
component: Index,
children: [
{
path: '',
component: Home,
meta: { hideHeader: true }
},
{
path: 'order',
component: Order,
meta: { hideHeader: false }
}
]
},
{
path: '/',
redirect: '/index'
}
]
})
8、App.vue
<template>
<div id="app">
<v-menu></v-menu>
<router-view></router-view>
</div>
</template>
<script>
import menu from 'components/menu'
export default {
components: {
vMenu: menu
}
}
</script>
<style lang="scss">
.menu {
transition: all 0.2s cubic-bezier(0.55, 0, 0.1, 1);
}
.slide-left-enter, .slide-right-leave-active {
opacity: 0;
-webkit-transform: translate(30px, 0);
transform: translate(30px, 0);
}
.slide-left-leave-active, .slide-right-enter {
opacity: 0;
-webkit-transform: translate(-30px, 0);
transform: translate(-30px, 0);
}
</style>
9、index.vue(作为滑动外层)
<template>
<transition :name="transitionName">
<router-view class="view"></router-view>
</transition>
</template>
<script>
export default {
name: 'index',
data () {
return {
transitionName: 'slide-left'
}
},
watch: {
'$route': function (to, from) {
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
}
</script>
<style lang="scss" scoped>
.view{
transition: all .2s ease;
}
</style>
10、home.vue
<template>
<div class="home">
<mt-header>
<router-link to="/" slot="left" class="logo"></router-link>
</mt-header>
<!-- icon -->
<ul class="service_layer">
<li v-for="(item, index) in service_a" :class="item.cln">
<a :href='item.link' @click="showIndex(index)">
<div class="icon"><img :src="item.imgUrl"></div>
<p class="service_txt">{{ item.name }}</p>
</a>
</li>
</ul>
<!-- 广告 -->
<div class="adv_layer" :class="{ 'hide': !advStatus }">
<span class="fa fa-close" @click="advClose"></span>
<a class="adv_inner" href="http://mall.866xq.com/g_43">
<img src="http://img.mall.8666.com/wxshop20170213/5ac796e10c.jpg">
<p class="adv_title">
<span class="text">情人节尊享——买女士养颜谷花香送58元999大罐甘肃特级玫瑰
<small>(小区特供)</small>
</span>
<span class="adv_btn">马上抢购</span>
</p>
</a>
</div>
<div class="bar-title">中海誉城</div>
<div class="residents_layer">
<span class="residents_tit"><i></i>小区已入住 6398 人</span>
<span class="share_btn">马上邀请邻居入住</span>
</div>
<!-- 选择时间与方向 -->
<div class="line-type">
<span>时间</span>
<mt-button type="primary" size="small" :plain="lineDateType != 'workDay'" @click.native="lineDateTypeHandle('workDay')">工作日</mt-button>
<mt-button type="primary" size="small" :plain="lineDateType != 'weekend'" @click.native="lineDateTypeHandle('weekend')">周末</mt-button>
</div>
<div class="line-type">
<span>方向</span>
<mt-button type="primary" size="small" :plain="lineDirection !='go'" @click.native="lineDirectionHandle('go')">出门</mt-button>
<mt-button type="primary" size="small" :plain="lineDirection !='back'" @click.native="lineDirectionHandle('back')">回家</mt-button>
</div>
<ul class="route-list">
<li v-for="item in routeList">
<div class="title">{{ item.title }}</div>
<div class="content" v-for="con in item.items">
<div class="route-name">{{ con.name }}</div>
<div class="route">
<span class="beginPoint">{{ con.beginPoint }}</span> → <span class="endPoint">{{con.endPoint}}</span>
</div>
<div class="select-time">
<mt-button type="primary" size="small" plain v-for="itemTime in con.time"
@click.native="orderTo(con.name, itemTime)"
>
{{ itemTime }}
</mt-button>
</div>
<div class="price"><span>¥</span>{{ con.price }}</div>
</div>
</li>
</ul>
<footer>
<p>©2016 xq.xxxx.com</p>
<p>我司诚征PHP程序员,详情联系hr@xxxxx.com.cn</p>
</footer>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'Home',
data () {
return {
service_a: [
{ cln: 'serviceLi1', link: '/#/index', name: '88巴士', imgUrl: 'http://css.xxxx.com/xq/images/bus_w.png' },
{ cln: 'serviceLi2', link: '/#/my/ticket', name: '我的车票', imgUrl: 'http://css.xxxx.com/xq/images/wodechepiao_w.png' },
{ cln: 'serviceLi3', link: '/#/my', name: '我', imgUrl: 'http://css.xxxx.com/xq/images/usericon_w.png' }
],
routeList: []
}
},
computed: {
...mapGetters(['advStatus', 'lineDateType', 'lineDirection'])
},
created () {
this.$http.get('../../static/data.json', {foo: 'bar'}).then(response => {
response = response.body
this.routeList = response.routeList
})
},
methods: {
advClose () {
this.$store.dispatch('advClose')
},
lineDateTypeHandle (value) {
this.$store.dispatch('lineDateTypeSwitch', value)
},
lineDirectionHandle (value) {
this.$store.dispatch('lineDirectionSwitch', value)
},
orderTo (id, time) {
console.log('id: ' + id + 'time:' + time)
console.log(this.$store.state.order.lineDateType + ' ' + this.$store.state.order.lineDirection)
this.$router.push({ path: '/index/order' })
}
}
}
</script>
<style lang='scss' scoped>
@import '../sass/home';
</style>
11、menu.vue
<template>
<transition :name="transitionName">
<div class="menu" v-if="isMenu">
<div class="inner">
<a href="" class="avatar">
<img :src="user.image">
<div class="name">{{ user.name }}</div>
</a>
<mt-cell title="886巴士" to="/index" is-link v-if="nowRoute!='/index'"><i slot="icon" class='fa fa-home'></i></mt-cell>
<mt-cell title="我的车票" to="/my/ticket" is-link><i slot="icon" class='fa fa-ticket'></i></mt-cell>
<mt-cell title="我的优惠券" to="/my/coupons" is-link><i slot="icon" class='fa fa-gift'></i></mt-cell>
<mt-cell title="个人中心" to="/my" is-link v-if="nowRoute!='/my'"><i slot="icon" class='fa fa-gear'></i></mt-cell>
<mt-cell title="所有小区" to="/" is-link><i slot="icon" class='fa fa-map-signs'></i></mt-cell>
</div>
<div class="spaceMask" @click="menuToggle"></div>
</div>
</transition>
</template>
<script>
export default {
data () {
return {
isMenu: true,
transitionName: '',
user: {
name: '曾小闲',
image: 'http://wx.qlogo.cn/mmopen/C1SzxNakKfYbL3cb4FOlXWv4WZwd3jc7sevqQx4Ku89ibxmGfVGd9ophyu6dhG6jtPzWvj9VDTHP7YxjaToJ8dscB9icx026X8/0',
phone: '135******80',
credit: 50
},
nowRoute: this.$route.path
}
},
methods: {
menuToggle () {
this.isMenu = !this.isMenu
this.transitionName = this.isMenu ? 'slide-left' : 'slide-right'
}
}
}
</script>
<style lang="scss" scoped>
@import '../sass/menu'
</style>
12、order.vue
<template>
<div class="order-wrap">
<mt-header fixed title="购票">
<router-link to="/" slot="left">
<mt-button icon="back">返回</mt-button>
</router-link>
</mt-header>
<div class="overflow-scroll">
<div class="lineInfo-box">
<div class="carIndicator">上车点<br>指示图</div>
<!-- 单个起点/终点info块 -->
<div class="info-con">
<div class="title start">起点:岭南雅筑/中海誉城</div>
<ul class="routeDetail">
<li><span class="time">07:00</span><span class="routeInfo">[岭南雅筑]正门路边</span></li>
<li><span class="time"></span><span class="routeInfo">请在途经点到达时间前候车,仅供参考.)</span></li>
<li><span class="time">07:03</span><span class="routeInfo">[金色梦想]中海公寓公交站(靠中海别墅那边)</span></li>
<li><span class="time">07:05</span><span class="routeInfo">[中海誉城]洋城一路公交站(南苑扶梯大门对面)</span></li>
</ul>
</div>
<!-- 单个起点/终点info块 -->
<div class="info-con">
<div class="title end">终点:岭南雅筑/中海誉城</div>
<ul class="routeDetail">
<li><span class="time">07:00</span><span class="routeInfo">[岭南雅筑]正门路边</span></li>
<li><span class="time"></span><span class="routeInfo">请在途经点到达时间前候车,仅供参考.)</span></li>
<li><span class="time">07:03</span><span class="routeInfo">[金色梦想]中海公寓公交站(靠中海别墅那边)</span></li>
<li><span class="time">07:05</span><span class="routeInfo">[中海誉城]洋城一路公交站(南苑扶梯大门对面)</span></li>
</ul>
</div>
<ul class="carRules" v-for="item in rules">
<li>{{item}}</li>
</ul>
<div class="ticket_price">
<span class="price">¥14.00</span><span class="unit">/座</span>
</div>
</div>
<div class="warning lineInfo-box">
<p>如有更多疑问请联系<i class="fa fa-phone"></i><a href="tel:137-1936-7437">137-1936-7437</a></p>
<p>(班车正常情况准点从发车点发车,不接受申请让班车等待乘客,请提前候车。)</p>
</div>
</div>
<footer>
<div class="rule-confirm">
<label class="checkbox">
<input type="checkbox" v-model="agreement">
<i></i>
我已阅读
</label>
<a class="a_lnk" href="#/index/gtxz">购票退票规则</a>
</div>
<mt-button type="primary" size="large" :disabled="buttonDisabled" @click.native="orderPop">购票 ¥14.00/座</mt-button>
</footer>
</div>
</template>
<script>
import { Indicator } from 'mint-ui'
export default {
data () {
return {
agreement: true,
popupLoading: false,
rules: [
'* 占座即需要购票,不设站票,儿童购票票价不变;',
'* 车型、车牌号请购票后到【我的车票】页面查看;',
'* 请提前5分钟候车,认准车牌号上车,并对号入座;',
'* 发车前20分钟不可退票,如因个人原因误车恕不退票;',
'* 如需更改班次请尽早退票或者改签,把座位释放给其他乘客购买;',
'* 严抓逃票,非上车前购票请统一到首页底部补票窗口补票。'
]
}
},
computed: {
buttonDisabled: function () {
return !this.agreement || this.popupLoading
}
},
methods: {
orderPop () {
Indicator.open({
spinnerType: 'fading-circle'
})
}
}
}
</script>
<style lang="scss" scoped>
@import '../sass/order';
</style>
13、vuex的使用
目录结构:
index.js为集中所有状态导出去,其中state为数据源,getters为过滤数据源(相当于vue里边的computed),mutations为定义的方法,actions为提交mutations的方法(即调用mutations里的函数)
import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import adv from './modules/adv'
import order from './modules/order'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
adv,
order
},
getters,
mutations,
actions
})
mutation-types.js为定义的方法名
export const ADV_CLOSE = 'ADV_CLOSE' // 关闭广告
export const LINE_DATE_TYPE = 'LINE_DATE_TYPE' // 路线时间
export const LINE_DIRECTION = 'LINE_DIRECTION' // 路线方向
modules/adv.js为广告相关代码
import * as types from '../mutation-types'
const state = {
adv: {
data: '',
status: true
}
}
const getters = {
advStatus: state => state.adv.status
}
const mutations = {
[types.ADV_CLOSE] (state) {
state.adv.status = false
}
}
const actions = {
advClose ({commit}) {
commit(types.ADV_CLOSE)
}
}
export default {
state,
getters,
mutations,
actions
}
modules/order.js为选择车票的相关操作
import * as types from '../mutation-types'
const state = {
lineDateType: 'workDay',
lineDirection: 'go' //go表示去 back表示回程
}
const getters = {
lineDateType: state => state.lineDateType,
lineDirection: state => state.lineDirection
}
const mutations = {
[types.LINE_DATE_TYPE] (state, value) {
state.lineDateType = value === 'workDay' ? 'workDay' : 'weekend'
},
[types.LINE_DIRECTION] (state, value) {
state.lineDirection = value === 'go' ? 'go' : 'back'
}
}
const actions = {
lineDateTypeSwitch ({ commit }, value) {
commit(types.LINE_DATE_TYPE, value)
},
lineDirectionSwitch ({ commit }, value) {
commit(types.LINE_DIRECTION, value)
}
}
export default {
state,
getters,
mutations,
actions
}
总结:
1、主要难点在于vuex状态管理,对于首次运用mvvm的我来说,这是比较难理解的。现在看看其实很简单,就是把一些全局需要用到的,有影响的数据提取到vuex里边集中管理,这样当在a处修改数据时,b处能及时响应更新
2、由于vue.js是组件化,数据驱动思想,所以在任何时候,需要修改页面内容或者结构时,首先想到的应该是通过操作数据从而达到结构变化的目的。多处用到的可以提取出来作为组件复用
3、由于这边用到了vue-cli + webpack,所以在写scss时,浏览器兼容问题构建工具来处理