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

最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是element + vue3记录一下。

本来是这样的三级联动:

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

 

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

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

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

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

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


import 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: "",	//省code
	city: "", 	//城市code
	area: "", 	//区县code
	street: "",	//街道乡镇code
	provinceName: "请选择", //省名称
	cityName: "请选择",// 城市名称
	areaName: "请选择", // 区县名称
	streetName: "请选择", //街道名称
	PCASName: "" //省市区街道名称
})

//点击省
const provinceItemFn = (val) => {
	dataForm.value.provinceName = val.name
	dataForm.value.PCASName = updatePCASName(val.name)
	dataForm.value.province = val.code
	dataForm.value.citesList = val.children || []
	activeName.value = "second"
	resetSelections([ "city", "area", "street" ])
	console.log(val)
}

//点击城市
const cityItemFn = (val) => {
	dataForm.value.cityName = val.name
	dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)
	dataForm.value.city = val.code
	dataForm.value.areaList = val.children || []
	activeName.value = "three"
	resetSelections([ "area", "street" ])
	console.log(val)
}

//点击区县
const areaItemFn = (val) => {
	dataForm.value.areaName = val.name
	dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, val.name)
	dataForm.value.area = val.code
	dataForm.value.streetsList = val.children
	resetSelections([ "street" ])
	activeName.value = "four"
	console.log(val)
}

//点击街道/乡镇
const streesItemFn = (val) => {
	dataForm.value.streetName = val.name
	dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, dataForm.value.areaName, val.name)
	dataForm.value.street = val.code
	popoverVisible.value = false
	console.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
			// 如果还有更深级别的名称,则继续递归,否则直接返回 codes
			return found.children && index + 1 < names.length ?
				findCodesByNames(found.children, names, index + 1, codes) : codes
		} else {
			// 如果未找到匹配项,证明省市区乡镇匹配错误,返回 false
			return false
		}
	}
	// 如果所有省市区乡镇都已成功匹配对应的code,返回 codes
	return 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>

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

现在这这个组件刚写完

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

以后再更新...

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据隐私安全趋势

在当今社交媒体和开源开发的世界中&#xff0c;共享似乎已成为社会常态。毕竟&#xff0c;我们都被教导分享就是关怀。这不仅适用于个人&#xff0c;也适用于公司&#xff1a;无论是有意在社交媒体帐户和公司网站上&#xff0c;还是无意中通过员工的行为&#xff0c;公司可能会…

P1450 [HAOI2008] 硬币购物 dp 容斥 —— s - c[i]*(d[i]+1)怎么理解

[HAOI2008] 硬币购物 - 洛谷 看了洛谷许多题解&#xff0c;一开始理解不了为什么是 s - c[i]*(d[i]1)&#xff0c;为什么要1呢&#xff1f; 其实是dp理解的不好。 这里的意思就是该枚硬币先超过限制&#xff0c;接下来剩下的背包也要填满&#xff0c;4种硬币随便组合的情况数…

【CMU 15-445】Lecture 15: Concurrency Control Theory 学习笔记

Concurrency Control Theory DefinitionsACID: AtomicityACID: ConsistencyACID: IsolationACID: Durability 本节课主要介绍事务的概念和特性。 Definitions 事务是一组数据库操作&#xff08;比如SQL语句&#xff09;的集合&#xff0c;进一步可以抽象为对某些数据对象的读写…

10W 音频功率放大电路芯片TDA2003,可用于汽车收音机及收录机中作音频功率放大器,内部具有短路保护和过热保护等功能

TDA2003 用于汽车收音机及收录机中作音频功率放大器。 采用 TO220B5 封装形式。 主要特点&#xff1a; ⚫ 内部具有短路保护和过热保护。内部具有地线开路、电源极性接 反和负载泄放电压反冲等保护电路。 ⚫ 输出电流大。 ⚫ 负载电阻可低至 1.6 。 …

matlab批量替换txt文本文件的特定行的内容

1.下图所示&#xff0c;我想要替换第14行。 2.运行代码后&#xff0c;第14行已经更改为需要的内容。 clc,clear; %%----------------------需要更改的地方------------------------------------ % 设置要操作的文本文件路径&#xff0c;替换为你自己的文件路径 path D:\paper_…

基于面结构光的高反射物体重建方法(相位偏折术)

Elon Musk曾表示&#xff0c;“在实现全自动驾驶的过程中&#xff0c;三维重建技术是不可或缺的一环。” Facebook的创始人Mark Zuckerberg也指出&#xff0c;“元宇宙时代的来临将更加依赖于高度精细的三维空间表达。” 这些业界巨擘的言论无疑为三维重建的未来发展注入了强大…

java 数据结构栈和队列

目录 栈(Stack) 栈的使用 栈的模拟实现 栈的应用场景 队列(Queue) 队列的使用 队列模拟实现 循环队列 双端队列 用队列实现栈 用栈实现队列 栈(Stack) 什么是栈&#xff1f; 栈 &#xff1a;一种特殊的线性表&#xff0c;其 只允许在固定的一端进行插入和删除元素操…

