uni-indexed-list 之扩展组件实现城市列表带索引查询过滤功能

uni-indexed-list 是 uni-app 的 uni-ui 组件库中的一个索引列表组件,用于创建带有索引栏的列表视图。这种组件通常用于长列表的快速导航,例如联系人列表、城市列表等,用户可以通过点击或滑动索引栏快速跳转到列表的指定部分。

原 uni-indexed-list 只支持特定数据结构:如

html 复制代码
[{
	"letter": "A",
	"data": [
		"阿克苏机场",
		"阿拉山口机场",
		"阿勒泰机场",
		"阿里昆莎机场",
		"安庆天柱山机场",
		"澳门国际机场"
	]
}, {
	"letter": "B",
	"data": [
		"保山机场",
		"包头机场",
		"北海福成机场",
		"北京南苑机场",
		"北京首都国际机场"
	]
}]

为了更好的兼容所有格式,现源码修改如下:

javascript 复制代码
	props: {
			options: {
				type: Array,
				default () {
					return []
				}
			},
// 添加属性配置
      prop:{
        type:Object,
        default:{
          letter:"letter",
          label:"name",
          data:"data"
        }
      },
			showSelect: {
				type: Boolean,
				default: false
			}
		},

.............

setList() {
				let index = 0;
				this.lists = []
				this.options.forEach((value, index) => {
// 根据prop 配置进行获取数据
          const dlst = value[this.prop.data];
					if (dlst.length === 0) {
						return
					}
					let indexBefore = index
					let items = dlst.map(item => {
						let obj = {}
						obj['key'] = value[this.prop.letter]
						obj['name'] = item[this.prop.label]
            obj['data'] = item
						obj['itemIndex'] = index
						index++
						obj.checked = item.checked ? item.checked : false
						return obj
					})
					this.lists.push({
						title: value[this.prop.letter],
						key: value[this.prop.letter],
						items: items,
						itemIndex: indexBefore
					})
				})

具体源码查下载:https://download.csdn.net/download/simayilong/92988910

使用事例代码如下:

html 复制代码
<template>
  <view class="city-container">

    <view class="search-box">
      <view class="search-input-wrapper">
        <icon type="search" size="16" color="#999" />
        <input type="text" v-model="searchKeyword" placeholder="输入城市名或拼音" />
      </view>
      <view class="city_current">
        <label @click="handleChangeCity(1)"
          :style="{color:province.name?'#F34A33':'#333' }">{{province.name||'请选择省份'}}</label>
        <label v-if="type===2" :style="{color:cityArea.name?'#F34A33':'#333' }"> -- {{cityArea.name||"请选择城市"}}</label>
      </view>
    </view>

    <scroll-view class="city-scroll" scroll-y  scroll-with-animation>
      <uni-indexed-list :options="newCityList" :prop="{label:'name',data:'list'}" :show-select="false"
        @click="bindClick">
      </uni-indexed-list>
    </scroll-view>
  </view>
</template>

<script>
  /**
   * 城市选择组件
   * 采用 Vue2 Options API 编写
   */

   const cityData=[{"letter":"A","list":[{"code":340000,"name":"安徽省"},{"code":820000,"name":"澳门特别行政区"}]},{"letter":"B","list":[{"code":110000,"name":"北京市"}]},{"letter":"C","list":[{"code":500000,"name":"重庆市"}]},{"letter":"F","list":[{"code":350000,"name":"福建省"}]},{"letter":"G","list":[{"code":440000,"name":"广东省"},{"code":450000,"name":"广西壮族自治区"},{"code":520000,"name":"贵州省"},{"code":620000,"name":"甘肃省"}]},{"letter":"H","list":[{"code":130000,"name":"河北省"},{"code":230000,"name":"黑龙江省"},{"code":410000,"name":"河南省"},{"code":420000,"name":"湖北省"},{"code":430000,"name":"湖南省"},{"code":460000,"name":"海南省"}]},{"letter":"J","list":[{"code":220000,"name":"吉林省"},{"code":320000,"name":"江苏省"},{"code":360000,"name":"江西省"}]},{"letter":"L","list":[{"code":210000,"name":"辽宁省"}]},{"letter":"N","list":[{"code":150000,"name":"内蒙古自治区"},{"code":640000,"name":"宁夏回族自治区"}]},{"letter":"Q","list":[{"code":630000,"name":"青海省"}]},{"letter":"S","list":[{"code":140000,"name":"山西省"},{"code":310000,"name":"上海市"},{"code":370000,"name":"山东省"},{"code":510000,"name":"四川省"},{"code":610000,"name":"陕西省"}]},{"letter":"T","list":[{"code":120000,"name":"天津市"},{"code":710000,"name":"台湾省"}]},{"letter":"X","list":[{"code":540000,"name":"西藏自治区"},{"code":650000,"name":"新疆维吾尔自治区"},{"code":810000,"name":"香港特别行政区"}]},{"letter":"Y","list":[{"code":530000,"name":"云南省"}]},{"letter":"Z","list":[{"code":330000,"name":"浙江省"}]}]



  export default {
    data() {
      return {
        searchKeyword: '',
        cityList: [],
        type: 1,
        cityCode: "",
        province: "",
        cityArea: ""
      };
    },
    onLoad() {
      this.getCityList(this.type, this.code)
    },

    computed: {
      // 过滤城市数据省市区数据
      newCityList() {
        if (this.searchKeyword) {
          //根据关键字过滤省份数据,搜索关键字(支持 name、code 或 letter)
          const kw = String(this.searchKeyword).trim().toLowerCase();
          return this.cityList.reduce((result, group) => {
            // letter 精确匹配
            if (group.letter.toLowerCase() === kw) {
              result.push({ letter: group.letter, list: [...group.list] });
              return result;
            }

            // code / name 模糊匹配 过滤 list
            const filtered = group.list.filter(item =>
              String(item.code).includes(kw) || item.name.includes(this.searchKeyword.trim())
            );

            if (filtered.length > 0) {
              result.push({ letter: group.letter, list: filtered });
            }

            return result;
          }, []);
        }
        return this.cityList
      }

    },

    onShow() {

    },

    methods: {

      getCityList(type = 1, code = "") {
        // TODO 获取城市列表,api接口待定
        this.cityList = cityData
      },

      bindClick(e) {
        console.log('点击item,返回数据' + JSON.stringify(e))
        this.handleSelectCity(e.item.data)
      },

      // 切换省份
      handleChangeCity(type) {
        this.type = 1
        this.province.name=""
        this.province.code=""
        this.getCityList(this.type, "")
        this.cityArea = {}
      },

      /**
       * 选中城市返回
       * @param {String} name 城市名称
       */
      handleSelectCity(item) {
        console.log('用户选择了城市:', item);
        if (this.type === 1) {
          this.searchKeyword=""
          this.province = item
          this.type = 2
          this.getCityList(this.type, item.code)
        } else if (this.type === 2) {
          this.cityArea = item
          // 返回上一页,并带上数据 省、市、区
          uni.$emit("handle-city", {
            name: `${this.province.name}-${this.cityArea.name}`,
            code: `${this.province.code}-${this.cityArea.code}`
          })
          uni.navigateBack()
        }
      }
    }
  };
</script>

<style scoped>
  .city-container {
    /*  display: flex;
    flex-direction: column; */
    width: 100%;
    height: 100%;
    background-color: #f7f7f7;
  }

  /* 搜索框样式 */
  .search-box {
    padding: 20rpx 30rpx;
    background-color: #ffffff;
  }

  .search-input-wrapper {
    display: flex;
    align-items: center;
    background-color: #f2f2f2;
    height: 72rpx;
    padding: 0 20rpx;
    border-radius: 36rpx;
  }

  .search-input-wrapper input {
    flex: 1;
    margin-left: 10rpx;
    font-size: 28rpx;
  }

  .city_current {
    border-bottom: 1rpx solid #E6E6E6;
    padding: 20rpx 10rpx;
    font-weight: 500;
    font-size: 28rpx;
    color: #F34A33;
    text-align: left;
  }

  /* 滚动区样式 */
  .city-scroll {
    flex: 1;
    overflow: hidden;
    height: calc(90vh - 80px);
  }

  .section {
    padding: 20rpx 30rpx;
  }

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

  /* 标签样式(定位和热门) */
  .city-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 20rpx;
  }

  .tag-item {
    background-color: #ffffff;
    width: 200rpx;
    height: 70rpx;
    line-height: 70rpx;
    text-align: center;
    border-radius: 8rpx;
    font-size: 28rpx;
    color: #333;
    border: 1rpx solid #eee;
  }

  /* 字母分组样式 */
  .group-title {
    background-color: #f0f0f0;
    padding: 10rpx 30rpx;
    font-size: 24rpx;
    color: #666;
  }

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

  /* 侧边索引条 */
  .index-bar {
    position: fixed;
    right: 10rpx;
    top: 55%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    background-color: rgba(255, 255, 255, 0.8);
    border-radius: 20rpx;
    padding: 10rpx 5rpx;
    box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
  }

  .index-item {
    font-size: 22rpx;
    color: #F34A33;
    padding: 6rpx 10rpx;
    font-weight: bold;
  }
</style>
相关推荐
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-06-16
前端·人工智能·经验分享·chatgpt·html
snow@li1 小时前
前端:构建工具(Vite / Webpack)的 文件指纹(File Hash) 机制 / 浏览器缓存控制
前端·webpack·哈希算法
ayqy贾杰2 小时前
SpaceX 收购 Cursor,马斯克花600亿美元买了个代码编辑器
前端·人工智能·机器学习
云飞云共享云桌面10 小时前
传统工作站 vs 云飞云共享云桌面:制造业设计云桌面选型深度对比
运维·服务器·前端·网络·3d·架构·制造
UXbot10 小时前
如何选择适合公司项目的UI设计工具?企业选型指南
前端·低代码·ui·团队开发·原型模式·设计规范·web app
llz_11211 小时前
web-第四次课后作业
前端·spring boot·web
武清伯MVP11 小时前
前端跨域方案大合集
前端·javascript
一杯奶茶¥12 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
小刘|12 小时前
Spring AI Alibaba 集成和风天气 API 实战
java·服务器·前端