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

news2024/11/19 3:31:14
最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是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 "@/const/json/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: "",//省市区街道名称
	isSpecial: false//是否是特别行政区
})

//点击省
const provinceItemFn = (val) => {
	dataForm.value.provinceName = val.name
	dataForm.value.PCASName = updatePCASName(val.name)
	dataForm.value.province = val.code
	dataForm.value.citesList = val.children || []

	resetSelections([ "city", "area", "street" ])
	if ( dataForm.value.provinceName === "台湾省" ) {
		dataForm.value.isSpecial = true
		popoverVisible.value = false
		return
	} else {
		activeName.value = "second"
	}
	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 || []
	resetSelections([ "area", "street" ])
	if ( dataForm.value.provinceName === "澳门特别行政区" || dataForm.value.provinceName === "香港特别行政区" ) {
		dataForm.value.isSpecial = true
		popoverVisible.value = false
		return
	} else {
		activeName.value = "three"
	}
	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.isSpecial && ( !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 || dataForm.value.isSpecial ) {
		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 {
			setTimeout(() => {
				dataForm.value.PCASName = ""
				resetSelections([ "province", "city", "area", "street" ])
				activeName.value = "first"
			}, 1000)
		}
	}
})


//重置选择
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>

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

现在这这个组件刚写完

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

以后再更新...

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

若依前后端分离版开源项目学习

前言&#xff1a;vscode中vue代码没有高亮显示&#xff0c;可以下载vetur插件解决&#xff0c;ctrl点击无法跳转函数定义问题&#xff0c;可以下载vue-helper插件解决&#xff1b;idea中ctrl点击函数即可跳转函数定义。 一、登录 1.生成验证码 基本思路&#xff1a; 后端生…

算法沉淀——动态规划之子序列问题(下)(leetcode真题剖析)

算法沉淀——动态规划之子序列问题 01.最长定差子序列02.最长的斐波那契子序列的长度03.最长等差数列04.等差数列划分 II - 子序列 01.最长定差子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/ 给你一个整数数…

高级语言期末2011级A卷(软件学院)

1.编写函数&#xff0c;判定正整数m和n&#xff08;均至少为2&#xff09;是否满足&#xff1a;数m为数n可分解的最小质因数&#xff08;数n可分解的最小质因数为整除n的最小质数&#xff09; 提示&#xff1a;判定m为质数且m是n的最小因数 #include <stdio.h> #include…

【kubernetes】关于k8s集群的资源发布方式(灰度/滚动发布)

目录 一、常见的发布方式 二、详解kubectl陈述式方式做灰度发布&#xff08;金丝雀发布&#xff09; 步骤一&#xff1a;先基于deployment控制器创建pod&#xff0c;然后发布 步骤二&#xff1a;基于命令行灰度发布 步骤三&#xff1a;测试等到版本稳定以后&#xff0c;再完…

Java项目开发如何设计整体架构,字节跳动服务端研发面试

并发编程共享模型篇 并发编程概览进程与线程Java线程共享模型之管程共享模型之内存共享模型之无锁共享模型之不可变共享模型之工具 共享模型之管程 原理之 Monitor(锁) 原理之伪共享 模式篇—正确姿势 同步模式之保护性智停同步模式之Blking同步模式之顺序控制异步模式之生产…

【数据结构(C语言)】排序详解

目录 文章目录 前言 一、排序的概念 1.1 排序的概念 1.2 常见的排序算法 二、插入排序 2.1 直接插入排序 2.1.1 基本思想 2.1.2 特性总结 2.1.3 代码实现 2.2 希尔排序 2.2.1 基本思想 2.2.2 特性总结 2.2.3 代码实现 三、选择排序 3.1 直接选择排序 3.1.1…

要在Javascript中实现表格新增行功能,且添加元素,增删操作

起始表格元素&#xff1a; <!-- table>(thead>tr>th*6)(tbody>tr>td*6) --><div class"container"><table id"myTable"><caption><h3>员工信息管理系统</h3></caption><thead><tr>&…

初识Lombok

前言 最近读一些公司的业务代码&#xff0c;发现近几年的java项目工程中都使用了lombok&#xff0c;lombok是一个可以自动生成get,set、toString等模板类方法的工具框架&#xff0c;程序再引入lombok后&#xff0c;添加一个注解便可以不写get\set\toString等方法。 Lombok示例…

