vue中城市页面的实现方法(带右侧字母滑动和点击)

本文介绍了如何在Vue.js项目中实现城市选择页面,包括引入必要的插件、父组件与子组件的代码结构、搜索功能、中间内容展示以及右侧字母模块的实现。同时,提供了city.json数据示例,展示了如何通过Ajax获取并使用城市数据,以支持用户按首字母快速筛选城市。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需要引入的插件

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">&#xe60e;</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”: “昌乐”
}, {

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值