省市区街道/乡镇四级联动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

相关文章

如何使用CSS3实现波浪式图片墙

《如何使用CSS3实现波浪式图片墙》:本文主要介绍了如何使用CSS3的transform属性和动画技巧实现波浪式图片墙,通过设置图片的垂直偏移量,并使用动画使其周期性地改变位置,可以创建出动态且具有波浪效果的图片墙,同时,还强调了响应式设计的重要性,以确保图片墙在不同设备上都能良好显示,详细内容请阅读本文,希望能对你有所帮助...

CSS3 最强二维布局系统之Grid 网格布局

《CSS3最强二维布局系统之Grid网格布局》CS3的Grid网格布局是目前最强的二维布局系统,可以同时对列和行进行处理,将网页划分成一个个网格,可以任意组合不同的网格,做出各种各样的布局,本文介... 深入学习 css3 目前最强大的布局系统 Grid 网格布局Grid 网格布局的基本认识Grid 网

HTML5中下拉框<select>标签的属性和样式详解

《HTML5中下拉框<select>标签的属性和样式详解》在HTML5中,下拉框(select标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中选择值的方式,本文将深入探讨select标签的... 在html5中,下拉框(<select>标签)作为表单的重要组成部分,为用户提供了一个从预定义选项中

前端 CSS 动态设置样式::class、:style 等技巧(推荐)

《前端CSS动态设置样式::class、:style等技巧(推荐)》:本文主要介绍了Vue.js中动态绑定类名和内联样式的两种方法:对象语法和数组语法,通过对象语法,可以根据条件动态切换类名或样式;通过数组语法,可以同时绑定多个类名或样式,此外,还可以结合计算属性来生成复杂的类名或样式对象,详细内容请阅读本文,希望能对你有所帮助...

禁止HTML页面滚动的操作方法

《禁止HTML页面滚动的操作方法》:本文主要介绍了三种禁止HTML页面滚动的方法:通过CSS的overflow属性、使用JavaScript的滚动事件监听器以及使用CSS的position:fixed属性,每种方法都有其适用场景和优缺点,详细内容请阅读本文,希望能对你有所帮助... 在前端开发中,禁止htm

Vue3中的动态组件详解

《Vue3中的动态组件详解》本文介绍了Vue3中的动态组件,通过`component:is=动态组件名或组件对象/component`来实现根据条件动态渲染不同的组件,此外,还提到了使用`markRa... 目录vue3动态组件动态组件的基本使用第一种写法第二种写法性能优化解决方法总结Vue3动态组件动态

spring-boot-starter-thymeleaf加载外部html文件方式

《spring-boot-starter-thymeleaf加载外部html文件方式》本文介绍了在SpringMVC中使用Thymeleaf模板引擎加载外部HTML文件的方法,以及在SpringBoo... 目录1.Thymeleaf介绍2.springboot使用thymeleaf2.1.引入spring

部署Vue项目到服务器后404错误的原因及解决方案

《部署Vue项目到服务器后404错误的原因及解决方案》文章介绍了Vue项目部署步骤以及404错误的解决方案,部署步骤包括构建项目、上传文件、配置Web服务器、重启Nginx和访问域名,404错误通常是... 目录一、vue项目部署步骤二、404错误原因及解决方案错误场景原因分析解决方案一、Vue项目部署步骤

前端原生js实现拖拽排课效果实例

《前端原生js实现拖拽排课效果实例》:本文主要介绍如何实现一个简单的课程表拖拽功能,通过HTML、CSS和JavaScript的配合,我们实现了课程项的拖拽、放置和显示功能,文中通过实例代码介绍的... 目录1. 效果展示2. 效果分析2.1 关键点2.2 实现方法3. 代码实现3.1 html部分3.2

CSS弹性布局常用设置方式

《CSS弹性布局常用设置方式》文章总结了CSS布局与样式的常用属性和技巧,包括视口单位、弹性盒子布局、浮动元素、背景和边框样式、文本和阴影效果、溢出隐藏、定位以及背景渐变等,通过这些技巧,可以实现复杂... 一、单位元素vm 1vm 为视口的1%vh 视口高的1%vmin 参照长边vmax 参照长边re