最近,MVC、MVVM框架愈加流行,固然必不可少的,我也趁着最近工做量小,學習了一個多星期的vue.js 2.0版本, 改造了一個購票項目,藉此分享下學習心得,但願對一部分人有所幫助,固然,有任何不對的地方還望各路大神指出批評,謝謝啦!css
vue.js是一種流行的mvvm模式的框架,其核心思想是 數據驅動 和 組件化,相比於Angular.js , vue.js提供了更加簡潔,更易理解的API, 使咱們可以快速上手開發。html
項目地址: https://github.com/maxiangsai/vue-demovue
一、vue-cli 安裝(默認你已有node.js環境)node
npm install vue-cli -g
二、建立一個以webpack爲模板的項目(一直yes就會有單元測試,eslint代碼檢查等,個人是默認vue init webpack後,一直enter)--個人根目錄爲m86xqwebpack
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,須要的依賴)
三、運行項目ios
npm run dev
四、瀏覽器自動打開後能夠看到vue.js的界面,則運行成功git
五、目錄結構(因爲項目是我作好了一部分的,因此有些文件是大家沒有的,後面會說到)程序員
src/store目錄下的是vuex的文件;es6
src/sass目錄爲各個組件的sass樣式以及定義的變量,mixins等github
router目錄爲項目路由設置
static/fonts爲移動端的字體圖標
static/css爲頁面初始化css(reset.css),我這是經過index.html直接寫在head裏
data.json是自定義的假數據
六、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)
七、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' } ] })
八、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>
九、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>
十、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>
十一、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>
十二、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>
1三、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 }
總結:
一、主要難點在於vuex狀態管理,對於首次運用mvvm的我來講,這是比較難理解的。如今看看其實很簡單,就是把一些全局須要用到的,有影響的數據提取到vuex裏邊集中管理,這樣當在a處修改數據時,b處能及時響應更新
二、因爲vue.js是組件化,數據驅動思想,因此在任什麼時候候,須要修改頁面內容或者結構時,首先想到的應該是經過操做數據從而達到結構變化的目的。多處用到的能夠提取出來做爲組件複用
三、因爲這邊用到了vue-cli + webpack,因此在寫scss時,瀏覽器兼容問題構建工具來處理