在后台管理系统中,省市区三级联动是非常常见的需求,无论是查询条件、新增表单、编辑弹窗,都需要用到。
为了避免重复代码,提升开发效率,我们可以把省市区三级联动封装成一个通用复用组件,支持:
- 三级联动
- v-model 双向绑定
- 数据回显(编辑页面必备)
- 清空、禁用联动
- 多页面复用
今天就带大家一步一步实现。
全部代码
javascript
<template>
<div class="region-picker">
<el-select v-model="provinceId" placeholder="省" clearable style="flex: 1" @change="handleProvinceChange">
<el-option v-for="item in provinceList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="cityId" placeholder="市" clearable :disabled="!provinceId" style="flex: 1; margin: 0 8px"
@change="handleCityChange">
<el-option v-for="item in cityList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="districtId" placeholder="区/县" clearable :disabled="!cityId" style="flex: 1">
<el-option v-for="item in areaList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
// 模拟省市区数据(实际项目可替换为接口)
const regionData = [
{
value: '110000',
label: '北京市',
children: [
{
value: '110100',
label: '北京市',
children: [
{ value: '110101', label: '东城区' },
{ value: '110102', label: '西城区' },
{ value: '110105', label: '朝阳区' },
{ value: '110106', label: '丰台区' },
{ value: '110108', label: '海淀区' },
]
}
]
},
{
value: '410000',
label: '河南省',
children: [
{
value: '410100',
label: '郑州市',
children: [
{ value: '410102', label: '中原区' },
{ value: '410103', label: '二七区' },
{ value: '410104', label: '管城回族区' },
{ value: '410105', label: '金水区' },
]
},
{
value: '410300',
label: '洛阳市',
children: [
{ value: '410302', label: '老城区' },
{ value: '410303', label: '西工区' },
]
}
]
}
]
// 类型定义
interface RegionModel {
provinceId: string
cityId: string
districtId: string
}
// 接收 v-model
const props = withDefaults(defineProps<{
modelValue?: RegionModel
}>(), {
modelValue: () => ({ provinceId: '', cityId: '', districtId: '' })
})
// 抛出事件
const emit = defineEmits<{
'update:modelValue': [value: RegionModel]
'change': [value: RegionModel]
}>()
// 内部选中值
const provinceId = ref('')
const cityId = ref('')
const districtId = ref('')
// 省列表
const provinceList = computed(() => regionData || [])
// 市列表(根据省动态计算)
const cityList = computed(() => {
if (!provinceId.value) return []
const p = regionData.find(item => item.value === provinceId.value)
return p?.children || []
})
// 区县列表(根据市动态计算)
const areaList = computed(() => {
if (!cityId.value) return []
const c = cityList.value.find(item => item.value === cityId.value)
return c?.children || []
})
// 切换省 → 清空市、区县
const handleProvinceChange = () => {
cityId.value = ''
districtId.value = ''
emitValue()
}
// 切换市 → 清空区县
const handleCityChange = () => {
districtId.value = ''
emitValue()
}
// 向外抛出数据
const emitValue = () => {
const value = {
provinceId: provinceId.value,
cityId: cityId.value,
districtId: districtId.value,
}
emit('update:modelValue', value)
emit('change', value)
}
// 监听父组件 v-model 实现回显
watch(
() => props.modelValue,
(val) => {
if (val) {
provinceId.value = val.provinceId || ''
cityId.value = val.cityId || ''
districtId.value = val.districtId || ''
}
},
{ deep: true, immediate: true }
)
// 监听内部变化 → 自动同步父组件
watch([provinceId, cityId, districtId], emitValue)
</script>
<style scoped>
.region-picker {
display: flex;
width: 100%;
gap: 8px;
}
</style>
任意页面直接调用⬇️
javascript
<template>
<RegionPicker v-model="queryRegion" @change="handleChange" />
</template>
<script setup>
import RegionPicker from '@/components/RegionPicker/index.vue'
const queryRegion = ref({
provinceId: '',
cityId: '',
districtId: ''
})
const handleChange = (val) => {
console.log('选中的省市区:', val)
}
</script>
最终达到的效果
三级联动 :选择省自动加载市,选择市自动加载区县
v-model 双向绑定 :父组件直接使用 v-model 绑定
数据回显:编辑页面可直接赋值
自动清空:切换上级,下级自动清空
禁用联动:未选择上级,下级无法选择
支持清空:每个 select 都支持 clearable
复用性强:查询表单、弹窗新增、弹窗编辑都能调用
总结
封装成通用组件后,所有页面只需要写一行代码,就能实现省市区三级联动,大大减少重复代码,提升开发效率和项目可维护性。
如果需要,还可以扩展:
- 接口动态获取省市区
- 支持四级联动(省市区街道)
- 支持默认值
- 支持禁用某项
感兴趣的小伙伴欢迎评论区留言讨论😄