Vue3 + Element Plus 省市区县级联组件封装,支持 v-model 双向绑定 + 回显,可直接复用

 更新时间:2026年04月13日 10:14:44   作者:程序媛七分  
本文介绍了如何封装一个支持省市区三级联动的通用复用组件,实现v-model双向绑定、数据回显、清空、禁用联动等功能,提高开发效率和项目可维护性,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧

在后台管理系统中,省市区三级联动是非常常见的需求,无论是查询条件、新增表单、编辑弹窗,都需要用到。

为了避免重复代码,提升开发效率,我们可以把省市区三级联动封装成一个通用复用组件,支持:

  • 三级联动
  • v-model 双向绑定
  • 数据回显(编辑页面必备)
  • 清空、禁用联动
  • 多页面复用

今天就带大家一步一步实现。

全部代码

<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>

任意页面直接调用⬇️

<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

        复用性强:查询表单、弹窗新增、弹窗编辑都能调用

总结

封装成通用组件后,所有页面只需要写一行代码,就能实现省市区三级联动,大大减少重复代码,提升开发效率和项目可维护性。

如果需要,还可以扩展:

  • 接口动态获取省市区
  • 支持四级联动(省市区街道)
  • 支持默认值
  • 支持禁用某项

到此这篇关于Vue3 + Element Plus 省市区县级联组件封装,支持 v-model 双向绑定 + 回显,可直接复用的文章就介绍到这了,更多相关Vue3 Element Plus 省市区县级联组件封装内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue3获取、设置元素高度的代码举例

    vue3获取、设置元素高度的代码举例

    这篇文章主要给大家介绍了关于vue3获取、设置元素高度的相关资料,小编通过实际案例向大家展示操作过程,操作方法简单易懂,需要的朋友可以参考下
    2024-08-08
  • Vue cli构建及项目打包以及出现的问题解决

    Vue cli构建及项目打包以及出现的问题解决

    这篇文章主要介绍了Vue cli构建及项目打包以及出现的问题解决,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • 如何解决Vue3组合式API模式下动态组件不渲染问题

    如何解决Vue3组合式API模式下动态组件不渲染问题

    这篇文章主要介绍了如何解决Vue3组合式API模式下动态组件不渲染问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教<BR>
    2024-03-03
  • 在vscode中使用脚手架搭建vue项目

    在vscode中使用脚手架搭建vue项目

    这篇文章主要介绍了在vscode中使用脚手架搭建vue项目的相关资料,包括创建Vue2和Vue3项目,并展示了如何自定义项目配置,如选择特性、路由模式、CSS预处理器和工具配置,通过代码介绍的非常详细,需要的朋友可以参考下
    2024-12-12
  • vue.js引入外部CSS样式和外部JS文件的方法

    vue.js引入外部CSS样式和外部JS文件的方法

    这篇文章主要介绍了vue.js引入外部CSS样式和外部JS文件的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-01-01
  • 带你一文了解Vue生命周期钩子

    带你一文了解Vue生命周期钩子

    生命周期钩子又被叫做生命周期时间,生命周期函数,生命周期钩子就是vue生命周期中出发的各类事件,这些事件被称为生命周期钩子,下面这篇文章主要给大家介绍了关于Vue生命周期钩子的相关资料,需要的朋友可以参考下
    2022-06-06
  • Vue中如何实现proxy代理

    Vue中如何实现proxy代理

    本篇文章主要介绍了Vue中如何实现proxy代理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04
  • vue 使用html2canvas将DOM转化为图片的方法

    vue 使用html2canvas将DOM转化为图片的方法

    这篇文章主要介绍了vue 使用html2canvas将DOM转化为图片的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • vue 根据选择条件显示指定参数的例子

    vue 根据选择条件显示指定参数的例子

    今天小编就为大家分享一篇vue 根据选择条件显示指定参数的例子,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-11-11
  • vue如何使用driver.js实现项目功能向导指引

    vue如何使用driver.js实现项目功能向导指引

    driver.js 是一个轻量级、无依赖的原生JavaScript引擎,在整个页面中驱动用户的注意力,强大的、高度可定制的原生JavaScript引擎,无外部依赖,支持所有主流浏览器,这篇文章主要介绍了vue如何使用driver.js实现项目功能向导指引,需要的朋友可以参考下
    2023-03-03

最新评论