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

2024-02-27 20:44

本文主要是介绍省市区街道/乡镇四级联动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>

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

现在这这个组件刚写完

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

以后再更新...

这篇关于省市区街道/乡镇四级联动vue3的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/753549

相关文章

HTML5中的Microdata与历史记录管理详解

《HTML5中的Microdata与历史记录管理详解》Microdata作为HTML5新增的一个特性,它允许开发者在HTML文档中添加更多的语义信息,以便于搜索引擎和浏览器更好地理解页面内容,本文将探... 目录html5中的Mijscrodata与历史记录管理背景简介html5中的Microdata使用M

html5的响应式布局的方法示例详解

《html5的响应式布局的方法示例详解》:本文主要介绍了HTML5中使用媒体查询和Flexbox进行响应式布局的方法,简要介绍了CSSGrid布局的基础知识和如何实现自动换行的网格布局,详细内容请阅读本文,希望能对你有所帮助... 一 使用媒体查询响应式布局        使用的参数@media这是常用的

HTML5表格语法格式详解

《HTML5表格语法格式详解》在HTML语法中,表格主要通过table、tr和td3个标签构成,本文通过实例代码讲解HTML5表格语法格式,感兴趣的朋友一起看看吧... 目录一、表格1.表格语法格式2.表格属性 3.例子二、不规则表格1.跨行2.跨列3.例子一、表格在html语法中,表格主要通过< tab

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

前端CSS Grid 布局示例详解

《前端CSSGrid布局示例详解》CSSGrid是一种二维布局系统,可以同时控制行和列,相比Flex(一维布局),更适合用在整体页面布局或复杂模块结构中,:本文主要介绍前端CSSGri... 目录css Grid 布局详解(通俗易懂版)一、概述二、基础概念三、创建 Grid 容器四、定义网格行和列五、设置行

前端下载文件时如何后端返回的文件流一些常见方法

《前端下载文件时如何后端返回的文件流一些常见方法》:本文主要介绍前端下载文件时如何后端返回的文件流一些常见方法,包括使用Blob和URL.createObjectURL创建下载链接,以及处理带有C... 目录1. 使用 Blob 和 URL.createObjectURL 创建下载链接例子:使用 Blob

Vuex Actions多参数传递的解决方案

《VuexActions多参数传递的解决方案》在Vuex中,actions的设计默认只支持单个参数传递,这有时会限制我们的使用场景,下面我将详细介绍几种处理多参数传递的解决方案,从基础到高级,... 目录一、对象封装法(推荐)二、参数解构法三、柯里化函数法四、Payload 工厂函数五、TypeScript

Vue3使用router,params传参为空问题

《Vue3使用router,params传参为空问题》:本文主要介绍Vue3使用router,params传参为空问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录vue3使用China编程router,params传参为空1.使用query方式传参2.使用 Histo

CSS Padding 和 Margin 区别全解析

《CSSPadding和Margin区别全解析》CSS中的padding和margin是两个非常基础且重要的属性,它们用于控制元素周围的空白区域,本文将详细介绍padding和... 目录css Padding 和 Margin 全解析1. Padding: 内边距2. Margin: 外边距3. Padd