Vue2.5 去哪兒 app實戰總結

 

技術棧: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 去掉正常運行

多謝指教

相關文章
相關標籤/搜索