vue2移动端仿淘宝APP省市区街道四级地区选择器

前言 :
这个选择器的的界面有使用到vant作为样式的编写,之后有时间会写一个完全无任何依赖的出来,作为以后组件库的使用.
最近写一个商城项目时,地区选择器上设计图上是这样的
在这里插入图片描述
总觉得好眼熟,但是又想不起来哪里见过,后来打开淘宝一看才发现,这就是淘宝APP上的地区选择器啊,然后经过一顿的头脑风暴(百度谷歌)之后,发现貌似并没有可以直接使用的组件,没办法,只能自己动手了.经过一番沟通,因为没有找到国外的地区json数据,并且该项目商城没有海外需求,所以海外的地区就去掉了,字母排序也去掉了.做出来的效果是这样的
在这里插入图片描述
样式编写不加累述,fixed布局,半透明背景颜色.flex布局,加上van-tabs的组件,很简单就写出来布局了.

接下来就是找地区json数据了,当时找了挺久,有一个后端的大佬在github上有一个地区库,很全,放一下链接,有需要同学可以去看一下地区json数据,如果有帮助到你,请不要吝啬你的star,

找到json数据之后,由于要对里面的一些区域进行修改,所以我就直接拷贝下来放到项目中去了.然后就是渲染数据了.由于是4级联动,并且是可以只选择省市就关闭保存的,所以省市区街道,我是分别定义了一个属性来表示,并且在没有选择之前就展示待选择的字符

   		province:"选择省份",
        city:"选择城市",
        district:"选择区域",
        street:"选择街道",

引用地区的json数据,并且绑定到vue实例的对象中去

  import areas from "../assets/jsons/area"

  data(){
      return{
        areaJson:areas,
      }
    },

接下来是做热门城市,地区的json数据中没有热门城市这些数据,所以我在代码直接写死,要注意的是这里热门城市的字符必须跟地区json数据的完全一样,因为要用来做配合,显示下级的联动显示,所以热门城市在代码中是这样的

   hotCity:[
          ["北京","上海","重庆","天津"],
          ["杭州市","南京市","苏州市","深圳市"],
          ["武汉市","长沙市","广州市","成都市"],
        ],

接下来就是数据列表的展示了,根据json数据中,省,市,区都是对象的形式,所以要在data中去定义好对应的值来分别表示,省,市,区,街道