人工智能_CPU微调ChatGLM大模型_使用P-Tuning v2进行大模型微调_007_微调_002---人工智能工作笔记0102

这里我们先试着训练一下,我们用官方提供的训练数据进行训练. 也没有说使用CPU可以进行微调,但是我们先执行一下试试: https://www.heywhale.com/mw/project/6436d82948f7da1fee2be59e 可以看到说INT4量化级别最低需要7GB显存可以启动微调,但是 并没有说CPU可以进行微调.我们…

C语言中如何进行内存管理

主页&#xff1a;17_Kevin-CSDN博客 收录专栏&#xff1a;《C语言》 C语言是一种强大而灵活的编程语言&#xff0c;但与其他高级语言不同&#xff0c;它要求程序员自己负责内存的管理。正确的内存管理对于程序的性能和稳定性至关重要。 一、引言 C 语言是一门广泛使用的编程语…

【算法历练】动态规划副本—路径问题

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;宙でおやすみ 1:02━━━━━━️&#x1f49f;──────── 2:45 &#x1f504; ◀️ ⏸ ▶️ ☰ &#…

现在在市场上云主机一般多少钱?影响其价格的因素有哪些

现在很多人都会购买云主机来帮助自己存储一些数据&#xff0c;但是很多人在购买云主机的时候最担心的就是云主机的价格。 由于很多人担心云服务器的价格会很高&#xff0c;因此一直在密切关注目前市场上各品牌云主机的相关价格。 下面就给大家详细介绍一下现在市场上一台云主机…

【DDD】学习笔记-领域驱动设计对持久化的影响

资源库的实现 如何重用资源库的实现&#xff0c;以及如何隔离领域层与基础设施层的持久化实现机制&#xff0c;具体的实现还要取决于开发者对 ORM 框架的选择。Hibernate、MyBatis、jOOQ 或者 Spring Data JPA&#xff08;当然也包括基于 .NET 的 Entity Framework、NHibernat…

若依Vue3:新一代前后端分离权限管理系统

若依Vue3&#xff1a;新一代前后端分离权限管理系统 随着技术的不断进步&#xff0c;前后端分离的开发模式逐渐成为主流&#xff0c;特别是在构建权限管理系统时。在这样的背景下&#xff0c;若依Vue3应运而生&#xff0c;作为基于Spring Boot、Spring Security、JWT、Vue3、V…

【C++】树形关联式容器set、multiset、map和multimap的介绍与使用

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.关联式容器 2.键…

二叉搜索树在线OJ题讲解

二叉树创建字符串 我们首先进行题目的解读&#xff1a; 大概意思就是用&#xff08;&#xff09;把每个节点的值给括起来&#xff0c;然后再经过一系列的省略的来得到最后的结果 大家仔细观察题目给出的列子就可以发现&#xff0c;其实这个题目可以大致分为三种情况&#xff1…

基于 LVGL 使用 SquareLine Studio 快速设计 UI 界面

目录 简介注册与软件获取工程配置设计 UI导出源码板级验证更多内容 简介 SquareLine Studio 是一款专业的 UI 设计软件&#xff0c;它与 LVGL&#xff08;Light and Versatile Graphics Library&#xff0c;轻量级通用图形库&#xff09;紧密集成。LVGL 是一个轻量化的、开源的…

[linux][xdp] xdp 入门

xdp 全称 eXpress Data Path&#xff0c;是 linux ebpf 中的一个功能。ebpf 在内核中预留了一些插入点&#xff0c;用户可以在这些插入点插入自己的处理逻辑&#xff0c;当数据路过插入点时可以做一些预期的处理&#xff0c;具体实现方式如下&#xff1a; ① 用户编写数据处理…

【C++私房菜】序列式容器的迭代器失效问题

目录 一、list的迭代器失效 二、vector的迭代器失效 1、空间缩小操作 2、空间扩大操作 三、总结 在C中&#xff0c;当对容器进行插入或删除操作时&#xff0c;可能会导致迭代器失效的问题。所谓迭代器失效指的是&#xff0c;原先指向容器中某个元素的迭代器&#xff0c;在…

尚硅谷webpack5笔记2

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