需求
目前在开发小程序,遇到一处需求,需要选择省/市/区,今天写一下简单的解决方案
解决方案
1. 尝试组件
目前开发小程序中,使用到了 UI 库vant
,vant-weapp
本身提供了组件address
,但是由于本项目接口返回的数据结构是这样的:
- 先选择省
- 再调用接口,获取该省下的市
- 再调用接口,获取该市下的区
而van-address
需要用到所有得省市区的数据,由于添加接口需要往后端传入省市区的id
,因此,直接使用组件是不现实的,需要另辟蹊径。
2. 另辟蹊径
搭建骨架
但是vant
提供了另一个组件van-picker
,这里先用最基础的picker
打底。
<van-picker column="{{data}}" />
vant
文档中介绍,column
是整个picker
的数据,如果想要有多个选择,结构是需要这样的
// 一列
data: ['aa', 'bb', 'cc'],
// 多列,按照顺序
data: [
{
values: ['1', '2', '3']
},
{
values: ['1-1', '1-2', '1-3']
},
{
values: ['1-1-1', '1-1-2', '1-1-3']
}
]
填充数据
这样,一个最基础的三列选择器就做好了,下一步就是填充数据了
// 这三个数据是缓存接口返回的数据
let _provinceList = []
let _cityList = []
let _districtList = []
// 这个数据是用来判断 picker 的数据有无
let __pickerDataInit__ = false
// 这三个数据用来获取当前选择的省市区的索引
let __selectProvinceId__ = 0
let __selectCityId__ = 0
let __selectDistrictId__ = 0
// 获取省
getProvince() {
// 接口交互
// res.data 即接口返回的数据
// 先缓存一份,因为接口返回的数据和 van-picker 需要的数据不同
// 接口返回的结构是 { id=xx, name='xx' } , vant 只需要 name 数据,写个函数转化一下
_provinceList = res.data
// 获取到合理的数据后,会直接放到 data 中,第一列是省
this.setData({
"data[0].values": _parse2VanPickerData(res.data)
})
__pikcerDataInit__ = true
}
// 获取市
getCity(provinceId) {
// 接口需要用到 provinceId 来获取到 cities
// res.data 即为接口返回的数据
// 缓存一份
_cityList = res.data
this.setData({
"data[1].values": _parse2VanPickerData(res.data)
})
// 因为在滑动市的时候会同步获取到区,所以获取市的时候就可以直接获取到区
this.getDistrict(_districtList(__selectDistrictId__))
}
// 获取区
getDistrict(cityId) {
// 接口需要用到 provinceId 来获取到 districts
// res.data 即为接口返回的数据
_districtList = res.data
this.setData({
"data[2].values": _parse2VanPickerData(res.data)
})
}
// 打开选择省市区的 popUp
showPopup() {
this.setData({
popUpShow: true
})
// 获取数据
this.getProvince()
if(!__pickerDataInit__){
// 第一次进入,还没有数据
// 我们要初始化数据后,显示第一个省的第一个市的第一个区
this.getProvince()
this.getCity(_cityList[__selectCityId__].id))
}
}
function _parse2VanPickerData(arr) {
let res = []
arr.forEach(item => {
res.push(item)
})
return res
}
切换选项
接下来,就是最关键的,切换时三级联动
<!-- bind:change van-picker 绑定切换方法 -->
<van-picker id="picker" column="{{pickerColumnData}}" bind:change="onPickerChange" />
// 回调参数有 e.detail.index 是当前切换到该列的下表,比如在滑动第一列,就是0,第二列,就是1
// 通过 selectComponent 获取 picker 实例,有一个方法 getIndexs 获取到三个组的所有的下标
onPickerChange(e) {
// 获取当前的下标s
const currentIndexs = this.selectComponent('#picker').getIndexes()
// 判断一下滑动的是三个中的哪一个
switch (e.detail.index) {
case 0:
// 如果是第一个,我们就获取到该省的id,获取该省下的市
const provinceId = PROVINCE_LIST[currentIndexs[0]].id
__selectProvinceIndex__ = currentIndexs[0]
this.getCityList(provinceId)
case 1:
// 如果滑动的市,就获取该市下的区
const cityId = CITY_LIST[currentIndexs[1]].id
this.getDistrictList(cityId)
__selectCityIndex__ = currentIndexs[1]
}
}
完成选择
onPickerConfirm() {
// 选择完毕了
const currentIndexs = this.selectComponent('#picker').getIndexes()
// 获取当前选择的省市区的 name、id
const province = PROVINCE_LIST[currentIndexs[0]]
const city = CITY_LIST[currentIndexs[1]]
const district = DISTRICT_LIST[currentIndexs[2]]
this.setData({
// 省市区的 id 是需要添加时传给后端的
addProvinceId: province.id,
addCityId: city.id,
addDistrictId: district.id,
// 这个是前端展示,只展示省市区的名字,拼接起来
addFormAreaValue: province.name + city.name + district.name,
// defaultIndex:省市区默认选择的索引
// 这样做的主要效果是:如果不小心按错了,再回来,就是刚才选择的地方
"data[0].defaultIndex": currentIndexs[0],
"data[1].defaultIndex": currentIndexs[1],
"data[2].defaultIndex": currentIndexs[2],
// 最后关闭 popup
popupShow: false,
})
}
至此,流程大概就完毕了
最终效果
优化
1. loading
<!-- van-picker 有一个属性 loading 我们可以在请求时打开 loading -->
<van-picker loading="{{pickerLoading}}"></van-picker>
2. 将全局变量初始化
一定要记得,在onUnload
中将全局变量初始化
onUnload() {
let _provinceList = []
let _cityList = []
let _districtList = []
let __pickerDataInit__ = false
let __selectProvinceId__ = 0
let __selectCityId__ = 0
let __selectDistrictId__ = 0
}