基于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 相关文章推荐
使用js实现关闭js弹出层的窗口
Feb 10 Javascript
jQuery实现按键盘方向键翻页特效
Mar 18 Javascript
jQuery插件扩展测试实例
Jun 21 Javascript
jQuery使用serialize()表单序列化时出现中文乱码问题的解决办法
Jul 27 Javascript
AngularJS中指令的四种基本形式实例分析
Nov 22 Javascript
详解Angular 4.x Injector
May 04 Javascript
浅谈Vuejs Prop基本用法
Aug 17 Javascript
浅谈vuex中store的命名空间
Nov 08 Javascript
Vue组件通信入门之Provide和Inject机制
Dec 29 Javascript
js实现图片实时时钟
Jan 15 Javascript
vue实现登录拦截
Jun 29 Javascript
vant 自定义 van-dropdown-item的用法
Aug 05 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 $_FILES函数详解
2011/03/09 PHP
怎么在Windows系统中搭建php环境
2013/08/31 PHP
PHP实现PDO的mysql数据库操作类
2014/12/12 PHP
php禁止某ip或ip地址段访问的方法
2015/02/25 PHP
你应该知道PHP浮点数知识
2015/05/13 PHP
Thinkphp开发--集成极光推送
2017/09/15 PHP
laravel框架实现为 Blade 模板引擎添加新文件扩展名操作示例
2020/01/25 PHP
web前端开发也需要日志
2010/12/09 Javascript
jQuery数据缓存功能的实现思路及简单模拟
2013/05/27 Javascript
Javascript设计模式理论与编程实战之简单工厂模式
2015/11/03 Javascript
jquery拼接ajax 的json和字符串拼接的方法
2017/03/11 Javascript
vue 点击按钮实现动态挂载子组件的方法
2018/09/07 Javascript
开源一个微信小程序仪表盘组件过程解析
2019/07/30 Javascript
微信小程序实现导航栏和内容上下联动功能代码
2020/06/29 Javascript
[02:45]2016年中国刀塔全程回顾,完美“圣”典即将上演
2016/12/15 DOTA
[31:55]完美世界DOTA2联赛循环赛 IO vs GXR BO2第一场 11.04
2020/11/05 DOTA
Python下的twisted框架入门指引
2015/04/15 Python
python字典排序实例详解
2015/05/20 Python
python3实现读取chrome浏览器cookie
2016/06/19 Python
深入分析python数据挖掘 Json结构分析
2018/04/21 Python
Python中pymysql 模块的使用详解
2019/08/12 Python
使用python和pygame制作挡板弹球游戏
2019/12/03 Python
Python3 虚拟开发环境搭建过程(图文详解)
2020/01/06 Python
TensorFlow tf.nn.softmax_cross_entropy_with_logits的用法
2020/04/19 Python
美国电子产品购物网站:BuyDig.com
2020/06/17 全球购物
为数据库创建索引都需要注意些什么
2012/07/17 面试题
值传递还是引用传递
2015/02/08 面试题
幼儿园消防安全制度
2014/01/26 职场文书
优秀实习生感言
2014/03/01 职场文书
事业单位鉴定材料
2014/05/25 职场文书
安全生产奖惩制度
2015/08/06 职场文书
搞笑婚礼主持词开场白
2015/11/24 职场文书
《钓鱼的启示》教学反思
2016/02/18 职场文书
党风廉政建设心得体会
2019/05/21 职场文书
创业计划书之韩国烧烤店
2019/09/19 职场文书
MySQL常用慢查询分析工具详解
2022/08/14 MySQL