当前位置:   article > 正文

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

vue 省市区 多选 组件

一、缘由

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

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

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

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

  1. <template>
  2. <el-row>
  3. <el-cascader
  4. size="small"
  5. :options="city.options"
  6. :props="props"
  7. v-model="cityValue"
  8. @active-item-change="handleItemChange"
  9. @change="cityChange">
  10. </el-cascader>
  11. </el-row>
  12. </template>
  13. <script>
  14. export default {
  15. name: 'addressSelector',
  16. props: {
  17. areaCode: null
  18. },
  19. model: {
  20. prop: 'areaCode',
  21. event: 'cityChange'
  22. },
  23. data () {
  24. return {
  25. // 所在省市
  26. city: {
  27. obj: {},
  28. options: []
  29. },
  30. props: { // 级联选择器的属性配置
  31. value: 'value',
  32. children: 'cities',
  33. checkStrictly: true
  34. },
  35. cityValue: [], // 城市代码
  36. }
  37. },
  38. computed: {
  39. },
  40. created () {
  41. this._initData()
  42. },
  43. mounted () {
  44. },
  45. methods: {
  46. _initData () {
  47. this.$http({
  48. method: 'get',
  49. url: this.API.province + '/156' // 中国
  50. }).then(res => {
  51. this.city.options = res.data.body.map(item => { // 所在省市
  52. return {
  53. value: item.provinceCode,
  54. label: item.provinceName,
  55. cities: []
  56. }
  57. })
  58. })
  59. },
  60. getCodeByAreaCode (code) {
  61. if (code == undefined) return false
  62. this.$http({
  63. method: 'get',
  64. url: this.API.addressCode + '/' + code
  65. })
  66. .then(res => {
  67. if (res.data.code === this.API.SUCCESS) {
  68. let provinceCode = res.data.body.provinceCode
  69. let cityCode = res.data.body.cityCode
  70. let areaCode = res.data.body.areaCode
  71. this.cityValue = [provinceCode, cityCode, areaCode]
  72. this.handleItemChange([provinceCode, cityCode])
  73. }
  74. })
  75. .finally(res => {
  76. })
  77. },
  78. handleItemChange (value) {
  79. let a = (item) => {
  80. this.$http({
  81. method: 'get',
  82. url: this.API.city + '/' + value[0],
  83. }).then(res => {
  84. item.cities = res.data.body.map(ite => {
  85. return {
  86. value: ite.cityCode,
  87. label: ite.cityName,
  88. cities: []
  89. }
  90. })
  91. if(value.length === 2){ // 如果传入的value.length===2 && 先执行的a(),说明是传入了areaCode,需要初始化多选框
  92. b(item)
  93. }
  94. }).finally(_ => {
  95. })
  96. }
  97. let b = (item) => {
  98. if (value.length === 2) {
  99. item.cities.find(ite => {
  100. if (ite.value === value[1]) {
  101. if (!ite.cities.length) {
  102. this.$http({
  103. method: 'get',
  104. url: this.API.area + '/' + value[1]
  105. }).then(res => {
  106. ite.cities = res.data.body.map(ite => {
  107. return {
  108. value: ite.areaCode,
  109. label: ite.areaName,
  110. }
  111. })
  112. }).finally(_ => {
  113. })
  114. }
  115. }
  116. })
  117. }
  118. }
  119. this.city.options.find(item => {
  120. if (item.value === value[0]) {
  121. if (item.cities.length) {
  122. b(item)
  123. } else {
  124. a(item)
  125. }
  126. return true
  127. }
  128. })
  129. },
  130. getCityCode () {
  131. return this.cityValue[2]
  132. },
  133. reset () {
  134. this.cityValue = []
  135. },
  136. cityChange (value) {
  137. if (value.length === 3) {
  138. this.$emit('cityChange', value[2])
  139. } else {
  140. this.$emit('cityChange', null)
  141. }
  142. }
  143. },
  144. watch: {
  145. areaCode: {
  146. deep: true,
  147. immediate: true,
  148. handler (newVal) {
  149. if (newVal) {
  150. this.getCodeByAreaCode(newVal)
  151. } else {
  152. this.$nextTick(() => {
  153. this.reset()
  154. })
  155. }
  156. }
  157. }
  158. }
  159. }
  160. </script>
  161. <style lang="less" scoped>
  162. </style>

最终效果如下(动图):

