当前位置: 首页 > news >正文

实现一个优雅的城市选择器组件 - Uniapp实战

本文将详细介绍如何使用Uniapp实现一个功能完善的城市选择器组件,包含字母索引、搜索功能和良好的用户体验。
组件概述

这个城市选择器组件主要包含以下功能:

  • 按字母分组显示城市列表
  • 右侧字母索引快速导航
  • 城市搜索功能
  • 平滑滚动和动画效果
  • 触摸交互反馈

核心代码实现

模板结构

<template><view class="city-selector"><!-- 触发按钮 --><view class="select-trigger" @click="showSelector"><text>{{ selectedCity || '选择城市' }}</text><uni-icons type="arrowdown" size="16" color="#999"></uni-icons></view><!-- 城市选择弹窗 --><uni-popup ref="popup" type="bottom" :safe-area="false"><view class="city-popup"><!-- 搜索框 --><view class="search-box"><uni-icons type="search" size="18" color="#999"></uni-icons><input class="search-input" placeholder="搜索城市" v-model="searchText"@input="onSearch"/><text class="cancel-btn" @click="closePopup">取消</text></view><!-- 城市列表 --><scroll-view class="city-list" scroll-y :scroll-into-view="scrollToId":scroll-with-animation="true"><!-- 搜索结果 --><view v-if="searchText" class="search-result"><view v-for="(city, index) in filteredCities" :key="index"class="city-item"@click="selectCity(city)">{{ city }}</view><view v-if="filteredCities.length === 0" class="no-result">未找到相关城市</view></view><!-- 按字母分组列表 --><view v-else><view v-for="(group, index) in cityData" :key="group.letter":id="'group-' + group.letter"class="city-group"><view class="group-title">{{ group.letter }}</view><view v-for="(city, cityIndex) in group.cities" :key="cityIndex"class="city-item"@click="selectCity(city)">{{ city }}</view></view></view></scroll-view><!-- 字母索引栏 --><view class="index-bar" v-if="!searchText"><view v-for="(item, index) in indexList" :key="index"class="index-item"@touchstart="onIndexTouchStart(item.letter)"@touchmove="onIndexTouchMove"@touchend="onIndexTouchEnd">{{ item.letter }}</view></view><!-- 当前选中字母提示 --><view class="index-tip" v-if="currentIndexTip">{{ currentIndexTip }}</view></view></uni-popup></view>
</template>

脚本部分

