前言
      在工作中遇到省市县三级联动的需求,传统的pc端大部分是用3个HTML
<select>标签根据选中的一级地址去获取二级地址,然后根据二级地址去获取三级地址。实现省市县三级地址选择,需要2次服务端请求。这里使用mint--ui的Popup弹出框组件和picker选择器组件,无需请求服务端,通过遍历本地的json文件,实现本地输出三级地址,同时给每级地址绑定一个code,从而满足与后台交互时,传递code代码,这个需求非常常见。

vue.js官网:https://cn.vuejs.org/ <https://cn.vuejs.org/>

mint-ui官网:http://mint-ui.github.io/#!/zh-cn <http://mint-ui.github.io/#!/zh-cn>
步骤一:使用vue-cli快速构建demo项目
       vue-cli是vue提供的官方命令行工具,用于快速搭建大型单页应用。



* 新建一个空文件夹demo,安装node.js,安装方法请谷歌或者百度,非常简单。
* 用IDEA开发工具打开demo文件夹,打开Terminal终端,输入下面的命令行全局安装vue-clinpm install vue-cli -g
* 安装成功后输入npm -v可以查看npm的版本
* 输入以下命令行回车,初始化项目配置vue init webpack demo
* 输入Project name, Project description , Author, Install vue-router?输入Y, Use
ESLint to lint your code?输入N, Set up unit tests 输入N, Setup e2e tests with
Nightwatch?输入N,回车初始化安装,初始化成功后可看到以下界面
* 终端输入以下命令行,浏览器打开http://localhost:8080,demo项目构建完毕cd demo npm run dev


步骤二:新建一个ThreeLevelAddress.vue文件,并引入mint-ui


* 在demo/src/components路径下新建ThreeLevelAddress.vue文件<template> <div
class="three-level-address"> hello </div> </template> <script> </script> <style
scoped> </style>


* 在demo/src/router/index.js进行路由定义import ThreeLevelAddress from
'@/components/ThreeLevelAddress' export default new Router({ routes: [ {
path:'/ThreeLevelAddress', name:'ThreeLevelAddress',
component:ThreeLevelAddress } ] })
* 在浏览器内输入http://localhost:8080/#/ThreeLevelAddress可预览到下图




* 在终端输入以下命令行,引入mint-uinpm i mint-ui -S
* 在demo/src/main.js路径下写入以下代码即可使用mint-uiimport MintUI from 'mint-ui' import
'mint-ui/lib/style.css' Vue.use(MintUI)
* 接下来测试一下是否引入成功,这里已mint-ui的button按钮组件为例。<template> <div
class="three-level-address"> <mt-button type="default"
size="large">default</mt-button> <mt-button type="primary"
size="large">primary</mt-button> <mt-button type="danger"
size="large">danger</mt-button> </div> </template> <script> import Vue from
'vue' import { Button } from 'mint-ui'; Vue.component(Button.name, Button);
</script> <style scoped> </style>
效果图:



步骤三:引入Popup组件import { Popup } from 'mint-ui'; Vue.component(Popup.name, Popup);
Popup的API




Popup API
参数说明类型可选值默认值
positionpopup 的位置。省略则居中显示String'top'
'right'
'bottom'
'left' 
pop-transition显示/隐藏时的动效,仅在省略 position 时可配置String'popup-fade' 
modal是否创建一个 modal 层Boolean true
closeOnClickModal是否可以通过点击 modal 层来关闭 popupBoolean true




















