前言 :
这个选择器的的界面有使用到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>