No matching version found for get-symbol-description@^1.0.2前端项目报错解决(亲测可用)

目录 一、问题详情 二、解决方案 一、问题详情 拉取一个新的项目的时候&#xff0c;前端进行install依赖的时候&#xff0c;报了如下的错误。 6120 verbose node v16.15.1 6121 verbose npm v8.11.0 6122 error code ETARGET 6123 error notarget No matching version foun…

推荐!2024年最热门的EDM邮件营销工具

独立站的潮流中&#xff0c;各个平台都在追求不同的流量策略。在这其中&#xff0c;EDM成为一个不可忽视的渠道。毕竟&#xff0c;以近乎零成本获取的流量&#xff0c;再加上高转化率和老客户的回购&#xff0c;简直让人心动。 在各类建站平台的应用库中&#xff0c;EDM工具种…

高通 AI Hub 上手指南

文章介绍 2月26日&#xff0c;高通在2024年世界移动通信大会&#xff08;MWC2024&#xff09;上发布高通AI Hub&#xff0c; AI Hub 简化了AI 模型部署到边缘设备的过程。可以利用AI-hub云端托管 Qualcomm 设备上&#xff0c;在几分钟内完成模型的优化、验证和部署。本文以Pyto…

前端sql条件拼接js工具

因为项目原因&#xff0c;需要前端写sql&#xff0c;所以弄了一套sql条件拼接的js工具 ​ /*常量 LT : " < ", LE : " < ", GT : " > ", GE : " > ", NE : " ! ", EQ : " ", LIKE : " like &qu…

浅谈集群的分类

本文主要介绍集群部署相关的知识&#xff0c;介绍集群部署的基础&#xff0c;集群的分类、集群的负载均衡技术&#xff0c;集群的可用性以及集群的容错机制。随后介绍Redis-Cluster以及Mysql的架构以及主从复制原理。 集群介绍 单台服务器本身会受到带宽、网卡、内存、磁盘、处…

Linux-Uboot命令

help命令 进入 uboot 的命令行模式后输入“help”或者“&#xff1f;”&#xff0c;然后按下回车即可查看当前 uboot 所支持的命令。 查看某一个命令的帮助信息&#xff1a;&#xff1f;命令名称 或 help命令名称 信息查询命令 常用的和信息查询有关的命令有 3 个…

R语言混合效应(多水平/层次/嵌套)模型及贝叶斯实现技术应用

回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展&#xff0c;回归分析方法得到了极大改进。混合效应模型&#xff08;Mixed effect model&#xff09;&#xff0c;即多水平模&#xff08;Multilevel model&#xff09;/分层模型(Hierarchical Model)/嵌套模…

即时设计和Axure对比,哪一个好用?

无论是国外页面设计工具&#xff0c;页面设计工具的发展从来没有停滞过&#xff0c; Axure&#xff0c;无论是国产设计工具即时设计&#xff0c;其功能都在不断更新迭代&#xff0c;为设计带来更高效的设计体验。今天对比两个设计工具&#xff0c;帮你找到最适合自己的&#xf…

SQL注入漏洞解析--less-46

我们先看一下46关 他说让我们先输入一个数字作为sort,那我们就先输入数字看一下 当我们分别输入1&#xff0c;2&#xff0c;3可以看到按照字母顺序进行了排序&#xff0c;所以它便是一个使用了order by语句进行排序的查询的一种查询输出方式 当输入时出现报错提示&#xff0c;说…

编曲学习:和声小调 终止式 离调和弦 转调应用

和声小调 音阶 大调音程关系排列:全 全 半 全 全 全 半 小调音程关系排列:全 半 全 全 半 全 全 C大调音阶: 1 2 3 4 5 6 7 1 C小调音阶: 1 2 b3 4 5 b6 b7 1 C大调基本音级构成的和弦: Cmaj7 Dmin7 Emin7 Fmaj7 G7 Amin7 Bm7-5 C小调基本音级构成的和弦: Cmin7 D…

LeetCode 刷题 [C++] 第141题.环形链表

题目描述 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&a…

Qt介绍以及qt_creater的安装和C++项目工程创建

最近天气严寒&#xff0c;同学们要注意保暖哦&#xff01;学习的同时别忘了照顾好自己呀&#xff01;o(*&#xffe3;▽&#xffe3;*)ブ 目录 一、Qt 1、Qt概念 2、常见的GUI 二、安装qt_creater 方法一&#xff1a; 方法二&#xff1a; 三、Qt_creater 中C项目的创建 …

学会玩游戏,智能究竟从何而来?

最近在读梅拉妮米歇尔《AI 3.0》第三部分第九章&#xff0c;谈到学会玩游戏&#xff0c;智能究竟从何而来&#xff1f; 作者: [美] 梅拉妮米歇尔 出版社: 四川科学技术出版社湛庐 原作名: Artificial Intelligence: A Guide for Thinking Humans 译者: 王飞跃 / 李玉珂 / 王晓…