微信小程序雖然已經有現成的封裝好的省市區選擇器給開發者使用,然鵝不幸的是,微信地址庫的數據和公司用的地址庫數據很難一一對上,那就只能擼起袖子自己寫個組件了。
組件 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> <image src="自行替換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"> <view class="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"> <view class="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-picker region-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: '...' }, ...] }, ...] }, ...] 復制代碼
寫的不是特別好,也希望能幫助到有需要的人吧,有疑問戳微信小程序官方文檔,沒有什么比官方文檔更靠譜的了!