乐趣区

关于javascript:小程序省市区选择器对接公司的地址数据

公司开始做小程序了,小程序的省市区三级联动 picker 组件 mode=”region”, 之前也有接触过,这一次一上来先尝试了一下,发现不能和之前公司的地址库联合,因为之前我的项目都是和后端通过地区编码来交互的,这个自带的无奈满足现有的状况。于是用小程序 picker 组件的多列选择器自制了一个。
开始主题之前先说一下地址数据的问题,我司的数据也是我本人 2020 年 8 月从高德接口导出来的最新数据并同步给后端的:

高德行政区查问接口

高德行政区查问接口文档

通过这个接口能够获取全国的行政区数据,至于拿到数据当前组装成什么格局,这里不细说了,都是基本功,只有注意着点省直辖县,市直辖镇等非凡级别关系的状况。

当然小程序省市区选择器数据咱们也是有方法弄到,只是,现有在用的数据库不宜大动,于是没有用,传送门也放在这里吧。

小程序官网地区选择器数据

过后我有这么两个格局:一个是树形的,拿到就能够用:

然而一看大小,好家伙,440kb,太大了,用不了。

另一个是平铺格局的:

这个 96kb,尽管还是有点大,压缩一下,勉强能够用了。只是这不是一个树形构造,所以后期还要筹备几个办法备用。

地区编码是有其规定的,比方,省级行政区是两位,后四位是“0000”,地市一级的是四位,后两位是“00”,到区县一级则是残缺的 6 位。
于是先把平铺的 json,过滤出省市区三个级别的数组进去。

// region.js
// 这个 data 就是那个平铺格局的 json
const data = require('./data.js');

const list = []
const province = []
const city = []
const area = []

Object.entries(data).forEach(val => {const key = Number.parseInt(val[0])
  const model = {key: val[0], value: val[1] }
  list.push(model)
  if (!(key % 1e4)) {province.push(model)
  } else if (!(key % 100)) {city.push(model)
  } else {const num = Number(val[0].substr(2))
    if (num > 9000) {city.push(model)
    } else {area.push(model)
    }
  }
})

module.exports = {
  srcList: list,
  srcProvince: province,
  srcCity: city,
  srcArea: area,
}

这样,省一级的行政区数组就拿到了,地市一级和区县一级的则是每次依据抉择的上一级的地区编码,来过滤出其管辖的下一级行政区划的数组:

// createTree.js
const region  = require('./region');

module.exports = {
  /**
   * load city list by province data
   *
   * @param province: {key: 330000, value: '浙江省'}
   * @returns {Array}
   */
  loadCity (province) {if (province && Object.keys(province).length) {
      const list = region.srcCity.filter(val => {const num = Number.parseInt(province.key)
        return (val.key - num) < 1e4 && (val.key % num) < 1e4
      })
      // Municipalities directly under the central government
      return list.length ? list : [province]
    } else return []},

  /**
   * load area list by city data
   *
   * @param city: {key: 330100, value: '杭州市'}
   * @returns {Array}
   */
  loadArea (city) {if (city && Object.keys(city).length) {const cityKey = Number.parseInt(city.key)
      const isNotProvince = cityKey % 1e4
      const calcNum = isNotProvince ? 100 : 1e4
      const list = region.srcArea.filter(val => {return (val.key - cityKey) < calcNum && val.key % cityKey < calcNum
      })
      // Prefecture-level city
      return list.length ? list : [city]
    } else return []},
}

好了,筹备工作结束,进入小程序页面。

<picker 
  class="picker" 
  mode="multiSelector" 
  bindchange="bindMultiPickerChange" 
  bindcolumnchange="bindMultiPickerColumnChange" 
  bindcancel="cancel"
  value="{{regionIndex}}"
  range="{{regionArray}}"
  range-key="value">
  <mp-cell ext-class="utils__item">
    <text class="text"> 所在地区 </text>
    <input bindinput="inputChange" 
    readonly 
    disabled 
    value="{{areaText}}" 
    class="weui-input" 
    placeholder="所在地区"/>
  </mp-cell>
</picker>

页面元素构造就是这样,没什么好说的,这个能够间接看小程序 picker 中多列选择器的文档。先看一下,页面用到的相干数据吧。

