基于Vue+ElementUI的省市區(qū)地址選擇通用組件
一、緣由
在項(xiàng)目開發(fā)過程中,有一個(gè)需求是省市區(qū)地址選擇的功能,一開始想的是直接使用靜態(tài)地址資源庫本地打包,但這種方式不方便維護(hù),于是放棄。后來又想直接讓后臺(tái)返回全部地址數(shù)據(jù),然后使用級(jí)聯(lián)選擇器進(jìn)行選擇,但發(fā)現(xiàn)數(shù)據(jù)傳輸量有點(diǎn)大且處理過程耗時(shí),于是又摒棄了這種方法。最后還是決定采用異步的方式進(jìn)行省市區(qū)地址選擇,即先查詢省份列表,然后根據(jù)選擇的省份code查詢城市列表,最后根據(jù)選擇的城市列表獲取區(qū)/縣列表,最終根據(jù)應(yīng)用場景不同,給出了兩種實(shí)現(xiàn)方案。
其中后臺(tái)總共需要提供4個(gè)接口,一個(gè)查詢所有省份的接口,一個(gè)根據(jù)省份code查詢其下所有城市的接口,一個(gè)根據(jù)城市code查詢其下所有區(qū)/縣的接口,以及一個(gè)根據(jù)地址code轉(zhuǎn)換成省市區(qū)三個(gè)code值的接口。
// 本人項(xiàng)目中使用的四個(gè)接口
`${this.API.province}/${countryCode}` // 根據(jù)國家code查詢省份列表,中國固定為156,可以拓展
`${this.API.city }/${provinceCode}` // 根據(jù)省份code查詢城市列表
`${this.API.area}/${cityCode}` // 根據(jù)城市code查詢區(qū)/縣列表
`${this.API.addressCode}/${addressCode}` // 地址code轉(zhuǎn)換為省市區(qū)code
二、基于el-cascader 級(jí)聯(lián)選擇器的單選擇框?qū)崿F(xiàn)方案
<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: { // 級(jí)聯(lián)選擇器的屬性配置
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 && 先執(zhí)行的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>
最終效果如下(動(dòng)圖):

截圖:

三、基于el-select選擇器的多選擇框?qū)崿F(xiàn)方案
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('區(qū)/縣')"
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: { // 隱藏區(qū)/縣
type: Boolean,
default: false
},
addressCode: null // 地址編碼
},
model: {
prop: 'addressCode',
event: 'addressSelect'
},
data() {
return {
provinceList: [], // 省份列表
cityList: [], // 城市列表
areaList: [], // 區(qū)/縣列表
provinceCode: '', // 省份編碼
cityCode: '', // 城市編碼
areaCode: '', // 區(qū)/縣編碼
cityFlag: false, // 避免重復(fù)請求的標(biāo)志
provinceFlag: false,
areaFlag: false
}
},
computed: {
span () {
if (this.hideCity) {
return 24
}
if (this.hideArea) {
return 12
}
return 8
}
},
watch: {
},
created () {
this.getProvinces()
},
methods: {
/**
* 獲取數(shù)據(jù)
* @param {Array} array 列表
* @param {String} url 請求url
* @param {String} code 編碼(上一級(jí)編碼)
*/
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 => {
})
},
// 根據(jù)國家編碼獲取省份列表
getProvinces () {
if (this.provinceFlag) {
return
}
this.fetchData(this.provinceList, this.API.province, 156)
this.provinceFlag = true
},
// 省份修改,拉取對應(yīng)城市列表
changeProvince (val) {
this.fetchData(this.cityList, this.API.city, this.provinceCode)
this.cityFlag = true
this.cityCode = ''
this.areaCode = ''
this.$emit('addressSelect', val)
},
// 根據(jù)省份編碼獲取城市列表
getCities () {
if (this.cityFlag) {
return
}
if (this.provinceCode) {
this.fetchData(this.cityList, this.API.city, this.provinceCode)
this.cityFlag = true
}
},
// 城市修改,拉取對應(yīng)區(qū)域列表
changeCity (val) {
this.fetchData(this.areaList, this.API.area, this.cityCode)
this.areaFlag = true
this.areaCode = ''
this.$emit('addressSelect', val)
},
// 根據(jù)城市編碼獲取區(qū)域列表
getAreas () {
if (this.areaFlag) {
return
}
if (this.cityCode) {
this.fetchData(this.areaList, this.API.area, this.cityCode)
}
},
// 區(qū)域修改
changeArea (val) {
this.$emit('addressSelect', val)
},
// 重置省市區(qū)/縣編碼
reset () {
this.provinceCode = '',
this.cityCode = '',
this.areaCode = ''
},
// 地址編碼轉(zhuǎn)換成省市區(qū)列表
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>
實(shí)現(xiàn)效果如下(動(dòng)圖):

四、總結(jié)
兩個(gè)組件都實(shí)現(xiàn)了雙向綁定,根據(jù)場景不同可以使用不同的組件,如果讀者有需求,根據(jù)自己的接口和場景進(jìn)行修改即可。
當(dāng)拓展至大洲-國家-省-市-區(qū)-街道等時(shí),第一種級(jí)聯(lián)選擇器的方案就會(huì)暴露出拓展性較差的問題,隨著層級(jí)加深,數(shù)據(jù)結(jié)構(gòu)會(huì)變得復(fù)雜,而第二種方案明顯可拓展性更強(qiáng)
相關(guān)文章
vue+spring boot實(shí)現(xiàn)校驗(yàn)碼功能
這篇文章主要為大家詳細(xì)介紹了vue+spring boot實(shí)現(xiàn)校驗(yàn)碼功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05
Vue項(xiàng)目如何根據(jù)圖片url獲取file對象并用axios上傳
這篇文章主要介紹了Vue項(xiàng)目如何根據(jù)圖片url獲取file對象并用axios上傳問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-09-09
vue啟動(dòng)后請求后端接口報(bào)ERR_EMPTY_RESPONSE錯(cuò)誤的解決
這篇文章主要介紹了vue啟動(dòng)后請求后端接口報(bào)ERR_EMPTY_RESPONSE錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-05-05
vue使用assign巧妙重置data數(shù)據(jù)方式
這篇文章主要介紹了vue使用assign巧妙重置data數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Vue 如何使用props、emit實(shí)現(xiàn)自定義雙向綁定的實(shí)現(xiàn)
這篇文章主要介紹了Vue 如何使用props、emit實(shí)現(xiàn)自定義雙向綁定的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
vue2.0基于vue-cli+element-ui制作樹形treeTable
這篇文章主要介紹了vue2.0基于vue-cli+element-ui制作樹形treeTable,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04

