基于Vue+ElementUI的省市区地址选择通用组件


Posted in Javascript onNovember 20, 2019

一、缘由

在项目开发过程中,有一个需求是省市区地址选择的功能,一开始想的是直接使用静态地址资源库本地打包,但这种方式不方便维护,于是放弃。后来又想直接让后台返回全部地址数据,然后使用级联选择器进行选择,但发现数据传输量有点大且处理过程耗时,于是又摒弃了这种方法。最后还是决定采用异步的方式进行省市区地址选择,即先查询省份列表,然后根据选择的省份code查询城市列表,最后根据选择的城市列表获取区/县列表,最终根据应用场景不同,给出了两种实现方案。

其中后台总共需要提供4个接口,一个查询所有省份的接口,一个根据省份code查询其下所有城市的接口,一个根据城市code查询其下所有区/县的接口,以及一个根据地址code转换成省市区三个code值的接口。

// 本人项目中使用的四个接口
`${this.API.province}/${countryCode}` // 根据国家code查询省份列表,中国固定为156,可以拓展
`${this.API.city }/${provinceCode}` // 根据省份code查询城市列表
`${this.API.area}/${cityCode}` // 根据城市code查询区/县列表
`${this.API.addressCode}/${addressCode}` // 地址code转换为省市区code

二、基于el-cascader 级联选择器的单选择框实现方案

<template>
 <el-row>
  <el-cascader
   size="small"
   :options="city.options"
   :props="props"
   v-model="cityValue"
   @active-item-change="handleItemChange"
   @change="cityChange">
  </el-cascader>
 </el-row>
</template>

<script>
export default {
 name: 'addressSelector',
 props: {
  areaCode: null
 },

 model: {
  prop: 'areaCode',
  event: 'cityChange'
 },

 data () {
  return {
   // 所在省市
   city: {
    obj: {},
    options: []
   },
   props: { // 级联选择器的属性配置
    value: 'value',
    children: 'cities',
    checkStrictly: true
   },
   cityValue: [], // 城市代码
  }
 },
 computed: {
 },
 created () {
  this._initData()
 },
 mounted () {
 },
 methods: {
  _initData () {
   this.$http({
    method: 'get',
    url: this.API.province + '/156' // 中国
   }).then(res => {
    this.city.options = res.data.body.map(item => { // 所在省市
     return {
      value: item.provinceCode,
      label: item.provinceName,
      cities: []
     }
    })
   })
  },
  getCodeByAreaCode (code) {
   if (code == undefined) return false
   this.$http({
    method: 'get',
    url: this.API.addressCode + '/' + code
   })
   .then(res => {
    if (res.data.code === this.API.SUCCESS) {
     let provinceCode = res.data.body.provinceCode
     let cityCode = res.data.body.cityCode
     let areaCode = res.data.body.areaCode
     this.cityValue = [provinceCode, cityCode, areaCode]
     this.handleItemChange([provinceCode, cityCode])
    }
   })
   .finally(res => {
   })
  },
  handleItemChange (value) {
   let a = (item) => {
    this.$http({
     method: 'get',
     url: this.API.city + '/' + value[0],
    }).then(res => {
     item.cities = res.data.body.map(ite => {
      return {
       value: ite.cityCode,
       label: ite.cityName,
       cities: []
      }
     })
     if(value.length === 2){ // 如果传入的value.length===2 && 先执行的a(),说明是传入了areaCode,需要初始化多选框
      b(item)
     }
    }).finally(_ => {
    })
   }
   let b = (item) => {
    if (value.length === 2) {
     item.cities.find(ite => {
      if (ite.value === value[1]) {
       if (!ite.cities.length) {
        this.$http({
         method: 'get',
         url: this.API.area + '/' + value[1]
        }).then(res => {
         ite.cities = res.data.body.map(ite => {
          return {
           value: ite.areaCode,
           label: ite.areaName,
          }
         })
        }).finally(_ => {
        })
       }
      }
     })
    }
   }
   this.city.options.find(item => {
    if (item.value === value[0]) {
     if (item.cities.length) {
      b(item)
     } else {
      a(item)
     }
     return true
    }
   })
  },
  getCityCode () {
   return this.cityValue[2]
  },
  reset () {
   this.cityValue = []
  },
  cityChange (value) {
   if (value.length === 3) {
    this.$emit('cityChange', value[2])
   } else {
    this.$emit('cityChange', null)
   }
  }
 },
 watch: {
  areaCode: {
   deep: true,
   immediate: true,
   handler (newVal) {
    if (newVal) {
     this.getCodeByAreaCode(newVal)
    } else {
     this.$nextTick(() => {
      this.reset()
     })
    }
   }
  }
 }
}
</script>

<style lang="less" scoped>
</style>

最终效果如下(动图):

基于Vue+ElementUI的省市区地址选择通用组件

截图:

基于Vue+ElementUI的省市区地址选择通用组件

三、基于el-select选择器的多选择框实现方案

