技術棧:css
Vue:Vue Vue-router Vuex Vue-clihtml
插件:vue-awesome-swiper better-scroll axiosvue
Css: styluswebpack
Api: 靜態json數據ios
項目結構git
首頁部分:github
iconfont引入和使用web
圖片輪播組件ajax
圖標區域輪播組件的使用vuex
axios獲取接口數據
組件間數據傳遞
城市選擇也部分
字母表佈局
better-scroll的使用
函數節流實現列表性能優化
搜索邏輯實現
Vuex實現數據共享
LocalStorage實現頁面數據存儲
keep-alive 優化路由性能
詳情頁部分
Banner佈局
動態路由配置
公用畫廊組件拆分
實現fixed header漸隱漸現效果
遞歸組件實現詳情類別
transition slot插槽實現animation簡單動畫效果
項目依賴包
fastClick:處理click 300ms延遲
npm i fastclick --save
在main.js引入 import FastClick form ‘fastclick’
FastClick.attach(document.body) // 使用
stylus:css預處理
下載 stylus 和 stylus-loader --save
vue-awesome-swiper:實現輪播插件 npm i vue-awesome-swiper --save 本項目使用2.6.7的版本
在main.js引入 import VueAwesomeAwiper form ‘vue-awesome-swiper’
axios:第三方交互插件 npm i axios --save
哪裏使用哪裏引入 import Axios form ‘axios’
better-scroll:實現滾動插件 npm i better-scroll --save
哪裏使用哪裏引入 import BScroll form ‘better-scroll’
首頁
HomeSwiper : 使用vue-awesome-swiper輪播插件
<swiper :options=「swiperOption></swiper>
在data裏寫swiperOption:{} 根據swiper3的api設置配置項
HomeIcons:使用swiper實現多頁自動分頁功能
computed: { pages () { const pages = [] this.iconsList.forEach((item, index) => { const page = Math.floor(index / 8) if (!pages[page]) { pages[page] = [] } pages[page].push(item) }) return pages } }
index-ajax:使用axios進行ajax請求
gitignore設置:添加文件目錄,推送到倉庫是,忽略添加的文件
設置json數據,開發環境轉發代理
設置 config 文件夾下的 index.js
設置 module.exports 下 dev 的 proxyTable 代理
webpack-dev-server 工具會自動將 /api 替換成 /static/data
城市頁
router-link:實現頁面跳轉
<router-link to='/'> 返回根目錄 </router-link>
City-list使用better-scroll插件實現上下滾動效果
html結構外層需寫 ref=‘wrapper’
在文件裏引入
import BScroll from 'better-scroll' mounted () { this.scroll = new BScroll(this.$refs.wrapper) },
city-ajax:同home-ajax 獲取數據,並在其餘組件中使用
獲取數據分佈等於data中定義的cities{} hotCities:[]
並在各個組件中綁定數據
<city-header></city-header> <city-search :cities="cities"></city-search> <list :cities="cities" :hotCities="hotCities" :letter="letter"></list> <city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>
在各個組件中使用props介紹這些數據 在html中使用這些接收的數據 以list.vue爲例
<div class="area"> <div class="title border-topbottom"> 熱門城市 </div> <div class="button-list"> <div class="button-wrapper" v-for="item in hotCities" :key="item.id" @click="handleCityClick(item.name)"> <div class="button">{{item.name}}</div> </div> </div> </div> <div class="area" v-for="(citiesItem, key) of cities" :key="key" :ref="key"> <div class="title border-topbottom"> {{key}} </div> <div class="item-list" v-for="item in citiesItem" :key="item.id" @click="handleCityClick(item.name)"> <div class="item"> {{item.name}} </div> </div> </div>
City-alphabet:26個字母,要獲取 city-list的數據(兄弟組件間的聯動)
子組件Alphabet.vue數據傳遞給父組件city.vue,經過父組件city.vue傳遞給子組件list.vue
<template> <ul class="alphabet"> <li class="alphabetItem" v-for="item of letters" :key="item" :ref="item" @touchstart.prevent="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @click="handleLetterClick" > {{item}} </li> </ul> </template>
在city.vue中箭頭change事件
<city-alphabet :cities="cities" @change="handleLetterChange"></city-alphabet>
在 methods 中定義事件 handleLetterClick,傳遞 letter 參數。
methods: { handleLetterChange (letter) { this.letter = letter } },
並在 data 中定義數據 letter。
data () { return { cities: {}, hotCities: [], letter: '' // Alphabet 經過 change 事件傳遞過來的數據 } }
並傳遞給list.vue
<list :cities="cities" :hotCities="hotCities" :letter="letter"></list>
而後在list.vue子組件props中接收letter
props: { cities: Object, hotCities: Array, letter: String },
並經過watch監聽letter的變化
watch: { letter () { this.scroll.scrollToElement(this.$refs[this.letter][0]) } }
alphabet滑動邏輯:
上下滑動時,取字母位置的邏輯
獲取A字母距離頂部高度
滑動時,取當前位置距離頂部高度
計算差值,獲得當前手指位置與A字母頂部差值
差值除以每一個字母的高度,得出當前的字母,觸發change事件給外部
<template> <ul class="alphabet"> <li class="alphabetItem" v-for="item of letters" :key="item" :ref="item" @touchstart.prevent="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @click="handleLetterClick" > {{item}} </li> </ul> </template> <script> export default { name: 'CityAlphabet', props: { cities: Object }, data () { return { touchStatus: false, startY: 0, timer: null } }, updated () { this.startY = this.$refs['A'][0].offsetTop }, computed: { letters () { const letters = [] for (let i in this.cities) { letters.push(i) } return letters } }, methods: { handleLetterClick (e) { this.$emit('change', e.target.innerText) }, handleTouchStart () { this.touchStatus = true }, handleTouchMove (e) { if (this.touchStatus) { if (this.timer) { clearTimeout(this.timer) } this.timer = setTimeout(() => { const touchY = e.touches[0].clientY - 83 const index = Math.floor((touchY - this.startY) / 20) if (index >= 0 && index < this.letters.length) { this.$emit('change', this.letters[index]) } }, 16) } }, handleTouchEnd () { this.touchStatus = false } } } </script>
city-search搜索功能邏輯
使用v-model作雙向綁定
在data中定義keyword(搜索的內容)keywordList(要顯示的內容)、timer(作節流優化)
<template> <div> <div class="search"> <input v-model="keyword" class="search-input" type="text" placeholder="輸入城市名或拼音"> </div> <div class="search-content" ref="search" v-show="keyword"> <ul> <li class=" search-item border-bottom" v-for="item of keywordList" :key="item.id" @click="handleCityClick(item.name)">{{item.name}}</li> <li class="search-item" v-show="!keywordList.length"> 沒有匹配數據 </li> </ul> </div> </div> </template> <script> import BScroll from 'better-scroll' import { mapMutations } from 'vuex' export default { name: 'CitySearch', props: { cities: Object }, data () { return { keyword: '', keywordList: [], timer: null } }, watch: { keyword () { if (!this.keyword) { this.keywordList = [] } if (this.timer) { clearTimeout(this.timer) } this.timer = setTimeout(() => { const result = [] for (let i in this.cities) { this.cities[i].forEach((value) => { if (value.name.indexOf(this.keyword) > -1 || value.spell.indexOf(this.keyword) > -1) { result.push(value) } }) } this.keywordList = result }, 100) } }, methods: { handleCityClick (city) { this.changeCity(city) this.$router.push('/') }, ...mapMutations(['changeCity']) }, mounted () { this.scroll = new BScroll(this.$refs.search) } } </script>
使用Vuex實現數據共享npm i vuex --save
建立文件夾 store,建index.js,state裏放置全局公用數據city
import Vue from 'vue' import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({ state: { city: '上海' }, mutations: { changeCity (state, city) { state.city = city } } })
在main.js中引入store
import store from './store' //引入 store
new Vue({
el: '#app',
router: router,
store: store, //傳遞進入根實例的 store
components: { App },
template: '<App/>'
})
在list.vue和search.vue組件中的城市選項綁定click事件handleCityClick
@click="handleCityClick(item.name)
在methods中:
methods: { handleCityClick (city) { console.log(city) this.$store.commit('changeCity', city) // 經過commit提交mutation // this.changeCity(city) this.$router.push('/') // 點擊以後跳到home頁 }, // ...mapMutations(['changeCity']) },
localStorage的使用 store index.js
export default new Vuex.Store({
state: {
city: localStorage.city || '上海'
},
mutations: {
changeCity (state, city) {
state.city = city
localStorage.city = city
}
}
})
有可能當用戶使用隱身模式或禁用 localStorage,會致使瀏覽器報錯。因此建議使用 try catch 進行優化
let defalutCity = '上海' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) {} export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity (state, city) { state.city = city try { localStorage.city = city } catch (e) {} } } })
Keep-alive 優化 :做用是把數據放到內存中,下次使用是無需從新加載組件,從內存中拿出之前的內容顯示就能夠了
在本項目中,<keep-alive></keeo-alive>中包裹這<router-view/>意思是路由內的內容被加載一次以後,把路由的內容放到了內存中,下次使用無需再次加載(致使頁面切換時,不一樣城市,請求的數據是同樣的,在network中能夠查看)
兩種解決方法:一、使用activated生命週期構造
在home.vue中,定義lastCity:‘’
data () { return { swiperList: [], iconsList: [], weekendList: [], recommendList: [], lastCity: '' } }, mounted () { this.lastCity = this.city this.getHomeInfo() }, activated () { if (this.lastCity !== this.city) { this.lastCity = this.city this.getHomeInfo() } },
方法二、
<keep-alive exclude="Detail"> // exclude =「」表示那個頁面不被緩存 <router-view/> </keep-alive>
詳情頁 :to實現動態路由
<router-link tag="div" class="recommend-list border-bottom" v-for="item in itemList" :key="item.id" :to="'/detail/' + item.id" ></router-link>
全局畫廊組件
新建common 用來放置全局組件,創建gallary.vue畫廊組件,並在build/webpack.base.conf.js 中進行路徑別名(alias)執行的設置
‘common’: resolve(‘src/common’)
畫廊組件:
在banner.vue中引入畫廊組件調用
header.vue漸隱漸現效果實現
遞歸組件:
之因此在組件當中須要一個 name 屬性,也是爲了方便在組件自身調用自身出現遞歸的時候便於調用。下面能夠看到,在下一個 div 標籤中作一個 v-if 判斷,若是存在 item.children。就把 item.children 當作 list 再傳遞給自身,進行遞歸調用。
<template>
<div class="lists">
<div class="item" v-for="(item, index) of list" :key="index">
<div class="item-title border-bottom">
<span class="item-title-icon"></span>
{{item.title}}
</div>
<div v-if="item.children"> <detail-list :list="item.children"></detail-list> </div>
</div>
</div>
</template>
<script>
export default {
name: 'DetailList',
props: {
list: Array
}
}
解決exclude帶來的bug
在app.vue中使用了exclude,那麼在Detail下的Header.vue中就不會執行activated構造,可是會執行created鉤子。這是header的漸隱漸現效果就不顯示了,因此在監聽scroll的事件中,把scroll寫到created中,就能夠解決這個bug了
created () { window.addEventListener('scroll', this.handleScroll) }
解決每次切換頁面,頁面不在頂部的bug
在main.js中引入一下代碼
router.afterEach((to, from, next) => { // to and from are both route objects. window.scrollTo(0, 0) })
《雖然官網有其餘解決方法在router文件夾裏的index.js裏寫scrollBehavior(to,from,savedPosition){return {x:0,y:0}}可是我寫了沒有用》
animation動畫效果
在common中新建fade.vue
<template> <div class="fade"> <transition> <slot></slot> </transition> </div> </template> <script> export default { name: 'DetailFade' } </script> <style lang="stylus" scoped> .v-enter, .v-leave-to opacity 0 .v-enter-active, .v-leave-active transition opacity .5s </style>
在其餘組件使用的時候直接引入此組件,並在<detail-fade></detail-fade>中包裹須要animation的內容便可 如:
<detail-fade> <common-gallary :imgs="gallaryImgs" v-show="showGallary" @close="handleBannerClose"></common-gallary> </detail-fade>
再下就是接口聯調,不作總結了
項目源碼 地址:https://github.com/adongP/Travel ,https://gitee.com/adong2269/Travel(第一次用碼雲,不知道能不能下載) 下載以後,運行的時候把localhost改爲電腦的ip地址,能夠訪問,或者在package.json中找到--host 0.0.0.0 去掉正常運行
多謝指教