<script>
export default {data() {return {selectedCity: '',searchText: '',scrollToId: '',currentIndexTip: '',indexList: [],cityData: [],filteredCities: [],firstWordList: ["A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "W", "X", "Y", "Z"],areaNameList: [["阿拉善盟", "鞍山市", "安庆市", "安阳市", "安顺市", "阿里地区", "安康市", "澳门", "阿拉尔市"],["北京市", "保定市", "包头市", "本溪市", "白山市", "白城市", "蚌埠市", "滨州市", "北海市", "百色市", "巴中市", "保山市", "宝鸡市", "白银市", "北区", "毕节市", "北屯市"],// 其他城市数据...]};},mounted() {this.processCityData();},methods: {// 处理城市数据processCityData() {this.cityData = [];this.indexList = [];this.firstWordList.forEach((letter, index) => {const cities = this.areaNameList[index] || [];if (cities.length > 0) {this.cityData.push({letter,cities});this.indexList.push({letter});}});},// 显示选择器showSelector() {this.$refs.popup.open();this.searchText = '';this.filteredCities = [];},// 关闭弹窗closePopup() {this.$refs.popup.close();},// 搜索城市onSearch() {if (!this.searchText) {this.filteredCities = [];return;}const keyword = this.searchText.toLowerCase();this.filteredCities = [];this.cityData.forEach(group => {group.cities.forEach(city => {if (city.toLowerCase().includes(keyword)) {this.filteredCities.push(city);}});});},// 选择城市selectCity(city) {this.selectedCity = city;this.closePopup();this.$emit('select', city);},// 字母索引触摸开始onIndexTouchStart(letter) {this.currentIndexTip = letter;this.scrollToId = `group-${letter}`;},// 字母索引触摸移动onIndexTouchMove(e) {if (!this.indexList.length) return;const query = uni.createSelectorQuery().in(this);query.select('.index-bar').boundingClientRect(data => {const barTop = data.top;query.select('.index-bar').node(res => {const touchY = e.touches[0].clientY;const index = Math.floor((touchY - barTop) / (data.height / this.indexList.length));if (index >= 0 && index < this.indexList.length) {const letter = this.indexList[index].letter;this.currentIndexTip = letter;this.scrollToId = `group-${letter}`;}}).exec();}).exec();},// 字母索引触摸结束onIndexTouchEnd() {setTimeout(() => {this.currentIndexTip = '';}, 500);}}
};
</script>

样式部分

<style lang="scss" scoped>
.city-selector {.select-trigger {display: flex;align-items: center;justify-content: space-between;padding: 12px 16px;background-color: #f5f5f5;border-radius: 6px;font-size: 14px;}
}.city-popup {height: 70vh;background-color: #fff;border-top-left-radius: 16px;border-top-right-radius: 16px;overflow: hidden;display: flex;flex-direction: column;.search-box {display: flex;align-items: center;padding: 12px 16px;border-bottom: 1px solid #eee;.search-input {flex: 1;height: 36px;padding: 0 12px;margin: 0 10px;background-color: #f5f5f5;border-radius: 18px;font-size: 14px;}.cancel-btn {color: #007aff;font-size: 14px;}}.city-list {flex: 1;.search-result {padding: 0 16px;}.city-group {.group-title {padding: 8px 16px;background-color: #f5f5f5;color: #666;font-size: 14px;}.city-item {padding: 12px 16px;border-bottom: 1px solid #f0f0f0;font-size: 16px;&:active {background-color: #f0f0f0;}}}.no-result {padding: 20px;text-align: center;color: #999;}}.index-bar {position: absolute;right: 0;top: 60px;bottom: 0;display: flex;flex-direction: column;justify-content: center;padding: 0 8px;.index-item {font-size: 10px;color: #007aff;text-align: center;padding: 1px 0;}}.index-tip {position: absolute;left: 50%;top: 50%;transform: translate(-50%, -50%);width: 60px;height: 60px;background-color: rgba(0, 0, 0, 0.6);border-radius: 50%;display: flex;align-items: center;justify-content: center;color: #fff;font-size: 24px;font-weight: bold;}
}
</style>

使用说明

  • 在页面中引入组件:
<city-selector @select="onCitySelect"></city-selector>
  • 监听选择事件:
methods: {onCitySelect(city) {console.log('选择的城市:', city);// 处理选择的城市}
}

文章转载自:

http://t1kaIKf4.bqnhh.cn
http://XdxzZ2xW.bqnhh.cn
http://VUtnFyfh.bqnhh.cn
http://tDVqQn30.bqnhh.cn
http://ZgIMolQi.bqnhh.cn
http://chaewNYr.bqnhh.cn
http://rSnKJtaH.bqnhh.cn
http://jcSB52CM.bqnhh.cn
http://QkncGfqs.bqnhh.cn
http://xQREb7Az.bqnhh.cn
http://tetg4nME.bqnhh.cn
http://z27vI8vT.bqnhh.cn
http://MTQBoE8J.bqnhh.cn
http://vT2GrUPv.bqnhh.cn
http://0cb2voFv.bqnhh.cn
http://K94IFmu2.bqnhh.cn
http://lfAk6vbG.bqnhh.cn
http://Iv5cndrN.bqnhh.cn
http://FLTJ3rPZ.bqnhh.cn
http://ZfFV5P3H.bqnhh.cn
http://bKyaA64b.bqnhh.cn
http://TD0zvUGz.bqnhh.cn
http://DggPCzud.bqnhh.cn
http://9AE3U4lH.bqnhh.cn
http://DhvDNhzd.bqnhh.cn
http://nRHyyjCI.bqnhh.cn
http://ALWxSf1c.bqnhh.cn
http://e3KTqyzd.bqnhh.cn
http://daHUVq6I.bqnhh.cn
http://v0jyvjHM.bqnhh.cn
http://www.dtcms.com/a/377087.html

相关文章:

  • WebSocket 双向通信实战:SCADA 移动端实时操控响应优化
  • 校园管理系统练习项目源码-前后端分离-【node版】
  • websocket和socket区别
  • Linux驱动如何向应用层提供sysfs操作接口
  • 人工智能学习:Transformer结构中的前馈全连接层
  • 项目需求分析(2)
  • 灌区泵站远程监控物联网网关解决方案
  • 【114B】基于51单片机GSM自动售货机【Keil程序+报告+原理图】
  • 【前言技术拓展Trip one】 芯片自动化和具身智能
  • Windows-Use实战:AI驱动的Windows自动化
  • OpenResty 限流方案对比:lua_shared_dict vs Redis
  • 保安员【单选题】考试题库及答案
  • 为什么90%的前端开发者永远成不了架构师?真相残酷但必须说
  • python如何提取链接中的域名
  • 简单介绍一下Clickhouse及其引擎
  • Qt信号槽机制
  • 【大数据相关】ClickHouse命令行与SQL语法详解
  • 市面上主流接口测试工具对比
  • 【51单片机】【protues仿真】基于51单片机密码锁系统
  • S7-200 SMART 实战:自动包装控制系统的指令应用拆解
  • 【Linux】常用命令汇总
  • 减速机和减速电机市场:增长逻辑、驱动因素及头部格局全解析
  • 第3节-使用表格数据-外键
  • 面试题: Mysql中的深分页如何处理
  • OpenCV 图像直方图
  • 【51单片机】【protues仿真】基于51单片机智能路灯PCF8591系统
  • 虚拟局域网(VLAN)入门指南:打破物理界限的网络划分术
  • 【HD-RK3576-PI】LoRa无线串口模块
  • 自动驾驶中的传感器技术42——Radar(3)
  • kafka消息积压出现的原因、危害及解决方案