vue.js+mint-ui的Popup组件和Picker组件实现省市县三级联动功能

前言

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

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

mint-ui官网http://mint-ui.github.io/#!/zh-cn

步骤一:使用vue-cli快速构建demo项目

       vue-cli是vue提供的官方命令行工具,用于快速搭建大型单页应用。

 

  • 新建一个空文件夹demo,安装node.js,安装方法请谷歌或者百度,非常简单。
  • 用IDEA开发工具打开demo文件夹,打开Terminal终端,输入下面的命令行全局安装vue-cli
npm 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项目构建完毕
  1.  
    cd demo
  2.  
    npm run dev

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

 

  • 在demo/src/components路径下新建ThreeLevelAddress.vue文件
     1 <template>
     2   <div class="three-level-address">
     3     hello
     4   </div>
     5 </template>
     6  
     7 <script>
     8 </script>
     9  
    10 <style scoped>
    11 </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-ui
npm i mint-ui -S
 
  • 在demo/src/main.js路径下写入以下代码即可使用mint-ui
  • import 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组件
1 import { Popup } from 'mint-ui';
2 Vue.component(Popup.name, Popup);

 

Popup的API

 

 

Popup API
参数说明类型可选值默认值
position popup 的位置。省略则居中显示 String 'top'
'right'
'bottom'
'left'
 
pop-transition 显示/隐藏时的动效,仅在省略 position 时可配置 String 'popup-fade'  
modal 是否创建一个 modal 层 Boolean   true
closeOnClickModal 是否可以通过点击 modal 层来关闭 popup Boolean   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 显示的文本
values slot 的备选值数组。若为对象数组,则需设置 value-key 属性来指定显示的字段名
defaultIndex picker初始选中值,需传入其在 values 数组中的序号,默认为 0
textAlign 文本对齐方式
flex slot 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
:visibleItemCount picker组件中可显示的slot个数 Number   5
:itemHeight 每个 slot 的高度 Number  

36

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

 

  1. getSlotValue(index):获取给定 slot 目前被选中的值
  2. setSlotValue(index, value):设定给定 slot 被选中的值,该值必须存在于该 slot 的备选值数组中
  3. getSlotValues(index):获取给定 slot 的备选值数组
  4. setSlotValues(index, values):设定给定 slot 的备选值数组
  5. getValues():获取所有 slot 目前被选中的值(分隔符 slot 除外)
  6. 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

 

最后,上效果图:

 

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

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

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

学海无涯,生命不息。

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

 

posted @ 2018-07-20 09:28  WRC_BlackPearl  阅读(712)  评论(0编辑  收藏  举报