更新记录

1.0.0(2025-09-26) 下载此版本

初版


平台兼容性

uni-app(4.51)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟

其他

多语言 暗黑模式 宽屏模式
× ×

Yo Select Location 城市选择组件

一个基于 uni-app 的城市选择组件,支持页面跳转模式,完美兼容 H5、小程序、APP 三大平台。

📚 文档导航

✨ 功能特性

  • 🚀 页面跳转模式:点击后进入独立城市选择页面
  • 🔍 智能搜索:支持城市名称模糊搜索
  • 📍 热门城市:支持热门城市快速选择
  • 🔤 字母索引:支持字母索引快速定位,滚动联动
  • 📱 响应式设计:完美适配各种屏幕尺寸
  • 🎨 高度自定义:可自定义样式和占位符文本
  • 🌐 多平台支持:H5、微信小程序、支付宝小程序、百度小程序、字节跳动小程序、QQ小程序、APP
  • 性能优化:JSON数据加载,条件编译优化
  • 🛠️ 错误处理:完善的错误处理和备用方案

🚀 快速开始

第一步:复制组件文件

uni_modules/yo-select-location 目录复制到你的 uni-app 项目的 uni_modules 目录下。

第二步:创建城市选择页面

在你的项目中创建 pages/city-select/city-select.vue 页面,直接复制以下完整代码:

<template>
  <view class="city-select-container">
    <!-- 搜索框 -->
    <view class="search-container">
      <input 
        v-model="searchKeyword" 
        placeholder="请输入城市名称" 
        class="search-input"
        @input="onSearchInput"
      />
    </view>

    <!-- 热门城市 -->
    <view v-if="showHotCities && hotCities.length > 0" class="hot-cities-section">
      <view class="section-title">热门城市</view>
      <view class="hot-cities">
        <view 
          v-for="city in hotCities" 
          :key="city.id"
          class="hot-city-item"
          @click="selectCity(city)"
        >
          {{ city.name }}
        </view>
      </view>
    </view>

    <!-- 城市列表 -->
    <scroll-view 
      class="city-list" 
      scroll-y 
      :scroll-into-view="scrollIntoView"
      @scroll=""
    >
      <view 
        v-for="(group, index) in filteredCityGroups" 
        :key="group.letter"
        :id="`letter-${group.letter}`"
        class="city-group"
      >
        <view class="group-header">{{ group.letter }}</view>
        <view 
          v-for="city in group.cities" 
          :key="city.id"
          class="city-item"
          @click="selectCity(city)"
        >
          {{ city.name }}
        </view>
      </view>
    </scroll-view>

    <!-- 字母索引 -->
    <view class="letter-index">
      <view 
        v-for="letter in letters" 
        :key="letter"
        class="letter-item"
        :class="{ active: currentLetter === letter }"
        @click="scrollToLetter(letter)"
      >
        {{ letter }}
      </view>
    </view>
  </view>
</template>

<script>
import { loadCityData, searchCities, getHotCities } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/city-data.js'
import { getCurrentCity, setTencentMapKey } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/location.js'

