在现代 Web 开发中,用户体验至关重要,而表单的设计常常是提升体验的关键环节之一。随着 Vue.js 的广泛应用,许多开发者开始寻求简洁、高效的方式来处理复杂的选择输入,特别是在需要用户选择地区信息的场景下。
想象一下,当用户在一个表单中需要准确选择省、市、区、乡镇等层级的地址时,一个直观易用的地区选择器将会极大地提升用户的操作便捷性。通过使用 Vue 及其生态系统中的组件库,比如 Element Plus,我们可以轻松实现一个层级清晰、交互友好的地区选择器界面。
本文将带您深入了解如何在 Vue 应用中构建一个地区选择器,从最顶层的省份到最底层的乡镇。我们将涵盖组件的实现、数据管理和样式美化等多个维度,帮助您不仅能实现基本功能,还能提升用户体验,让用户在选择地区时流畅而开心。
无论你是 Vue 开发新手还是有一定经验的开发者,这篇文章都将为你提供实用的知识和技巧,帮助你打造更加出色的用户界面。接下来,让我们一起开始这个旅程吧!
第一步 下载 element-china-area-data
npm install element-china-area-data
这里选择element plus的级联选择器,所以没有安装element plus的
npm install element-plus
- 第一个选择到乡镇的数据需要我们自己下载 因为博主也是找了好久有没有现成的数据
下载地址
下载之后将导入到项目中,博主这里是导入到 assets文件夹里,大家也可以修改,但是不在assets里的话,在RegionView.vue导入时要修改导入路径
新建一个页面 RegionView.vue
<template>
<div>
<div class="cascader-container">
<h2 class="title">到乡镇选择地区</h2>
<el-cascader
:options="options"
:props="{ checkStrictly: true, value: 'code', label: 'name' }"
ref="cascaderAddr"
v-model="selectedOptions"
@change="handleChange"
clearable
class="cascader"
>
<template #default="{ node, data }">
<span>{{ data.name }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ addrCodes.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ addrCodesSelected.join('') }}</strong></div>
</div>
</div>
<div class="cascader-container">
<h2 class="title">到区选择地区</h2>
<el-cascader
size="large"
:options="regionData"
@change="handleChange1"
class="cascader"
v-model="selectedOptions1">
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ temp.areaCode.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ temp.areaName.join('') }}</strong></div>
</div>
</div>
<div class="cascader-container">
<h2 class="title">到市选择地区</h2>
<el-cascader
size="large"
:options="provinceAndCityData"
@change="handleChange2"
class="cascader"
v-model="selectedOptions2">
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ areaCode.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ areaName.join('') }}</strong></div>
</div>
</div>
</div>
</template>
<script>
let pcas = require('@/assets/pcas-code.json');
import {regionData,provinceAndCityData ,codeToText} from 'element-china-area-data';
export default {
name: 'RegionView',
data() {
return {
options: pcas,
provinceAndCityData,
selectedOptions: [],
addrCodes: [],
addrCodesSelected: [],
regionData,
codeToText,
selectedOptions1: [],
selectedOptions2: [],
temp: {
areaName: [],
areaCode: []
},
areaName: [],
areaCode: []
};
},
methods: {
// 获取省市区地址级联
handleChange() {
const thsAreaCode = this.$refs['cascaderAddr'].getCheckedNodes()[0];
if (thsAreaCode) {
this.addrCodes = thsAreaCode.path;
this.addrCodesSelected = thsAreaCode.pathLabels;
console.log(this.addrCodesSelected);
} else {
this.addrCodes = [];
this.addrCodesSelected = [];
}
},
handleChange1() {
this.temp.areaName=[]
this.temp.areaCode=[]
if (this.selectedOptions1[0] != null && this.selectedOptions1[1] != null && this.selectedOptions1[2] != null) {
for (let i = 0; i < 3; i++) {
this.temp.areaName.push(this.codeToText[this.selectedOptions1[i]])
this.temp.areaCode.push(this.selectedOptions1[i])
}
}
console.log(this.temp)
},
handleChange2() {
this.areaName=[]
this.areaCode=[]
if (this.selectedOptions2[0] != null && this.selectedOptions2[1] != null) {
for (let i = 0; i < 2; i++) {
this.areaName.push(this.codeToText[this.selectedOptions2[i]])
this.areaCode.push(this.selectedOptions2[i])
}
}
}
}
};
</script>
<style scoped>
.cascader-container {
max-width: 500px;
margin: 20px auto;
font-family: Arial, sans-serif;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 20px;
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
text-align: center;
}
.cascader {
width: 100%;
}
.address-info {
margin-top: 20px;
border-top: 1px solid #e0e0e0;
padding-top: 10px;
}
.info-item {
margin: 5px 0;
font-size: 16px;
}
</style>
这里为了方便查看效果,所以样式居中显示,样式根据自己需求修改,也可以封装成单独组件
第二步封装成单独组件
,需要选择到哪一级,就选择相应的组件 将地区选择器封装成三个独立的组件,可以使代码更具可复用性和可维护性。每个组件可以专注于处理不同层级的地区选择(乡镇、区和市)。以下是对代码的重构,包括三个独立的组件以及父组件的整合。
1. 地区选择器组件结构
- TownSelector.vue:用于选择乡镇
- DistrictSelector.vue:用于选择区
- CitySelector.vue:用于选择市
- RegionView.vue:父组件,整合上述三个选择器
TownSelector.vue
<template>
<div class="cascader-container">
<h2 class="title">到乡镇选择地区</h2>
<el-cascader
:options="options"
:props="{ checkStrictly: true, value: 'code', label: 'name' }"
v-model="selectedOptions"
@change="handleChange"
clearable
class="cascader"
>
<template #default="{ node, data }">
<span>{{ data.name }}</span>
<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
</template>
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ addrCodes.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ addrCodesSelected.join('') }}</strong></div>
</div>
</div>
</template>
<script>
export default {
props: {
options: Array,
selectedOptions: Array,
addrCodes: Array,
addrCodesSelected: Array,
onChange: Function
},
methods: {
handleChange() {
const thsAreaCode = this.$refs.cascaderAddr.getCheckedNodes()[0];
if (thsAreaCode) {
this.addrCodes = thsAreaCode.path;
this.addrCodesSelected = thsAreaCode.pathLabels;
this.onChange(this.addrCodes, this.addrCodesSelected);
} else {
this.addrCodes = [];
this.addrCodesSelected = [];
}
}
}
};
</script>
<style scoped>
.cascader-container {
max-width: 500px;
margin: 20px auto;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
text-align: center;
}
.cascader {
width: 100%;
}
.address-info {
margin-top: 20px;
border-top: 1px solid #e0e0e0;
padding-top: 10px;
}
.info-item {
margin: 5px 0;
font-size: 16px;
}
</style>
DistrictSelector.vue
<template>
<div class="cascader-container">
<h2 class="title">到区选择地区</h2>
<el-cascader
size="large"
:options="regionData"
v-model="selectedOptions"
@change="handleChange"
clearable
class="cascader"
>
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ temp.areaCode.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ temp.areaName.join('') }}</strong></div>
</div>
</div>
</template>
<script>
export default {
props: {
regionData: Array,
selectedOptions: Array,
codeToText: Object,
onChange: Function
},
data() {
return {
temp: {
areaName: [],
areaCode: []
}
};
},
methods: {
handleChange() {
this.temp.areaName = [];
this.temp.areaCode = [];
if (this.selectedOptions.length > 0) {
this.selectedOptions.forEach((code) => {
this.temp.areaName.push(this.codeToText[code]);
this.temp.areaCode.push(code);
});
this.onChange(this.temp.areaName, this.temp.areaCode);
}
}
}
};
</script>
<style scoped>
.cascader-container {
max-width: 500px;
margin: 20px auto;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
text-align: center;
}
.cascader {
width: 100%;
}
.address-info {
margin-top: 20px;
border-top: 1px solid #e0e0e0;
padding-top: 10px;
}
.info-item {
margin: 5px 0;
font-size: 16px;
}
</style>
CitySelector.vue
<template>
<div class="cascader-container">
<h2 class="title">到市选择地区</h2>
<el-cascader
size="large"
:options="provinceAndCityData"
v-model="selectedOptions"
@change="handleChange"
clearable
class="cascader"
>
</el-cascader>
<div class="address-info">
<div class="info-item">地区编码:<strong>{{ areaCode.join(',') }}</strong></div>
<div class="info-item">地区:<strong>{{ areaName.join('') }}</strong></div>
</div>
</div>
</template>
<script>
export default {
props: {
provinceAndCityData: Array,
selectedOptions: Array,
codeToText: Object,
onChange: Function
},
data() {
return {
areaName: [],
areaCode: []
};
},
methods: {
handleChange() {
this.areaName = [];
this.areaCode = [];
if (this.selectedOptions.length > 0) {
this.selectedOptions.forEach((code) => {
this.areaName.push(this.codeToText[code]);
this.areaCode.push(code);
});
this.onChange(this.areaName, this.areaCode);
}
}
}
};
</script>
<style scoped>
.cascader-container {
max-width: 500px;
margin: 20px auto;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 16px;
text-align: center;
}
.cascader {
width: 100%;
}
.address-info {
margin-top: 20px;
border-top: 1px solid #e0e0e0;
padding-top: 10px;
}
.info-item {
margin: 5px 0;
font-size: 16px;
}
</style>
RegionView.vue
<template>
<div>
<TownSelector
:options="options"
v-model="selectedOptions"
:addrCodes="addrCodes"
:addrCodesSelected="addrCodesSelected"
:onChange="updateAddressInfo"
/>
<DistrictSelector
:regionData="regionData"
v-model="selectedOptions1"
:codeToText="codeToText"
:onChange="updateDistrictInfo"
/>
<CitySelector
:provinceAndCityData="provinceAndCityData"
v-model="selectedOptions2"
:codeToText="codeToText"
:onChange="updateCityInfo"
/>
</div>
</template>
<script>
import TownSelector from './TownSelector.vue';
import DistrictSelector from './DistrictSelector.vue';
import CitySelector from './CitySelector.vue';
let pcas = require('@/assets/pcas-code.json');
import { regionData, provinceAndCityData, codeToText } from 'element-china-area-data';
export default {
name: 'RegionView',
components: {
TownSelector,
DistrictSelector,
CitySelector
},
data() {
return {
options: pcas,
provinceAndCityData,
selectedOptions: [],
addrCodes: [],
addrCodesSelected: [],
regionData,
codeToText,
selectedOptions1: [],
selectedOptions2: []
};
},
methods: {
updateAddressInfo(codes, names) {
this.addrCodes = codes;
this.addrCodesSelected = names;
},
updateDistrictInfo(names, codes) {
console.log('District Updated:', names, codes);
},
updateCityInfo(names, codes) {
console.log('City Updated:', names, codes);
}
}
};
</script>
<style scoped>
/* 可选的全局样式 */
</style>
- TownSelector.vue:处理乡镇的选择,获取编码和地区名称。
- DistrictSelector.vue:处理区的选择,获取编码及地区名称。
- CitySelector.vue:处理市的选择,获取编码和地区名称。
- RegionView.vue:父级组件,整合三个选择器组件,并管理数据的传递和更新。
总结
将地区选择器拆分为不同的组件,使得逻辑更加清晰,代码结构更易于维护与扩展,符合组件化开发的最佳实践。这样也有助于将来根据需要复用某个选择器组件