基于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 相关文章推荐
使用JavaScript检测Firefox浏览器是否启用了Firebug的代码
Dec 28 Javascript
js实现杯子倒水问题自动求解程序
Mar 25 Javascript
jquery禁用右键示例
Apr 28 Javascript
js实现图片在未加载完成前显示加载中字样
Sep 03 Javascript
基于jQuery实现仿51job城市选择功能实例代码
Mar 02 Javascript
概述jQuery中的ajax方法
Dec 16 Javascript
js实现tab选项卡切换功能
Jan 13 Javascript
js+css3实现旋转效果
Jan 20 Javascript
详解easyui 切换主题皮肤
Apr 04 Javascript
Node.js爬虫如何获取天气和每日问候详解
Aug 26 Javascript
Vue+Node实现的商城用户管理功能示例
Dec 23 Javascript
node.JS二进制操作模块buffer对象使用方法详解
Feb 06 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操作mysqli(示例代码)
2013/10/28 PHP
PHP限制页面只能在微信自带浏览器访问的代码
2014/01/15 PHP
将FCKeditor导入PHP+SMARTY的实现方法
2015/01/15 PHP
mysql_escape_string()函数用法分析
2016/04/25 PHP
php使用变量动态创建类的对象用法示例
2017/02/06 PHP
用javascript父窗口控制只弹出一个子窗口
2007/04/10 Javascript
angularJS中$apply()方法详解
2015/01/07 Javascript
深入探讨javascript中的数据类型
2015/03/04 Javascript
angular.js分页代码的实例
2016/07/27 Javascript
JS去除重复并统计数量的实现方法
2016/12/15 Javascript
layui之select的option叠加问题的解决方法
2018/03/08 Javascript
angular2实现统一的http请求头方法
2018/08/13 Javascript
vue插槽slot的理解和使用方法
2019/04/03 Javascript
Python微信库:itchat的用法详解
2017/08/14 Python
Python实现爬虫从网络上下载文档的实例代码
2018/06/13 Python
pytorch permute维度转换方法
2018/12/14 Python
Django实现简单网页弹出警告代码
2019/11/15 Python
Python configparser模块配置文件过程解析
2020/03/03 Python
Pytorch转onnx、torchscript方式
2020/05/25 Python
python使用隐式循环快速求和的实现示例
2020/09/11 Python
python用tkinter实现一个gui的翻译工具
2020/10/26 Python
Fossil加拿大官网:化石手表、手袋、首饰及配饰
2019/04/23 全球购物
高中物理教学反思
2014/02/08 职场文书
中国入世承诺
2014/04/01 职场文书
初中班主任评语
2014/04/24 职场文书
优秀党员先进材料
2014/12/18 职场文书
会计稽核岗位职责
2015/04/13 职场文书
2015年七一建党节活动方案
2015/05/05 职场文书
生日宴会祝酒词
2015/08/10 职场文书
留学文书中的个人陈述,应该注意哪些问题?
2019/08/23 职场文书
Python+Appium新手教程
2021/04/17 Python
吃通javascript正则表达式
2021/04/21 Javascript
详解MySQL的Seconds_Behind_Master
2021/05/18 MySQL
SQL Server中使用判断语句(IF ELSE/CASE WHEN )案例
2021/07/07 SQL Server
CSS3中Animation实现简单的手指点击动画的示例
2021/07/15 HTML / CSS
HttpClient实现文件上传功能
2022/08/14 Java/Android