省市区街道/乡镇四级联动vue3

news/发布时间2024/9/20 9:33:51
最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是element + vue3记录一下。

本来是这样的三级联动:

这个三级联动很简单,直接利用el-select组件把地区值带进去就行了,现在要优化成省.市.区/县、乡镇/街道的四级联动,变成这样:

 

下面进入正文: (说一下主要流程,最后附上全部代码)

首先要准备省市区和对应编码的JSON文件:

GitHub - modood/Administrative-divisions-of-China: 中华人民共和国行政区划:省级(省份)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村二级三级四级五级联动地址数据。

可以参考这个地址,直接在浏览器下载也行,git 克隆到本地也行,这个json文件很大,大概两三兆,可以让后端返回。

省份分组时用到了一个三方包,需要把省份转成拼音获取首字母,直接下载就行
yarn add chinese-to-pinyin  或者  npm i chinese-to-pinyinimport pinyin from "chinese-to-pinyin"

然后调整数据结构,

//省份分组
const groupedProvinces = ref({"A-G": [],"H": [],"J-Q": [],"S-T": [],"X-Z": [],"其它": []
})//分解省市区数据
function extractLocations(data, level = 0, results = { provinces: [], cities: [], districts: [], streets: [] }) {for ( const item of data ) {// 根据层级确定当前是省/直辖市、市/区或街道,并存储数据if ( level === 0 ) {results.provinces.push(item)} else if ( level === 1 ) {results.cities.push(item)} else if ( level === 2 ) {results.districts.push(item)} else if ( level === 3 ) {results.streets.push(item)}// 如果存在子级,递归调用自身if ( item.children && item.children.length ) {extractLocations(item.children, level + 1, results)}}return results
}//省市区数组集合
const pcasList = ref(extractLocations(pcasCodeList))//按首字母分类的省份
function groupProvinces(provinces) {pcasList.value.provinces.forEach(province => {let firstLetter = pinyin(province.name, { removeTone: true }).charAt(0).toUpperCase()if ( province.name === "澳门特别行政区" || province.name === "台湾省" || province.name === "香港特别行政区" ) {// 澳门、台湾、香港特殊处理switch ( province.name ) {case "澳门特别行政区":case "台湾省":case "香港特别行政区":groupedProvinces.value["其它"].push(province)break}} else {if ( "ABCDEFG".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["A-G"].push(province)} else if ( "H".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["H"].push(province)} else if ( "JKLMNOPQ".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["J-Q"].push(province)} else if ( "ST".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["S-T"].push(province)} else if ( "XYZ".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["X-Z"].push(province)} else {// 其他不识别省份的处理console.warn("未识别的省份:", province.name)}}})
}groupProvinces(pcasList.value.provinces)

这样就实现了这个页面了

交互逻辑太多

为了避免文章太长

直接上全部代码
<template><el-popover ref="popoverRef"  :visible="popoverVisible" :width="460"placement="bottom" trigger="click" ><template #reference><el-input ref="inputRef" v-model="dataForm.PCASName" placeholder="请选择省市区街道/乡镇"@blur="inputBlurFn" @click="popoverVisible =true" @input="pcasInputFn"/></template><div v-click-outside="contentClick"><el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick"><el-tab-pane :label="dataForm.provinceName" name="first"><div><div v-for="(item, itemName) in groupedProvinces" :key="itemName" class="addressItem"><div class="left">{{ itemName }}</div><div class="right"><div v-for="(item,index) in item" :key="index":class="{'active': dataForm.provinceName === item.name }"class="provinceItem"@click="provinceItemFn(item)">{{ item.name }}</div></div></div></div></el-tab-pane><el-tab-pane v-if="dataForm.province" :label="dataForm.cityName" name="second"><div class="cityContent"><div v-for="(item, index) in dataForm.citesList" :key="index":class="{'active': dataForm.cityName === item.name }"class=" cityItem" @click="cityItemFn(item)">{{ item.name }}</div></div></el-tab-pane><el-tab-pane v-if="dataForm.city && !dataForm.isSpecial" :label="dataForm.areaName" name="three"><div class="cityContent"><div v-for="(item, index) in dataForm.areaList" :key="index":class="{'active': dataForm.areaName === item.name }"class=" cityItem" @click="areaItemFn(item)">{{ item.name }}</div></div></el-tab-pane><el-tab-pane v-if="dataForm.area" :label="dataForm.streetName" name="four"><div class="cityContent"><div v-for="(item, index) in dataForm.streetsList" :key="index":class="{'active': dataForm.streetName === item.name }"class=" cityItem" @click="streesItemFn(item)">{{ item.name }}</div></div></el-tab-pane></el-tabs></div></el-popover>
</template><script setup>
import { unref, ref, watchEffect } from "vue"
import { ClickOutside as vClickOutside } from "element-plus"
import { cloneDeep } from "lodash-es"
import pcasCode from "@/const/json/pcas-code.json"
import pinyin from "chinese-to-pinyin"//弹出框是否显示
let popoverVisible = ref(false)let inputRef = ref()//省市区选择区域外内容点击事件
const contentClick = () => {if ( !dataForm.value.area && !dataForm.value.isSpecial ) {resetSelections([ "province", "city", "area", "street" ])activeName.value = "first"dataForm.value.PCASName = ""}if (  dataForm.value.effectiveVal && dataForm.value.effectiveVal.split("/").length > 2 ) {dataForm.value.PCASName = dataForm.value.effectiveVal}popoverVisible.value = false
}
//省市区tab
const activeName = ref("first")//省市区code数据
let pcasCodeList = cloneDeep(pcasCode)//省份分组
const groupedProvinces = ref({"A-G": [],"H": [],"J-Q": [],"S-T": [],"X-Z": [],"其它": []
})//分解省市区数据
function extractLocations(data, level = 0, results = { provinces: [], cities: [], districts: [], streets: [] }) {for ( const item of data ) {// 根据层级确定当前是省/直辖市、市/区或街道,并存储数据if ( level === 0 ) {results.provinces.push(item)} else if ( level === 1 ) {results.cities.push(item)} else if ( level === 2 ) {results.districts.push(item)} else if ( level === 3 ) {results.streets.push(item)}// 如果存在子级,递归调用自身if ( item.children && item.children.length ) {extractLocations(item.children, level + 1, results)}}return results
}//省市区数组集合
const pcasList = ref(extractLocations(pcasCodeList))//按首字母分类的省份
function groupProvinces(provinces) {pcasList.value.provinces.forEach(province => {let firstLetter = pinyin(province.name, { removeTone: true }).charAt(0).toUpperCase()if ( province.name === "澳门特别行政区" || province.name === "台湾省" || province.name === "香港特别行政区" ) {// 澳门、台湾、香港特殊处理switch ( province.name ) {case "澳门特别行政区":case "台湾省":case "香港特别行政区":groupedProvinces.value["其它"].push(province)break}} else {if ( "ABCDEFG".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["A-G"].push(province)} else if ( "H".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["H"].push(province)} else if ( "JKLMNOPQ".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["J-Q"].push(province)} else if ( "ST".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["S-T"].push(province)} else if ( "XYZ".indexOf(firstLetter) !== -1 ) {groupedProvinces.value["X-Z"].push(province)} else {// 其他不识别省份的处理console.warn("未识别的省份:", province.name)}}})
}groupProvinces(pcasList.value.provinces)//tab栏点击事件
const handleClick = (tab) => {popoverVisible.value = trueif ( tab.props.name === "first" ) {resetSelections([ "province", "city", "area", "street" ])} else if ( tab.props.name === "second" ) {resetSelections([ "city", "area", "street" ])dataForm.value.citesList = pcasCodeList.find(item => item.code === dataForm.value.province)?.children || []} else if ( tab.props.name === "three" ) {resetSelections([ "area", "street" ])const childrenArray = findChildrenByCode(pcasCodeList, dataForm.value.city)dataForm.value.areaList = childrenArray || []} else if ( tab.props.name === "four" ) {resetSelections([ "street" ])const childrenArray = findChildrenByCode(pcasCodeList, dataForm.value.area)dataForm.value.streetsList = childrenArray || []}
}let dataForm = ref({citesList: [],	//城市分组areaList: [],	//区县分组streetsList: [],	//街道乡镇分组province: "",	//省codecity: "", 	//城市codearea: "", 	//区县codestreet: "",	//街道乡镇codeprovinceName: "请选择", //省名称cityName: "请选择",// 城市名称areaName: "请选择", // 区县名称streetName: "请选择", //街道名称PCASName: "",//省市区街道名称isSpecial: false,//是否是特别行政区effectiveVal: ""//有效省市区
})//点击省
const provinceItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.value.provinceName = val.namedataForm.value.PCASName = updatePCASName(val.name)dataForm.value.province = val.codedataForm.value.citesList = val.children || []resetSelections([ "city", "area", "street" ])if ( dataForm.value.provinceName === "台湾省" ) {dataForm.value.isSpecial = truepopoverVisible.value = false} else {activeName.value = "second"dataForm.value.isSpecial = false}
}//点击城市
const cityItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.value.cityName = val.namedataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)dataForm.value.city = val.codedataForm.value.areaList = val.children || []resetSelections([ "area", "street" ])if ( dataForm.value.provinceName === "澳门特别行政区" || dataForm.value.provinceName === "香港特别行政区" ) {dataForm.value.isSpecial = truepopoverVisible.value = false} else {activeName.value = "three"dataForm.value.isSpecial = false}
}//点击区县
const areaItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.value.areaName = val.namedataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, val.name)dataForm.value.area = val.codedataForm.value.streetsList = val.childrenresetSelections([ "street" ])activeName.value = "four"
}//点击街道/乡镇
const streesItemFn = (val) => {dataForm.value.effectiveVal = ''dataForm.value.streetName = val.namedataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, dataForm.value.areaName, val.name)dataForm.value.street = val.codepopoverVisible.value = false
}const inputBlurFn = ( (event) => {popoverVisible.value = false
} )//输入框input事件
const pcasInputFn = (val) => {if ( val ) {let str = val.replace(/\//g, "")let str2 = matchPath(str, pcasCodeList)dataForm.value.effectiveVal = str2const parts = str2.split("/")console.log("parts", parts)const matchedCodes = findCodesByNames(pcasCodeList, parts)if ( matchedCodes ) {dataForm.value.citesList = findChildrenByCode(pcasCodeList, matchedCodes[0])dataForm.value.areaList = findChildrenByCode(pcasCodeList, matchedCodes[1])dataForm.value.streetsList = findChildrenByCode(pcasCodeList, matchedCodes[2])if ( parts.length === 1 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]resetSelections([ "city", "area", "street" ])activeName.value = "second"}if ( parts.length === 2 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]resetSelections([ "area", "street" ])activeName.value = "three"}if ( parts.length === 3 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]dataForm.value.area = matchedCodes[2]dataForm.value.areaName = parts[2]resetSelections([ "street" ])activeName.value = "four"}if ( parts.length === 4 ) {activeName.value = "four"dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]dataForm.value.area = matchedCodes[2]dataForm.value.areaName = parts[2]dataForm.value.street = matchedCodes[3]dataForm.value.streetName = parts[3]}} else {resetSelections([ "province", "city", "area", "street" ])activeName.value = "first"}} else {resetSelections([ "province", "city", "area", "street" ])activeName.value = "first"}
}//重置选择
const resetSelections = (clearLevels) => {// 根据传入的层级清除选项if ( clearLevels.includes("province") ) {dataForm.value.province = ""dataForm.value.provinceName = "请选择"}if ( clearLevels.includes("city") ) {dataForm.value.city = ""dataForm.value.cityName = "请选择"dataForm.value.areaList = []}if ( clearLevels.includes("area") ) {dataForm.value.areaName = "请选择"dataForm.value.area = ""dataForm.value.streetsList = []}if ( clearLevels.includes("street") ) {dataForm.value.streetName = "请选择"dataForm.value.street = ""}
}// 更新省市区名称
const updatePCASName = (provinceName = "", cityName = "", areaName = "", streetName = "") => {const names = [ provinceName, cityName, areaName, streetName ].filter(name => name.trim() !== "")// 使用“/”连接数组中的名称return names.join("/")
}//根据输入框内容匹配对应的code值
function findCodesByNames(data, names, index = 0, codes = []) {if ( index < names.length ) {// 根据当前索引的名称查找数据const found = data.find(item => item.name === names[index])if ( found ) {// 如果找到了匹配项,加入 code,并继续递归搜索下一级codes[index] = found.code// 如果还有更深级别的名称,则继续递归,否则直接返回 codesreturn found.children && index + 1 < names.length ?findCodesByNames(found.children, names, index + 1, codes) : codes} else {// 如果未找到匹配项,证明省市区乡镇匹配错误,返回 falsereturn false}}// 如果所有省市区乡镇都已成功匹配对应的code,返回 codesreturn codes
}//根据某个code值寻找对应的子集地区数组
function findChildrenByCode(data, targetCode) {for ( const item of data ) {if ( item.code === targetCode ) {return item.children || []}if ( item.children ) {const result = findChildrenByCode(item.children, targetCode)if ( result ) return result}}return null
}//输入省市区时逐层匹配输入框内容
function matchPath(input, data) {let currentData = data // 当前正在查找的数据层级let resultPaths = []let startIndex = 0 // 输入字符串的检索起始位置// 移除输入字符串末尾的数字和其他非中文字符input = input.replace(/[^\u4e00-\u9fa5]+$/, "")while ( startIndex < input.length && currentData ) {let found = false // 是否找到匹配项的标识for ( let i = 0; i < currentData.length; i++ ) {const item = currentData[i]if ( input.startsWith(item.name, startIndex) ) {// 累加路径并更新索引和数据层级resultPaths.push(item.name)startIndex += item.name.lengthcurrentData = item.children || [] // 移动至下一级found = truebreak // 匹配到项目,跳出内循环进行下一轮搜索}}// 如果当前层级未找到匹配项,则终止搜索if ( !found ) break}return resultPaths.join("/") // 以顿号连接路径数组并返回
}defineExpose({dataForm
})
</script><style lang="scss" scoped>
.addressItem{display: flex;font-size: 14px;margin-bottom: 4px;.left{min-width: 40px;color: #ee675b;margin-right: 16px;}.right{display: flex;flex-wrap: wrap;.provinceItem{margin-right: 18px;margin-bottom: 10px;&:hover{cursor: pointer;color: #1166fe;}}}
}.cityContent{display: flex;flex-wrap: wrap;font-size: 14px;.cityItem{margin-right: 18px;margin-bottom: 10px;cursor: pointer;&:hover{color: #1166fe;}}
}.active{color: #1166fe !important;
}</style>

这里我觉得有点冗余的是输入框输入地址和选择省市区乡镇的的联动效果,毕竟大部分人能选的话不会手输,如果不用的话直接禁用输入框就行,省下很多逻辑处理。

现在这这个组件刚写完

肯定涉及到父组件值的传入和子组件的值传出

以后再更新...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.bcls.cn/RKNa/9981.shtml

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

C# 多线程(3)——线程池

文章目录 1 定义2 线程池使用3 安全取消线程池中任务 1 定义 线程是计算机宝贵的资源&#xff0c;频繁的创建和销毁线程将会大量的占用计算机资源&#xff08;为每个线程单独分配内存空间&#xff0c;并且多线程下的CPU时间片的切换也会耗费一定的时间&#xff09;。为了充分利…

LabVIEW眼结膜微血管采集管理系统

LabVIEW眼结膜微血管采集管理系统 开发一套基于LabVIEW的全自动眼结膜微血管采集管理系统&#xff0c;以提高眼结膜微血管临床研究的效率。系统集成了自动化图像采集、图像质量优化和规范化数据管理等功能&#xff0c;有效缩短了图像采集时间&#xff0c;提高了图像质量&#…

Langchain-Chatchat:离线运行的大模型知识库 | 开源日报 No.182

chatchat-space/Langchain-Chatchat Stars: 22k License: Apache-2.0 基于 ChatGLM 等大语言模型与 Langchain 等应用框架实现的开源、可离线部署的检索增强生成 (RAG) 大模型知识库项目。该项目是一个可以实现完全本地化推理的知识库增强方案&#xff0c;重点解决数据安全保护…

Palworld 幻兽帕鲁面板开服教程

介绍 在本文档中&#xff0c;您将学习如何构建 Palworld 服务器。 关于 Palworld 通过设置服务器&#xff0c;您可以与您的 Palworld 好友以及其他玩 Palworld 的人共享同一个世界。 推荐的服务器设置 内存 由于至少一次内存泄漏&#xff0c;服务器需要大约 16-32GB RAM。…

FPGA之16:1复选器

每个slice 都有一个F8MUX。F8MUX原语&#xff1a; MUXF8 MUXF8_inst&#xff08; .0&#xff08;0&#xff09;&#xff0c;Il Output of MUX to general routing .I0&#xff08;10&#xff09;&#xff0c;//Input&#xff08;tie to MUXF7L/LO out&#xff09; .I1&#xf…

纽约纳斯达克大屏投放受众群体有哪些-大舍传媒

纽约纳斯达克大屏投放受众群体有哪些-大舍传媒 1. 纳斯达克大屏的概述 纳斯达克大屏是全球金融市场中最出名的电子交易平台之一。作为一个重要的金融信息传递渠道&#xff0c;纳斯达克大屏吸引了来自全球的投资者的目光。在这个巨大的投放平台上&#xff0c;大舍传媒希望为客…

1.QT简介(介绍、安装,项目创建等)

1. QT介绍 Qt&#xff08;官方发音 [kju:t]&#xff09;是一个跨平台的C开发库&#xff0c;主要用来开发图形用户界面&#xff08;Graphical User Interface&#xff0c;GUI&#xff09;程序 Qt 是纯 C 开发的&#xff0c;正常情况下需要先学习C语言、然后在学习C然后才能使用…

计算机设计大赛 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的行人重识别算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

【前端素材】推荐优质后台管理系统cassie平台模板(附源码)

一、需求分析 1、系统定义 后台管理系统是一种用于管理网站、应用程序或系统的管理界面&#xff0c;通常由管理员和工作人员使用。它提供了访问和控制网站或应用程序后台功能的工具和界面&#xff0c;使其能够管理用户、内容、数据和其他各种功能。 2、功能需求 后台管理系…

【JavaEE】_前端使用GET请求的queryString向后端传参

目录 1. GET请求的query string 2. 关于query string的urlencode 1. GET请求的query string 1. 在HttpServletRequest请求中&#xff0c;getParameter方法用于在服务器这边获取到请求中的参数&#xff0c;主要在query string中&#xff1b; query string中的键值对都是程序…

Docker容器(3)单容器管理

一、单容器 1.1概念简介 Docker三个重要概念: 仓库(Repository); 镜像(Image); 容器(Container). *Docker的三个重要概念是仓库(Repository)、镜像(Image)和容器(Container)**。具体如下&#xff1a; **镜像(Image)**&#xff1a;Docker镜像是创建容器的基础&#xff0c;它类似…

SpringCloud(17)之SpringCloud Stream

一、Spring Cloud Stream介绍 Spring Cloud Stream是一个框架&#xff0c;用于构建与共享消息系统连接的高度可扩展的事件驱动微服务。该框架提供了一个灵活的编程模型&#xff0c;该模型建立在已经建立和熟悉的Spring习惯用法和最佳实践之上&#xff0c;包括对持久发布/子语义…

《读写算》杂志社读写算杂志社2024年第2期目录

教育资讯 教育部印发通知部署&#xff1a;做好2024年寒假期间校外培训治理工作 1《读写算》投稿&#xff1a;cn7kantougao163.com 北京提升学校心理健康工作水平——每校至少配备一名专职心理健康教育教师 1 湖北孝感&#xff1a;2026年达成小学毕业时人人会游泳 2…

C语言编程安全规范

目的 本规范旨在加强编程人员在编程过程中的安全意识&#xff0c;建立编程人员的攻击者思维&#xff0c;养成安全编码的习惯&#xff0c;编写出安全可靠的代码。 2 宏 2.1 用宏定义表达式时&#xff0c;要使用完备的括号 2.2 使用宏时&#xff0c;不允许参数发生变化 3 变量 …

关于页表,页号,物理块号的例题

课本上的图解 题目:在分页系统中地址结构长度为16位&#xff0c;页面大小为2K&#xff0c;作业地址空间为6K&#xff0c;该作业的各页依次存放在2、3、6号物理块中&#xff0c;相对地址2500处有一条指令store 1, 4500&#xff0c;请给出该作业的页表&#xff0c;该指令的物理单…

css5定位

css 一.定位1.概念&#xff08;定位定位模式边位移&#xff09;2.静态位移static&#xff08;不常用&#xff09;3.相对定位relative&#xff08;不脱标&#xff09;&#xff08;占位置&#xff09;4.绝对定位absolute&#xff08;脱标&#xff09;&#xff08;不占位置&#x…

Thomson(汤姆森)简化了其螺旋千斤顶产品的CAD选型配置

线性运动控制解决方案提供商Thomson在其在线工程设计工具中添加了独特的螺旋千斤顶配置和选择工具。新的Thomson螺旋千斤顶产品选型器可帮助设计工程师优化和选定螺旋千斤顶&#xff0c;以满足高达100吨的负载应用。 Thomson螺旋千斤顶产品系列负责人Mitch Katona说&#xff1…

Java学习--学生管理系统(残破版)

代码 Main.java import java.util.ArrayList; import java.util.Scanner;public class Main {public static void main(String[] args) {ArrayList<Student> list new ArrayList<>();loop:while (true) {System.out.println("-----欢迎来到阿宝院校学生管理系…

docker (十二)-私有仓库

docker registry 我们可以使用docker push将自己的image推送到docker hub中进行共享&#xff0c;但是在实际工作中&#xff0c;很多公司的代码不能上传到公开的仓库中&#xff0c;因此我们可以创建自己的镜像仓库。 docker 官网提供了一个docker registry的私有仓库项目&#…

MySQL的21个SQL经验

1. 写完SQL先explain查看执行计划(SQL性能优化) 日常开发写SQL的时候,尽量养成这个好习惯呀:写完SQL后,用explain分析一下,尤其注意走不走索引。 explain select userid,name,age from user where userid =10086 or age =18;2、操作delete或者update语句,加个limit(S…
推荐文章