前言
在微信小程序中,官方文档提供的省市区组件,可以让用户更加方便快捷地选择省市区,但是官方提供的组件有一个缺点,无法自定义数据,但如果项目中需要使用自己的数据,显然就得寻找其它的组件实现。
官方组件
-
优点
- 使用官方组件具有稳定性和兼容性,可以保证在不同的微信小程序版本中正常运行;
- 官方组件的使用文档详细,易于上手,可以快速实现级联省市区组件;
- 官方组件的样式和交互效果都比较简洁,符合微信小程序的设计风格。
-
缺点
- 官方组件的样式和交互效果比较单一,无法满足一些特殊需求;
- 官方组件的自定义能力比较有限,无法进行个性化定制。
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方法设置选择器的label
observer(selectedValues) {
if (selectedValues && selectedValues.length) {
this.setLabel();
}
}
}
},
data: {
// label为选择器的显示值
label: '',
// range为选择器的可选项,类型为数组,包含三个数组,分别为省、市、区/县
range: [],
// pickerValue为选择器的选中值,类型为数组,包含三个数字,分别为省、市、区/县的下标
pickerValue: [],
// addressList为选择器的数据源,类型为数组,包含省、市、区/县的信息
addressList: [],
},
// attached生命周期函数,在组件实例进入页面节点树时执行
attached() {
// 获取地址列表,如果value为空则初始化range
this.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和pickerValue
columnChange(e) {
const {
column,
value
} = e.detail;
this.setColumn(column, value);
},
// setColumn方法用于设置range和pickerValue
setColumn(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方法用于设置label
setLabel() {
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方法用于初始化range
initRange() {
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
}
]
}
]
}
]
实现效果