撸个微信小程序的省市区选择器

起因

微信小程序虽然已经有现成的封装好的省市区选择器给开发者使用,然鹅不幸的是,微信地址库的数据和公司用的地址库数据很难一一对上,那就只能撸起袖子自己写个组件了。

最终效果

最终效果

思维导图

思维导图

主要代码

组件 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: '...'
        }, ...]
    }, ...]
}, ...]

写的不是特别好,也希望能帮助到有需要的人吧,有疑问戳微信小程序官方文档,没有什么比官方文档更靠谱的了!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容