API中比较常用的是position,可配置弹出框的位置,四种效果都不太一样,可自行体验官网的demo。这里我使用的是位置为bottom的Popup组件,其他均使用默认配置。
<mt-popup v-model="popupVisible" position="bottom"> </mt-popup>步骤四:引入Picker组件
import { Picker } from 'mint-ui'; Vue.component(Picker.name, Picker);
picker组件比如Popup组件复杂许多。已官方给出的demo为例,:slots绑定的组件的插槽,传入的是一个对象数组,用于配置picker组件的内容。@change绑定的是picker被选中的值发生变化时的change事件,在这里可以做你想做事情,比如给组件的插槽赋值,获取插槽的值,或者处理其他业务逻辑。
<mt-picker :slots="slots" @change="onValuesChange"></mt-picker>
我在本例中传入的插槽对象数组如下:
myAddressSlots: [ { flex: 1, values: this.getProvinceArr(), //省份数组 className:
'slot1', textAlign: 'center' }, { divider: true, content: '', className:
'slot2' }, { flex: 1, values: this.getCityArr("北京市"), className: 'slot3',
textAlign: 'center' }, { divider: true, content: '', className: 'slot4' }, {
flex: 1, values: this.getCountyArr("北京市","北京市"), className: 'slot5', textAlign:
'center' } ]
slots对象数组键值对解释:



key描述
divider分隔符
content显示的文本
valuesslot 的备选值数组。若为对象数组,则需设置 value-key 属性来指定显示的字段名
defaultIndexpicker初始选中值,需传入其在 values 数组中的序号,默认为 0
textAlign文本对齐方式
flexslot CSS 的 flex 值
className
slot 的类名,可以自定义你的样式


























比较重要的是values,必须传入数组。因为本例中需要输出地址代码,所以这里必须传入对象数组,形如:
[ { "code": "110000", "name": "北京市", "children": [ { "code": "110100", "name":
"北京市", "children": [ { "code": "110101", "name": "东城区" }, { "code": "110102",
"name": "西城区" }, { "code": "110105", "name": "朝阳区" }, { "code": "110106",
"name": "丰台区" }, { "code": "110107", "name": "石景山区" }, { "code": "110108",
"name": "海淀区" }, { "code": "110109", "name": "门头沟区" }, { "code": "110111",
"name": "房山区" }, { "code": "110112", "name": "通州区" }, { "code": "110113",
"name": "顺义区" }, { "code": "110114", "name": "昌平区" }, { "code": "110115",
"name": "大兴区" }, { "code": "110116", "name": "怀柔区" }, { "code": "110117",
"name": "平谷区" }, { "code": "110128", "name": "密云县" }, { "code": "110129",
"name": "延庆县" } ] } ] } ]
并在picker组件里设置value-key属性,这样picker就只会显示json对象中name属性。
value-key="name"
这里再学习一下picker组件的API


Picker API
参数   说明类型可选值默认值
:slots插槽数组Array []
valueKey当 slots插槽的values 为对象数组时,作为文本显示
在 Picker 中的对应字段的字段名String  
:showToolbar是否在组件顶部显示一个 toolbar,内容自定义Boolean false
:visibleItemCountpicker组件中可显示的slot个数Number 5
:itemHeight每个 slot 的高度Number 
36

change事件中还给我们暴露了许多有用的方法:



* getSlotValue(index):获取给定 slot 目前被选中的值

* setSlotValue(index, value):设定给定 slot 被选中的值,该值必须存在于该 slot 的备选值数组中
* getSlotValues(index):获取给定 slot 的备选值数组

* setSlotValues(index, values):设定给定 slot 的备选值数组
* getValues():获取所有 slot 目前被选中的值(分隔符 slot 除外)

* setValues(values):设定所有 slot 被选中的值(分隔符 slot 除外),该值必须存在于对应 slot 的备选值数组中

在本例中,我们可以打印上述函数的输出结果:
console.log(picker.getSlotValue(0));//获取被选中的省,单个slot
console.table(picker.getSlotValues(0));//获取被选中的省份备选数组
console.table(picker.getValues());//获取被选中的省市县,全部slot,分隔符除外