"河北省": {
    "石家庄市": {
      "长安区": ["建北街道", "青园街道", "广安街道", "育才街道", "跃进街道", "河东街道", "长丰街道", "谈固街道", "中山东路街道", "阜康街道", "建安街道", "胜利北街道", "西兆通镇", "南村镇", "高营镇", "桃园镇"],
   data(){
      return{
        active:0, // 这个是vant用来表示当前的tab的,可以忽略
        province:"选择省份",
        city:"选择城市",
        district:"选择区域",
        street:"选择街道",
        hotCity:[
          ["北京","上海","重庆","天津"],
          ["杭州市","南京市","苏州市","深圳市"],
          ["武汉市","长沙市","广州市","成都市"],
        ], // 热门的城市
        areaJson:areas, // 地区的json数据
        cityData:{}, // 当前城市的json数据
        districtData:{}, // 当前区域的json数据
        streetData:{}, // 当前街道的json数据
      }
    },

数据准备完成,渲染数据列表,这些的代码不是完整的,我只是表现出来给大家看,整体的数据渲染的结构,

         <div class="area-list">
              <div class="fs-24 color999">选择省区</div>
              <div class="area-item" @click="clickProvince(key)" v-for="(area,key) in areaJson">
                  {{key}}
              </div>
         </div>
         <div class="area-list">
              <div class="fs-24 color999">选择城市</div>
              <div class="area-item" @click="clickCity(key)" v-for="(area,key) in cityData">
                {{key}}
              </div>
        </div>   
        <div class="area-list">
              <div class="fs-24 color999">选择区域</div>
              <div class="area-item" @click="clickDistrict(key)" v-for="(area,key) in districtData">
                {{key}}
              </div>
       </div>
       <div class="area-list">
              <div class="fs-24 color999">选择街道</div>
              <div class="area-item" @click="clickStree(area)" v-for="(area,key) in streetData">
                {{area}}
              </div>
       </div>

最外层的省,直接使用引用的json数据渲染即可,剩下的市,区,街道在上级没有选择之前,是空的,也就是我们常见的联动,接下来是联动的关键代码,当选择了省份之后,代码是这样的

     clickProvince(name){
        // name就是当前选中的省份
        if(this.province == name){
          // 判断它是否跟当前已经选中的省份相同,如果相同,就把tab栏切换到市的选择.不用做其他操作,因为在选中的省有数据的情况下,市的数据是肯定有的.
          this.active = 1;
        }else{
          // 如果不相同,就代表选择了其他的省份
          this.province = name; // 把选中的省份赋值给之前绑定好的tab栏上显示省的值
          this.active = 1; // 当前的tab栏切换到市
          this.cityData = this.areaJson[name]; // 在地区的json数据中拿选中的省当key值,就可以取出对应的市区数据
          // 联动到下级的值,防止显示上的错误,比如已选择,广东,广州,天河,如果直接跳回省份选择了广西,那对应的市与区的值就要重置
          this.city = "选择城市"; 
          this.district = "选择区域";
          this.districtData = {};
        }
      },

剩下的市,区的选择都跟上面省份的选择是一样的

    clickCity(city){
        if(this.city == city){
          this.active = 2;
        }else{
          this.city = city;
          this.active = 2;
          this.districtData = this.cityData[city];
        }
      },
      clickDistrict(district){
        this.active = 3;
        this.district = district;
        this.streetData = this.districtData[district];
      },
      clickStree(street){
        this.street = street;
        // 街道选择完就直接保存
        this.saveData();
      },
      saveData(){
      // 由于可以不选择到最后一级也可以保存,所以需要做好判断,并且拼装好数据返回出去
        let data = {};
        if(this.province != "选择省份"){
          data.province = this.province;
        }
        if(this.city != "选择城市"){
          data.city = this.city;
          data.cityJson = this.cityData; // 把对应选择好的市,区数据保存好并且返回出去,是为了再次点击进入时方便渲染
        }
        if(this.district != "选择区域"){
          data.district = this.district;
          data.districtJson = this.districtData;
        }
        if(this.street != "选择街道"){
          data.street = this.street;
          data.streetJson = this.streetData;
        }
        this.$emit("save",data);
      }

然后就是热门城市的点击之后的匹配

    clickHotCity(name){
          for (let province in areas){
            // 取出所有的市区数据
            let cityObj = areas[province];
              for (let city in cityObj){
                // 热门城市字符直接配合当前循环出来的市的名称
                  if(city == name){
                    this.province = province;
                    this.city = name;
                    this.cityData = cityObj;
                    this.districtData = this.cityData[name];
                    this.active = 2;
                  }
              }
          }
      },

如果是已选择好之后,再次进入选择地区界面

    mounted(){
    // 先判断父级是否有数据传入,有的话就做对应的赋值并且展示即可
      if(this.data){
          this.province = this.data.province;
        if(this.data.city){
          this.city = this.data.city;
          this.cityData = this.data.cityJson;
        }
        if(this.data.district){
          this.district = this.data.district;
          this.districtData = this.data.districtJson;
        }
        if(this.data.street){
          this.street = this.data.street;
          this.streetData = this.data.streetJson;
        }
      }
    },
    props:["data"],

下面是完整代码,代码还有很多可优化的地方.所以这个仅仅是个人记录.请看客勿喷

<template>
  <div class="area-box">
    <div class="main-box">
      <div class="fs-24 color333 text-c title-box">
        选择国家地区
      </div>
      <div class="select">
        <van-tabs v-model="active">
          <van-tab :title="province">
            <div class="hot-province">
              <div class="fs-24 color999">热门城市</div>
              <div class="hot-row" v-for="hotRow in hotCity">
                <div class="fs-28 color333" v-for="cityname in hotRow" @click="clickHotCity(cityname)">{{cityname}}</div>
              </div>
            </div>
            <div class="area-list">
              <div class="fs-24 color999">选择省区</div>
              <div class="area-item" @click="clickProvince(key)" v-for="(area,key) in areaJson">
                  {{key}}
              </div>
            </div>
          </van-tab>
          <van-tab :title="city">
            <div class="area-list">
              <div class="fs-24 color999">选择城市</div>
              <div class="area-item" @click="clickCity(key)" v-for="(area,key) in cityData">
                {{key}}
              </div>
            </div>
          </van-tab>
          <van-tab :title="district">
            <div class="area-list">
              <div class="fs-24 color999">选择区域</div>
              <div class="area-item" @click="clickDistrict(key)" v-for="(area,key) in districtData">
                {{key}}
              </div>
            </div>
          </van-tab>
          <van-tab :title="street">
            <div class="area-list">
              <div class="fs-24 color999">选择街道</div>
              <div class="area-item" @click="clickStree(area)" v-for="(area,key) in streetData">
                {{area}}
              </div>
            </div>
          </van-tab>
        </van-tabs>
      </div>

      <div class="close-icon" @click="saveData">
        <van-icon name="close" size="20px" color="#cfcfcf" />
      </div>
    </div>
  </div>
</template>
<script>
  import areas from "../assets/jsons/area"
  export default {
    mounted(){
      if(this.data){
          this.province = this.data.province;
        if(this.data.city){
          this.city = this.data.city;
          this.cityData = this.data.cityJson;
        }
        if(this.data.district){
          this.district = this.data.district;
          this.districtData = this.data.districtJson;
        }
        if(this.data.street){
          this.street = this.data.street;
          this.streetData = this.data.streetJson;
        }
      }
    },
    props:["data"],
    data(){
      return{
        active:0,
        province:"选择省份",
        city:"选择城市",
        district:"选择区域",
        street:"选择街道",
        hotCity:[
          ["北京","上海","重庆","天津"],
          ["杭州市","南京市","苏州市","深圳市"],
          ["武汉市","长沙市","广州市","成都市"],
        ],
        areaJson:areas,
        cityData:{}, // 当前城市的json数据
        districtData:{}, // 当前区域的json数据
        streetData:{}, // 当前街道的json数据
      }
    },
    methods:{
      clickHotCity(name){
          for (let province in areas){
            // 取出所有的市区数据
            let cityObj = areas[province];
              for (let city in cityObj){
                // 热门城市字符直接配合当前循环出来的市的名称
                  if(city == name){
                    this.province = province;
                    this.city = name;
                    this.cityData = cityObj;
                    this.districtData = this.cityData[name];
                    this.active = 2;
                  }
              }
          }
      },
      clickProvince(name){
        // name就是当前选中的省份
        if(this.province == name){
          // 判断它是否跟当前已经选中的省份相同,如果相同,就把tab栏切换到市的选择.不用做其他操作,因为在选中的省有数据的情况下,市的数据是肯定有的.
          this.active = 1;
        }else{
          // 如果不相同,就代表选择了其他的省份
          this.province = name; // 把选中的省份赋值给之前绑定好的tab栏上显示省的值
          this.active = 1; // 当前的tab栏切换到市
          this.cityData = this.areaJson[name]; // 在地区的json数据中拿选中的省当key值,就可以取出对应的市区数据
          // 联动到下级的值,防止显示上的错误,比如已选择,广东,广州,天河,如果直接跳回省份选择了广西,那对应的市与区的值就要重置
          this.city = "选择城市";
          this.district = "选择区域";
          this.districtData = {};
        }
      },
      clickCity(city){
        if(this.city == city){
          this.active = 2;
        }else{
          this.city = city;
          this.active = 2;
          this.districtData = this.cityData[city];
        }
      },
      clickDistrict(district){
        this.active = 3;
        this.district = district;
        this.streetData = this.districtData[district];
      },
      clickStree(street){
        this.street = street;
        this.saveData();
      },
      saveData(){
        let data = {};
        if(this.province != "选择省份"){
          data.province = this.province;
        }
        if(this.city != "选择城市"){
          data.city = this.city;
          data.cityJson = this.cityData;
        }
        if(this.district != "选择区域"){
          data.district = this.district;
          data.districtJson = this.districtData;
        }
        if(this.street != "选择街道"){
          data.street = this.street;
          data.streetJson = this.streetData;
        }
        this.$emit("save",data);
      }
    }
  }
</script>
<style scoped rel="stylesheet/less" lang="less">
  .close-icon{
    position: absolute;
    right:20px;
    top:20px;
  }
  .area-list{
    box-sizing: border-box;
    padding:38px 40px;

  }
  .hot-province{
    box-sizing: border-box;
    padding:38px 40px;
  }
  .hot-row{
    display: flex;
    justify-content: space-around;
    align-items: center;
    height: 80px;
  }
  .area-box{
    position: fixed;
    top:0;
    left:0;
    right:0;
    bottom:0;
    background-color: rgba(178, 178, 178, 0.65);
    z-index: 100;
  }
  .title-box{
    height: 98px;
    line-height: 98px;
  }
  .main-box{
    position: fixed;
    left:0;
    right:0;
    bottom:0;
    top:100px;
    border-top-left-radius: 30px;
    border-top-right-radius: 30px;
    background-color: #ffffff;
  }
  .area-item{
    font-size: 30px;
    height: 80px;
    line-height: 80px;
  }

</style>
<style>
  .area-box .van-tabs__wrap.van-hairline--top-bottom{
    height: 78px;
    background-color: #f1f1f1;
  }
  .area-box .van-tabs__wrap.van-hairline--top-bottom .van-ellipsis{
    line-height: 78px;
    font-size: 30px;
    color:#333;
  }
  .area-box .van-tabs__content{
    margin-top: 20px;
    height: 880px;
    overflow-y: auto;
  }
</style>


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值