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

最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是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 v-model:visible="popoverVisible" :width="460" placement="bottom" trigger="click"><template #reference><el-input v-model="dataForm.PCASName" placeholder="请选择省市区街道/乡镇" /></template><div><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" :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 { reactive, ref, watchEffect } from "vue"
import { cloneDeep } from "lodash-es"
import pcasCode from "@/views/owner_center/usualAddress/pcas-code.json"
import pinyin from "chinese-to-pinyin"//弹出框是否显示
let popoverVisible = ref(null)//省市区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) => {if ( tab.props.name === "second" ) {dataForm.value.citesList = pcasCodeList.find(item => item.code === dataForm.value.province)?.children || []} else if ( tab.props.name === "three" ) {const childrenArray = findChildrenByCode(pcasCodeList, dataForm.value.city)dataForm.value.areaList = childrenArray || []} else if ( tab.props.name === "four" ) {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: "" //省市区街道名称
})//点击省
const provinceItemFn = (val) => {dataForm.value.provinceName = val.namedataForm.value.PCASName = updatePCASName(val.name)dataForm.value.province = val.codedataForm.value.citesList = val.children || []activeName.value = "second"resetSelections([ "city", "area", "street" ])console.log(val)
}//点击城市
const cityItemFn = (val) => {dataForm.value.cityName = val.namedataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)dataForm.value.city = val.codedataForm.value.areaList = val.children || []activeName.value = "three"resetSelections([ "area", "street" ])console.log(val)
}//点击区县
const areaItemFn = (val) => {dataForm.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"console.log(val)
}//点击街道/乡镇
const streesItemFn = (val) => {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 = falseconsole.log(val)
}watchEffect(() => {//判断某个地区为空时清空输入框内容if ( !popoverVisible.value && ( !dataForm.value.province || !dataForm.value.city || !dataForm.value.area || !dataForm.value.street ) ) {dataForm.value.PCASName = ""}//判断如果手动输入地区如“安徽省/芜湖市/弋江区/瀂港街道”匹配到对应code值等逻辑,否则清空const parts = dataForm.value.PCASName.split("/")if(parts.length === 4) {const matchedCodes = findCodesByNames(pcasCodeList, parts)if ( matchedCodes ) {dataForm.value.province = matchedCodes[0]dataForm.value.city = matchedCodes[1]dataForm.value.area = matchedCodes[2]dataForm.value.street = matchedCodes[3]dataForm.value.provinceName = parts[0]dataForm.value.cityName = parts[1]dataForm.value.areaName = parts[2]dataForm.value.streetName = parts[3]dataForm.value.citesList = findChildrenByCode(pcasCodeList, matchedCodes[0])dataForm.value.areaList = findChildrenByCode(pcasCodeList, matchedCodes[1])dataForm.value.streetsList = findChildrenByCode(pcasCodeList, matchedCodes[2])console.log(matchedCodes) // 输出找到的 code 数组} 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.streetNameName = "请选择"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
}</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;}}}
}.cityContent{display: flex;flex-wrap: wrap;font-size: 14px;.cityItem{margin-right: 18px;margin-bottom: 10px;cursor: pointer;}
}.active{color: #1166fe !important;
}</style>

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

现在这这个组件刚写完

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

以后再更新...

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

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

相关文章

uniapp小程序uView自定义tabbar

两年没接触小程序&#xff0c;又重新拾请来 前言 工具&#xff1a;HBuilder X 3.99版本 微信开发者工具 1.06 语言&#xff1a;vue2 uView 一、创建项目 先使用HBuilder X工具创建一个空白uni-app项目 uviewTest 二、安装和配置 HBuilder X找到工具-》插件安装-》插件市场 u…

mac打不开xxx软件, 因为apple 无法检查其是否包含恶意

1. 安全性与隐私下面的允许来源列表&#xff0c;有些版本中的‘任何来源’选项被隐藏了&#xff0c;有些从浏览器下载的软件需要勾选这个选项才能安装 打开‘任何来源’选项 sudo spctl --master-disable 关闭‘任何来源’选项 sudo spctl --master-enable