setSlot相关方法就不再一一举例,你可以在本地控制台自行实验查看效果。
步骤五:实现省市县三级联动并输出地址和关联code
HTML代码:
<template> <div class="three-level-address" id="three_level_address"> <div
class="region-div"> <span class="input-icon"><i class="iconfont
icon-dizhi"></i></span> <input type="text" placeholder="请选择三级地址"
v-model="region" maxlength="80" readonly="readonly" @click="showAddressPicker"
/> <mt-popup v-model="regionVisible" position="bottom" class="region-popup">
<mt-picker :slots="myAddressSlots" valueKey="name" :visibleItemCount ="5"
@change="addressChange" :itemHeight="40"></mt-picker> </mt-popup> <div
class="data-show-div"> <p><span>三级地址:</span>{{region}}</p>
<p><span>省:</span>{{province}}</p> <p><span>市:</span>{{city}}</p>
<p><span>县:</span>{{county}}</p> <p><span>省级代码:</span>{{provinceCode}}</p>
<p><span>市级代码:</span>{{cityCode}}</p> <p><span>县级代码:</span>{{countyCode}}</p>
</div> </div> </div> </template>
JavaScript代码:
<script> import Vue from 'vue' import { Popup } from 'mint-ui';
Vue.component(Popup.name, Popup);   import { Picker } from 'mint-ui';
Vue.component(Picker.name, Picker); //引入省市区数据json文件 import threeLevelAddress
from '../assets/commom/json/threeLevelAddress.json' export default { data(){
return{ region:'',//三级地址 province:'',//省 city:'',//市 county:'',//县
provinceCode:'',//省级代码 cityCode:'', //市级代码 countyCode:'',//县级代码
regionVisible:false,//弹出框是否可见
regionInit:false,//禁止地区选择器自动初始化,picker组件会默认进行初始化,导致一进入页面就会默认选中一个初始3级地址
//picker组件插槽 myAddressSlots: [           //省 { flex: 1, values:
this.getProvinceArr(), //省份数组 className: 'slot1', textAlign: 'center' },
           //分隔符 { divider: true, content: '', className: 'slot2' },         
//市 { flex: 1, values: this.getCityArr("北京市"), className: 'slot3', textAlign:
'center' }, { divider: true, content: '', className: 'slot4' },          //县 {
flex: 1, values: this.getCountyArr("北京市","北京市"), className: 'slot5', textAlign:
'center' } ], } }, methods:{ //打开地址选择器 showAddressPicker(){ this.regionVisible
= true; }, //picker组件的change事件,进行取值赋值 addressChange(picker, values){
console.log(picker); console.table(values); if (this.regionInit){
          //取值并赋值 this.region = values[0]["name"] + values[1]["name"] +
values[2]["name"]; this.province = values[0]["name"]; this.city =
values[1]["name"]; this.county = values[2]["name"]; this.provinceCode =
values[0]["code"]; this.cityCode = values[1]["code"]; this.countyCode =
values[2]["code"]; console.log(picker.getSlotValue(0));
console.table(picker.getSlotValues(0)); console.table(picker.getValues());
          //给市、县赋值 picker.setSlotValues(1, this.getCityArr(values[0]["name"]));
picker.setSlotValues(2, this.getCountyArr(values[0]["name"],
values[1]["name"])); }else { this.regionInit = true; } },     
//遍历json,返回省级对象数组 getProvinceArr() { let provinceArr = [];
threeLevelAddress.forEach(function (item) { let obj = {}; obj.name = item.name;
obj.code = item.code; provinceArr.push(obj); }); return provinceArr; },
      //遍历json,返回市级对象数组 getCityArr(province) { // console.log("省:" + province);
