需要引入的插件
npm install better-scroll --save
npm install axios --save
父组件代码
<template>
<div>
<city-header></city-header>
<city-serach :cities='cities'></city-serach>
<city-list :cities='cities' :hot='hotCities' :letter='letter'></city-list>
<city-alpha :cities='cities' @change='handlechange'></city-alpha>
</div>
</template>
<script>
import axios from 'axios'
import CityHeader from './components/header.vue'
import CitySerach from './components/serach.vue'
import CityList from './components/list.vue'
import CityAlpha from './components/alpha.vue'
export default {
name: 'City',
components: {
CityHeader,
CitySerach,
CityList,
CityAlpha
},
data () {
return {
cities: {},
hotCities: [],
letter: ''
}
},
methods: {
cityinfo () {
axios.get('/api/city.json').then(this.cityhello)
},
cityhello (res) {
res = res.data
if (res.ret && res.data) {
this.cities = res.data.cities
this.hotCities = res.data.hotCities
}
},
handlechange (letter) {
this.letter = letter
}
},
mounted () {
this.cityinfo()
}
}
</script>
<style lang="stylus" scoped="">
</style>
子组件Header
<template>
<div class="header">
<router-link to="/">
<div class="iconfont header-icon"></div>
</router-link>
城市选择
</div>
</template>
<script>
export default {
name: 'CityHeader'
}
</script>
<style lang="stylus" scoped="">
@import '~@/assets/styles/variable.styl'
.header
position: relative
height: .84rem
line-height: .84rem
text-align: center
overflow: hidden
color: #fff
background-color: $bgcolor
font-size: .32rem
letter-spacing: .03rem
.header-icon
position: absolute
top: 0
left: .01rem
font-size: .4rem
width: .4rem
color: #fff
</style>
子组件serach部分
<template>
<div>
<div class="serach">
<input type="text" v-model="keyword" class="serach-input" placeholder="输入城市名或拼音">
</div>
<div class="serach-content" ref='serach' v-show="keyword">
<ul>
<li class="serach-item border-bottom"
v-for="item of list"
:key='item.id'
@click="handleclick(item.name)">
{
{item.name}}
</li>
<li class="serach-item border-bottom" v-show="hasNodata">未找到匹配项</li>
</ul>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
import { mapMutations } from 'vuex'
export default {
name: 'CitySerach',
props: {
cities: Object
},
data () {
return {
keyword: '',
list: [],
timer: null
}
},
methods: {
handleclick (city) {
this.changecity(city)
this.$router.push('/')
},
...mapMutations(['changecity'])
},
mounted () {
this.scroll = new Bscroll(this.$refs.serach, {
click: true
})
},
computed: {
hasNodata () {
return !this.list.length
}
},
watch: {
keyword () {
if (this.timer) {
clearTimeout(this.timer)
}
if (!this.keyword) {
this.list = []
return this.list
}
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)
}
}
}
</script>
<style lang="stylus" scoped="">
@import '~@/assets/styles/variable.styl'
.serach
height: .72rem
background-color: $bgcolor
padding: 0 .1rem
.serach-input
width: 100%
height: .62rem
line-height: .62rem
text-align: center
border-radius: .04rem
padding: 0 1rem
box-sizing: border-box
color: #25A4BB
.serach-content
z-index: 1
overflow: hidden
position: absolute
top:1.56rem
right: 0
left: 0
bottom:0
background-color: #00BCD4
.serach-item
line-height: .62rem
padding-left: .2rem
color: #666
background-color: #ccc
</style>
子组件middle部分
<template>
<div class="citylist" ref="wrapper">
<div>
<div class="area">
<div class="title border-topbottom">当前城市</div>
<div class="city-header">
<div class="city-center">
<div class="city-name">{
{this.currientcity}}</div>
</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">热门城市</div>
<div class="city-header">
<div class="city-center" v-for="item of hot" :key='item.id' @click="handleclick(item.name)">
<div class="city-name">{
{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="handleclick(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',
props: {
cities: Object,
hot: Array,
letter: String
},
computed: {
...mapState({
currientcity: 'city'
})
},
methods: {
handleclick (city) {
this.changecity(city)
this.$router.push('/')
},
...mapMutations(['changecity'])
},
mounted () {
this.scroll = new Bscroll(this.$refs.wrapper, { click: true })
},
watch: {
letter () {
if (this.letter) {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
}
}
}
</script>
<style lang="stylus" scoped="">
.border-topbottom
&:before
border-color: #ccc
&:after
border-color: #ccc
.border-bottom
&:before
border-color: #ccc
.citylist
position: absolute
top: 1.58rem
left: 0
right: 0
bottom: 0
overflow: hidden
.title
width: 100%
height: .44rem
line-height: .44rem
background-color: #eee
padding-left: .2rem
color: #666
font-size: .26rem
.city-header
padding: .1rem .6rem .1rem .1rem
overflow: hidden
.city-center
width: 33.33%
float: left
.city-name
margin: .1rem
border: .02rem solid #CCCCCC
text-align: center
padding: .1rem 0
border-radius: .06rem
.item-list
.item
line-height: .84rem
padding-left: .2rem
</style>
子组件右侧字母模块
<template>
<div class="alpha">
<div class="item"
v-for="item of letter"
:key='item'
@touchstart.prevent='handletouchstart'
@touchmove='handletouchmove'
@touchend='handletouchend'
@click='handleclick'
:ref='item'
>
{
{item}}
</div>
</div>
</template>
<script>
export default {
name: 'CityAlpha',
data () {
return {
touchStatus: false,
startY: 0,
timer: null
}
},
props: {
cities: Object
},
computed: {
letter () {
const letter = []
for (let i in this.cities) {
letter.push(i)
}
return letter
}
},
methods: {
handleclick (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 step = this.$refs['B'][0].offsetTop - this.$refs['A'][0].offsetTop
const topY = event.touches[0].target.offsetParent.offsetTop
const touchY = event.touches[0].clientY - topY
const index = Math.floor((touchY - this.startY) / step)
if (index >= 0 && index <= this.letter.length) {
this.$emit('change', this.letter[index])
}
// }, 16)
}
},
handletouchend () {
this.touchStatus = false
}
},
updated () {
this.startY = this.$refs['A'][0].offsetTop
}
}
</script>
<style lang="stylus" scoped="">
@import '~@/assets/styles/variable.styl'
.alpha
position: absolute
top: 1.58rem
right: 0
bottom: 0
width: .4rem
display: flex
flex-direction: column
justify-content: center
.item
text-align: center
line-height: .4rem
color: #00BCD4
</style>
结果
## 附上city.json,可通过ajax获取其中的数据
{
“ret”: true,
“data”:{
“hotCities”: [{
“id”: 1,
“spell”: “beijing”,
“name”: “北京”
}, {
“id”: 3,
“spell”: “shanghai”,
“name”: “上海”
}, {
“id”: 47,
“spell”: “xian”,
“name”: “西安”
}, {
“id”: 239,
“spell”: “sanya”,
“name”: “三亚”
}, {
“id”: 188,
“spell”: “lijiang”,
“name”: “丽江”
}, {
“id”: 125,
“spell”: “guilin”,
“name”: “桂林”
}],
“cities”: {
“A”: [{
“id”: 56,
“spell”: “aba”,
“name”: “阿坝”
}, {
“id”: 57,
“spell”: “akesu”,
“name”: “阿克苏”
}, {
“id”: 58,
“spell”: “alashanmeng”,
“name”: “阿拉善盟”
}, {
“id”: 59,
“spell”: “aletai”,
“name”: “阿勒泰”
}, {
“id”: 60,
“spell”: “ali”,
“name”: “阿里”
}, {
“id”: 61,
“spell”: “ankang”,
“name”: “安康”
}, {
“id”: 62,
“spell”: “anqing”,
“name”: “安庆”
}, {
“id”: 63,
“spell”: “anshan”,
“name”: “鞍山”
}, {
“id”: 64,
“spell”: “anshun”,
“name”: “安顺”
}, {
“id”: 65,
“spell”: “anyang”,
“name”: “安阳”
}, {
“id”: 338,
“spell”: “acheng”,
“name”: “阿城”
}, {
“id”: 339,
“spell”: “anfu”,
“name”: “安福”
}, {
“id”: 340,
“spell”: “anji”,
“name”: “安吉”
}, {
“id”: 341,
“spell”: “anning”,
“name”: “安宁”
}, {
“id”: 342,
“spell”: “anqiu”,
“name”: “安丘”
}, {
“id”: 343,
“spell”: “anxi”,
“name”: “安溪”
}, {
“id”: 344,
“spell”: “anyi”,
“name”: “安义”
}, {
“id”: 345,
“spell”: “anyuan”,
“name”: “安远”
}],
“B”: [{
“id”: 1,
“spell”: “beijing”,
“name”: “北京”
}, {
“id”: 66,
“spell”: “baicheng”,
“name”: “白城”
}, {
“id”: 67,
“spell”: “baise”,
“name”: “百色”
}, {
“id”: 68,
“spell”: “baishan”,
“name”: “白山”
}, {
“id”: 69,
“spell”: “baiyin”,
“name”: “白银”
}, {
“id”: 70,
“spell”: “bangbu”,
“name”: “蚌埠”
}, {
“id”: 71,
“spell”: “baoding”,
“name”: “保定”
}, {
“id”: 72,
“spell”: “baoji”,
“name”: “宝鸡”
}, {
“id”: 73,
“spell”: “baoshan”,
“name”: “保山”
}, {
“id”: 74,
“spell”: “baotou”,
“name”: “包头”
}, {
“id”: 75,
“spell”: “bayannaoer”,
“name”: “巴彦淖尔”
}, {
“id”: 76,
“spell”: “bayinguoleng”,
“name”: “巴音郭楞”
}, {
“id”: 77,
“spell”: “bazhong”,
“name”: “巴中”
}, {
“id”: 78,
“spell”: “beihai”,
“name”: “北海”
}, {
“id”: 79,
“spell”: “benxi”,
“name”: “本溪”
}, {
“id”: 80,
“spell”: “bijie”,
“name”: “毕节”
}, {
“id”: 81,
“spell”: “binzhou”,
“name”: “滨州”
}, {
“id”: 82,
“spell”: “boertala”,
“name”: “博尔塔拉”
}, {
“id”: 83,
“spell”: “bozhou”,
“name”: “亳州”
}, {
“id”: 346,
“spell”: “baoying”,
“name”: “宝应”
}, {
“id”: 347,
“spell”: “bayan”,
“name”: “巴彦”
}, {
“id”: 348,
“spell”: “binhai”,
“name”: “滨海”
}, {
“id”: 349,
“spell”: “binxian”,
“name”: “宾县”
}, {
“id”: 350,
“spell”: “binyang”,
“name”: “宾阳”
}, {
“id”: 351,
“spell”: “bishan”,
“name”: “璧山”
}, {
“id”: 352,
“spell”: “boai”,
“name”: “博爱”
}, {
“id”: 353,
“spell”: “boluo”,
“name”: “博罗”
}, {
“id”: 354,
“spell”: “boxing”,
“name”: “博兴”
}],
“C”: [{
“id”: 2,
“spell”: “chongqing”,
“name”: “重庆”
}, {
“id”: 5,
“spell”: “changchun”,
“name”: “长春”
}, {
“id”: 6,
“spell”: “changsha”,
“name”: “长沙”
}, {
“id”: 7,
“spell”: “changzhou”,
“name”: “常州”
}, {
“id”: 8,
“spell”: “chengdu”,
“name”: “成都”
}, {
“id”: 84,
“spell”: “cangzhou”,
“name”: “沧州”
}, {
“id”: 85,
“spell”: “changde”,
“name”: “常德”
}, {
“id”: 86,
“spell”: “changdu”,
“name”: “昌都”
}, {
“id”: 87,
“spell”: “changji”,
“name”: “昌吉”
}, {
“id”: 88,
“spell”: “changzhi”,
“name”: “长治”
}, {
“id”: 89,
“spell”: “chaohu”,
“name”: “巢湖”
}, {
“id”: 90,
“spell”: “chaoyang”,
“name”: “朝阳”
}, {
“id”: 91,
“spell”: “chaozhou”,
“name”: “潮州”
}, {
“id”: 92,
“spell”: “chengde”,
“name”: “承德”
}, {
“id”: 93,
“spell”: “chenzhou”,
“name”: “郴州”
}, {
“id”: 94,
“spell”: “chifeng”,
“name”: “赤峰”
}, {
“id”: 95,
“spell”: “chizhou”,
“name”: “池州”
}, {
“id”: 96,
“spell”: “chongzuo”,
“name”: “崇左”
}, {
“id”: 97,
“spell”: “chuxiong”,
“name”: “楚雄”
}, {
“id”: 98,
“spell”: “chuzhou”,
“name”: “滁州”
}, {
“id”: 355,
“spell”: “cangnan”,
“name”: “苍南”
}, {
“id”: 356,
“spell”: “cangshan”,
“name”: “苍山”
}, {
“id”: 357,
“spell”: “caoxian”,
“name”: “曹县”
}, {
“id”: 358,
“spell”: “changdao”,
“name”: “长岛”
}, {
“id”: 359,
“spell”: “changfeng”,
“name”: “长丰”
}, {
“id”: 360,
“spell”: “changhai”,
“name”: “长海”
}, {
“id”: 361,
“spell”: “changle”,
“name”: “长乐”
}, {
“id”: 362,
“spell”: “changle”,
“name”: “昌乐”
}, {