无人机飞行控制系统技术,四旋翼无人机控制系统建模技术详解

物理建模是四旋翼无人机控制系统建模的基础&#xff0c;主要涉及到无人机的物理特性和运动学特性。物理建模的目的是将无人机的运动与输入信号&#xff08;如控制电压&#xff09;之间的关系进行数学描述。 四旋翼无人直升机是具有四个输入力和六个坐标输出的欠驱动动力学旋翼…

【.NET Core】深入理解IO之File类

【.NET Core】深入理解IO之File类 文章目录 【.NET Core】深入理解IO之File类一、概述二、File类2.1 File.AppendAllLines方法2.2 File.AppendAllText方法2.3 File.Copy 方法2.4 File.Create 方法2.5 File.Decrypt(String) 方法2.6 File.Delete(String) 方法2.7 File.Move 方法…

Linux shell:补充命令的使用

目录 一.导读 二.正文 三.结语 一.导读 上一篇介绍了脚本的简单概念以及使用&#xff0c;现在补充一些命令。 二.正文 目前处于全局目录&#xff0c;通过mkdir创建名我为day01的文件。 通过cd命令day01 切换至day01文件当中。 使用vim文本编辑器文件名&#xff08;firstdir&…

爆火的1分钟声音克隆GPT-SoVITS项目 linux系统 ubuntu22.04安装2天踩坑教程

原项目地址&#xff1a;https://github.com/RVC-Boss/GPT-SoVITS 1分钟素材&#xff0c;最后出来的效果确实不错。 1. cuda环境安装 cuda环境准备 根据项目要求在cuda11.8和12.3都测试了通过。我这里是用cuda11.8 cuda11.8安装教程&#xff1a; ubuntu 22.04 cuda多版本和…

docker-mysql:5.7安装

1、下载mysql:5.7镜像 [rootlocalhost ~]# docker search mysql (某个XXX镜像名字) [rootlocalhost ~]# docker pull mysql:5.7 按装之前查看一下是否按装过mysql。如果安装过会占用3306端口。 [rootlocalhost ~]# ps -ef | grep mysql 2、简单的安装 [rootlocalhost ~]# d…

【pytorch】函数记录

你好你好&#xff01; 以下内容仅为当前认识&#xff0c;可能有不足之处&#xff0c;欢迎讨论&#xff01; 文章目录 torch.sum()torch.argmax()torch.nn.Parametertorch.unbindtorch.optim.Adam()[^adam]torch.cattorch.unsqueeze()torch.normalize()[^l2]torch.eyetorch.mmto…

亚信安慧AntDB:数据处理的好帮手

亚信安慧AntDB分布式数据库凭借平滑扩展、高可用性和低成本三大核心优势&#xff0c;在业界获得了极高的评价和认可。这些优点不仅为AntDB提供了巨大的市场发展潜力&#xff0c;也使其成为众多企业在数据管理上的首选解决方案。 AntDB的平滑扩展特性极大地提升了企业的灵活性和…

project.config.json 文件内容错误] project.config.json: libVersion 字段需为 string, string

家人们&#xff0c;遇到了一个新的报错 于是从网上找了各种方法&#xff0c;有说把开发者工具关闭重启的&#xff0c;有说开发者工具下载重新下载的&#xff0c;有说开发者工具路径安装得在C盘的&#xff0c;均没有效果 解决方法&#xff1a; 1、运行项目&#xff0c;在开发者…

Netty权威指南——基础篇3(AIO编程)备份

1 概述 NIO 2.0 引入了新的异步通道概念&#xff0c;并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取操作结果。 1、通过java.util.concurrent.Future类来表示异步操作的结果&#xff1b; 2、在执行异步操作的时候传入一个java.nio.channels Comple…

尚硅谷webpack5笔记2

Loader 原理 loader 概念 帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。 loader 执行顺序 分类pre: 前置 loadernormal: 普通 loaderinline: 内联 loaderpost: 后置 loader执行顺序4 类 loader 的执行优级为:pre > normal > inline > post 。相…