城市选择页面路由配置

  • 创建一个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

    在文档可以看到better-scroll的使用better-scroll的GitHub地址

  •  修改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上

Logo

前往低代码交流专区

更多推荐