export default {
  data() {
    return {
      searchKeyword: '',
      cityGroups: [],
      filteredCityGroups: [],
      hotCities: [],
      showHotCities: true,
      letters: [],
      currentLetter: '',
      scrollIntoView: '',
      tencentMapKey: ''
    }
  },

  onLoad(options) {
    // 获取页面参数
    this.showHotCities = options.showHotCities !== 'false'
    this.tencentMapKey = options.tencentMapKey || ''

    // 配置API Key
    if (this.tencentMapKey) {
      setTencentMapKey(this.tencentMapKey)
    }

    this.initData()
  },

  methods: {
    async initData() {
      try {
        // 加载城市数据
        const cityData = await loadCityData()
        this.cityGroups = cityData.groups
        this.filteredCityGroups = this.cityGroups
        this.letters = cityData.letters
        this.hotCities = getHotCities()

        // 尝试获取当前位置
        if (this.tencentMapKey) {
          try {
            const currentCity = await getCurrentCity()
            console.log('当前位置:', currentCity)
          } catch (error) {
            console.log('定位失败:', error.message)
          }
        }
      } catch (error) {
        console.error('数据加载失败:', error)
        uni.showToast({
          title: '数据加载失败',
          icon: 'none'
        })
      }
    },

    onSearchInput() {
      if (this.searchKeyword.trim()) {
        const results = searchCities(this.searchKeyword)
        this.filteredCityGroups = results
        this.showHotCities = false
      } else {
        this.filteredCityGroups = this.cityGroups
        this.showHotCities = true
      }
    },

    selectCity(city) {
      // 通过事件总线发送城市选择事件
      uni.$emit('citySelected', city)

      // 返回上一页
      uni.navigateBack()
    },

    scrollToLetter(letter) {
      this.scrollIntoView = `letter-${letter}`
      this.currentLetter = letter
    },

    (e) {
      // 根据滚动位置更新当前字母
      // 这里可以添加更精确的字母检测逻辑
    }
  }
}
</script>

<style scoped>
.city-select-container {
  height: 100vh;
  display: flex;
  flex-direction: column;
  background-color: #f5f5f5;
}

.search-container {
  padding: 20rpx;
  background-color: #fff;
  border-bottom: 1rpx solid #eee;
}

.search-input {
  width: 100%;
  height: 80rpx;
  padding: 0 20rpx;
  border: 1rpx solid #ddd;
  border-radius: 40rpx;
  font-size: 28rpx;
}

.hot-cities-section {
  background-color: #fff;
  padding: 20rpx;
  margin-bottom: 20rpx;
}

.section-title {
  font-size: 28rpx;
  color: #666;
  margin-bottom: 20rpx;
}

.hot-cities {
  display: flex;
  flex-wrap: wrap;
  gap: 20rpx;
}

.hot-city-item {
  padding: 10rpx 20rpx;
  background-color: #f0f0f0;
  border-radius: 30rpx;
  font-size: 26rpx;
  color: #333;
}

.city-list {
  flex: 1;
  background-color: #fff;
}

.city-group {
  margin-bottom: 20rpx;
}

.group-header {
  padding: 20rpx;
  background-color: #f8f8f8;
  font-size: 24rpx;
  color: #999;
  font-weight: bold;
}

.city-item {
  padding: 30rpx 20rpx;
  border-bottom: 1rpx solid #f0f0f0;
  font-size: 28rpx;
  color: #333;
}

.city-item:active {
  background-color: #f0f0f0;
}

.letter-index {
  position: fixed;
  right: 20rpx;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 10rpx;
}

.letter-item {
  width: 40rpx;
  height: 40rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20rpx;
  color: #666;
  border-radius: 50%;
}

.letter-item.active {
  background-color: #007aff;
  color: #fff;
}
</style>

第三步:配置页面路由

pages.json 中添加城市选择页面:

{
  "pages": [
    {
      "path": "pages/city-select/city-select",
      "style": {
        "navigationBarTitleText": "选择城市"
      }
    }
  ]
}

第四步:配置腾讯地图API Key(可选)

获取API Key

  1. 访问 腾讯位置服务控制台
  2. 注册并登录账号
  3. 创建应用并获取API Key

配置方式

方式一:通过页面参数传递(推荐)

// 跳转时传递API Key
uni.navigateTo({
  url: `/pages/city-select/city-select?tencentMapKey=${encodeURIComponent('YOUR_API_KEY')}`
})

方式二:全局配置

// 在应用启动时配置
import { setTencentMapKey } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/location.js'

// 设置API Key
setTencentMapKey('YOUR_API_KEY')

方式三:使用配置文件

// 复制 config-example.js 为 config.js 并填入您的API Key
import { TENCENT_MAP_CONFIG } from './config.js'
import { setTencentMapKey } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/location.js'

setTencentMapKey(TENCENT_MAP_CONFIG.key)

