element + vue3,级联选择器实现省市区

文章讲述了如何利用ES6的特性简化el-cascader组件的使用,避免频繁调接口获取下一级数据,通过预先获取所有数据并处理展示,提高用户体验。同时,也展示了Vue组件中如何管理多选状态和层级结构的动态调整。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 由于es6支持哈希,所以数据量只要不太大,就不需要对el-cascader进行点击后在调接口出现下一级,很简单的就是直接获取所有数据。

<template>
    <div>
        <el-cascader :modelValue="modelValue" :props="innerProps" :options="options" @change="handleChange" filterable
            :show-all-levels="showAllLevels" :disabled="disabled" :placeholder="placeholder" :clearable="clearable"
            style="width:100%" @blur="onBlur" @focus="onFocus" @close="handleClose" @visible-change="onVisibleChange" />
        <!-- --- {{ modelValue }} -->
    </div>
</template>

<script lang="ts" setup>
import { ref, reactive, toRaw, onMounted, watch, onBeforeUnmount } from 'vue'
import type { CascaderProps } from 'element-plus'
import * as api from "./api.js";

const props = defineProps({
    modelValue: {
        type: Array,
        default: []
    },
    // 是否支持清空选项
    clearable: {
        type: Boolean,
        default: true,
    },
    // 是否多选
    // multiple: {
    //     type: Boolean,
    //     default: false,
    // },
    // 指定某省,传入该自治区的areacode
    designation: {
        type: String,
        default: '',
    },
    // 仅显示最后一级
    showAllLevels: {
        type: Boolean,
        default: true,
    },
    // 是否禁用
    disabled: {
        type: Boolean,
        default: false,
    },
    // 输入框占位文本
    placeholder: {
        type: String,
        default: '',
    },
    // 是否选择任意一级选项,还是最后叶子结点
    checkStrictly: {
        type: Boolean,
        default: true,
    },
    // 次级菜单的展开方式,click和hover
    expandTrigger: {
        type: String,
        default: 'click',
    },
    // 多选个数限制selectNum
    multipleSelectNum: {
        type: Number,
        default: 1,
    },
     // 省市县乡镇,显示层级,默认为2,到区县。0为省,1为市,2为区县,3为乡镇
     level: {
        type: Number,
        default: 2,
    },
})

let options: any = ref([])  //树
const watchArr = ref([])

onMounted(() => {
    // 获取全量数据
    getAllData()
})

//重新定义一个值来接受prop
const isMultiple = ref(false)

//因为prop中的值非动态响应,所以需要通过watch监听,immediate 初始化时接收父组件中的传值
watch(() => props.multipleSelectNum, () => {
    // isMultiple.value = props.multipleSelectNum;
    if(props.multipleSelectNum > 1) {
        isMultiple.value = true
    }
    console.log(isMultiple.value,props.multipleSelectNum,'---props.multipleSelectNum;');
    
}, {
    immediate: true
})

onBeforeUnmount(() => {})

const emit = defineEmits(['update:modelValue', 'change'])
const handleChange = (e: any, e2: any) => {
    watchArr.value = e
    emit('change', e)
    emit('update:modelValue', e)
}

async function getAllData() {
    let res = await api.SearchAdministrativeDivisions({
        queryArea: props.designation,
        level: props.level
    })
    options.value = res.data
}

function onFocus(e: any) { }
async function onBeforeFilter(e: any) {
    let res = await api.SearchAdministrativeDivisions({
        queryArea: e
    })
    const fn = (arr: any[]) => {
        arr.forEach((item, index) => {
            if (item.children && item.children.length) {
                item.children = fn(item.children)
            } else {
                if (item.children) delete item.children
            }
        })
        return arr
    }
    // console.log(options, '---options');
    options.value = fn(res.data)
}
// const isMultiple = ref(false)
const innerProps: CascaderProps = {
    checkStrictly: props.checkStrictly,
    // lazy: true,
    value: 'areaCode',
    label: 'name',
    multiple: isMultiple.value, //modify
    expandTrigger: props.expandTrigger,
}
const handleClose = () => { }
const onBlur = () => {
    // console.log('---onBlur');
}
const onVisibleChange = (e: any) => {
    // if (props.modelValue.length < props.multipleSelectNum && props.multiple == true) {
        if (props.modelValue.length < props.multipleSelectNum && isMultiple.value == true) { //modify
        function onRabbit(list: any) {
            list.forEach((el: any) => {
                if (el.children && el.children.length > 0) {
                    el.children = onRabbit(el.children);
                }
                el.disabled = false;
                el.isFlag = false
            });
            return list;
        }
        const newList = onRabbit(options.value);
        options.value = newList;
    }
}
// 监听
watch(
    () => watchArr.value,
    (newVal, oldVal) => {
        // if (props.multiple == true) {
            if (isMultiple.value == true) {   //modify
            // console.log(oldVal,`watch监听变化前的数`)
            // console.log(newVal, `watch监听变化后的数据`)
            if (newVal.length >= props.multipleSelectNum) {
                function onRabbit(list: any) {
                    list.forEach((el: any) => {
                        if (el.children && el.children.length > 0) {
                            el.children = onRabbit(el.children);
                        }
                        newVal.forEach((v: any) => {
                            if (v[v.length - 1] != el.areaCode) {
                                if (el.isFlag) {
                                } else {
                                    el.disabled = true
                                    el.isFlag = true
                                }
                            } else {
                                el.disabled = false
                                el.isFlag = true
                            }
                        })
                    });
                    return list;
                }
                const newList = onRabbit(options.value);
                options.value = newList;
            } else {
                function onRabbit(list: any) {
                    list.forEach((el: any) => {
                        if (el.children && el.children.length > 0) {
                            el.children = onRabbit(el.children);
                        }
                        el.disabled = false;
                        el.isFlag = false
                    });
                    return list;
                }
                const newList = onRabbit(options.value);
                options.value = newList;
            }
        }
    },
    {
        immediate: true, // 立即执行
        deep: true // 深度监听
    }
)
</script>
  
