微信小程序全国城市按首字母检索

最近做在做一个小程序项目,需要用到全国城市列表的选择,然后通过城市的首字母快速定位到要选择的城市,那么今天就记录下实现的过程,最终的效果如图:


timer.gif

整体结构是,左侧是城市的首字母和与首字母开头的所有城市的列表,右侧是一个城市首字母的快速定位列表,中间显示的白色阴影框中的大字母即为当前选中的字母。
实现步骤:

一、获取到全国城市的数据

1、通过腾讯地图接口获取数据 https://lbs.qq.com/qqmap_wx_jssdk/method-getcitylist.html
2、直接下载本地使用:https://raw.githubusercontent.com/749264345/wechat-miniapp-map/master/libs/city.js
本人就是采用下载本地js文件使用,具体看个人需求

二、页面布局:

页面布局相信大家都会了,直接上代码

<view class="city-page">
    <scroll-view class="city-scrollView" scroll-y="true" scroll-into-view="{{scrollTopId}}"
        scroll-with-animation="true" enable-back-to-top="true" bindscroll="handleScroll">
        <view class="city-item" id="current">
            <view class="city-sub-item city-text v-flex city-sub-location" bindtap="handleClickLocation">
                <image src="{{images.location}}" mode="widthFix" class="location-image"></image>
                当前位置
                <text class="city-sub-text">{{locationCity}}</text>
            </view>
        </view>
        <view class="city-item listGroup" wx:for="{{cityList}}" wx:for-index="idx" wx:for-item="group" wx:key="key">
            <view class="city-sub-item city-py" id="{{idx}}">{{idx}}</view>
            <view class="city-sub-item city-text" wx:for="{{group}}" wx:key="key" 
                data-fullname="{{item.fullname}}" data-lat="{{item.location.lat}}" 
                data-lng="{{item.location.lng}}" bindtap="selectCity">{{item.fullname}}</view>
        </view>
    </scroll-view>
    
    <!-- 右侧字母表 -->
    <view class="city-py-label">
        <view wx:for="{{cityPy}}" wx:key="index" data-id="{{item}}" data-index="{{index}}"
            bindtouchstart="handleTouchStart" bindtouchend="handleTouchEnd" bindtouchmove="handleTouchMove"
            class="{{currentIndex==index?'text-primary':''}}">{{item}}</view>
    </view>
    
    <!-- 当前触摸的字母 -->
    <view class="v-flex v-shadow city-single-py" wx:if="{{hidden}}">
        <view>{{scrollTopId}}</view>
    </view>
</view>

因为需要滚动列表,所以必须用小程序的标签scroll-view布局,
scroll-y="true":表示y轴方向上滚动
scroll-into-view="{{scrollTopId}}" : 设置滚动到某个元素
scroll-with-animation="true" : 在设置滚动条位置时使用动画过渡
具体其他属性值可参考微信小程序官方文档
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html

page,.city-page{
    width: 100%;
    height: 100%;
    overflow: hidden;
}
.city-scrollView{
    width: 100%;
    height: 100%;
}
.city-item{
    width: 100%;
}
.city-sub-location{
    align-items: center;
}
.city-sub-item{
    width: 100%;
    height: 80rpx;
    line-height: 80rpx;
    padding: 0 30rpx;
    box-sizing: border-box;
    border-bottom: 1px solid #f5f4f3;
}
.location-image{
    width: 38rpx;
    height: 38rpx;
    margin-right: 20rpx;
}
.city-sub-text{
    color: #333;
    font-weight: bold;
    display: block;
    margin-left: 36rpx;
}
.city-text{
    background: #fff;
}
.city-py{
    /* background: #e8e8e8; */
}
.city-py-label{
    width: 100rpx;
    height: 100%;
    position: absolute;
    display: flex;
    flex-direction: column;
    justify-content: center;
    top: 0;
    right: 0;
}
.city-py-label>view{
    text-align: center;
    height: 20px;
    line-height: 20px;
}
.city-single-py{
    position: absolute;
    width: 145rpx;
    height: 145rpx;
    justify-content: center;
    align-items: center;
    z-index: 2;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%,-50%,0);
}
.city-single-py>view{
    width: 140rpx;
    height: 140rpx;
    border-radius: 5rpx;
    background: #fff;
    text-align: center;
    line-height: 140rpx;
    color: #333;
    font-weight: bold;
    font-size: 40rpx;
    border: 1px solid #f5f4f3;
}

三、js实现逻辑

引入全国城市列表数据,并在data初始化,在页面渲染数据