截图:

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

  1. <template>
  2. <div id="addressHorizontalSelect">
  3. <el-row>
  4. <el-col
  5. :span="span">
  6. <el-select
  7. size="small"
  8. v-model="provinceCode"
  9. @focus="getProvinces"
  10. @change="changeProvince"
  11. :placeholder="$t('省')"
  12. filterable>
  13. <el-option
  14. v-for="item in provinceList"
  15. :key="item.provinceCode"
  16. :label="item.provinceName"
  17. :value="item.provinceCode">
  18. </el-option>
  19. </el-select>
  20. </el-col>
  21. <el-col
  22. :span="span"
  23. v-if="!hideCity">
  24. <el-select
  25. size="small"
  26. v-model="cityCode"
  27. @focus="getCities"
  28. @change="changeCity"
  29. :placeholder="$t('市')"
  30. filterable>
  31. <el-option
  32. v-for="item in cityList"
  33. :key="item.cityCode"
  34. :label="item.cityName"
  35. :value="item.cityCode">
  36. </el-option>
  37. </el-select>
  38. </el-col>
  39. <el-col
  40. :span="span"
  41. v-if="!hideCity && !hideArea">
  42. <el-select
  43. size="small"
  44. v-model="areaCode"
  45. @focus="getAreas"
  46. @change="changeArea"
  47. :placeholder="$t('区/县')"
  48. filterable>
  49. <el-option
  50. v-for="item in areaList"
  51. :key="item.areaCode"
  52. :label="item.areaName"
  53. :value="item.areaCode">
  54. </el-option>
  55. </el-select>
  56. </el-col>
  57. </el-row>
  58. </div>
  59. </template>
  60. <script>
  61. export default {
  62. name: 'addressHorizontalSelect',
  63. components: {},
  64. props: {
  65. hideCity: { // 隐藏市
  66. type: Boolean,
  67. default: false
  68. },
  69. hideArea: { // 隐藏区/县
  70. type: Boolean,
  71. default: false
  72. },
  73. addressCode: null // 地址编码
  74. },
  75. model: {
  76. prop: 'addressCode',
  77. event: 'addressSelect'
  78. },
  79. data() {
  80. return {
  81. provinceList: [], // 省份列表
  82. cityList: [], // 城市列表
  83. areaList: [], // 区/县列表
  84. provinceCode: '', // 省份编码
  85. cityCode: '', // 城市编码
  86. areaCode: '', // 区/县编码
  87. cityFlag: false, // 避免重复请求的标志
  88. provinceFlag: false,
  89. areaFlag: false
  90. }
  91. },
  92. computed: {
  93. span () {
  94. if (this.hideCity) {
  95. return 24
  96. }
  97. if (this.hideArea) {
  98. return 12
  99. }
  100. return 8
  101. }
  102. },
  103. watch: {
  104. },
  105. created () {
  106. this.getProvinces()
  107. },
  108. methods: {
  109. /**
  110. * 获取数据
  111. * @param {Array} array 列表
  112. * @param {String} url 请求url
  113. * @param {String} code 编码(上一级编码)
  114. */
  115. fetchData (array, url, code) {
  116. this.$http({
  117. method: 'get',
  118. url: url + '/' + code
  119. })
  120. .then(res => {
  121. if (res.data.code === this.API.SUCCESS) {
  122. let body = res.data.body || []
  123. array.splice(0, array.length, ...body)
  124. }
  125. })
  126. .catch(err => {
  127. console.log(err)
  128. })
  129. .finally(res => {
  130. })
  131. },
  132. // 根据国家编码获取省份列表
  133. getProvinces () {
  134. if (this.provinceFlag) {
  135. return
  136. }
  137. this.fetchData(this.provinceList, this.API.province, 156)
  138. this.provinceFlag = true
  139. },
  140. // 省份修改,拉取对应城市列表
  141. changeProvince (val) {
  142. this.fetchData(this.cityList, this.API.city, this.provinceCode)
  143. this.cityFlag = true
  144. this.cityCode = ''
  145. this.areaCode = ''
  146. this.$emit('addressSelect', val)
  147. },
  148. // 根据省份编码获取城市列表
  149. getCities () {
  150. if (this.cityFlag) {
  151. return
  152. }
  153. if (this.provinceCode) {
  154. this.fetchData(this.cityList, this.API.city, this.provinceCode)
  155. this.cityFlag = true
  156. }
  157. },
  158. // 城市修改,拉取对应区域列表
  159. changeCity (val) {
  160. this.fetchData(this.areaList, this.API.area, this.cityCode)
  161. this.areaFlag = true
  162. this.areaCode = ''
  163. this.$emit('addressSelect', val)
  164. },
  165. // 根据城市编码获取区域列表
  166. getAreas () {
  167. if (this.areaFlag) {
  168. return
  169. }
  170. if (this.cityCode) {
  171. this.fetchData(this.areaList, this.API.area, this.cityCode)
  172. }
  173. },
  174. // 区域修改
  175. changeArea (val) {
  176. this.$emit('addressSelect', val)
  177. },
  178. // 重置省市区/县编码
  179. reset () {
  180. this.provinceCode = '',
  181. this.cityCode = '',
  182. this.areaCode = ''
  183. },
  184. // 地址编码转换成省市区列表
  185. addressCodeToList (addressCode) {
  186. if (!addressCode) return false
  187. this.$http({
  188. method: 'get',
  189. url: this.API.addressCode + '/' + addressCode
  190. })
  191. .then(res => {
  192. let data = res.data.body
  193. if (!data) return
  194. if (data.provinceCode) {
  195. this.provinceCode = data.provinceCode
  196. this.fetchData(this.cityList, this.API.city, this.provinceCode)
  197. } else if (data.cityCode) {
  198. this.cityCode = data.cityCode
  199. this.fetchData(this.areaList, this.API.area, this.cityCode)
  200. } else if (data.areaCode) {
  201. this.areaCode = data.areaCode
  202. }
  203. })
  204. .finally(res => {
  205. })
  206. }
  207. },
  208. watch: {
  209. addressCode: {
  210. deep: true,
  211. immediate: true,
  212. handler (newVal) {
  213. if (newVal) {
  214. this.addressCodeToList(newVal)
  215. } else {
  216. this.$nextTick(() => {
  217. this.reset()
  218. })
  219. }
  220. }
  221. }
  222. }
  223. }
  224. </script>
  225. <style lang="less" scoped>
  226. </style>

实现效果如下(动图):

四、总结

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/608424
推荐阅读
相关标签
  

闽ICP备14008679号