前言
在微信小程序中,官方文档提供的省市区组件,可以让用户更加方便快捷地选择省市区,但是官方提供的组件有一个缺点,无法自定义数据,但如果项目中需要使用自己的数据,显然就得寻找其它的组件实现。
官方组件
-
优点
- 使用官方组件具有稳定性和兼容性,可以保证在不同的微信小程序版本中正常运行;
- 官方组件的使用文档详细,易于上手,可以快速实现级联省市区组件;
- 官方组件的样式和交互效果都比较简洁,符合微信小程序的设计风格。
-
缺点
- 官方组件的样式和交互效果比较单一,无法满足一些特殊需求;
- 官方组件的自定义能力比较有限,无法进行个性化定制。
wxml
文件
<picker mode="region" bindchange="bindRegionChange" value="{{region}}" custom-item="{{customItem}}"><view class="picker">当前选择:{{region[0]}},{{region[1]}},{{region[2]}}</view>
</picker>
js
文件
Page({data: {region: [],},bindRegionChange: function (e) {console.log('picker发送选择改变,携带值为', e.detail.value)this.setData({region: e.detail.value})}
})
实现效果
vant-weapp 实现
vant-weapp 中为我们提供了级联选择器,并且组件的样式和交互效果比较丰富,可以满足各种特殊需求,使用这个组件也可以实现,但是 vant-weapp
也有一个问题,当数据量比较大时,组件就会变得异常卡顿。
wxml
文件
<van-field value="{{ fieldValue }}" is-link readonly label="地区" placeholder="请选择所在地区" bind:tap="onClick" />
<van-popup show="{{ show }}" round position="bottom"><van-cascader field-names="{{ fieldNames }}" wx:if="{{ show }}" value="{{ cascaderValue }}" title="请选择所在地区"options="{{ options }}" bind:close="onClose" bind:finish="onFinish" />
</van-popup>
js
文件
Page({data: {show: false,fieldValue: '',cascaderValue: '',fieldNames: {text: 'label',value: 'value',children: 'children',},options: [],},onLoad() {this.setData({options: JSON.parse(wx.getStorageSync('addressInfo'))})},onClick() {this.setData({show: true,});},onClose() {this.setData({show: false,});},onFinish(e) {const {selectedOptions,value} = e.detail;const fieldValue = selectedOptions.map((item) => item.label || item.value).join('/');this.setData({fieldValue,cascaderValue: value,})console.log(fieldValue);this.onClose()},
});
实现效果
肉眼可见非常卡顿,每点击一次都要等好几秒才能反应过来。
自定义组件
既然上面两种实现方法都不符合我们的需求,那么自己自定义组件就可以完全按照自己的需求进行设计和开发。
封装
wxml
文件
<picker mode="multiSelector" model:value="{{pickerValue}}" range-key="label" range="{{range}}" bindchange="onChange"bindcolumnchange="columnChange"><view><!-- 如果已经选择了选项,则显示选项的label属性,否则显示placeholder属性。 --><text wx:if="{{label}}"> {{ label }} </text><text style="color: #999" wx:else> {{ placeholder }}</text></view>
</picker>
封装
js
文件
Component({properties: {// placeholder为选择器的默认提示文字placeholder: {type: String,value: '请选择',},// value为选择器的默认值,类型为数组value: {type: Array,value: [],// observer监听value的变化,如果有值则调用setLabel方法设置选择器的labelobserver(selectedValues) {if (selectedValues && selectedValues.length) {this.setLabel();}}}},data: {// label为选择器的显示值label: '',// range为选择器的可选项,类型为数组,包含三个数组,分别为省、市、区/县range: [],// pickerValue为选择器的选中值,类型为数组,包含三个数字,分别为省、市、区/县的下标pickerValue: [],// addressList为选择器的数据源,类型为数组,包含省、市、区/县的信息addressList: [],},// attached生命周期函数,在组件实例进入页面节点树时执行attached() {// 获取地址列表,如果value为空则初始化rangethis.getAddressList().then(() => {if (!this.data.value.length) {this.initRange();}});},methods: {// getAddressItem方法用于将地址信息转换为选择器可用的格式getAddressItem(data) {return {label: data.label,value: data.value};},// setAddressList方法用于将地址列表转换为选择器可用的格式setAddressList(list) {return list.map((v) => this.getAddressItem(v));},// getAddressByCode方法用于根据value值获取地址信息及其在数组中的下标getAddressByCode(list = [], value) {let index = list.findIndex(item => item.value === value);return [index, list[index] || {}];},// openChildren方法用于根据value值打开下一级选择器openChildren(list, keys) {let result = [];const handle = (arr, keys) => {let newarr = arr.map((v, index) => {if (keys && keys.length) {let [currentKey, ...nextKey] = keys;if (currentKey === index && Array.isArray(v.children)) {handle(v.children, nextKey);}}return this.getAddressItem(v);});result.push(newarr);}handle(list, keys);return result.reverse();},// onChange方法为选择器的change事件处理函数,用于设置label和触发change事件onChange(e) {let [r1, r2, r3] = this.data.range;const [v1, v2, v3] = e.detail.value;const selected = [r1[v1], r2[v2], r3[v3]];const values = selected.map(v => v.value);const label = selected.map(v => v.label).join('-');this.setData({label});this.triggerEvent("change", {value: values,label});},// columnChange方法为选择器的columnchange事件处理函数,用于设置range和pickerValuecolumnChange(e) {const {column,value} = e.detail;this.setColumn(column, value);},// setColumn方法用于设置range和pickerValuesetColumn(column, value) {let addressList = this.data.addressList;if (!addressList || addressList.length === 0) return;let [r1, r2, r3] = this.data.range;if (column === 0) {r2 = this.setAddressList(addressList[value].children);r3 = this.setAddressList(addressList[value].children[0].children);this.setData({pickerValue: [value, 0, 0]});} else if (column === 1) {const [v1] = this.data.pickerValue;r3 = this.setAddressList(addressList[v1].children[value].children);this.setData({pickerValue: [v1, value, 0]});}this.setData({range: [r1, r2, r3]});},// setLabel方法用于设置labelsetLabel() {let addressList = this.data.addressList;if (addressList && addressList.length) {const [v1, v2, v3] = this.data.value;const [s1, {label: t1,children: l1}] = this.getAddressByCode(addressList, v1);const [s2, {label: t2,children: l2}] = this.getAddressByCode(l1, v2);const [s3, {label: t3}] = this.getAddressByCode(l2, v3);const label = [t1, t2, t3].filter(v => v).join('-');const pickerValue = [s1, s2, s3];const range = this.openChildren(addressList, [s1, s2, s3]);if (label.length) {this.setData({label,range,pickerValue});}} else {this.getAddressList().then(() => {this.setLabel();});}},// initRange方法用于初始化rangeinitRange() {if (!this.data.value.length) {const range = this.openChildren(this.data.addressList, [0, 0, 0]);this.setData({range});}},// getAddressList方法用于获取地址列表getAddressList() {return new Promise((resolve, reject) => {wx.getStorage({key: 'addressInfo',success: (res) => {this.setData({addressList: JSON.parse(res.data)});resolve();},fail: (err) => {reject(err);}});});},},
});
使用
wxml
文件
<picker-region bindchange="regionChange" placeholder="请选择所在区域" value="{{value}}" />
使用
js
文件
Page({data: {value: ""},regionChange(e) {console.log(e)},
})
模拟
json
数据格式
[{"value": "110000","label": "北京市","regionLevel": "1","parentRegionCode": "0","children": [{"value": "110100","label": "市辖区","regionLevel": "2","parentRegionCode": "110000","children": [{"value": "110101","label": "东城区","regionLevel": "3","parentRegionCode": "110100","children": null},{"value": "110118","label": "密云区","regionLevel": "3","parentRegionCode": "110100","children": null}]}]},{"value": "120000","label": "天津市","regionLevel": "1","parentRegionCode": "0","children": [{"value": "120100","label": "市辖区","regionLevel": "2","parentRegionCode": "120000","children": [{"value": "120101","label": "和平区","regionLevel": "3","parentRegionCode": "120100","children": null},{"value": "120102","label": "河东区","regionLevel": "3","parentRegionCode": "120100","children": null}]}]}
]
实现效果