1. 程式人生 > >Vue實戰專案開發--城市列表頁面

Vue實戰專案開發--城市列表頁面

城市選擇頁面路由配置

  • 建立一個city-router的分支,用於城市列表的功能實現
  • 路由配置,修改router/index.js的內容
    import Vue from 'vue'
    import Router from 'vue-router'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'Home',
          component:  () => import( '@/pages/home/Home')
        },
        {
          path: '/city',
          name: 'City',
          component:  () => import( '@/pages/city/City')
        }
      ]
    })

    然後在pages資料夾下新建city資料夾,並建立City.vue,最後在city資料夾下新建components資料夾,然後建立City.vue的頂部Header.vue             

  • City.vue:
    <template>
      <div>
        <city-header></city-header>
      </div>
    </template>
    <script>
    import CityHeader from './components/Header'
    export default {
      name:'City',
      components: {
        CityHeader
      },
      data () {
        return {
          
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
    
    </style>
    

    Header.vue:

    <template>
      <div class="header">
        城市選擇
        <router-link to='/'>
           <div class="iconfont header-back">&#xe624;</div>
        </router-link>
      </div>
    </template>
    <script>
    export default {
      name:'CityHeader',
      data () {
        return {
          
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .header
        position relative
        height $headerHeight
        line-height $headerHeight
        text-align center
        color #fff
        background $bgColor
        font-size .32rem
        .header-back
          position absolute
          top 0
          left 0
          width .64rem
          text-align center
          font-size .4rem
          color #ffffff
    </style>
    
  • 實現點選首頁的header部分的成都進入城市列表和點選城市列表的返回按鈕返回首頁,這裡使用router-link實現的,router-link類似a標籤
    <router-link to='/city'>
           <div class="header-right">
            {{this.city}}
            <i class="iconfont arrow-icon">&#xe737;</i>
          </div>
    </router-link>
     <router-link to='/'>
           <div class="iconfont header-back">&#xe624;</div>
     </router-link>
  • 程式碼優化:這裡把頂部的height在varibles.styl檔案中定義為一個常量,方便後期修改
    $headerHeight = .86rem

    使用

    @import '~styles/varibles.styl'   (這裡需要先引入才可以使用)
     
    height $headerHeight  
    line-height $headerHeight

    最後,把程式碼提交到GitHub上

搜尋框佈局

  • 建立city-search分支,然後把本地的專案改在新建的這個分支上進行開發
  • 在components中新建Search.vue檔案
    <template>
      <div class="search">
        <input class="search-input" type="text" placeholder="輸入城市名或拼音">
      </div>
    </template>
    <script>
    export default {
      name:'CitySearch',
      data () {
        return {
          
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .search
        height .72rem
        padding 0 .1rem
        background $bgColor
        .search-input
          box-sizing border-box
          width 100%
          height .62rem
          padding 0 .1rem
          line-height .62rem
          text-align center
          border-radius .06rem
          color #666666
    </style>

    然後在City.vue中引入並使用

    <template>
      <div>
        <city-header></city-header>
        <city-search></city-search>
      </div>
    </template>
    <script>
    import CityHeader from './components/Header'
    import CitySearch from './components/Search'
    
    export default {
      name:'City',
      components: {
        CityHeader,
        CitySearch
      },
      data () {
        return {
          
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
    
    </style>
    

    然後搜尋框佈局就完成了,最後把程式碼提交到GitHub上

列表佈局

  • 建立city-list分支,然後把本地的專案改在新建的這個分支上進行開發
  • 在components中新建List.vue檔案
    <template>
      <div class="list">
        <div class="area">
          <div class="title border-topbottom">當前城市</div>
          <div class="button-list">
            <div class="button-wrapper">
              <div class="button">成都</div>
            </div>
          </div>
        </div>
        <div class="area">
          <div class="title border-topbottom">熱門城市</div>
          <div class="button-list">
            <div class="button-wrapper">
              <div class="button">成都</div>
            </div>
            <div class="button-wrapper">
              <div class="button">成都</div>
            </div>
            <div class="button-wrapper">
              <div class="button">成都</div>
            </div>
            <div class="button-wrapper">
              <div class="button">成都</div>
            </div>
          </div>
        </div>
        <div class="area">
          <div class="title border-topbottom">A</div>
          <div class="item-list">
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
          </div>
        </div>
        <div class="area">
          <div class="title border-topbottom">B</div>
          <div class="item-list">
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
          </div>
        </div>
        <div class="area">
          <div class="title border-topbottom">C</div>
          <div class="item-list">
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
          </div>
        </div>
        <div class="area">
          <div class="title border-topbottom">D</div>
          <div class="item-list">
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
            <div class="item border-bottom">阿拉爾</div>
          </div>
        </div>
    
      </div>
    </template>
    <script>
    export default {
      name:'CityList',
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .border-topbottom
        &:before
          border-color #ccc
        &:after
          border-color #ccc
       .border-bottom
        &:before
          border-color #ccc
      .list
        overflow hidden
        position absolute
        top 1.58rem
        left 0
        right 0
        bottom 0
        .title
          line-height .44rem
          background #eee
          padding-left .2rem
          color #666
          font-size .26rem
        .button-list
          padding .1rem .6rem .1rem .1rem
          overflow hidden 
          .button-wrapper
            float left
            width 33.33%
            .button
              margin .1rem
              padding .1rem 0
              text-align center
              border .02rem solid #ccc
              border-radius .06rem
        .item-list
        .item
          line-height .76rem
          padding-left .2rem
    </style>
    

    然後在City.vue中引入並使用

    <template>
      <div>
        <city-header></city-header>
        <city-search></city-search>
        <city-list></city-list>
      </div>
    </template>
    <script>
    import CityHeader from './components/Header'
    import CitySearch from './components/Search'
    import CityList from './components/List'
    
    export default {
      name:'City',
      components: {
        CityHeader,
        CitySearch,
        CityList
      }
    }
    </script>
    <style lang="stylus" scoped>
    
    </style>
    

    然後列表佈局就完成了,最後把程式碼提交到GitHub上

Better-scroll的使用及字母表佈局

  • 安裝better-scroll外掛
    npm install better-scroll --save
  •  修改List.vue的程式碼
     <div class="list" ref='wrapper'>    (使用ref來獲取當前元素)
        <div>
             <- 這裡放之前的內容 ->
        </div>
      </div>

    引入並使用

    import BScroll from 'better-scroll'
    export default {
      name:'CityList',
      mounted () {
        this.scroll = new BScroll(this.$refs.wrapper)
      }
    }

    完整程式碼

    <template>
      <div class="list" ref='wrapper'>
        <div>
          <div class="area">
            <div class="title border-topbottom">當前城市</div>
            <div class="button-list">
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">熱門城市</div>
            <div class="button-list">
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">A</div>
            <div class="item-list">
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">B</div>
            <div class="item-list">
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">C</div>
            <div class="item-list">
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">D</div>
            <div class="item-list">
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
              <div class="item border-bottom">阿拉爾</div>
            </div>
          </div>
         </div>
      </div>
    </template>
    
    <script>
    import BScroll from 'better-scroll'
    export default {
      name:'CityList',
      mounted () {
        this.scroll = new BScroll(this.$refs.wrapper)
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .border-topbottom
        &:before
          border-color #ccc
        &:after
          border-color #ccc
       .border-bottom
        &:before
          border-color #ccc
      .list
        overflow hidden
        position absolute
        top 1.58rem
        left 0
        right 0
        bottom 0
        .title
          line-height .54rem
          background #eee
          padding-left .2rem
          color #666
          font-size .26rem
        .button-list
          padding .1rem .6rem .1rem .1rem
          overflow hidden 
          .button-wrapper
            float left
            width 33.33%
            .button
              margin .1rem
              padding .1rem 0
              text-align center
              border .02rem solid #ccc
              border-radius .06rem
        .item-list
        .item
          line-height .76rem
          padding-left .2rem
    </style>
    

    better-scroll就引入並使用完成了

  • 字母表佈局:在componens資料夾下新建Alphabet.vue
    <template>
      <ul class="list">
        <li class="item">A</li>
        <li class="item">A</li>
        <li class="item">A</li>
        <li class="item">A</li>
        <li class="item">A</li>
      </ul>
    </template>
    <script>
    export default {
      name:'CityAlphabet',
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .list
        display flex
        flex-direction column
        justify-content center
        position absolute
        top 1.58rem
        right 0
        bottom 0
        width .4rem
        .item
          line-height .4rem
          text-align center
          color $bgColor
    </style>
    

    然後在City.vue中引入:

    <template>
      <div>
        <city-header></city-header>
        <city-search></city-search>
        <city-list></city-list>
        <city-alphabet></city-alphabet>
      </div>
    </template>
    <script>
    import CityHeader from './components/Header'
    import CitySearch from './components/Search'
    import CityList from './components/List'
    import CityAlphabet from './components/Alphabet'
    
    export default {
      name:'City',
      components: {
        CityHeader,
        CitySearch,
        CityList,
        CityAlphabet
      },
      data () {
        return {
          
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
    
    </style>
    

    字母表佈局就完成了,最後把程式碼提交到線上

頁面的動態資料渲染

  • 建立city-Ajax分支,然後把本地的專案改在新建的這個分支上進行開發
  • 使用Ajax模擬網路請求,在static/mock下新建一個city.json(有需要的可以在我的GitHub上拉取),在使用axios來模擬請求資料
  • City.vue的js部分:
    <script>
    import axios from 'axios'
    import CityHeader from './components/Header'
    import CitySearch from './components/Search'
    import CityList from './components/List'
    import CityAlphabet from './components/Alphabet'
    
    export default {
      name:'City',
      components: {
        CityHeader,
        CitySearch,
        CityList,
        CityAlphabet
      },
      data () {
        return {
          cities: {},
          hotCities: []
        }
      },
      methods: {
        getCityInfo () {
          axios.get('/api/city.json')
            .then(this.handleGetCityInfoSucc)
        },
        handleGetCityInfoSucc (res) {
          console.log(res)
          res = res.data
          if(res.ret && res.data){
            const data = res.data
            this.cities = data.cities
            this.hotCities = data.hotCities
          }
        }
      },
      mounted () {
        this.getCityInfo()
      }
      
    }
    </script>

     City.vue:

    <template>
      <div>
        <city-header></city-header>
        <city-search></city-search>
        <city-list :cities="cities" :hot="hotCities"></city-list>
        <city-alphabet :cities="cities"></city-alphabet>
      </div>
    </template>
    <script>
    import axios from 'axios'
    import CityHeader from './components/Header'
    import CitySearch from './components/Search'
    import CityList from './components/List'
    import CityAlphabet from './components/Alphabet'
    
    export default {
      name:'City',
      components: {
        CityHeader,
        CitySearch,
        CityList,
        CityAlphabet
      },
      data () {
        return {
          cities: {},
          hotCities: []
        }
      },
      methods: {
        getCityInfo () {
          axios.get('/api/city.json')
            .then(this.handleGetCityInfoSucc)
        },
        handleGetCityInfoSucc (res) {
          console.log(res)
          res = res.data
          if(res.ret && res.data){
            const data = res.data
            this.cities = data.cities
            this.hotCities = data.hotCities
          }
        }
      },
      mounted () {
        this.getCityInfo()
      }
      
    }
    </script>
    <style lang="stylus" scoped>
    
    </style>
    

    List.vue:

    <template>
      <div class="list" ref='wrapper'>
        <div>
          <div class="area">
            <div class="title border-topbottom">當前城市</div>
            <div class="button-list">
              <div class="button-wrapper">
                <div class="button">成都</div>
              </div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">熱門城市</div>
            <div class="button-list">
              <div class="button-wrapper" v-for="item of hot" :key="item.id">
                <div class="button">{{item.name}}</div>
              </div>
            </div>
          </div>
          <div class="area" v-for="(item,key) of cities" :key="key">
            <div class="title border-topbottom">{{key}}</div>
            <div class="item-list">
              <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{{innerItem.name}}</div>
            </div>
          </div>
         </div>
      </div>
    </template>
    
    <script>
    import BScroll from 'better-scroll'
    export default {
      name:'CityList',
      props: {  
        hot: Array,
        cities: Object
      },
      mounted () {
        this.scroll = new BScroll(this.$refs.wrapper)
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .border-topbottom
        &:before
          border-color #ccc
        &:after
          border-color #ccc
       .border-bottom
        &:before
          border-color #ccc
      .list
        overflow hidden
        position absolute
        top 1.58rem
        left 0
        right 0
        bottom 0
        .title
          line-height .54rem
          background #eee
          padding-left .2rem
          color #666
          font-size .26rem
        .button-list
          padding .1rem .6rem .1rem .1rem
          overflow hidden 
          .button-wrapper
            float left
            width 33.33%
            .button
              margin .1rem
              padding .1rem 0
              text-align center
              border .02rem solid #ccc
              border-radius .06rem
        .item-list
        .item
          line-height .76rem
          padding-left .2rem
    </style>
    

    Alphabet.vue:

    <template>
      <ul class="list">
        <li class="item" v-for="(item,key) of cities" :key="key">{{key}}</li>
      </ul>
    </template>
    <script>
    export default {
      name:'CityAlphabet',
      props: {
       cities: Object
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .list
        display flex
        flex-direction column
        justify-content center
        position absolute
        top 1.58rem
        right 0
        bottom 0
        width .4rem
        .item
          line-height .4rem
          text-align center
          color $bgColor
    </style>
    
  • 最後看一下最後的效果,記住把程式碼提交到GitHub上

           

兄弟組建間聯動

  • 實現點選右側字母,左邊的內容跟著字母變化

             

         就是點選H字母,城市列表就跳到H首字母的城市列表

         這裡是通過在Alphabet.vue監聽點選字母的點選事件,然後把點選對應的元素的值傳給父元件,父元件再將這個值傳給List.vue子元件的

        Alphabet.vue(增加了handleLetterClick,通過emit把值傳給父元件的):

<template>
  <ul class="list">
    <li 
      class="item" 
      v-for="(item,key) of cities" 
      :key="key"
      @click="handleLetterClick"
    >
      {{key}}
    </li>
  </ul>
</template>
<script>
export default {
  name:'CityAlphabet',
  props: {
   cities: Object
  },
  methods: {
    handleLetterClick (e) {
      this.$emit('change',e.target.innerText)
    }
  }
}
</script>
<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .list
    display flex
    flex-direction column
    justify-content center
    position absolute
    top 1.58rem
    right 0
    bottom 0
    width .4rem
    .item
      line-height .4rem
      text-align center
      color $bgColor
</style>

   City.vue(通過@change="handleLetterChange"來接受Alphabet.vue子元件傳過來的值,並在data中定義letter值,在前面的方法把從子元件得到的值賦給data中定義好的letter,然後把這個值通過屬性傳給List.vue子元件):

<template>
  <div>
    <city-header></city-header>
    <city-search></city-search>
    <city-list 
      :cities="cities" 
      :hot="hotCities"
      :letter="letter"
    ></city-list>
    <city-alphabet 
      :cities="cities" 
      @change="handleLetterChange"
    ></city-alphabet>
  </div>
</template>
<script>
import axios from 'axios'
import CityHeader from './components/Header'
import CitySearch from './components/Search'
import CityList from './components/List'
import CityAlphabet from './components/Alphabet'

export default {
  name:'City',
  components: {
    CityHeader,
    CitySearch,
    CityList,
    CityAlphabet
  },
  data () {
    return {
      cities: {},
      hotCities: [],
      letter: ''
    }
  },
  methods: {
    getCityInfo () {
      axios.get('/api/city.json')
        .then(this.handleGetCityInfoSucc)
    },
    handleGetCityInfoSucc (res) {
      res = res.data
      if(res.ret && res.data){
        const data = res.data
        this.cities = data.cities
        this.hotCities = data.hotCities
      }
    },
    handleLetterChange (letter) {
      this.letter = letter;
    }
  },
  mounted () {
    this.getCityInfo()
  }
  
}
</script>
<style lang="stylus" scoped>

</style>

List.vue(在這裡使用props接受父元件傳來的letter值,並使用watch來監聽letter值的變化,然後讓內容跟隨右側字母的變化):

<template>
  <div class="list" ref='wrapper'>
    <div>
      <div class="area">
        <div class="title border-topbottom">當前城市</div>
        <div class="button-list">
          <div class="button-wrapper">
            <div class="button">成都</div>
          </div>
        </div>
      </div>
      <div class="area">
        <div class="title border-topbottom">熱門城市</div>
        <div class="button-list">
          <div class="button-wrapper" v-for="item of hot" :key="item.id">
            <div class="button">{{item.name}}</div>
          </div>
        </div>
      </div>
      <div 
        class="area" 
        v-for="(item,key) of cities" 
        :key="key"
        :ref="key"
      >
        <div class="title border-topbottom">{{key}}</div>
        <div class="item-list">
          <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{{innerItem.name}}</div>
        </div>
      </div>
     </div>
  </div>
</template>

<script>
import BScroll from 'better-scroll'
export default {
  name:'CityList',
  props: {  
    hot: Array,
    cities: Object,
    letter: String
  },
  mounted () {
    this.scroll = new BScroll(this.$refs.wrapper)
  },
  watch: {
    letter () {
      if (this.letter) {
        const element = this.$refs[this.letter][0]
        this.scroll.scrollToElement(element)
      }
    }
  }
}
</script>
<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .border-topbottom
    &:before
      border-color #ccc
    &:after
      border-color #ccc
   .border-bottom
    &:before
      border-color #ccc
  .list
    overflow hidden
    position absolute
    top 1.58rem
    left 0
    right 0
    bottom 0
    .title
      line-height .54rem
      background #eee
      padding-left .2rem
      color #666
      font-size .26rem
    .button-list
      padding .1rem .6rem .1rem .1rem
      overflow hidden 
      .button-wrapper
        float left
        width 33.33%
        .button
          margin .1rem
          padding .1rem 0
          text-align center
          border .02rem solid #ccc
          border-radius .06rem
    .item-list
    .item
      line-height .76rem
      padding-left .2rem
</style>
  •  在左側字母表中做上下拖拽的時候,城市列表內容也跟著變化

          主要是監聽拖拽時間,在li增加了三個事件,分別是touchstart、touchmove、touchend來監聽拖拽事件,同時在li上使用ref,在data定義了一個touchStatus來儲存是否在拖拽的狀態,並使用計算屬性把cities中的字母取出來放在陣列letters中,最後計算拖拽到那個字母:

    handleTouchStart () {
      this.touchStatus = true
    },
    handleTouchMove (e) {
      if(this.touchStatus){
        const startY = this.$refs['A'][0].offsetTop
        const touchY = e.touches[0].clientY -79
        const index = Math.floor((touchY-startY) / 20)
        if(index >= 0 && index < this.letters.length) {
          this.$emit('change',this.letters[index])
        }
      }
    },
    handleTouchEnd () {
      this.touchStatus = false
    }

    Alphabet.vue:

<template>
  <ul class="list">
    <li 
      class="item" 
      v-for="item of letters" 
      :key="item"
      :ref="item"
      @touchstart="handleTouchStart"
      @touchmove="handleTouchMove"
      @touchend="handleTouchEnd"
      @click="handleLetterClick"
    >
      {{item}}
    </li>
  </ul>
</template>
<script>
export default {
  name:'CityAlphabet',
  props: {
   cities: Object
  },
  data () {
    return {
      touchStatus: false
    }
  },
  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){
        const startY = this.$refs['A'][0].offsetTop
        const touchY = e.touches[0].clientY -79
        const index = Math.floor((touchY-startY) / 20)
        if(index >= 0 && index < this.letters.length) {
          this.$emit('change',this.letters[index])
        }
      }
    },
    handleTouchEnd () {
      this.touchStatus = false
    }
  }
}
</script>
<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .list
    display flex
    flex-direction column
    justify-content center
    position absolute
    top 1.58rem
    right 0
    bottom 0
    width .4rem
    .item
      line-height .4rem
      text-align center
      color $bgColor
</style>

列表切換效能優化

  • 把A字母離頂部的高度使用updated鉤子函式來計算,不用每次執行handleTouchMove函式的時候都計算

           

  • 函式節流(定義了一個timer,如果正在執行拖拽操作,讓它延遲16毫秒來執行,如果在16毫秒之間又執行了手指滑動操作,它就會把上次要做的操作清楚掉,重新執行這次要做的事情,從而減少handleTouchMove函式的執行頻率

         

Alphabet.vue:

<template>
  <ul class="list">
    <li 
      class="item" 
      v-for="item of letters" 
      :key="item"
      :ref="item"
      @touchstart="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: ''
    }
  },
  computed: {
    letters () {
      const letters = []
      for( let i in this.cities) {
        letters.push(i)
      }
      return letters
    }
  },
  updated () {
    this.startY = this.$refs['A'][0].offsetTop
  },
  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 -79
          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>
<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .list
    display flex
    flex-direction column
    justify-content center
    position absolute
    top 1.58rem
    right 0
    bottom 0
    width .4rem
    .item
      line-height .4rem
      text-align center
      color $bgColor
</style>

最後git提交程式碼到GitHub上

搜尋功能實現

  • City.vue傳值到Search.vue子元件,通過watch監聽使用者輸入,從而修改輸入框下面顯示的陣列
  • 使用v-show判斷使用者是否輸入了資料,從而實現查詢到的陣列內容區域的顯示隱藏
  •  使用v-show判斷使用者輸入的值是否有對應的值,來實現查詢到的陣列資料的顯示和沒有查到內容時的提示
  • 還使用了函式節流,讓使用者在輸入後100毫秒再執行,如果使用者在100毫秒之間再輸入就會把上次的操作清楚掉,增強效能

       

      Search.vue:

<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 list"
          :key="item.id"
        >
          {{item.name}}
        </li>
        <li class="search-item border-bottom" v-show="hasNoData">
          沒有找到匹配資料
        </li>
      </ul>
    </div>
  </div>
 
</template>
<script>
import Bscroll from 'better-scroll'
export default {
  name:'CitySearch',
  props: {
    cities: Object
  },
  data () {
    return {
      keyword: '',
      list: [],
      timer: null
    }
  },
  computed: {
    hasNoData () {
      return !this.list.length
    }
  },
  watch: {
    keyword () {
      if (this.timer) {
        clearTimeout(this.timer)
      }
      if (!this.keyword) {
        this.list = []
        return 
      }
      this.timer = setTimeout(() => {
        const result = []
        for (let i in this.cities) {
          this.cities[i].forEach((value) => {
            if (value.spell.indexOf(this.keyword) > -1 ||
              value.name.indexOf(this.keyword) > -1){
                result.push(value)
              }
          })
        }
        this.list = result
      },100)
    }
  },
  mounted () {
    this.scroll = new Bscroll(this.$refs.search)
  }
}
</script>
<style lang="stylus" scoped>
  @import '~styles/varibles.styl'
  .search
    height .72rem
    padding 0 .1rem
    background $bgColor
    .search-input
      box-sizing border-box
      width 100%
      height .62rem
      padding 0 .1rem
      line-height .62rem
      text-align center
      border-radius .06rem
      color #666666
  .search-content
    z-index 1
    position absolute
    overflow hidden
    top 1.58rem
    left 0
    right 0
    bottom 0
    background #eee
    .search-item
      line-height .62rem
      padding-left .2rem
      background #ffffff
      color #666

</style>

使用Vuex實現資料共享

  • 首先建立一個city-vuex的分支,這裡需要實現的功能是點選城市列表頁面中城市,首頁的城市會發生改變
  • vuex資料框架是vue大型專案的資料傳輸使用的框架

       重要: vuex,我們可以想象成一個倉庫,其中有State、Actions、Mutations;State是所有的公用資料都存在State中,組建需要使用公用的資料,直接去呼叫State就可以了;Actions是我們可以把需要實現非同步操作或者是一些比較複雜的同步操作(批量的同步操作)存在Actions中;Mutations是對State同步的修改操作;元件中怎麼使用?(1.元件先去呼叫Actions,緊接著Actions去呼叫Mutations,然後Motations去修改State中的資料;2.元件直接呼叫Motations去修改State中的資料)

         

  • 在專案中使用,先安裝這個vuex
    npm install vuex --save

    然後在專案中建立這個"倉庫區域",我們把它叫做store,並建立index.js檔案,最後在main.js中引入這個檔案

    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
        }
      }
    })
    

    在元件中的使用(在List.vue和Search.vue中使用到了,實現效果就是在點選城市列表頁的時候,通過呼叫mutations中的方法來改變State中的資料),對對應的地方繫結handleCityClick()方法並且在methods中定義這個方法

    <div 
       class="button-wrapper" 
       v-for="item of hot" 
       :key="item.id"
       @click="handleCityClick(item.name)"
    >
      <div class="button">{{item.name}}</div>
    </div>
    <div 
       class="item border-bottom" 
       v-for="innerItem of item" 
       :key="innerItem.id"
       @click="handleCityClick(innerItem.name)"
    >
       {{innerItem.name}}
    </div>
    handleCityClick (city) {
      this.$store.commit('changeCity', city)
      this.$router.push('/')
    }

    這個呼叫mutations中的方法使用的是commit,可以從上面的圖看到;這樣最簡單的使用vuex就完成了

Vuex的高階使用及localStorage

  • 上面實現的功能會有問題,就是每次重啟應用的時候,選擇的城市會重置為State中預設的城市名,為了解決這個問題,在這裡就引入了h5中的localStorage本地快取來實現
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state: {
        city: localStorage.city || '成都'
      },
      mutations: {
        changeCity (state, city) {
          state.city = city
          localStorage.city = city
        }
      }
    })

    改進版(防止使用者沒有開啟本地快取功能或者是隱身模式):

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    Vue.use(Vuex)
    
    let defaultCity = '上海'
    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) {}
        }
      }
    })
    

    最終版(拆分成了state.js和mutations.js):

    // state.js
    let defaultCity = '上海'
    try {
      if (localStorage.city) {
        defaultCity = localStorage.city
      }
    } catch (e) {}
    
    export default {
      city: defaultCity
    }
    
    // mutations.js
    export default{
      changeCity (state, city) {
        state.city = city
        try {
          localStorage.city = city
        } catch (e) {}
      }
    }
    
    // index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    import state from './state'
    import mutations from './mutations'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      state,
      mutations
    })
    
  • 還有個樣式問題,就是選擇四個字的城市時,頂部會被撐開,直接修改home/components下的Header.vue中的.header-right樣式
     .header-right
        min-width 1.04rem  //修改的
        padding 0 .1rem    //新增的
        float right
        text-align center
        color #ffffff
  • 優化程式碼:使用vuex提供的mapState來簡化程式碼
    // home下的Header.vue的js部分
    <script>
    import { mapState } from 'vuex'    //新增
    export default {
      name: 'HomeHeader',
     computed: {                        //計算屬性新增的
       ...mapState(['city'])
     }
    }
    </script>

    使用:

    <router-link to='/city'>
      <div class="header-right">
        {{this.city}}            //從this.$store.state.city精簡成的this.city
        <i class="iconfont arrow-icon">&#xe737;</i>
      </div>
    </router-link>

     city/components中List.vue:

    <template>
      <div class="list" ref='wrapper'>
        <div>
          <div class="area">
            <div class="title border-topbottom">當前城市</div>
            <div class="button-list">
              <div class="button-wrapper">
                <div class="button">{{this.currentCity}}</div>
              </div>
            </div>
          </div>
          <div class="area">
            <div class="title border-topbottom">熱門城市</div>
            <div class="button-list">
              <div 
                class="button-wrapper" 
                v-for="item of hot" 
                :key="item.id"
                @click="handleCityClick(item.name)"
              >
                <div class="button">{{item.name}}</div>
              </div>
            </div>
          </div>
          <div 
            class="area" 
            v-for="(item,key) of cities" 
            :key="key"
            :ref="key"
          >
            <div class="title border-topbottom">{{key}}</div>
            <div class="item-list">
              <div 
                class="item border-bottom" 
                v-for="innerItem of item" 
                :key="innerItem.id"
                @click="handleCityClick(innerItem.name)"
              >
                {{innerItem.name}}
              </div>
            </div>
          </div>
         </div>
      </div>
    </template>
    
    <script>
    import BScroll from 'better-scroll'
    import { mapState, mapMutations } from 'vuex'
    export default {
      name:'CityList',
      computed: {
       ...mapState({
         currentCity: 'city'  //這裡使用的物件,就是把state中的city對映到這個元件的計算屬性中這個值
       })
      },
      props: {  
        hot: Array,
        cities: Object,
        letter: String
      },
      methods: {
        handleCityClick (city) {
          // this.$store.commit('changeCity', city) 使用mutations的方法之前的呼叫
          this.changeCity(city)
          this.$router.push('/')
        },
        ...mapMutations(['changeCity'])   //使用mapMutations來簡化mutations中的方法呼叫
      },
      mounted () {
        this.scroll = new BScroll(this.$refs.wrapper)
      },
      watch: {
        letter () {
          if (this.letter) {
            const element = this.$refs[this.letter][0]
            this.scroll.scrollToElement(element)
          }
        }
      }
    }
    </script>
    <style lang="stylus" scoped>
      @import '~styles/varibles.styl'
      .border-topbottom
        &:before
          border-color #ccc
        &:after
          border-color #ccc
       .border-bottom
        &:before
          border-color #ccc
      .list
        overflow hidden
        position absolute
        top 1.58rem
        left 0
        right 0
        bottom 0
        .title
          line-height .54rem
          background #eee
          padding-left .2rem
          color #666
          font-size .26rem
        .button-list
          padding .1rem .6rem .1rem .1rem
          overflow hidden 
          .button-wrapper
            float left
            width 33.33%
            .button
              margin .1rem
              padding .1rem 0
              text-align center
              border .02rem solid #ccc
              border-radius .06rem
        .item-list
        .item
          line-height .76rem
          padding-left .2rem
    </style>
    

    其他的元件一樣使用這種方法來精簡程式碼(這裡我把之前建立的沒有用的資料夾List和元件也刪除了)

  • 另外講一下vuex中的gettermodule,這 getter就是store 的計算屬性,通過把State中的資料計算後,元件就可以之間呼叫getter使用;module是當由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的物件。當應用變得非常複雜時,store 物件就有可能變得相當臃腫。 為了解決以上問題,Vuex 允許我們將 store 分割成模組(module)。每個模組擁有自己的 state、mutation、action、getter、甚至是巢狀子模組——從上至下進行同樣方式的分割

        

使用keep-alive優化頁面效能

  • 首先建立在GitHub一個city-keepalive的分支
  • 在App.vue中使用<keep-alive>標籤
    <template>
      <div id="app">
        <keep-alive>
          <router-view/>
          <!-- 顯示的是當前路由地址所對應的內容 -->
        </keep-alive>
      </div>
    </template>
    
    <script>
    export default {
      name: 'App'
    }
    </script>
    
    <style></style>

    作用是路由的內容載入過一次之後,就把路由中的內容放到記憶體之中,下次再重新進這個路由時,不需要重新渲染元件

  • 還有邏輯問題,就是在重新選擇城市後在Home.vue中需要重新進行Ajax請求,在Home.vue中引入vuex
    import { mapState } from 'vuex'

     修改getHomeinfo方法,在請求的時候帶上state中的city 

    getHomeinfo () {
        axios.get('/api/index.json?city=' + this.city)
          .then(this.getHomeinfoSucc)
    },

    這樣修改後,重新選擇城市也不會重新進行Ajax請求,這裡就需要用到activated(使用keep-alive才有的鉤子函式)使用mounted和activated這兩個鉤子函式來實現

    mounted () {
        this.lastCity = this.city
        this.getHomeinfo()
      },
      activated () {
        if (this.lastCity !== this.city) {
          this.lastCity = this.city
          this.getHomeinfo()
        }
      }

    最後把代表提交到GitHub上