返回

微信小程序自定义数据实现级联省市区组件功能

原创
admin的头像admin·发布于 2025-07-16 12:30·阅读 248

在微信小程序中,省市区三级联动是常见的功能需求。一种轻量、高效且不依赖第三方组件的实现方式是使用自定义组件和本地 JSON 数据来驱动联动逻辑。本文将从数据准备、组件设计、逻辑处理三方面全面讲解如何实现该功能。


效果图

file

✨ 一、准备数据源

我们用一份结构清晰的 JSON 文件来定义所有省、市、区信息,例如 regions.json

json 复制代码
[
  { "id": "11", "name": "北京市", "parent": "0" },
  { "id": "1101", "name": "北京市市辖区", "parent": "11" },
  { "id": "110101", "name": "东城区", "parent": "1101" },
  // ...
  { "id": "31", "name": "上海市", "parent": "0" },
  { "id": "3101", "name": "上海市市辖区", "parent": "31" },
  { "id": "310101", "name": "黄浦区", "parent": "3101" }
]
  • id:每一条的唯一编码
  • name:显示名称
  • parent:关联层级,上级编码为 0 表示顶级省份

保存到项目 data/regions.json 中,便于后续调用。


🛠 二、自定义组件设计

components/area-picker/ 下创建 area-picker 组件,包含:

  • area-picker.json

    json 复制代码
    {
      "component": true
    }
  • area-picker.wxml

    xml 复制代码
    <!-- 三列 Picker 用于省市区选择 -->
    <view class="area-picker">
      <picker mode="selector" range="{{provinceList}}" value="{{provinceIndex}}" bindchange="onProvinceChange">
        <view>{{provinceList[provinceIndex]}}</view>
      </picker>
      <picker mode="selector" range="{{cityList}}" value="{{cityIndex}}" bindchange="onCityChange">
        <view>{{cityList[cityIndex]}}</view>
      </picker>
      <picker mode="selector" range="{{districtList}}" value="{{districtIndex}}" bindchange="onDistrictChange">
        <view>{{districtList[districtIndex]}}</view>
      </picker>
    </view>
  • area-picker.wxss

    css 复制代码
    .area-picker {
      display: flex;
      justify-content: space-between;
    }
    picker {
      flex: 1;
      margin: 0 5px;
    }
  • area-picker.js

    js 复制代码
    import regions from '../../data/regions.json';
    
    Component({
      properties: {
        value: {
          type: Object,
          value: { provinceId: '', cityId: '', districtId: '' }
        }
      },
      data: {
        provinceList: [],
        cityList: [],
        districtList: [],
        provinceIndex: 0,
        cityIndex: 0,
        districtIndex: 0
      },
      lifetimes: {
        attached() {
          this.initData();
        }
      },
      methods: {
        initData() {
          const provinces = regions.filter(r => r.parent === '0');
          const provinceList = provinces.map(r => r.name);
          this.setData({ regionData: regions, provinceList });
          this.updateCities(0);
        },
        updateCities(provIdx) {
          const provinces = this.data.regionData.filter(r => r.parent === '0');
          const seleProv = provinces[provIdx];
          const cities = this.data.regionData.filter(r => r.parent === seleProv.id);
          const cityList = cities.map(r => r.name);
          this.setData({ cityList, cityIndex: 0 });
          this.updateDistricts(cities, 0);
        },
        updateDistricts(cities, cityIdx) {
          if (!cities.length) return this.setData({ districtList: [] });
          const seleCity = cities[cityIdx];
          const districts = this.data.regionData.filter(r => r.parent === seleCity.id);
          const districtList = districts.map(r => r.name);
          this.setData({ districtList, districtIndex: 0 });
        },
        onProvinceChange(e) {
          const index = e.detail.value;
          this.setData({ provinceIndex: index });
          this.updateCities(index);
          this.triggerPick();
        },
        onCityChange(e) {
          const index = e.detail.value;
          const provs = this.data.regionData.filter(r => r.parent === '0');
          const selProv = provs[this.data.provinceIndex];
          const cities = this.data.regionData.filter(r => r.parent === selProv.id);
          this.setData({ cityIndex: index });
          this.updateDistricts(cities, index);
          this.triggerPick();
        },
        onDistrictChange(e) {
          const index = e.detail.value;
          this.setData({ districtIndex: index });
          this.triggerPick();
        },
        triggerPick() {
          const provs = this.data.regionData.filter(r => r.parent === '0');
          const selProv = provs[this.data.provinceIndex];
          const cities = this.data.regionData.filter(r => r.parent === selProv.id);
          const selCity = cities[this.data.cityIndex];
          const districts = this.data.regionData.filter(r => r.parent === selCity.id);
          const selDistrict = districts[this.data.districtIndex] || {};
          this.triggerEvent('change', {
            provinceId: selProv.id,
            provinceName: selProv.name,
            cityId: selCity.id,
            cityName: selCity.name,
            districtId: selDistrict.id || '',
            districtName: selDistrict.name || ''
          });
        }
      }
    });

💡 三、实际页面中的调用示例

在页面 pages/index/index.wxml 调用组件:

xml 复制代码
<area-picker value="{{selectedArea}}" bindchange="onAreaChange" />
<text>当前选择:{{selectedArea.provinceName}} - {{selectedArea.cityName}} - {{selectedArea.districtName}}</text>

index.js 中:

js 复制代码
Page({
  data: {
    selectedArea: {
      provinceId: '', cityId: '', districtId: '',
      provinceName: '', cityName: '', districtName: ''
    }
  },
  onAreaChange(e) {
    this.setData({ selectedArea: e.detail });
  }
});

效果图

file
file


✅ 小结

  1. 自定义 JSON 数据源:避免调用接口,加载快,操作自由。
  2. 组件封装:分离视图和业务,复用更高。
  3. 联动逻辑:根据父节点变更动态更新下级列表。

通过以上方法,即可实现一个轻量、可维护、适配线下环境的省市区三级联动组件。欢迎分享与交流!


作者的其他文章
2025 年最新国内 npm / pnpm / Yarn 镜像源设置指南
2025 年最新国内 npm、pnpm、Yarn 镜像源设置教程,包含多种可用镜像源及 `nrm` 一键切换使用方法,助你快速安装依赖、提升效率。
2025 年最新国内 npm / pnpm / Yarn 镜像源设置指南
前端网站加速:Vue 与纯 HTML 项目配置 CDN 实践指南
本文深入讲解如何在 Vue 和纯 HTML 项目中配置 CDN,提供实战代码示例,并推荐 Cloudflare、AWS CloudFront、腾讯云 CDN、阿里云 CDN、jsDelivr、Fastly 等主流服务商,助力前端性能优化与快速部署。
在现有 Vue3 + Vite + TypeScript 项目中使用 Vike 实现静态预渲染(SSG)
本文演示如何在现有 Vue3 + Vite + TypeScript 项目中快速集成 Vike(原 vite-plugin-ssr)实现静态预渲染(SSG),从目录结构、渲染流程到打包部署,全面提升 SEO 与首屏性能。
在现有 Vue3 + Vite + TypeScript 项目中使用 Vike 实现静态预渲染(SSG)
快速构建高性能前端 —— 推荐 CDNJS 免费公共 CDN 加速服务
CDNJS 是一款免费、开源的公共 CDN 服务,提供全球 Anycast 加速、4,000+ 前端库资源,助力网站快速构建和性能优化。
快速构建高性能前端 —— 推荐 CDNJS 免费公共 CDN 加速服务