注意事项

  • 如果不配置API Key,定位功能将不可用,城市选择功能也需要key
  • 建议在H5环境下配置代理以避免跨域问题
  • 小程序和APP环境需要在小程序后台和APP配置中添加相应的域名白名单

第五步:在页面中使用

<template>
  <view>
    <view class="city-select-trigger" @tap="openCitySelect">
      <text v-if="selectedCity">{{ selectedCity.t }}</text>
      <text v-else class="placeholder">请选择城市</text>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      selectedCity: null
    }
  },
  onLoad() {
    // 监听城市选择事件
    uni.$on('citySelected', this.onCitySelected)
  },
  onUnload() {
    // 移除事件监听
    uni.$off('citySelected', this.onCitySelected)
  },
  methods: {
    // 打开城市选择页面
    openCitySelect() {
      // 配置您的腾讯地图API Key
      const tencentMapKey = 'YOUR_TENCENT_MAP_KEY_HERE'

      uni.navigateTo({
        url: `/pages/city-select/city-select?tencentMapKey=${encodeURIComponent(tencentMapKey)}`
      })
    },

    // 城市选择完成
    onCitySelected(city) {
      this.selectedCity = city
      console.log('选中的城市:', city)
    }
  }
}
</script>

使用方式

基本使用

  1. 在需要城市选择的地方添加触发器
  2. 点击触发器跳转到城市选择页面
  3. 通过事件总线监听城市选择结果

自定义触发器样式

<template>
  <view class="custom-trigger" @tap="openCitySelect">
    <text class="trigger-text">选择城市</text>
    <text class="trigger-arrow">></text>
  </view>
</template>

<style>
.custom-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20rpx;
  background-color: #fff;
  border-radius: 10rpx;
}
</style>

🌐 平台支持

数据加载方式

平台 主要方案 备用方案 数据文件
H5 fetch API import 动态导入 city.data.jsonindex.city.data.js
小程序 uni.request require 静态导入 city.data.jsonindex.city.data.js
APP plus.io 文件系统 require 静态导入 city.data.jsonindex.city.data.js

平台特性

  • H5 平台:原生 fetch API,性能最佳
  • 微信小程序:uni.request + DOM查询备用方案
  • 支付宝小程序:uni.request 加载
  • 百度小程序:uni.request 加载
  • 字节跳动小程序:uni.request 加载
  • QQ小程序:uni.request 加载
  • APP平台:plus.io 文件系统,原生性能

📊 城市数据格式

数据来源

城市数据来源于 腾讯位置服务 API,包含全国 493 个城市的完整信息,数据准确可靠。

数据格式

{
  v: 110100,           // 城市编码
  t: '北京市',          // 城市名称
  lng: '116.413383',   // 经度
  lat: '39.910924'     // 纬度
}

数据特点

  • 数据来源:腾讯位置服务 API
  • 城市数量:493 个城市
  • 数据字段:城市编码、名称、经纬度坐标
  • 拼音索引:支持按拼音首字母分组和搜索
  • 坐标精度:高精度经纬度坐标,支持地图定位

📁 文件结构

yo-select-location/
├── pages/city-select/
│   └── city-select.vue          # 城市选择页面
├── static/data/
│   ├── city.data.json           # 主要数据文件(腾讯位置API数据)
│   └── index.city.data.js       # 备用数据文件(JS格式)
├── uni_modules/
│   └── yo-select-location/      # 组件文件
│       ├── components/
│       └── package.json
├── README.md                    # 项目说明
├── QUICK_START.md              # 5分钟快速上手
├── USAGE_EXAMPLES.md           # 使用示例
├── API_KEY_CONFIGURATION.md    # API Key配置指南
└── config-example.js           # 配置文件示例

🎯 事件说明

事件名 说明 回调参数
citySelected 城市选择完成时触发(通过事件总线) city: 选中的城市对象

⚙️ 配置说明

1. 腾讯地图API Key配置

获取API Key

  1. 访问 腾讯位置服务控制台
  2. 注册并登录账号
  3. 创建应用并获取API Key

配置方式

方式一:通过页面参数传递(推荐)

