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

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

2018.10.25 更新

很多人反映弹出选择器控件时,不滑动直接点击“确定”按钮没有值返回显示。其实默认坐标是[0,0,0]的,只是没有调用更新数据而已。现已更正。

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

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

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

推荐阅读更多精彩内容