<style></style>

<think>好的,用户想用Vue、TypeScript和Element Plus实现省市区三级联动选择器,需要示例代码。首先,我需要回顾自己对这些技术的了解。Element Plus确实有级联选择器组件,应该可以用Cascader来实现。 接下来,数据源的获取很重要。通常省市区数据是树形结构,可能需要后端接口或者静态JSON文件。用户可能没有后端支持,所以先考虑静态数据。比如,使用本地的area-data.ts这样的文件,里面包含省、市、区的嵌套数据。 然后,安装必要的依赖。Element Plus已经包含级联选择器,但可能需要安装额外的数据包,比如一些第三方提供的省市区数据。不过用户可能希望自己处理数据,所以示例中可以手动定义几个示例数据,说明结构。 在组件部分,需要引入ElCascader,并在模板中使用它。数据绑定方面,使用v-model来关联选择的值,还有props配置来指定数据结构的字段名,比如label、value、children。 TypeScript方面,需要定义数据类型。比如,定义一个AreaData接口,包含value、label和children。这样在组件中使用时,类型检查会更严格,避免错误。 还要考虑表单验证,如果用户是在表单中使用这个选择器,可能需要结合Element Plus的表单验证规则。不过用户当前的问题可能先关注基本实现,验证可以后续补充。 另外,用户可能需要动态加载数据,比如当省份选择后,再加载对应的市数据。但静态示例可能更简单,直接展示三级联动。如果有异步加载的需求,可以后续扩展,但当前示例保持简单。 代码结构方面,先导入组件,定义数据,然后在setup中返回数据和配置。确保props正确设置,比如checkStrictly是否允许选择任意一级,或者是否只能选最后一级。 测试一下示例代码是否有问题,比如数据是否正确绑定,选择器能否正常显示三级选项。如果有拼写错误或数据字段不对,级联选择器可能无法正确渲染。 最后,考虑可能的常见问题,比如数据格式不正确,或者样式未正确引入Element Plus的组件。确保用户按照步骤安装和引入必要的依赖。 总结步骤:安装Element Plus,准备数据,使用ElCascader组件,配置props,绑定数据,处理选择事件。示例代码尽量简洁,同时说明关键点,如数据结构和组件配置。</think>以下是通过Vue 3 + TypeScript + Element Plus 实现省市区三级联动选择器的完整实现步骤和示例代码: ### 一、基础实现步骤 1. **安装依赖** ```bash npm install element-plus @element-plus/icons-vue ``` 2. **准备行政区划数据** ```typescript // src/data/area-data.ts export interface AreaData { value: string label: string children?: AreaData[] } export const provinceData: AreaData[] = [ { value: &#39;310000&#39;, label: &#39;上海市&#39;, children: [ { value: &#39;310100&#39;, label: &#39;市辖区&#39;, children: [ { value: &#39;310101&#39;, label: &#39;黄浦区&#39; }, { value: &#39;310104&#39;, label: &#39;徐汇区&#39; } ] } ] }, { value: &#39;320000&#39;, label: &#39;江苏省&#39;, children: [ { value: &#39;320100&#39;, label: &#39;南京市&#39;, children: [ { value: &#39;320102&#39;, label: &#39;玄武区&#39; }, { value: &#39;320104&#39;, label: &#39;秦淮区&#39; } ] } ] } ] ``` 3. **组件实现** ```vue <!-- src/components/AreaSelector.vue --> <template> <el-cascader v-model="selectedArea" :options="areaData" :props="cascaderProps" placeholder="请选择省市区" clearable /> </template> <script setup lang="ts"> import { ref } from &#39;vue&#39; import type { AreaData } from &#39;@/data/area-data&#39; import { provinceData } from &#39;@/data/area-data&#39; const selectedArea = ref<string[]>([]) const cascaderProps = { value: &#39;value&#39;, label: &#39;label&#39;, children: &#39;children&#39;, checkStrictly: true // 允许选择任意一级 } const areaData = ref<AreaData[]>(provinceData) </script> ``` ### 二、关键实现细节说明 1. **数据绑定**:使用`v-model`双向绑定选择结果,返回值为包含各级value的数组 2. **组件配置**:通过props配置指定数据结构的字段映射[^2] 3. **类型定义**:使用TypeScript接口明确数据结构类型,增强代码可维护性 ### 三、实际应用示例 ```vue <!-- src/views/UserForm.vue --> <template> <el-form :model="form" label-width="120px"> <el-form-item label="所在地区"> <AreaSelector v-model="form.area" /> </el-form-item> </el-form> </template> <script setup lang="ts"> import { reactive } from &#39;vue&#39; import AreaSelector from &#39;@/components/AreaSelector.vue&#39; const form = reactive({ area: [] }) </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值