// 跳转时传递API Key
uni.navigateTo({
  url: `/pages/city-select/city-select?tencentMapKey=${encodeURIComponent('YOUR_API_KEY')}`
})

方式二:全局配置

// 在应用启动时配置
import { setTencentMapKey } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/location.js'

// 设置API Key
setTencentMapKey('YOUR_API_KEY')

方式三:使用配置文件

// 复制 config-example.js 为 config.js 并填入您的API Key
import { TENCENT_MAP_CONFIG } from './config.js'
import { setTencentMapKey } from '@/uni_modules/yo-select-location/components/yo-select-location/utils/location.js'

setTencentMapKey(TENCENT_MAP_CONFIG.key)

注意事项

  • 如果不配置API Key,定位功能将不可用,但城市选择功能仍然正常
  • 建议在H5环境下配置代理以避免跨域问题
  • 小程序和APP环境需要在小程序后台和APP配置中添加相应的域名白名单

2. 数据文件配置

  • 主要文件/static/data/city.data.json(腾讯位置API数据)
  • 备用文件/static/data/index.city.data.js
  • 文件格式:JSON 优先,JS 作为备用
  • 数据来源:腾讯位置服务 API,包含 493 个城市信息

3. 页面路由配置

pages.json 中添加城市选择页面:

{
  "pages": [
    {
      "path": "pages/city-select/city-select",
      "style": {
        "navigationBarTitleText": "选择城市"
      }
    }
  ]
}

4. 小程序域名配置

在微信小程序中,需要在 manifest.json 中配置域名白名单(如果使用网络请求):

{
  "mp-weixin": {
    "permission": {
      "scope.userLocation": {
        "desc": "你的位置信息将用于小程序位置接口的效果展示"
      }
    }
  }
}

🛠️ 注意事项

  1. 数据文件:确保 static/data/city.data.json 文件存在(腾讯位置API数据)
  2. 页面路由:城市选择页面需要正确配置路由
  3. 事件监听:使用事件总线监听城市选择结果
  4. 样式自定义:触发器样式可以完全自定义
  5. 平台兼容:不同平台使用不同的数据加载方式
  6. 错误处理:包含完整的错误处理和备用方案
  7. 数据更新:如需更新城市数据,请使用腾讯位置服务 API 获取最新数据

📈 性能优化

  • 条件编译:不同平台使用最优的加载方式
  • JSON数据:文件更小,加载更快
  • 备用方案:确保数据加载的可靠性
  • 错误处理:完善的错误提示和降级处理

🔧 调试说明

开启调试模式

在浏览器开发者工具中查看控制台输出:

// 数据加载日志
H5平台使用fetch加载JSON文件
H5平台JSON数据加载成功: [城市数据]

// 滚动联动日志
滚动事件触发: {isSearching: false, scrollTop: xxx}
找到匹配字母: A
字母状态更新: {from: "", to: "A"}

常见问题排查

  1. 数据加载失败:检查文件路径和格式
  2. 滚动联动不准确:查看控制台调试日志
  3. 平台兼容问题:确认条件编译配置正确

📝 更新日志

v3.0.0 (最新)

  • ✅ 完美支持 H5、小程序、APP 三大平台
  • ✅ 优化数据加载方式,使用腾讯位置API数据
  • ✅ 修复 APP 平台数据加载问题
  • ✅ 优化微信小程序滚动联动
  • ✅ 添加完善的错误处理和备用方案
  • ✅ 性能优化和调试功能增强
  • ✅ 数据源升级为腾讯位置服务 API,包含 493 个城市

v2.0.0

  • 从弹层模式改为页面跳转模式
  • 简化组件结构,提高性能
  • 支持自定义城市选择页面路径
  • 优化用户体验

🤝 贡献指南

欢迎提交 Issue 和 Pull Request 来帮助改进这个组件!

📚 相关文档

📄 许可证

MIT License

隐私、权限声明

1. 本插件需要申请的系统权限列表:

none

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

none

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

none

许可协议

MIT协议