const city = require('./../../../utils/city')
Page({
  /**
   * 页面的初始数据
   */
  data: {
      images: {
          location: app.globalData.imagePath + "icon_location.png"
      },
      cityList: city.all, //所有城市列表
      cityPy: '', //右侧首字母列表
      currentIndex: 0, //当前显示的字母index
      hidden: false, //是否隐藏当前显示中间大字母提示
      scrollTopId: 'current', //滚动到当前的字母城市
      locationCity: ''
  }
})

在生命周期函数onShow遍历cityList得到所有首字母数据并存放早cityPy数据里,在页面渲染,以供后面使用

/**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
      let cityPy = []
      // 遍历城市数据,把字母放到cityPy数据里
      for(var key in this.data.cityList){
          cityPy.push(key)
      }
      this.setData({
          cityPy: cityPy
      })
}

接下来当点击右侧字母表的时候滚动到当前所点击对应首字母的城市,
点击字母,将当前的index序号和对应的首字母传过来,并通过设置scroll-into-view的值就可以滚动到对应字母城市,(并记录当前触摸的clientY的值,接下来会用到)

// 触摸开始
  handleTouchStart(e) {
      // 首次获取到clientY的值并记录为y1
      var y = e.touches[0].clientY
      this.touches.y1 = y
      // 获取到当前的index,并记录在touches里
      this.touches.anchorIndex = e.currentTarget.dataset.index
      // 把当前点击的字母显示在屏幕中央
      this.setData({
          hidden: true,
          scrollTopId: e.currentTarget.dataset.id,
          currentIndex: e.currentTarget.dataset.index
      })
  },

滑动时获取到当前的clientY的值存放到touches里,这时候要用到handleTouchStart时存放在touches里的y1了,从开始触摸到滑动一段距离,那么y2-y1就是滑动的距离,然后右侧字母列表的高度是我们设置的,比如我设置了20px的高度,那么(y2-y1)/2再取整就是当前所滑动的个数了,即滑动了多少个字母的高度,再加上touchstart触摸时的index即为当前所滑动目标的字母的序号anchorIndex ,再由这个anchorIndex 在cityPy数组中找到对应序号的字母,从而滚动到该位置的城市。

// 滑动触摸
  handleTouchMove(e){
      // 滑动过程中获取当前的clientY值并记录为y2
      var y = e.touches[0].clientY
      this.touches.y2 = y
      // 根据y2-y1得到所滑动的距离除以每个字母的高度20得到字母的个数,
      // 加上第一次获取的anchorindex得到当前的序号index
      const delt = (this.touches.y2 - this.touches.y1) / 20 | 0
      let anchorIndex = this.touches.anchorIndex + delt
      // 由当前的序号index在字母表数组中找到字母并显示在屏幕中
      this.setData({
          hidden: true,
          scrollTopId: this.data.cityPy[anchorIndex],
          currentIndex: anchorIndex
      })
  },
  // 触摸结束
  handleTouchEnd() {
      setTimeout(() =>{
          this.setData({
              hidden: false,
          })
      }, 0)
  },

通过上的计算我们的功能已经实现了一大半了,但是一个完整的功能应该是当你点击字母列表或者触摸滑动字母列表的时候选中的当前字母应该高亮,同样当你滑动或者滚动左侧城市列表的时候,滚动到哪个区域应该高亮显示对应的首字母,那么我们就来实现这个功能:

<view class="city-item listGroup" wx:for="{{cityList}}" wx:for-index="idx" wx:for-item="group" wx:key="key">
            <view class="city-sub-item city-py" id="{{idx}}">{{idx}}</view>
            <view class="city-sub-item city-text" wx:for="{{group}}" wx:key="key" 
                data-fullname="{{item.fullname}}" data-lat="{{item.location.lat}}" 
                data-lng="{{item.location.lng}}" bindtap="selectCity">{{item.fullname}}</view>
        </view>

首先页面加载的时候执行_calculateHeight()方法,必须要在城市渲染完在页面里才能执行,因为这是计算dom节点高度的方法。通过这个方法可以获取到所有listGroup距离容器顶部的距离,并保存在listHeight数组

然后滑动左侧城市列表时遍历listHeight数组,看当前滚动的长度scrollTop(scrollTop是由小程序bindscroll方法提供的数值)是否落在listHeight数组某个数据区域高度内,然后得到对应的序号index

// 计算listGroup高度
  _calculateHeight() {
      const that = this
      this.listHeight = []
      let height = 0
      this.listHeight.push(height)
      wx.createSelectorQuery().selectAll('.listGroup').boundingClientRect(function(rects){
          rects.forEach(function(rect){
              height += rect.height
              that.listHeight.push(height)
          })
      }).exec()
  },
  // 滚动时触发
  handleScroll(e) {
      let scrollTop = e.detail.scrollTop
      const listHeight = this.listHeight
      // 遍历listHeight数据,如果当前的scrollTop大于height1小于height2时
      // 说明当前滚到到这个字母城市区域,获取到当前的索引i值
      for(var i=0;i<listHeight.length;i++){
          let height1 = listHeight[i]
          let height2 = listHeight[i + 1]
          if(scrollTop > height1 && scrollTop < height2){
              this.setData({
                  currentIndex: i
              })
          }
      }
  },

到这里我们的代码就实现完了,最后贴上完整的js代码

// pages/index/cityList/city.js
const city = require('./../../../utils/city')
const app = getApp()
Page({

  /**
   * 页面的初始数据
   */
  data: {
      images: {
          location: app.globalData.imagePath + "icon_location.png"
      },
      cityList: city.all, //所有城市列表
      cityPy: '', //右侧首字母列表
      currentIndex: 0, //当前显示的字母index
      hidden: false, //是否隐藏当前显示中间大字母提示
      scrollTopId: 'current', //滚动到当前的字母城市
      locationCity: ''
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
      this.listHeight = []
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
      // 触摸左侧字母表所用的数据
      this.touches = {}
      let cityPy = []
      // 遍历城市数据,把字母放到cityPy数据里
      for(var key in this.data.cityList){
          cityPy.push(key)
      }
      this.setData({
          cityPy: cityPy
      })
      
      // 获取当前定位城市
      this.setData({
          locationCity: app.globalData.locationInfo.fullname
      })
      
      // 页面显示后计算listGroup的高度
      this._calculateHeight()
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  },
  /**
   * 自定义事件
   */
  // 点击选择城市
  selectCity(e) {
      const dataset = e.currentTarget.dataset
      app.globalData.selectCityInfo = {
          fullname: dataset.fullname,
          lat: dataset.lat,
          lng: dataset.lng
      }
      wx.navigateBack()
  },
  // 触摸开始
  handleTouchStart(e) {
      // 首次获取到clientY的值并记录为y1
      var y = e.touches[0].clientY
      this.touches.y1 = y
      // 获取到当前的index,并记录在touches里
      this.touches.anchorIndex = e.currentTarget.dataset.index
      // 把当前点击的字母显示在屏幕中央
      this.setData({
          hidden: true,
          scrollTopId: e.currentTarget.dataset.id,
          currentIndex: e.currentTarget.dataset.index
      })
  },
  // 滑动触摸
  handleTouchMove(e){
      // 滑动过程中获取当前的clientY值并记录为y2
      var y = e.touches[0].clientY
      this.touches.y2 = y
      // 根据y2-y1得到所滑动的距离除以每个字母的高度20得到字母的个数,
      // 加上第一次获取的anchorindex得到当前的序号index
      const delt = (this.touches.y2 - this.touches.y1) / 20 | 0
      let anchorIndex = this.touches.anchorIndex + delt
      // 由当前的序号index在字母表数组中找到字母并显示在屏幕中
      this.setData({
          hidden: true,
          scrollTopId: this.data.cityPy[anchorIndex],
          currentIndex: anchorIndex
      })
  },
  // 触摸结束
  handleTouchEnd() {
      setTimeout(() =>{
          this.setData({
              hidden: false,
          })
      }, 0)
  },
  // 获取定位地址
  handleClickLocation() {
      app.getLocation(()=>{
          wx.navigateBack()
      })
  },
  
  // 计算listGroup高度
  _calculateHeight() {
      const that = this
      this.listHeight = []
      let height = 0
      this.listHeight.push(height)
      wx.createSelectorQuery().selectAll('.listGroup').boundingClientRect(function(rects){
          rects.forEach(function(rect){
              height += rect.height
              that.listHeight.push(height)
          })
      }).exec()
  },
  // 滚动时触发
  handleScroll(e) {
      let scrollTop = e.detail.scrollTop
      const listHeight = this.listHeight
      // 遍历listHeight数据,如果当前的scrollTop大于height1小于height2时
      // 说明当前滚到到这个字母城市区域,获取到当前的索引i值
      for(var i=0;i<listHeight.length;i++){
          let height1 = listHeight[i]
          let height2 = listHeight[i + 1]
          if(scrollTop > height1 && scrollTop < height2){
              this.setData({
                  currentIndex: i
              })
          }
      }
  },
  
})
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容