vue-cli 3.0 实现A-Z字母滑动选择城市列表

项目地址:

https://github.com/caochangkui/vue-cli3

项目代码:

城市列表首页:

City.vue

<template>
  <div id="city">
    <!-- <img src="/logo.png" alt="" height="10px"> -->
    <div class="word" v-show="showWord">
      <span>{{letter}}</span>
    </div>
    <div class="title">城市选择</div>
    <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 CityList from './components/List'
import CityAlphabet from './components/Alphabet'
export default {
  name: 'City',
  components: {
    CityList,
    CityAlphabet
  },
  data () {
    return {
      showWord: false,
      cities: {},
      hotCities: [],
      letter: ''
    }
  },
  methods: {
    getCityInfo () {
      axios.get('/mock/city.json').then(this.handleGetCityInfoSucc)
    },
    handleGetCityInfoSucc (res) {
      console.log(res.data)
      res = res.data
      if (res.ret && res.data) {
        const data = res.data
        this.cities = data.cities
        this.hotCities = data.hotCities
      }
    },
    handleLetterChange (letter) {
      console.log(letter)
      this.letter = letter
      this.showWord = true
      setTimeout(() => {
        this.showWord = false
        console.log(this.showWord)
      }, 500)
    }
  },
  mounted () {
    this.getCityInfo()
  }
}
</script>

<style scoped>
.title {
  line-height: 40px;
  background: #10d1eb;
  color: #fff;
}
.word {
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 99;
}
.word span {
  display: inline-block;
  height: 60px;
  width: 60px;
  background: rgba(0, 0, 0, .2);
  display: flex;
  justify-content: center;
  align-items: center;
}
</style>

城市列表组件:

List.vue

<template>
  <div class="list" ref="wrapper">
    <div>
      <div class="area">
        <div class="title">当前城市</div>
        <div class="button-list">
          <div class="button-wrapper">
            <div class="button">{{this.currentCity}}</div>
          </div>
        </div>
      </div>
      <div class="area">
        <div class="title">热门城市</div>
        <div class="button-list">
          <div
            class="button-wrapper"
            v-for="item in hot"
            :key="item.id"
            @click="handleCityClick(item.name)"
          >
            <div class="button">{{item.name}}</div>
          </div>
        </div>
      </div>
      <div
        class="area"
        v-for="(item, index) in cities"
        :key="index"
        :ref="index"
      >
        <div class="title">{{index}}</div>
        <div
          class="item-list"
          v-for="innerItem in item"
          :key=innerItem.id
          @click="handleCityClick(innerItem.name)"
          >
          <div class="item"> {{innerItem.name}} </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import Bscroll from 'better-scroll'
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'CityList',
  props: {
    hot: Array,
    cities: Object,
    letter: String
  },
  data () {
    return {

    }
  },
  computed: {
    ...mapState({
      currentCity: 'city'
    })
  },
  watch: {
    // 监听 Alphabet 中传过来的letter,如有变化,则滚动区域自动滚动到对应元素上
    letter () {
      if (this.letter) {
        const element = this.$refs[this.letter][0] // 获取对应字母的ref
        this.scroll.scrollToElement(element) // 利用better-scroll插件 滚动到指定元素element
        console.log(element)
      }
    }
  },
  methods: {
    ...mapMutations(['changeCity']),
    handleCityClick (city) {
      console.log(city)
      // this.$store.commit('changeCity', city) // 将参数city传给vuex中的mutations中的changeCity函数
      this.changeCity(city)
      this.$router.push('/') // 页面跳转 参考
    }
  },
  mounted () {
    this.scroll = new Bscroll(this.$refs.wrapper, {
      click: true
    })
  },

}
</script>

<style scoped>
.list {
  position: absolute;
  top: 40px;
  left: 0;
  right: 0;
  bottom: 0;
  overflow: hidden;
}
.title {
  line-height: 40px;
  background: #eee;
  padding-left: 10px;
  color: #666;
  font-size: 14px;
  text-align: left;
}
.button-list {
  overflow: hidden;
  padding: 10px 30px 10px 10px;
}
.button-wrapper {
  float: left;
  width: 33.33%;
}
.button {
  margin: 4px;
  padding: 4px 0;
  text-align: center;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
  color: #555;
}
.item {
  line-height: 40px;
  padding-left: 16px;
  text-align: left;
  border-bottom: 1px solid #eee;
}
</style>

字母检索组件:

Alphabet.vue

<template>
  <div class="list-wrapper">
    <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>
  </div>
</template>

<script>
export default {
  name: 'CityAlphabet',
  props: {
    cities: Object
  },
  data () {
    return {
      touchStatus: false,
      startY: 0,
      timeer: null
    }
  },
  computed: {
    letters () {
      const letters = []
      for (let i in this.cities) {
        letters.push(i)
      }
      return letters
    }
  },
  // 生命周期函数
  updated () {
    this.startY = this.$refs['A'][0].offsetTop // A字母距离滚动条顶部距离
    console.log('updated---> ', this.startY)
  },
  methods: {
    handleLetterClick (e) {
      this.$emit('change', e.target.innerText)
      console.log(1)
    },
    handleTouchStart () {
      console.log('开始滑动')
      this.touchStatus = true
    },
    handleTouchMove (e) {
      if (this.touchStatus) {
        if (this.timeer) {
          clearTimeout(this.timeer)
        }
        this.timeer = setTimeout(() => {
          console.log(e.touches[0])
          const touchY = e.touches[0].clientY - 40 // 手指触摸当前位置距离视口顶部的距离减去40(40指滚动区域最上边和页面顶部之间的距离)
          const index = Math.floor((touchY - this.startY) / 26) // 手指触摸当前位置所在的字母索引(26指单个字母的高度)
          if (index >= 0 && index < this.letters.length) {
            this.$emit('change', this.letters[index])
          }
        }, 16)
      }
    },
    handleTouchEnd () {
      this.touchStatus = false
    }
  }
}
</script>

<style scoped>
  .list {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: absolute;
    right: 0;
    top: 40px;
    bottom: 0;
    width: 30px;
    list-style: none;
    background: rgba(0, 0, 0, .2);
    margin: 0;
    padding: 0;
    z-index: 999;
  }
  .item {
    line-height: 24px;
    color: #068b9c;
    font-size: 14px;
    text-align: center;
    width: 100%;
  }

</style>

通过vuex管理已选城市:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

let defaultCity = '北京'

try {
    if (localStorage.city) {
      defaultCity = localStorage.city
    }
  } catch (e) {e}

const state = {
    count: 1,
    city: defaultCity
}

const mutations = { 
    changeCity (state, city) {
        state.city = city
        try {
          localStorage.city = city
        } catch (e) {e}
    }
}

const actions = { 

}

export default new Vuex.Store({
    state,
    mutations,
    actions
})
posted @ 2019-01-14 11:16  Mr.曹  阅读(4703)  评论(0编辑  收藏  举报