微信小程序选择省市区三级联动解决方案

2,967 阅读3分钟

需求

目前在开发小程序,遇到一处需求,需要选择省/市/区,今天写一下简单的解决方案

解决方案

1. 尝试组件

目前开发小程序中,使用到了 UI 库vantvant-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']
    }
]
填充数据

这样,一个最基础的三列选择器就做好了,下一步就是填充数据了

流程.png

// 这三个数据是缓存接口返回的数据
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,
    })
}

至此,流程大概就完毕了

最终效果

录制_2021_09_10_16_21_45_501.gif

优化

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
}