1.起源
本来已经实现纯前端省-市-区联动,接口返回省-市-区字符串即可,后面又改需求从接口渲染,页面展示中文接口传id数组;
2.对比
✅ 用mode="region"的场景 【纯前端】
1.仅需要省市区名称,无需自定义 ID(如简单的表单提交、地址展示);
2.追求开发效率,不想维护数据 / 写联动逻辑;
3.注重用户体验,需要真机端的原生搜索 / 选择交互;
4.业务需要全国完整的省市区数据,且无需频繁更新。
✅ 用mode="multiSelector"的场景 【通过服务】
1.需要给省市区绑定自定义业务 ID(如省市区 ID,对接后端接口);
2.不需要省市区全量数据,仅需自定义部分数据(如仅显示某几个省份);
3.需要修改省市区名称(如改英文、改简称),或增加额外字段(如邮编);
4.非省市区的多列联动选择(如选择「年级 - 班级 - 学号」「品牌 - 系列 - 型号」)。
3.实现(mode="multiSelector")
这里只介绍mode="multiSelector"通过接口获取省市区数据,并页面选中出现文字,传给接口是对应省市区id数组,另一种后面有时间在补...
(wxml+js+wxss)效果图:
css
<view class="content-v">
<view class="form-container" style="position: relative;z-index: 1;background-color: #fff;">
<view class="form-item">
<label class="form-label">所在地区</label>
<picker mode="multiSelector" bindchange="bindMultiPickerChange" bindcolumnchange="bindMultiPickerColumnChange"
value="{{multiIndex}}" range="{{multiArray}}" catchtap="{{isReadonly ? 'noop' : ''}}">
<view class="picker-view" style="{{isReadonly ? 'color:#999;' : ''}}">
{{formData.fullRegion || '请选择省市区'}}
<text class="arrow" style="{{isReadonly ? 'display:none;' : ''}}">▼</text>
</view>
</picker>
</view>
</view>
</view>
css
const unitapi = require('../../utils/distanceUtil.js');
Page({
/**
* 页面的初始数据
*/
data: {
// ========== 替换原生region,新增三级联动核心数据 ==========
regionOriginData: [], // 接口/兜底的省市区原始数据(含id/name/children)
multiArray: [], // 多列选择器显示的纯名称数组 [[省1,省2],[市1,市2],[区1,区2]]
multiIndex: [0, 0, 0], // 多列选择器选中索引 [省索引,市索引,区索引]
selectedRegionIds: [], // 最终省市区ID数组 [省id,市id,区id]
formData: {
province: '', //省名称
city: '', //市名称
district: '', //区名称
fullRegion: '', // 拼接回显用
selectedServices: [],
selectedServicesText: '',
},
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.queryS_S_Q(); // 加载省市区数据(接口+本地兜底)
},
// ========== 重构:加载省市区数据+初始化三级联动 ==========
queryS_S_Q() {
let that = this;
// 本地兜底数据(接口失败时用,和接口字段一致:id/name/children)
const mockRegionData = [{
id: '110000',
name: '北京市',
children: [{
id: '110100',
name: '北京市',
children: [{
id: '110101',
name: '东城区'
}, {
id: '110102',
name: '西城区'
}, {
id: '110105',
name: '朝阳区'
}]
}]
},
{
id: '310000',
name: '上海市',
children: [{
id: '310100',
name: '上海市',
children: [{
id: '310101',
name: '黄浦区'
}, {
id: '310104',
name: '徐汇区'
}]
}]
},
{
id: '440000',
name: '广东省',
children: [{
id: '440100',
name: '广州市',
children: [{
id: '440103',
name: '荔湾区'
}, {
id: '440104',
name: '越秀区'
}]
}, {
id: '440300',
name: '深圳市',
children: [{
id: '440303',
name: '罗湖区'
}, {
id: '440304',
name: '福田区'
}]
}]
},
{
id: '320000',
name: '江苏省',
children: [{
id: '320100',
name: '南京市',
children: [{
id: '320102',
name: '玄武区'
}, {
id: '320104',
name: '秦淮区'
}]
}, {
id: '320500',
name: '苏州市',
children: [{
id: '320505',
name: '虎丘区'
}, {
id: '320506',
name: '吴中区'
}]
}]
}
];
unitapi.getS_S_Q_api().then((res) => {
console.warn("【调试1】接口原始返回:", res);
// 宽松校验接口数据
let originData = [];
if (Array.isArray(res) === true) {
originData = res;
console.warn("【省市区接口】使用接口第一条:", originData[0]);
} else {
originData = mockRegionData;
console.warn("【接口请求失败】,使用本地模拟数据");
wx.showToast({
title: '地区数据加载中',
icon: 'none',
duration: 1500
});
}
// 初始化三级联动的显示数据和默认ID
const provinceList = originData;
const cityList = provinceList[0]?.children || [];
const distList = cityList[0]?.children || [];
// 多列显示的纯名称数组
const multiArray = [
provinceList.map(item => item.name),
cityList.map(item => item.name),
distList.map(item => item.name)
];
// 默认选中的第一个省市区ID
const defaultIds = [
provinceList[0]?.id || '',
cityList[0]?.id || '',
distList[0]?.id || ''
];
// 默认拼接名称
const defaultFullName = `${provinceList[0]?.name || ''}-${cityList[0]?.name || ''}-${distList[0]?.name || ''}`;
that.setData({
regionOriginData: originData,
multiArray,
multiIndex: [0, 0, 0],
selectedRegionIds: defaultIds,
'formData.fullRegion': defaultFullName,
'formData.province': provinceList[0]?.name || '',
'formData.city': cityList[0]?.name || '',
'formData.district': distList[0]?.name || ''
}, () => {
console.log("【初始化成功】默认省市区ID:", that.data.selectedRegionIds);
});
}).catch(err => {
console.error("【接口失败】", err);
// 接口报错直接用本地兜底数据初始化
const mockRegionData = [{
id: '110000',
name: '北京市',
children: [{
id: '110100',
name: '北京市',
children: [{
id: '110101',
name: '东城区'
}, {
id: '110102',
name: '西城区'
}]
}]
}];
const multiArray = [
mockRegionData.map(item => item.name),
mockRegionData[0]?.children.map(item => item.name) || [],
mockRegionData[0]?.children[0]?.children.map(item => item.name) || []
];
const defaultIds = [mockRegionData[0]?.id || '', mockRegionData[0]?.children[0]?.id || '', mockRegionData[0]?.children[0]?.children[0]?.id || ''];
that.setData({
regionOriginData: mockRegionData,
multiArray,
multiIndex: [0, 0, 0],
selectedRegionIds: defaultIds,
'formData.fullRegion': `${mockRegionData[0]?.name || ''}-${mockRegionData[0]?.children[0]?.name || ''}-${mockRegionData[0]?.children[0]?.children[0]?.name || ''}`
});
wx.showToast({
title: '地区接口失败,使用本地数据',
icon: 'none'
});
});
},
// ========== 新增:三级联动列滚动事件(切省更市,切市更区) ==========
bindMultiPickerColumnChange(e) {
const {
column,
value
} = e.detail; // column=0(省)/1(市)/2(区),value=滚动后索引
let {
multiIndex,
regionOriginData
} = this.data;
multiIndex[column] = value; // 更新当前列索引
// 字段名适配(默认id/name/children,接口恢复后改这里即可)
const NAME_FIELD = 'name';
const CHILD_FIELD = 'children';
// 切换省份(列0):重置市、区索引为0,更新市、区列表
if (column === 0) {
const provinceItem = regionOriginData[value] || {};
const cityList = provinceItem[CHILD_FIELD] || [];
const distList = cityList[0]?.[CHILD_FIELD] || [];
this.setData({
multiIndex: [value, 0, 0],
'multiArray[1]': cityList.map(item => item[NAME_FIELD]),
'multiArray[2]': distList.map(item => item[NAME_FIELD])
});
}
// 切换城市(列1):重置区索引为0,更新区列表
if (column === 1) {
const provinceItem = regionOriginData[multiIndex[0]] || {};
const cityList = provinceItem[CHILD_FIELD] || [];
const cityItem = cityList[value] || {};
const distList = cityItem[CHILD_FIELD] || [];
this.setData({
multiIndex: [multiIndex[0], value, 0],
'multiArray[2]': distList.map(item => item[NAME_FIELD])
});
}
},
// ========== 新增:三级联动选择确认事件(核心:获取ID+更新页面) ==========
bindMultiPickerChange(e) {
const that = this;
const multiIndex = e.detail.value; // 最终选中的索引 [省,市,区]
const {
regionOriginData
} = that.data;
// 字段名适配(默认id/name/children,接口恢复后仅改这3行!)
const ID_FIELD = 'id';
const NAME_FIELD = 'name';
const CHILD_FIELD = 'children';
// 根据索引精准获取省市区项(无匹配失败可能,因为数据来自同一源)
const provinceItem = regionOriginData[multiIndex[0]] || {};
const cityList = provinceItem[CHILD_FIELD] || [];
const cityItem = cityList[multiIndex[1]] || {};
const distList = cityItem[CHILD_FIELD] || [];
const distItem = distList[multiIndex[2]] || {};
// 精准获取ID和名称(无空值、无匹配失败)
const provId = provinceItem[ID_FIELD] || '';
const cityId = cityItem[ID_FIELD] || '';
const distId = distItem[ID_FIELD] || '';
const provName = provinceItem[NAME_FIELD] || '';
const cityName = cityItem[NAME_FIELD] || '';
const distName = distItem[NAME_FIELD] || '';
const selectedRegionIds = [provId, cityId, distId];
const fullRegion = `${provName}-${cityName}-${distName}`;
// 更新页面显示和存储ID
that.setData({
multiIndex,
selectedRegionIds,
'formData.province': provName,
'formData.city': cityName,
'formData.district': distName,
'formData.fullRegion': fullRegion
}, () => {
// 每次选择都稳定打印ID,无任何报错
console.log('✅ 页面显示:', fullRegion);
console.log('★★★ 省市区ID数组:★★★', selectedRegionIds);
});
},
})
bash
.content-v {
}
.form-container {
background: #dd9595;
border-radius: 40rpx;
scrollbar-width: thin;
}
.form-container::-webkit-scrollbar {
width: 2rpx;
}
.form-item {
margin-bottom: 30rpx;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 80rpx;
padding: 0 10rpx;
}
.form-label {
font-size: 28rpx;
color: #333;
white-space: normal;
word-break: break-all;
height: auto;
min-height: 40rpx;
max-width: 224rpx;
line-height: 40rpx;
margin-bottom: 0;
}
.picker-view {
height: 60rpx;
line-height: 60rpx;
background: #fff;
border-radius: 8rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
color: #666;
box-sizing: border-box;
min-width: 400rpx;
}
.arrow {
font-size: 24rpx;
color: #999;
margin-left: 10rpx;
line-height: 60rpx;
}
4.放入app.json里第一行,默认加载页