lt;template>
 <div id="addressHorizontalSelect">
  <el-row>
   <el-col
    :span="span">
    <el-select
     size="small"
     v-model="provinceCode"
     @focus="getProvinces"
     @change="changeProvince"
     :placeholder="$t('省')"
     filterable>
     <el-option
      v-for="item in provinceList"
      :key="item.provinceCode"
      :label="item.provinceName"
      :value="item.provinceCode">
     </el-option>
    </el-select>
   </el-col>
   <el-col
    :span="span"
    v-if="!hideCity">
    <el-select
     size="small"
     v-model="cityCode"
     @focus="getCities"
     @change="changeCity"
     :placeholder="$t('市')"
     filterable>
     <el-option
      v-for="item in cityList"
      :key="item.cityCode"
      :label="item.cityName"
      :value="item.cityCode">
     </el-option>
    </el-select>
   </el-col>
   <el-col
    :span="span"
    v-if="!hideCity && !hideArea">
    <el-select
     size="small"
     v-model="areaCode"
     @focus="getAreas"
     @change="changeArea"
     :placeholder="$t('区/县')"
     filterable>
     <el-option
      v-for="item in areaList"
      :key="item.areaCode"
      :label="item.areaName"
      :value="item.areaCode">
     </el-option>
    </el-select>
   </el-col>
  </el-row>
 </div>
</template>

<script>
export default {
 name: 'addressHorizontalSelect',

 components: {},

 props: {
  hideCity: { // 隐藏市
   type: Boolean,
   default: false
  },
  hideArea: { // 隐藏区/县
   type: Boolean,
   default: false
  },
  addressCode: null // 地址编码
 },

 model: {
  prop: 'addressCode',
  event: 'addressSelect'
 },

 data() {
  return {
   provinceList: [], // 省份列表
   cityList: [], // 城市列表
   areaList: [], // 区/县列表
   provinceCode: '', // 省份编码
   cityCode: '', // 城市编码
   areaCode: '', // 区/县编码
   cityFlag: false, // 避免重复请求的标志
   provinceFlag: false,
   areaFlag: false
  }
 },

 computed: {
  span () {
   if (this.hideCity) {
    return 24
   }
   if (this.hideArea) {
    return 12
   }
   return 8
  }
 },

 watch: {
 },

 created () {
  this.getProvinces()
 },

 methods: {
  /**
   * 获取数据
   * @param {Array} array 列表
   * @param {String} url 请求url
   * @param {String} code 编码(上一级编码)
   */
  fetchData (array, url, code) {
   this.$http({
    method: 'get',
    url: url + '/' + code
   })
   .then(res => {
    if (res.data.code === this.API.SUCCESS) {
     let body = res.data.body || []
     array.splice(0, array.length, ...body)
    }
   })
   .catch(err => {
    console.log(err)
   })
   .finally(res => {
   })
  },
  // 根据国家编码获取省份列表
  getProvinces () {
   if (this.provinceFlag) {
    return
   }
   this.fetchData(this.provinceList, this.API.province, 156)
   this.provinceFlag = true
  },
  // 省份修改,拉取对应城市列表
  changeProvince (val) {
   this.fetchData(this.cityList, this.API.city, this.provinceCode)
   this.cityFlag = true
   this.cityCode = ''
   this.areaCode = ''
   this.$emit('addressSelect', val)
  },
  // 根据省份编码获取城市列表
  getCities () {
   if (this.cityFlag) {
    return
   }
   if (this.provinceCode) {
    this.fetchData(this.cityList, this.API.city, this.provinceCode)
    this.cityFlag = true
   }
  },
  // 城市修改,拉取对应区域列表
  changeCity (val) {
   this.fetchData(this.areaList, this.API.area, this.cityCode)
   this.areaFlag = true
   this.areaCode = ''
   this.$emit('addressSelect', val)
  },
  // 根据城市编码获取区域列表
  getAreas () {
   if (this.areaFlag) {
    return
   }
   if (this.cityCode) {
    this.fetchData(this.areaList, this.API.area, this.cityCode)
   }
  },
  // 区域修改
  changeArea (val) {
   this.$emit('addressSelect', val)
  },
  // 重置省市区/县编码
  reset () {
   this.provinceCode = '',
   this.cityCode = '',
   this.areaCode = ''
  },
  // 地址编码转换成省市区列表
  addressCodeToList (addressCode) {
   if (!addressCode) return false
   this.$http({
    method: 'get',
    url: this.API.addressCode + '/' + addressCode
   })
   .then(res => {
    let data = res.data.body
    if (!data) return
    if (data.provinceCode) {
     this.provinceCode = data.provinceCode
     this.fetchData(this.cityList, this.API.city, this.provinceCode)
    } else if (data.cityCode) {
     this.cityCode = data.cityCode
     this.fetchData(this.areaList, this.API.area, this.cityCode)
    } else if (data.areaCode) {
     this.areaCode = data.areaCode
    }
   })
   .finally(res => {
   })
  }
 },

 watch: {
  addressCode: {
   deep: true,
   immediate: true,
   handler (newVal) {
    if (newVal) {
     this.addressCodeToList(newVal)
    } else {
     this.$nextTick(() => {
      this.reset()
     })
    }
   }
  }
 }
}
</script>