data: {
    areaText: '', // 显示的省市区文字
    blockArray: [], // 显示的省市区三级数组,二维数组
    blockIndex: [0, 0, 0], // 显示进去的抉择下标,默认,[0,0,0]
    regionArray: [], // 选择器以后的省市区三级数组,二维数组
    regionIndex: [0, 0, 0], // 选择器以后选中的下标
    provinceList: srcProvince, // 省级数组,cityList: [], // 地市级数组,areaList: [], // 区县数组}

能够看到,我这里有两个 array 和 index 的数组,这是因为,其中一个保留的是已被抉择的数据,另一个则是以后正在抉择的数据,次要思考到一个勾销性能。你要把之前选中的保存起来,上面抉择的是点击了勾销按钮,能力还原为上次抉择的数据。

const  {srcProvince} = require('../../utils/region.js');
const { 
    loadCity, 
    loadArea, 
} =  require('../../utils/createTree.js');

onLoad() { // 初始化数据,这里还波及到一个数据回显,比拟麻烦,放到前面再说
  let cityList, areaList,
  cityList = loadCity(srcProvince[0]);
  areaList = loadArea(cityList[0]);
  this.setData({blockArray: [srcProvince, cityList, areaList],
    regionArray: [srcProvince, cityList, areaList],
    cityList,
    areaList,
  })
},

bindMultiPickerChange (e) { // 点击确定按钮的事件
    const oldKey = this.data.blockArray[2][this.data.blockIndex[2]].key; // 这是留住上次选中的第三级区域代码
    const newKey = this.data.regionArray[2][this.data.regionIndex[2]].key; // 本次选中的第三级区域代码
    
    if(oldKey !== newKey) { // 如果本次抉择和之前的不一样,则批改数据
      this.setData({
        blockArray: this.data.regionArray,
        regionIndex: e.detail.value,
        blockIndex: e.detail.value,
        areaText: `${this.data.regionArray[0][this.data.regionIndex[0]].value}${this.data.regionArray[1][this.data.regionIndex[1]].value}${this.data.regionArray[2][this.data.regionIndex[2]].value}`
      })
    }
  },
  
bindMultiPickerColumnChange (e) { // 这是选择器列数据发生变化的时候,就是选择器滚动的时候
    const regionIndex = this.data.regionIndex;
    // 这个 column 就是发生变化的是第几列,0,1,2,value 则是滚动下标
    regionIndex[e.detail.column] = e.detail.value;
    // 上面的解决是当抉择上一级的时候,要把下一级下标设置为 0,避免下一级下标越界
    if(e.detail.column === 0) {regionIndex[2] = 0;
      regionIndex[1] = 0;
    } else if(e.detail.column === 1) {regionIndex[2] = 0;
    }
    const provinceItem = srcProvince[regionIndex[0]];
    let cityList = [], areaList = [];
    cityList = loadCity(provinceItem);
    areaList = loadArea(cityList[regionIndex[1]]);

    this.setData({regionArray: [srcProvince, cityList, areaList],
      regionIndex: regionIndex,
      cityList,
      areaList,
    })
  },
  
  cancel() { // 点击勾销的时候须要把数据还原回抉择之前的状态
    this.setData({ 
      regionArray: this.data.blockArray,
      regionIndex: this.data.blockIndex, 
    });
  },

这个选择器的逻辑就是以上这些,次要是选择器列变动的时候要把当前列下一级的行政区划数组找进去,并且注意用户勾销的状况。

最初再说说数据回显,当初是没有数据的时候,就是默认的 [0,0,0] 的状况,解决很简略,否则,就是有数据的状况,咱们跟后端之间的交互都是通过最初一级的行政编码来的,就是我这边给后盾就只有一个 areaCode,后端返回也只有一个 areaCode。因为这个在无论咱们以后我的项目还是在这个小程序的后盾我的项目上,逻辑上都不会存在只有省一级或者只有省市两级的状况,所以。只有有数据,这个 areaCode 必然是区县一级的代码。于是在 createTree.js 里减少一个办法:

/*
  * areaCodeToMap
  * @param {string} code
  * @param {array} list
  */

  areaCodeToMap(code, list) {
    const result = {map: {},
      index: 0,
    };
    list.map((item, i) => {if(item.key === code) {
        result.map = item;
        result.index = i;
      }
    })
    return result;
  },

这是通过这个 areaCode 把下面两级的行政区对象及其下标找进去,只有有了下面两级的行政区对象,则又能够通过之前的 loadCity 和 loadArea 两个办法设置选择器数据了:

setArea() { // 省市区数据回显
    let index = 0;
    let cityList = [], areaList = [], provinceIndex = 0, cityIndex = 0, areaIndex = 0;
    const areaCode = this.data.form.areaCode;
    if(areaCode) {
      // 省级信息回显
      const province = areaCodeToMap(`${areaCode.substring(0, 2)}0000`, srcProvince);
      const provinceItem = province.map;
      provinceIndex = province.index;

      // 地市级信息回显
      cityList = loadCity(provinceItem);
      const city = areaCodeToMap(`${areaCode.substring(0, 4)}00`, cityList);
      const cityItem = city.map;
      cityIndex = city.index;

      // 区县级信息回显
      areaList = loadArea(cityItem);
      const area = areaCodeToMap(areaCode, areaList);
      const areaItem = area.map;
      areaIndex = area.index;
      
      this.setData({blockArray: [srcProvince, cityList, areaList],
        regionArray: [srcProvince, cityList, areaList],
        blockIndex: [provinceIndex, cityIndex, areaIndex],
        regionIndex: [provinceIndex, cityIndex, areaIndex],
        cityList,
        areaList,
      })
      this.setData({areaText: `${this.data.regionArray[0][this.data.regionIndex[0]].value}${this.data.regionArray[1][this.data.regionIndex[1]].value}${this.data.regionArray[2][this.data.regionIndex[2]].value}`
      })
}

上真机效果图:

其实这里还波及到省市区选择器和地图选址组件的交互,比方抉择完地区当前,将经纬度设置成该地区的经纬度,进入地图后定位到该地区,还有在地图定位完地位后依据地图返回的 areaCode 再调用一遍 setArea 办法间接回显省市区等等,这些不是本文探讨范畴,不再细说了。

文章写完了,喜爱的给个赞吧!

退出移动版