let cityArr = []; threeLevelAddress.forEach(function (item) { if (item.name ===
province) { item.children.forEach(function (args) { let obj = {}; obj.name =
args.name; obj.code = args.code; cityArr.push(obj); }); } }); return cityArr;
},      //遍历json,返回县级对象数组 getCountyArr(province,city){ let countyArr = [];
threeLevelAddress.forEach(function(item){ if (item.name === province){
item.children.forEach(function (args) { if (args.name === city){
args.children.forEach(function (param) { let obj = {}; obj.name=param.name;
obj.code=param.code; countyArr.push(obj); }) } }); } }); //
console.log(countyArr); return countyArr; }, }, mounted(){
      //初始化设备高度为设备高度height 100% let orderHeight = window.innerHeight;
document.getElementById("three_level_address").style.height = orderHeight +
'px'; } } </script>CSS样式:
<style scoped> .three-level-address{ width: 100%; text-align: left;
background: black; color: #ffffff; } .region-div{ width: 100%; padding-top:
1rem; } .input-icon{ display: inline-block; vertical-align: middle; }
.input-icon i{ font-size: 2rem; } .region-div input{ width: 70%; font-size:
1rem; line-height: 2rem; border-radius: 5px; outline: none; text-align: right;
color: black; } .region-popup{ width: 100%; } .data-show-div{ margin-top: 1rem;
margin-left: 1rem; color: #45C473; } .data-show-div span{ color: #ffffff;
font-size: 0.8rem; } </style>
threeLevelAddress.json部分结构

[ { "code": "110000", "name": "北京市", "children": [ { "code": "110100", "name":
"北京市", "children": [ { "code": "110101", "name": "东城区" }, { "code": "110102",
"name": "西城区" }, { "code": "110105", "name": "朝阳区" }, { "code": "110106",
"name": "丰台区" }, { "code": "110107", "name": "石景山区" }, { "code": "110108",
"name": "海淀区" }, { "code": "110109", "name": "门头沟区" }, { "code": "110111",
"name": "房山区" }, { "code": "110112", "name": "通州区" }, { "code": "110113",
"name": "顺义区" }, { "code": "110114", "name": "昌平区" }, { "code": "110115",
"name": "大兴区" }, { "code": "110116", "name": "怀柔区" }, { "code": "110117",
"name": "平谷区" }, { "code": "110128", "name": "密云县" }, { "code": "110129",
"name": "延庆县" } ] } ] }, { "code": "120000", "name": "天津市", "children": [ {
"code": "120100", "name": "天津市", "children": [ { "code": "120101", "name":
"和平区" }, { "code": "120102", "name": "河东区" }, { "code": "120103", "name": "河西区"
}, { "code": "120104", "name": "南开区" }, { "code": "120105", "name": "河北区" }, {
"code": "120106", "name": "红桥区" }, { "code": "120110", "name": "东丽区" }, {
"code": "120111", "name": "西青区" }, { "code": "120112", "name": "津南区" }, {
"code": "120113", "name": "北辰区" }, { "code": "120114", "name": "武清区" }, {
"code": "120115", "name": "宝坻区" }, { "code": "120116", "name": "滨海新区" }, {
"code": "120121", "name": "宁河县" }, { "code": "120123", "name": "静海县" }, {
"code": "120125", "name": "蓟县" } ] } ] } ]中华人民共和国行政区划:省级(省份直辖市自治区)、 地级(城市)、
县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村三级四级五级联动地址数据可见GitHub:
https://github.com/modood/Administrative-divisions-of-China
<https://github.com/modood/Administrative-divisions-of-China>



最后,上效果图:







如果你觉得这篇博客不错的话,记得收藏关注一波哦。我会不定时更新,分享自己学习的有趣的前端知识。

我的gitbook地址是https://legacy.gitbook.com/@lzweife
<https://legacy.gitbook.com/@lzweife>,不定时更新学习笔记。

限于本人水平有限,难免出现纰漏错误,欢迎指正,相互学习。首次尝试写博客,语言不当不通顺之处,请先凑合看。

学海无涯,生命不息。

版权声明:本文为博主原创文章,转载请先征求博主同意并附上原文链接。
https://blog.csdn.net/lzw_1994/article/details/79981166