本文基于uniapp实现省市区三级联动功能,核心采用picker组件联动机制,通过本地JSON数据源高效加载各级区域信息,实现过程中,监听省份选择事件动态加载对应城市数据,再监听城市选择事件动态加载区县数据,形成完整联动链路,同时优化数据加载逻辑,采用异步缓存策略减少重复请求,确保切换流畅性,最终实现用户交互友好、数据准确的省市区三级选择功能,适用于各类地址选择场景,为uniapp开发者提供可直接复用的实践方案。
Uniapp 省市区三级联动实战:从零教你轻松搞定地址选择功能
在电商、物流、表单填写等业务场景中,省市区三级联动选择器是高频需求组件,本文将基于Uniapp框架,结合实际开发经验,详细讲解如何实现一个流畅、易用的省市区三级联动组件,涵盖数据获取、组件联动、交互优化等关键环节,适合初学者快速上手。
前置准备:明确需求与技术方案
需求拆解
在开始实现之前,我们需要明确核心需求点:
- 三级联动机制:实现省→市→区的级联选择,选择省级后动态加载对应市级数据,选择市级后动态加载对应区级数据;
- 数据准确性:采用权威的省市区数据源,确保地址信息准确无误;
- 交互友好性:支持默认选中当前地区(可选)、加载状态提示、滚动选择体验优化;
- 跨平台兼容:确保组件能在App、H5、小程序等多端正常运行。
技术选型
根据需求特点,我们选择以下技术方案:
- 组件选择:使用Uniapp原生
picker组件,它支持多列联动且具有良好的多端兼容性; - 数据源:采用开源的
china-area-data数据集,包含全国省市区层级数据,JSON格式便于解析; - 数据管理:将数据存放在本地静态资源中,避免接口请求,提升加载速度和用户体验;
- 组件封装:将三级联动功能封装为可复用的Vue组件,提高代码复用性。
数据准备:获取并整理省市区数据
数据源下载
从china-area-data官方仓库(或国内镜像源)下载最新版JSON数据,该数据集包含三个层级:
province_list:省级数据(如"北京市": "110000");city_list:市级数据(如"北京市": "110100",对应省级代码"110000");area_list:区级数据(如"东城区": "110101",对应市级代码"110100")。
数据存放
将下载的JSON文件存放在Uniapp项目的static目录下(如static/area-data/),确保打包后能被正确访问,建议在项目中创建专门的资源目录,便于管理和维护。
数据格式化
原始数据是"名称-代码"键值对格式,需要转换为picker组件所需的数组格式([{text: '北京市', value: '110000'}]),我们封装一个工具函数formatAreaData.js来处理数据转换:
// utils/formatAreaData.js
export function formatAreaData(data) {
return Object.keys(data).map(key => ({
text: key,
value: data[key]
}))
}
// 加载本地JSON数据
export function loadAreaData() {
return new Promise((resolve) => {
// 通过uni.request读取本地静态文件(H5端可用import,但多端兼容用request)
uni.request({
url: '/static/area-data/china-area-data.json',
success: (res) => {
const { province_list, city_list, area_list } = res.data
resolve({
provinces: formatAreaData(province_list),
cities: formatAreaData(city_list),
areas: formatAreaData(area_list)
})
},
fail: () => {
console.error('省市区数据加载失败')
resolve({ provinces: [], cities: [], areas: [] })
}
})
})
}
注意事项:
- 确保JSON文件路径正确,特别是在不同平台(H5、App、小程序)上的访问方式可能不同;
- 考虑数据更新机制,可以定期检查并更新数据源;
- 对于大型项目,可以考虑将数据按需加载,减少初始包体积。
组件实现:三级联动核心代码
组件结构设计
创建area-picker.vue组件,包含以下部分:
- 模板:使用
picker组件绑定三列数据,监听选择变化事件; - 逻辑:处理数据加载、选中状态管理、动态更新子级数据;
- 样式:优化picker高度、字体大小等视觉体验。
完整代码实现
<!-- components/area-picker.vue -->
<template>
<view class="area-picker">
<picker
mode="multiSelector"
:range="columns"
:value="indexArray"
@change="handleChange"
@columnchange="handleColumnChange"
:disabled="disabled"
>
<view class="picker-input" :class="{ 'placeholder': !selectedText }">
{{ selectedText || placeholder }}
</view>
</picker>
</view>
</template>
<script>
import { loadAreaData } from '@/utils/formatAreaData'
export default {
name: 'AreaPicker',
props: {
// 默认选中地区(格式:['北京市', '110000', '北京市', '110100', '东城区', '110101'])
defaultValue: {
type: Array,
default: () => []
},
// 占位文本
placeholder: {
type: String,
default: '请选择省/市/区'
},
// 是否禁用
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
columns: [[], [], []], // 三列数据(省、市、区)
indexArray: [0, 0, 0], // 三列选中索引
selectedData: [], // 选中数据([省, 市, 区])
areaInfo: { // 存储原始数据,通过code查询
provinces: [],
cities: {},
areas: {}
}
}
},
computed: {
// 选中后的展示文本
selectedText() {
return this.selectedData.length === 3 ? this.selectedData.join(' ') : ''
}
},
async mounted() {
await this.initAreaData()
if (this.defaultValue.length) {
this.setDefaultValues()
}
},
methods: {
// 初始化数据
async initAreaData() {
const { provinces, cities, areas } = await loadAreaData()
this.areaInfo.provinces = provinces
this.areaInfo.cities = cities
this.areaInfo.areas = areas
this.columns[0] = provinces
// 默认加载第一列省份对应的城市
if (provinces.length) {
this.updateCities(provinces[0].value)
}
},
// 设置默认选中值
setDefaultValues() {
const [provinceName, provinceCode, cityName, cityCode, areaName, areaCode] = this.defaultValue
const provinceIndex = this.areaInfo.provinces.findIndex(p => p.value === provinceCode)
if (provinceIndex !== -1) {
this.indexArray[0] = provinceIndex
this.updateCities(provinceCode)
this.$nextTick(() => {
const cityIndex = this.areaInfo.cities[provinceCode].findIndex(c => c.value === cityCode)
if (cityIndex !== -1) {
this.indexArray[1] = cityIndex
this.updateAreas(cityCode)
this.$nextTick(() => {
const areaIndex = this.areaInfo.areas[cityCode].findIndex(a => a.value === areaCode)
if (areaIndex !== -1) {
this.indexArray[2] = areaIndex
this.updateSelectedData()
}
})
}
})
}
},
// 更新城市数据
updateCities(provinceCode) {
const cities = this.areaInfo.cities[provinceCode] || []
this.columns[1 标签: #联动简书