我們繼續前兩節的開發。本節教程實現的效果如下:

效果很簡單,但是實現起來卻要用到Vue的很多知識,下面我們將一步一步的實現這個效果。
首先這些城市的信息都是從后台的server里面獲取的,所以我們需要一個后台,后台的代碼可以從
https://github.com/EzrealDeng/Taopiaopiao里面的server 文件夾獲取,這個server端具體怎么實現的我們暫時不用關心,只需要知道這是一個可以返回我們需要的數據的后台服務即可。
下載完后進入文件夾執行:
npm install //安裝所有的包
npm run start //啟動后台服務
即可啟動服務,(如果啟動過程中出錯,可以使用npm run start:strict 啟動,或者升級node版本,默認的是9090端口,可以手動進行修改)。
成功啟動的話我們就有了一個可以使用的數據后台了。那么Vue如何訪問這個接口的呢,我們這里使用vue-resource(類似於Jquery里的ajax的功能)進行訪問后台接口。vue-resource的使用方式類似下面的例子:
this.$http.get('/movie/swiper').then(function(res){ //返回的是promise對象,res為返回的對象 console.log(res); this.images = res.body.data.data.returnValue; console.log(this.images); })
有了這個我們就可以和后台進行數據交互了,但是還有一個問題,我們的開發是vue腳手架自動搭建的一個基於Express的服務,我們的前端代碼實際上直接訪問的都是這個前端項目的后台,想要直接訪問我們剛才搭建的后台會有跨域問題的,怎么辦呢?幸好有一個叫做http-proxy的東西,可以幫我們實現代理,將我們要訪問的接口都映射到真正的服務器上,后端進行跨域問題的解決。而且Vue腳手架也幫我們集成了這個插件,只要在配置文件里修改即可。這個文件在:config/index.js里,修改這個文件里的proxyTable如下
.....//省略 dev: { env: require('./dev.env'), port: 8080, autoOpenBrowser: true, assetsSubDirectory: 'static', assetsPublicPath: '/', proxyTable: { '/movie/coming': 'http://localhost:9090', '/movie/hot': 'http://localhost:9090', '/movie/info': 'http://localhost:9090', '/movie/evaluation': 'http://localhost:9090', '/movie/cinema': 'http://localhost:9090', '/movie/cinema_detail': 'http://localhost:9090', '/movie/swiper': 'http://localhost:9090', '/movie/city': 'http://localhost:9090' }, ...//省略
到現在為止,接口的訪問問題已經解決了。
還有最后一個准備知識需要介紹下,那就是Vuex,這是個啥呢?就是個狀態管理機,由於Vue好多個組件可能會共用同一個狀態信息,例如我們的淘票票,選擇城市的這個組件需要知道當前的城市信息,而每個城市會有當前不同的上映的電影,這個反應當前熱映電影的組件也需要知道這個信息,如何保持數據的同步,光靠組件之間的通信將會十分復雜,所以有了Vuex這個東西,具體如何使用可以去看https://vuex.vuejs.org/zh-cn/官網的介紹。本例中用到的地方我會寫上注釋。接下來開始正式的編碼吧。
上面我們介紹了這么多的東西,使用起來當然得先一一的安裝一遍。
npm install vuex --save //vuex npm install vue-resource --save //vue-resource
另外我們選擇城市的組件用到了mint-ui的Vue組件庫,所以要也要安裝。
npm install mint-ui //mint-ui
接下來新建文件和目錄如下:

主要是新建立home/city.vue和store文件夾。city.vue是我們的選擇城市的組件,store存放的是Vuex狀態管理文件。
依次修改city.vue如下:
<template>
<section ref="city" id="select-city" class="pf fadeInDown" v-if="$store.state.city.show">
<header class="city-header mint-1px-b pr">
<span class="fb">選擇城市</span>
<span class="close-city pa" @click="cancelCityList">×</span>
</header>
<div ref="city" @click="selectCity">
<mt-index-list>
<mt-index-section :index="city.sort" v-for="city in cityList" key="city.id">
<mt-cell :title="name.regionName" v-for="name in city.data" key="name.id"></mt-cell>
</mt-index-section>
</mt-index-list>
</div>
</section>
</template>
<script>
//mapActions,mapMutations可以獲取我們在store里面的所有actions,mutations方法,
import { mapActions, mapMutations } from 'vuex'
export default{
data () {
return {
showCityList: true,
cityList: []
}
},
methods: {
...mapActions([
'updateCityAsync'
]),
...mapMutations([
'pushLoadStack',
'completeLoad'
]),
//封裝了一下vue-resource的請求方法
requestData (url, fn) {
this.pushLoadStack()
this.$http.get(url).then(fn).then(this.completeLoad)
},
changeCityData (data) {
this.pushLoadStack()
this.$refs.city.className = "pf fadeOutTop"
this.$store.dispatch('updateCityAsync', data).then(this.completeLoad)
},
matchCityStr (str) {
let randomList = ['bj', 'sh', 'gz']
let randomCity = randomList[Math.floor(3*Math.random())]
switch (str) {
case '北京': return 'bj'
case '上海': return 'sh'
case '廣州': return 'gz'
default: return randomCity
}
},
//選擇城市事件
selectCity (event) {
let ele = event.target
let className = ele.className
let name = ''
if (className === "mint-indexsection-index" || className ==="mint-indexlist-nav" || className === "mint-indexlist-navitem") {
name = ''
} else if (className === 'mint-cell-wrapper') {
name = ele.children[0].children[0].innerHTML
} else if (className === "mint-cell-title") {
name = ele.children[0].innerHTML
} else {
name = ele.innerHTML
}
if (name) {
this.changeCityData({
city: {
name: name,
rN: this.matchCityStr(name)
}
})
} else {
return false
}
},
cancelCityList () {
this.changeCityData({city: {}})
}
},
created () {
//this.$store.dispatch('updateCityAsync', {city: {}})
this.requestData('/movie/city', (response) => {
// let data = JSON.parse(response.data)
let data = response.data
let cityObj = data.data.data.returnValue
let citySort = Object.keys(cityObj)
this.cityList.push({ //先push進去三個熱門城市
sort: '熱門',
data: [{
regionName: '北京',
id: 1,
rN: 'bj'
}, {
regionName: '上海',
id: 2,
rN: 'sh'
}, {
regionName: '廣州',
id: 3,
rN: 'gz'
}]
})
citySort.forEach((item) => { //獲取后台的城市信息並且按分類信息進行排序
this.cityList.push({
sort: item,
data: cityObj[item]
})
})
})
}
}
</script>
<style>
.mint-indicator-wrapper {
z-index: 1000
}
#select-city {
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 9999999;
background-color: #fff;
}
.city-header {
height: 46px;
line-height: 46px;
text-align: center;
color: #000;
font-size: 16px;
background-color: #fff;
}
.close-city {
font-size: 28px;
color: #666;
display: inline-block;
height: 46px;
width: 50px;
line-height: 38px;
text-align: center;
right: 0px;
}
@-webkit-keyframes fadeInDown {
0% {
opacity: .7;
-webkit-transform: translateY(-50px);
transform: translateY(-50px)
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0)
}
}
@keyframes fadeInDown {
0% {
opacity: .7;
-webkit-transform: translateY(-50px);
-ms-transform: translateY(-50px);
transform: translateY(-50px)
}
100% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0)
}
}
.fadeInDown {
-webkit-animation: fadeInDown .3s;
animation: fadeInDown .3s;
}
@-webkit-keyframes fadeOutTop {
0% {
opacity: 1;
-webkit-transform: translateY(0);
transform: translateY(0)
}
100% {
opacity: 0;
-webkit-transform: translateY(-50px);
transform: translateY(-50px)
}
}
@keyframes fadeOutTop {
0% {
opacity: 1;
-webkit-transform: translateY(0);
-ms-transform: translateY(0);
transform: translateY(0)
}
100% {
opacity: 0;
-webkit-transform: translateY(-50px);
-ms-transform: translateY(-50px);
transform: translateY(-50px)
}
}
.fadeOutTop {
-webkit-animation: fadeOutTop .35s;
animation: fadeOutTop .35s;
}
</style>
store/city/actions.js如下:
import Vue from 'vue' export default {
//異步的更新城市信息 updateCityAsync ({ commit, state }, {city}) { //commit對象可以用來觸發mutations里面的同步更新城市方法 if (!city.name) { city.name = state.name city.rN = state.rN } return Vue.http.get(`/movie/hot/?city=${city.rN}`).then((response) => { let data = response.data let lists = data.data.data.returnValue //模擬索引數據的id號 lists.forEach((item, index) => { item.mID = index }) city.data = lists commit('UPDATE', { city }) // 更新城市信息 }) } }
mutations.js如下:
export default{ UPDATE (state , { city }){ //根據傳入的city對象來改變狀態 if(city.name){ state.name = city.name; state.data = city.data; state.rN = city.rN; } state.show = false; }, showCityList (state) { //顯示城市選擇 state.show = true } }
store/loading/mutations.js如下:
//loading組件
import { Indicator } from 'mint-ui'; export default { pushLoadStack (state) { Indicator.open({ text: 'loading...', spinnerType: 'snake' }); state.stack.push(1) }, completeLoad (state) { //完成加載 let stack = state.stack stack.pop() if (!stack.length) { //延時為了更好顯示loading效果 setTimeout(() => { Indicator.close() }, 500) } } }
然后再修改store下的index.js,這個文件是所有的mutations和actions的總出口。
import Vue from 'vue' import cityMutations from './city/mutations' import cityAcions from './city/actions' import loadingMutations from './loading/mutations' import Vuex from 'vuex' Vue.use(Vuex) //vue插件只需要在這里use一次 const cityGetters = { movies: state => state.data, cityName: state => state.name } const city = { state: { name: '北京', show: true, rN: 'bj', data: [] }, actions: cityAcions, mutations: cityMutations, getters: cityGetters } const loading = { state: { stack: [] }, mutations: loadingMutations } export default new Vuex.Store({ modules: { city, loading } })
然后再修改src下的main.js如下:
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' import Mint from 'mint-ui'; import store from './store' import VueResource from 'vue-resource' import 'mint-ui/lib/style.css' Vue.config.productionTip = false Vue.use(Mint) Vue.use(VueResource) /* eslint-disable no-new */ new Vue({ el: '#app', router, store, template: '<App/>', components: { App } })
然后再使用我們剛才建立的city組件,修改views/movie.vue如下:
<template>
<div>
<header class="home-header border-bottom">
<city></city>
<div class="top-section">
<div class="" @click="showCityList">
{{ $store.state.city.name }}
<i class="icon-arrow"></i>
</div>
<div>
正在熱映
</div>
<div>
即將上映
</div>
</div>
</header>
</div>
</template>
<script>
import city from '../components/home/city.vue'
import { mapMutations } from 'vuex'
export default{
data(){
return {
}
},
components:{
city
},
methods:{
...mapMutations([
'showCityList'
])
}
}
</script>
<style>
.top-section{
display: flex;
justify-content: space-around;
}
.icon-arrow{
height: 12px;
display: inline-block;
}
.icon-arrow:after{
content: "";
display: block;
width: 6px;
height: 6px;
border: 1px solid #50505a;
border-top: 0 none;
border-left: 0 none;
margin-left: 2px;
transform: rotate(45deg);
}
</style>
所有文件修改完成之后重新啟動項目,不出意外的話應該就會完成我們開頭的效果了。
這里面具體的city.vue的實現可能有些地方看不太清楚,沒關系,再后面的章節后我們會一步步自己實現簡單的類似的組件。
謝謝閱讀,如果有問題歡迎在評論區一起討論。
更新: 由於寫一篇教程還是需要費一定的時間的,工作繁忙后續可能沒有時間更新這個系列的文章了,但是整個淘票票的代碼我是有更新的,需要的話可以在我的gitHub上查看:
https://github.com/EzrealDeng/Taopiaopiao
不能繼續更新博客還請大家原諒,有問題的話可以留言一起討論
注:本文出自博客園 https://home.cnblogs.com/u/mdengcc/ ,轉載請注明出處。
