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