美文网首页微信小程序Angular微信开发运营
微信小程序-省市区县三级联动选择器

微信小程序-省市区县三级联动选择器

作者: 生无可恋的程序员 | 来源:发表于2017-07-11 17:58 被阅读13273次

    项目地址:https://github.com/leesonp/littleAPP.git

    2017.07.21 17:30 更新
    之前版本是从头写到尾没有封装的,有的同学可能看的一脸懵逼,昨天我用微信官方提供的WXML模板(template)对代码进行了封装,在模板中定义代码片段,通过封装以后我们就可以在不同的地方调用了。
    Demo已上传到github。

    文件组成:
    1.模板类(model)
    2.旧版未封装类 (index)
    3.新版调用类 (modelTest)
    4.数据源 (area.js)
    

    怎么使用呢?
    只需在要用到的类中导入模板模板,引入对应后缀的文件,做一些简单必要操作即可。
    以上传的Demo为例:

    // modelTest.js
    var model = require('../../model/model.js')
    
    var show = false;
    var item = {};
    
    Page({
      data: {
        item: {
          show: show
        }
      },
       //生命周期函数--监听页面初次渲染完成
      onReady: function (e) {
        var that = this;
        //请求数据
        model.updateAreaData(that, 0, e);
      },
      //点击选择城市按钮显示picker-view
      translate: function (e) {
        model.animationEvents(this, 0, true,400);  
      },
      //隐藏picker-view
      hiddenFloatView: function (e) {
        model.animationEvents(this, 200, false,400);
      },
      //滑动事件
      bindChange: function (e) {
        model.updateAreaData(this, 1, e);
        item = this.data.item;
        this.setData({
          province: item.provinces[item.value[0]].name,
          city: item.citys[item.value[1]].name,
          county: item.countys[item.value[2]].name
        });
      },
      onReachBottom: function (){
      },
      nono: function (){}
    })
    
    <!--modelTest.wxml-->
    <import src="../../model/model.wxml"/>
    <view class="infoText">{{province}} {{city}} {{county}}</view>
    <button class="animation-button" bindtap="translate">选择城市</button>
    <template is="areaData" data="{{...item}}"/>
    
    /* modelTest.wxss */
    @import '../../model/model.wxss'
    

    在model.js文件中我就暴露了两个接口,引入模板后,全部代码就这些。是不是感觉舒服很多?
    代码简洁、逻辑清晰,是封装给我们带来的好处,希望没试过封装的同学可以尝试下。

    -- END --

    ------------------------------ 分割线 ------------------------------
    2017.07.17 更新
    本人之前一直从事iOS开发,最近无项目闲来无事研究了下微信小程序。
    我没有像从新学一门语言一样从头到尾看一遍文档,我个人感觉小程序类似前端开发,正好之前对前端也略有涉猎,所以直接就拿我之前iOS的项目的某个模块练手,不懂的再去查阅官方文档。
    因为有个创建红包的界面,UITableView的某个cell点击会弹出选择区域和时间选择器,下面就给大家讲一下小程序版是怎样的一种操作。

    图1 iOS版

    在iOS中我用的是UIPickView,体验还OK。那在小程序中选什么控件呢?查阅官方文档,有两种滑动选择器

    图2 官方列出的两种滑动选择器

    picker:从底部弹起的滚动选择器,现支持三种选择器,通过mode来区分,分别是普通选择器,时间选择器,日期选择器,默认是普通选择器。因为是固定的,直接pass掉了。但是最开始我以为只有这一种,然后想去百度找找有没有自定义的。结果发现几乎网上没有很好的区域选择器的方案。我就再回过头来查阅官方文档发现还有个picker-view。
    picker-view:嵌入页面的滚动选择器。官方的示例咋一看还是时间选择器,以为也是定死的呢。但是看代码就可以发现,其实跟iOS的写法逻辑相似。
    既然相似我们就可以着手写代码了。但是开始写之前首先我们要拿到省市区县的数据。网上搜索下中国行政区域可以找到很多,我选了这个。下载下来是txt文件的Json数据。那怎么导入工程呢? 有两种方法:
    1.在utils文件夹下新建一个js文件,写一个函数把txt里的json数据复制粘贴进去,然后在需要的类目下调用这个函数接收返回值就行了。

    //area.js
    function getAreaInfo(callBack){
    var str =["此处粘贴json数据":... ];//因数据量太大就不在此处全部展示,知道是个数组就行了。
    
    callBack(str);
    }
    module.exports.getAreaInfo = getAreaInfo;
    ------------------------------ 分割线 ------------------------------
    //index.js
    var area = require('../../utils/area.js')
    ...
    Page({
      data: { },
    onLoad: function (options) {
        var that = this;
        //获取省市区县数据
        area.getAreaInfo(function (arr) {
        });
    
      }
    })
    ...
    

    2.写一个接口调用,我自己就是写的一个PHP接口。

    //php代码
    $filename = "ChinaArea.txt";//网络上下载的文件(PHP或Java需要配置环境才能请求到数据)。
    $json_string = file_get_contents($filename);
    echo print_r($json_string,true);
    

    个人建议还是不要把数据源直接写在项目,因为小程序包是有大小限制的如若超过是无法提交的所以建议写接口,或者拿别人现成的接口。

    话不多说直接贴代码吧。(代码已做必要注释)

    <!--index.wxml-->
    
      <view class="infoText">{{province}} {{city}} {{county}}</view> 
    
      <view class="aaaa" >
      <button class="animation-button" bindtap="translate">选择城市</button>
      </view>
          
      <view class="animation-element-wrapper" animation="{{animation}}" style="visibility:{{show ? 'visible':'hidden'}}" bindtap="hiddenFloatView" data-id="444">
         <view class="animation-element" catchtap="nono">
            <text class="left-bt" catchtap="hiddenFloatView" data-id="555">取消</text>
            <text class="right-bt" catchtap="hiddenFloatView" data-id="666">确定</text>
              <view class="line"></view> 
    
            <picker-view indicator-style = "height: 50rpx;" value="{{value}}" bindchange="bindChange" catchtap="nono">
            <!--省-->
            <picker-view-column>
               <view wx:for="{{provinces}}" wx:key="" >
                 {{item.name}}
              </view>
            </picker-view-column>
            <!--地级市-->
            <picker-view-column>
              <view wx:for="{{citys}}" wx:key="" >
                {{item.name}}
              </view>
            </picker-view-column>
            <!--区县-->
            <picker-view-column>
              <view wx:for="{{countys}}" wx:key="" >
                {{item.name}}
              </view>
            </picker-view-column>
            </picker-view>
        </view>
      </view>
    
    /**index.wxss**/
    page{
      background-color: rgba(255, 255, 255, 1); 
    }
    
    .infoText{
        margin-top: 20rpx;
        text-align: center;
        width: 100%;
        justify-content: center; 
    }
    
    picker-view{
      background-color: white;
      padding: 0;
      width: 100%; 
      height: 380rpx;
      bottom: 0;
      position: fixed;
    }
    
    picker-view-column view{
      vertical-align:middle; 
      font-size: 28rpx;
      line-height: 28rpx;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    
    /* ----------------------------------------- */
    
    .animation-element-wrapper {
      display: flex;  
      position: fixed;
      left: 0;
      top:0;
      height: 100%;
      width: 100%;
      background-color: rgba(0, 0, 0, 0.6);
    }
    .animation-element {
      display: flex;
      position: fixed;
      width: 100%;
      height: 470rpx;
      bottom: 0;
      background-color: rgba(255, 255, 255, 1);
    }
    
    .animation-button {
      top:20rpx;
      width: 290rpx;
      height: 100rpx;  
      align-items:center;
    }
    
    
    text{
      color: #999999;
      display: inline-flex;  
      position: fixed;
      margin-top: 20rpx;
      height: 50rpx;
      text-align: center;
      line-height: 50rpx;
      font-size: 34rpx;
      font-family: Arial, Helvetica, sans-serif;
    }
    
    .left-bt{
      left: 30rpx;
    }
    .right-bt {
      right: 30rpx;
    }
    
    .line{
      display: block;
      position: fixed;
      height: 1rpx;
      width: 100%;
      margin-top: 89rpx; 
      background-color: #eeeeee;
    }
    
    //index.js
    //获取应用实例
    var area = require('../../utils/area.js')
    
    var areaInfo = [];//所有省市区县数据
    
    var provinces = [];//省
    
    var citys = [];//城市
    
    var countys = [];//区县
    
    var index = [0, 0, 0];
    
    var cellId;
    
    var t = 0;
    var show = false;
    var moveY = 200;
    
    Page({
      data: {
        show: show,
        provinces: provinces,
        citys: citys,
        countys: countys,
        value: [0, 0, 0]
      },
      //滑动事件
      bindChange: function (e) {
        var val = e.detail.value
    
        //判断滑动的是第几个column
        //若省份column做了滑动则定位到地级市和区县第一位
        if (index[0] != val[0]) {
          val[1] = 0;
          val[2] = 0;
          getCityArr(val[0], this);//获取地级市数据
          getCountyInfo(val[0], val[1], this);//获取区县数据
        } else {    //若省份column未做滑动,地级市做了滑动则定位区县第一位
          if (index[1] != val[1]) {
            val[2] = 0;
            getCountyInfo(val[0], val[1], this);//获取区县数据
          }
        }
        index = val;
    
        console.log(index + " => " + val);
    
        //更新数据
        this.setData({
          value: [val[0], val[1], val[2]],
          province: provinces[val[0]].name,
          city: citys[val[1]].name,
          county: countys[val[2]].name
        })
    
      },
      onLoad: function (options) {
        cellId = options.cellId;
        var that = this;
        var date = new Date()
        console.log(date.getFullYear() + "年" + (date.getMonth() + 1) + "月" + date.getDate() + "日");
    
        //获取省市区县数据
        area.getAreaInfo(function (arr) {
          areaInfo = arr;
          //获取省份数据
          getProvinceData(that);
        });
    
      },
      // ------------------- 分割线 --------------------
      onReady: function () {
        this.animation = wx.createAnimation({
          transformOrigin: "50% 50%",
          duration: 0,
          timingFunction: "ease",
          delay: 0
        }
        )
        this.animation.translateY(200 + 'vh').step();
        this.setData({
          animation: this.animation.export(),
          show: show
        })
      },
      //移动按钮点击事件
      translate: function (e) {
        if (t == 0) {
          moveY = 0;
          show = false;
          t = 1;
        } else {
          moveY = 200;
          show = true;
          t = 0;
        }
        // this.animation.translate(arr[0], arr[1]).step();
        animationEvents(this,moveY, show);
        
      },
      //隐藏弹窗浮层
      hiddenFloatView(e){
        console.log(e);
        moveY = 200;
        show = true;
        t = 0;
        animationEvents(this,moveY, show);
    
      },
      //页面滑至底部事件
      onReachBottom: function () {
        // Do something when page reach bottom.
      }
    })
    
    //动画事件
    function animationEvents(that,moveY,show){
      console.log("moveY:" + moveY + "\nshow:" + show);
      that.animation = wx.createAnimation({
        transformOrigin: "50% 50%",
        duration: 400,
        timingFunction: "ease",
        delay: 0
      }
      )
      that.animation.translateY(moveY + 'vh').step()
    
      that.setData({
        animation: that.animation.export(),
        show: show
      })
    
    }
    
    // ---------------- 分割线 ---------------- 
    
    //获取省份数据
    function getProvinceData(that) {
      var s;
      provinces = [];
      var num = 0;
      for (var i = 0; i < areaInfo.length; i++) {
        s = areaInfo[i];
        if (s.di == "00" && s.xian == "00") {
          provinces[num] = s;
          num++;
        }
      }
      that.setData({
        provinces: provinces
      })
    
      //初始化调一次
      getCityArr(0, that);
      getCountyInfo(0, 0, that);
      that.setData({
        province: "北京市",
        city: "市辖区",
        county: "东城区",
      })
    
    }
    
    // 获取地级市数据
    function getCityArr(count, that) {
      var c;
      citys = [];
      var num = 0;
      for (var i = 0; i < areaInfo.length; i++) {
        c = areaInfo[i];
        if (c.xian == "00" && c.sheng == provinces[count].sheng && c.di != "00") {
          citys[num] = c;
          num++;
        }
      }
      if (citys.length == 0) {
        citys[0] = { name: '' };
      }
    
      that.setData({
        city: "",
        citys: citys,
        value: [count, 0, 0]
      })
    }
    
    // 获取区县数据
    function getCountyInfo(column0, column1, that) {
      var c;
      countys = [];
      var num = 0;
      for (var i = 0; i < areaInfo.length; i++) {
        c = areaInfo[i];
        if (c.xian != "00" && c.sheng == provinces[column0].sheng && c.di == citys[column1].di) {
          countys[num] = c;
          num++;
        }
      }
      if(countys.length == 0){
        countys[0] = {name:''};
      }
      that.setData({
        county: "",
        countys: countys,
        value: [column0, column1, 0]
      })
    }
    

    最终效果:

    3. 最终效果

    本文的难点主要集中在滑动的是哪一列、数据的处理、每次滑动每一列数据的更新数组的定位:
    1.滑动第一列: 第二列和第三列位置都置零;更新数组citys和countys。
    2.滑动第二列: 第一列不动,第三列置零;更新数组countys。
    3.滑动第三列: 第一列和第二列都不动;数据不用更新,改变val[2]即可。

    处理不好的话体验会很不好,会有闪跳的感觉。

    项目地址:https://github.com/leesonp/littleAPP.git

    这是我第一次写文章,写的不好或者有纰漏的地方,希望大家多多包容、指正,谢谢。

    相关文章

      网友评论

      • 听风吹过来:楼主这个在选择了城市之后还可以选择全部全部的城市吗?我使用的Picker选择城市之后不能显示全部的城市
      • 944a7287815d:楼主我正好也要做个类似的基于地理位置的红包推广,想问下楼主给被推广的人发红包和地理位置的识别是怎么实现的呀?
        生无可恋的程序员:这个主要的逻辑不在前台,一般是你获取到地理位置或者选择位置后把位置信息比如code或者地理坐标传给后台,后台生成地图标记点传给前台你再显示在地图上或者做相应的操作。
      • d9261630af50:要怎样才能设置默认的省市区?比如一打开就定位到四川、成都、xx
        生无可恋的程序员:这个你可以手动根据字符遍历查找,或者找到具体省市区的下标,初始化的时候给值就行了。
      • 挑水的小和尚:你这个需要在加载完成县区一级的时候再次调用一次将value值重新设定才可以在返回相应的坐标的,就可以显示正确,比如 3,0,3 最完成县区操作时再次复制给va[3,0,3],作者可有其他的方式处理?(将坐标位置提交以后重新拉取之后直接定位到相关位置)
      • 挑水的小和尚:你这有一个问题当选中之后获取返回的下标时前两级是可以正常显示,第三极只能默认给0,比如2,2,2 显示是:2,2,0
        生无可恋的程序员:第三级是故意默认给0。因为一、二级省、市发生改变,第三级区县一级肯定是会变化的假如还是上次的位置的话逻辑是不对的,因为可能滑动之前第三级数组元素有10个你定位在8的位置,滑动后可能区县一级只有5个数据,你不置零的话数组肯定越界,程序崩溃。

      本文标题:微信小程序-省市区县三级联动选择器

      本文链接:https://www.haomeiwen.com/subject/lpumhxtx.html