小工具知识网

当前位置: 首页 > 行业知识 > 省市区街道/乡镇四级联动vue3

行业知识

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

2026-10-29 19:56:17 来源:互联网转载
最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是element + vue3记录一下。

���来是这样的三级联动:

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

 

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

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

GitHub - modood/Administrative-pisions-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)

这样就实现了这个页面了

交互逻辑太多

为了避免文章太长

直接上全部代码
{{ itemName }}{{ item.name }}{{ item.name }}{{ item.name }}{{ item.name }}import { unref, ref, watchEffect } from "vue"import { ClickOutside as vClickOutside } from "element-plus"import { cloneDeep } from "lodash-es"import pcasCode from "@/const/json/pcas-code.json"import pinyin from "chinese-to-pinyin"//弹出框是否显示let popoverVisible = ref(false)let inputRef = ref()//省市区选择区域外内容点击事件const contentClick = () => {if ( !dataForm.value.area && !dataForm.value.isSpecial ) {resetSelections([ "province", "city", "area", "street" ])activeName.value = "first"dataForm.value.PCASName = ""}if (  dataForm.value.effectiveVal && dataForm.value.effectiveVal.split("/").length > 2 ) {dataForm.value.PCASName = dataForm.value.effectiveVal}popoverVisible.value = false}//省市区tabconst 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) => {popoverVisible.value = trueif ( tab.props.name === "first" ) {resetSelections([ "province", "city", "area", "street" ])} else if ( tab.props.name === "second" ) {resetSelections([ "city", "area", "street" ])dataForm.value.citesList = pcasCodeList.find(item => item.code === dataForm.value.province)?.children || []} else if ( tab.props.name === "three" ) {resetSelections([ "area", "street" ])const childrenArray = findChildrenByCode(pcasCodeList, dataForm.value.city)dataForm.value.areaList = childrenArray || []} else if ( tab.props.name === "four" ) {resetSelections([ "street" ])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: "",//省市区街道名称isSpecial: false,//是否是特别行政区effectiveVal: ""//有效省市区})//点击省const provinceItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.value.provinceName = val.namedataForm.value.PCASName = updatePCASName(val.name)dataForm.value.province = val.codedataForm.value.citesList = val.children || []resetSelections([ "city", "area", "street" ])if ( dataForm.value.provinceName === "台湾省" ) {dataForm.value.isSpecial = truepopoverVisible.value = false} else {activeName.value = "second"dataForm.value.isSpecial = false}}//点击城市const cityItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.value.cityName = val.namedataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)dataForm.value.city = val.codedataForm.value.areaList = val.children || []resetSelections([ "area", "street" ])if ( dataForm.value.provinceName === "澳门特别行政区" || dataForm.value.provinceName === "香港特别行政区" ) {dataForm.value.isSpecial = truepopoverVisible.value = false} else {activeName.value = "three"dataForm.value.isSpecial = false}}//点击区县const areaItemFn = (val) => {dataForm.value.effectiveVal = ''popoverVisible.value = truedataForm.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"}//点击街道/乡镇const streesItemFn = (val) => {dataForm.value.effectiveVal = ''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 = false}const inputBlurFn = ( (event) => {popoverVisible.value = false} )//输入框input事件const pcasInputFn = (val) => {if ( val ) {let str = val.replace(/\//g, "")let str2 = matchPath(str, pcasCodeList)dataForm.value.effectiveVal = str2const parts = str2.split("/")console.log("parts", parts)const matchedCodes = findCodesByNames(pcasCodeList, parts)if ( matchedCodes ) {dataForm.value.citesList = findChildrenByCode(pcasCodeList, matchedCodes[0])dataForm.value.areaList = findChildrenByCode(pcasCodeList, matchedCodes[1])dataForm.value.streetsList = findChildrenByCode(pcasCodeList, matchedCodes[2])if ( parts.length === 1 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]resetSelections([ "city", "area", "street" ])activeName.value = "second"}if ( parts.length === 2 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]resetSelections([ "area", "street" ])activeName.value = "three"}if ( parts.length === 3 ) {dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]dataForm.value.area = matchedCodes[2]dataForm.value.areaName = parts[2]resetSelections([ "street" ])activeName.value = "four"}if ( parts.length === 4 ) {activeName.value = "four"dataForm.value.province = matchedCodes[0]dataForm.value.provinceName = parts[0]dataForm.value.city = matchedCodes[1]dataForm.value.cityName = parts[1]dataForm.value.area = matchedCodes[2]dataForm.value.areaName = parts[2]dataForm.value.street = matchedCodes[3]dataForm.value.streetName = parts[3]}} else {resetSelections([ "province", "city", "area", "street" ])activeName.value = "first"}} 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.streetName = "请选择"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  item.name === names[index])if ( found ) {// 如果找到了匹配项,加入 code,并继续递归搜索下一级codes[index] = found.code// 如果还有更深级别的名称,则继续递归,否则直接返回 codesreturn found.children && index + 1  

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

现在这这个组件刚写完

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

以后再更新...

上一篇:荣耀战魂怎么服务器繁忙?

下一篇:Autoxjs下载攻略,一键安装,轻松上手!