<style lang="less" scoped>
</style>

实现效果如下(动图):

基于Vue+ElementUI的省市区地址选择通用组件

四、总结

两个组件都实现了双向绑定,根据场景不同可以使用不同的组件,如果读者有需求,根据自己的接口和场景进行修改即可。

当拓展至大洲-国家-省-市-区-街道等时,第一种级联选择器的方案就会暴露出拓展性较差的问题,随着层级加深,数据结构会变得复杂,而第二种方案明显可拓展性更强

Javascript 相关文章推荐
jQuery scroll事件实现监控滚动条分页示例
Apr 04 Javascript
JS获取iframe中longdesc属性的方法
Apr 01 Javascript
微信小程序 开发工具快捷键整理
Oct 31 Javascript
jQuery实现文章图片弹出放大效果
Apr 06 jQuery
基于JS代码实现简单易用的倒计时 x 天 x 时 x 分 x 秒效果
Jul 13 Javascript
浅谈基于Vue.js的移动组件库cube-ui
Dec 20 Javascript
Node.js使用MySQL连接池的方法实例
Feb 11 Javascript
vue中利用simplemde实现markdown编辑器(增加图片上传功能)
Apr 29 Javascript
JavaScript Tab菜单实现过程解析
May 13 Javascript
5个你不知道的JavaScript字符串处理库(小结)
Jun 01 Javascript
从0到1学习JavaScript编写贪吃蛇游戏
Jul 28 Javascript
在vue-cli创建的项目中使用sass操作
Aug 10 Javascript
解决node.js含有%百分号时发送get请求时浏览器地址自动编码的问题
Nov 20 #Javascript
Node.JS发送http请求批量检查文件中的网页地址、服务是否有效可用
Nov 20 #Javascript
详解Nuxt.js 实战集锦
Nov 19 #Javascript
javascript的delete运算符知识点总结
Nov 19 #Javascript
100行代码实现vue表单校验功能(小白自编)
Nov 19 #Javascript
Angular 多级路由实现登录页面跳转(小白教程)
Nov 19 #Javascript
nodemon实现Typescript项目热更新的示例代码
Nov 19 #Javascript
You might like
PHP压缩html网页代码(清除空格,换行符,制表符,注释标记)
2012/04/02 PHP
探讨Hessian在PHP中的使用分析
2013/06/13 PHP
php将数组存储为文本文件方法汇总
2015/10/28 PHP
PHP标准类(stdclass)用法示例
2016/09/28 PHP
JQuery获取元素文档大小、偏移和位置和滚动条位置的方法集合
2010/01/12 Javascript
JavaScript的继承的封装介绍
2013/10/15 Javascript
基于jquery的simpleValidate简易验证插件
2014/01/31 Javascript
JavaScript 事件绑定及深入
2015/04/13 Javascript
AspNet中使用JQuery上传插件Uploadify详解
2015/05/20 Javascript
thinkphp实现无限分类(使用递归)
2015/12/19 Javascript
js实现可键盘控制的简单抽奖程序
2016/07/13 Javascript
Javascript从数组中随机取出不同元素的两种方法
2016/09/22 Javascript
jQuery事件_动力节点Java学院整理
2017/07/05 jQuery
vue.js删除列表中的一行
2018/06/30 Javascript
element-ui中的select下拉列表设置默认值方法
2018/08/24 Javascript
JS常见构造模式实例对比分析
2018/08/27 Javascript
详解vuejs2.0 select 动态绑定下拉框支持多选
2019/04/25 Javascript
element日历calendar组件上月、今天、下月、日历块点击事件及模板源码
2020/07/27 Javascript
vue从后台渲染文章列表以及根据id跳转文章详情详解
2020/12/14 Vue.js
[54:57]DOTA2-DPC中国联赛定级赛 Aster vs DLG BO3第二场 1月8日
2021/03/11 DOTA
python使用socket向客户端发送数据的方法
2015/04/29 Python
python 通过类中一个方法获取另一个方法变量的实例
2019/01/22 Python
Python使用ctypes调用C/C++的方法
2019/01/29 Python
Python合并2个字典成1个新字典的方法(9种)
2019/12/19 Python
python实现信号时域统计特征提取代码
2020/02/26 Python
python 错误处理 assert详解
2020/04/20 Python
Python实现发票自动校核微信机器人的方法
2020/05/22 Python
CSS3 实现弹幕的示例代码
2017/08/07 HTML / CSS
JPA的特点
2014/10/25 面试题
优秀社区干部事迹材料
2014/02/03 职场文书
食堂采购员岗位职责
2014/03/17 职场文书
《歌唱二小放牛郎》教学反思
2014/04/19 职场文书
先进党支部事迹材料
2014/12/24 职场文书
市直属机关2016年主题党日活动总结
2016/04/05 职场文书
Java 超详细讲解ThreadLocal类的使用
2022/04/07 Java/Android
JS实现简单的九宫格抽奖
2022/06/28 Javascript