起因
微信小程序虽然已经有现成的封装好的省市区选择器给开发者使用,然鹅不幸的是,微信地址库的数据和公司用的地址库数据很难一一对上,那就只能撸起袖子自己写个组件了。
最终效果
思维导图
主要代码
组件 region-picker.js
/* region-picker.js */
import area from '本地 json 数据';
Component({properties: {showRegion: {type: Boolean,observer: function(newVal, oldVal) {this.setData({dialog: newVal,});},},regionValue: {type: Array,value: [],observer: function(newVal, oldVal) {if (newVal.length > 0) {let select = -1;for (let i = newVal.length - 1; i >= 0; i--) {if (newVal[i].id !== '') {select = i;break;}}// 除最低级别区(select = 2)以外,需要获取当前级别下一级的数据this.setData({['region.tabs']: newVal,['region.select']: select < 2 ? select 1 : select,}, () => {this.setData({area: this.getChildArea(select < 2 ? select 1 : select),});});}},},},data: {dialog: false,area: area,region: {tabs: [{name: '请选择',id: '',},{name: '请选择',id: '',},{name: '请选择',id: '',},],select: 0,},},methods: {// 关闭 picker 触发的方法emitHideRegion: function() {if (this.data.region.tabs[2].id === '') {wx.showToast({title: '请选择所在地',icon: 'none',duration: 2000,});return false;}let myEventDetail = {}; // detail对象,提供给事件监听函数let myEventOption = {}; // 触发事件的选项this.setData({dialog: !this.data.dialog,});myEventDetail = {showRegion: this.data.dialog,regionValue: this.data.region.tabs,};this.triggerEvent('myevent', myEventDetail, myEventOption);},bindRegionChange: function(e) {// 获取当前选中项的name和id并赋值给data中的数据let id ='region.tabs[' this.data.region.select '].id';let name ='region.tabs[' this.data.region.select '].name';this.setData({[id]: e.target.dataset.id,[name]: e.target.dataset.name,});// 除了三级以外的需要获取对应子选项if (this.data.region.select < 2) {this.setData({['region.select']: this.data.region.select,}, () => {// 获取子选项this.setData({area: this.getChildArea(this.data.region.select),});});} else {// 三级选项选择完毕关闭省市区选择器this.emitHideRegion();}},getChildArea: function(level) {let _id = '';// 默认取完整的数据let _area = area;// 根据层级取当前层级下的数据for (let i = 0; i < level; i ) {_id = this.data.region.tabs[i].id;for (let j = 0; j < _area.length; j ) {if (_area[j].id === _id) {_area = _area[j]._child;break;}}}return _area;},// 省市区tab切换changeRegionLevel: function(e) {let level = e.target.dataset.level;// 三级选项的tab点击无效果if (level === 2) return false;// 当前选中tab和级别小于当前选中tab的状态都置为初始化状态for (let i = level; i < 3; i ) {let string = 'region.tabs[' i ']';this.setData({[string]: {name: '请选择',id: '',},});}this.setData({['region.select']: level,});this.setData({area: this.getChildArea(level),});},},
});
组件 region-picker.wxml
/* region-picker.wxml */
<view class="free-dialog {{dialog ? 'free-dialog--show' : ''}}"><view class="free-dialog__mask" bindtap="emitHideRegion"></view><view class="free-dialog__container"><view class="free-dialog__container__header"><view>选择所在地区</view><imagesrc="自行替换36rpx*36rpx的x图标"class="close"bindtap="emitHideRegion"></image></view><view class="free-dialog__container__content"><view class="free-content {{isIphoneX ? 'ipx' : ''}}"><view class="free-content__tabs"><viewclass="free-content__tabs__tab {{region.select === index ? 'select' : ''}}"wx:for="{{region.tabs}}"wx:key="{{index}}"wx:if="{{index <= region.select}}"data-level="{{index}}"bindtap="changeRegionLevel">{{item.name}}</view></view><scroll-view scroll-y class="free-content__scroll"><viewclass="free-content__scroll__item"wx:for="{{area}}"wx:key="id"data-id="{{item.id}}"data-name="{{item.name}}"bindtap="bindRegionChange">{{item.name}}</view></scroll-view></view></view></view>
</view>
组件 region-picker.wxss
/* region-picker.wxss */
.free-dialog__mask {position: fixed;top: 0;left: 0;right: 0;bottom: 0;z-index: 10;background: rgba(0, 0, 0, 0.7);display: none;
}
.free-dialog__container {position: fixed;left: 0;bottom: 0;width: 100%;background: #F1F1F1;transform: translateY(150%);transition: all 0.4s ease;z-index: 11;
}
.free-dialog--show .free-dialog__container {transform: translateY(0);
}
.free-dialog--show .free-dialog__mask {display: block;
}
.free-dialog__container__header {padding: 24rpx 30rpx;text-align: center;background: white;
}
.free-dialog__container__header .close {position:absolute;right:30rpx;top:31rpx;width:36rpx;height:36rpx;
}
.free-content {background: white;border-bottom: 40rpx solid white;
}
.free-content.ipx {border-bottom: 72rpx solid white;
}
.free-content__tabs__tab {display: inline-block;padding: 12rpx 46rpx;font-size: 32rpx;color: #333;border-bottom: 4rpx solid white;
}
.free-content__tabs__tab.select {border-color: #FA263C;
}
.free-content__scroll {padding: 0 40rpx;height: 480rpx;box-sizing: border-box;
}
.free-content__scroll__item {margin-top: 40rpx;height: 40rpx;line-height: 40rpx;font-size: 28rpx;color: #333;
}
页面的 WXML
/* 页面的 WXML */
<view bindtap="chooseRegion">请选择</view>
<view><text wx:if="{{regionValue[0].id}}">{{regionValue[0].name}}</text><text wx:if="{{regionValue[1].id}}">{{regionValue[1].name}}</text><text wx:if="{{regionValue[2].id}}">{{regionValue[2].name}}</text>
</view>
...
<region-pickerregion-value="{{regionValue}}"show-region="{{showRegion}}"bind:myevent="emitHideRegion">
</region-picker>
页面的 js
/* 页面的 js */
Page({data: {regionValue: [],showRegion: false,},chooseRegion: function() {this.setData({showRegion: true,});},emitHideRegion: function(e) {this.setData({showRegion: e.detail.showRegion,regionValue: e.detail.regionValue,});},
});
总结
需要注意下的是,最低级别区级别是个特殊的临界点,因为区后面没有更低级别,所以不需要获取下一级别的数据,也不能触发 tab 事件。
然后父组件传递子自组件的值,如果后期父组件变更了这个值,子组件可以在响应函数 observer 里监听到值的变化。
我本次使用的本地省市区 JSON 数据格式为:
/* area.js */
module.exports = [{id: '...',name: '...',_child: [{id: '...',name: '...',_child: [{id: '...',name: '...'}, ...]}, ...]
}, ...]
写的不是特别好,也希望能帮助到有需要的人吧,有疑问戳微信小程序官方文档,没有什么比官方文档更靠谱的了!