最近优化了一个省.市.区/县、乡镇/街道的四级联动组件,技术栈是element + vue3记录一下。
本来是这样的三级联动:
这个三级联动很简单,直接利用el-select组件把地区值带进去就行了,现在要优化成省.市.区/县、乡镇/街道的四级联动,变成这样:
下面进入正文: (说一下主要流程,最后附上全部代码)
首先要准备省市区和对应编码的JSON文件:
可以参考这个地址,直接在浏览器下载也行,git 克隆到本地也行,这个json文件很大,大概两三兆,可以让后端返回。
省份分组时用到了一个三方包,需要把省份转成拼音获取首字母,直接下载就行
yarn add chinese-to-pinyin 或者 npm i chinese-to-pinyin
import pinyin from "chinese-to-pinyin"
然后调整数据结构,
<template>
<el-popover ref="popoverRef" :visible="popoverVisible" :width="dataForm.isInputting ? 460 : 300"
placement="bottom" trigger="click">
<template #reference>
<el-input v-model="dataForm.PCASName" placeholder="请选择省市区街道/乡镇" :size="props.inputSize"
@blur="inputBlurFn" @click="popoverVisible =true" @focus="inputFocusFn" @input="pcasInputFn"/>
</template>
<div v-if="dataForm.isInputting" v-click-outside="contentClick">
<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 && !dataForm.isSpecial" :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>
<div v-else class="aimContent">
<div v-if="dataForm.dimList.length > 0">
<div v-for="(item, index) in dataForm.dimList" :key="index" class="dimItem" @click="dimItem(item)">
{{ item.name }}
</div>
</div>
<div v-else>暂无数据</div>
</div>
</el-popover>
</template>
<script setup>
import { unref, ref, watchEffect, onMounted } 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"
// <AddressGanged ref="AddressGangedRef" :PCASCode="['110000', '110100', '110101']" :inputSize="'large'" :PCASName="PCASName"></AddressGanged>
const props = defineProps({
//省市区乡镇名称
PCASName: {
type: String,
default: ""
},
//省市区乡镇code
PCASCode: {
type: Array,
default: []
},
//输入框大小('large','small')
inputSize: {
type: String,
default: "default"
},
})
let dataForm = ref({
citesList: [], //城市分组
areaList: [], //区县分组
streetsList: [], //街道乡镇分组
dimList: [], //模糊搜索省市区数据
codeList: [], //省市区数据code
province: "", //省code
city: "", //城市code
area: "", //区县code
street: "", //街道乡镇code
provinceName: "请选择", //省名称
cityName: "请选择",// 城市名称
areaName: "请选择", // 区县名称
streetName: "请选择", //街道名称
PCASName: "",//省市区街道名称
isSpecial: false,//是否是特别行政区
isInputting: true//是否是输入中
})
onMounted(() => {
if ( props.PCASName ) {
dataForm.value.PCASName = props.PCASName
}
if ( props.PCASCode.length > 0 ) {
dataForm.value.codeList = props.PCASCode
const levels = [ "province", "city", "area", "street" ]
props.PCASCode.forEach((code, index) => {
const level = levels[index]
if ( level ) {
dataForm.value[level] = code
}
})
}
})
//弹出框是否显示
let popoverVisible = ref(false)
//省市区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) => {
popoverVisible.value = true
if ( 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 || []
}
}
//点击省
const provinceItemFn = (val) => {
popoverVisible.value = true
dataForm.value.provinceName = val.name
dataForm.value.PCASName = updatePCASName(val.name)
dataForm.value.codeList = [ val.code ]
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
} else {
activeName.value = "second"
dataForm.value.isSpecial = false
}
}
//点击城市
const cityItemFn = (val) => {
popoverVisible.value = true
dataForm.value.cityName = val.name
dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)
dataForm.value.city = val.code
dataForm.value.codeList = [ dataForm.value.province, val.code ]
dataForm.value.areaList = val.children || []
resetSelections([ "area", "street" ])
if ( dataForm.value.provinceName === "澳门特别行政区" || dataForm.value.provinceName === "香港特别行政区" ) {
dataForm.value.isSpecial = true
popoverVisible.value = false
} else {
activeName.value = "three"
dataForm.value.isSpecial = false
}
}
//点击区县
const areaItemFn = (val) => {
popoverVisible.value = true
dataForm.value.areaName = val.name
dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, val.name)
dataForm.value.area = val.code
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, val.code ]
dataForm.value.streetsList = val.children
resetSelections([ "street" ])
activeName.value = "four"
}
//点击街道/乡镇
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
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, dataForm.value.area, val.code ]
popoverVisible.value = false
}
//点击模糊搜索结果
const dimItem = (val) => {
dataForm.value.PCASName = val.name
dataForm.value.codeList = val.code.split("/")
}
//省市区选择区域外内容点击事件
const contentClick = () => {
popoverVisible.value = false
}
//输入框失去焦点事件
const inputBlurFn = ( (event) => {
if ( !dataForm.value.isInputting ) popoverVisible.value = false
if ( ( dataForm.value.codeList.length < 1 || dataForm.value.dimList.length < 1 ) && dataForm.value.PCASName ) {
resetSelections([ "province", "city", "area", "street" ])
activeName.value = "first"
dataForm.value.PCASName = ""
}
// console.log("输入框失去焦点事件", dataForm.value.codeList)
} )
//输入框获取焦点事件
const inputFocusFn = (val) => {
if ( val && dataForm.value.PCASName ) {
dataForm.value.isInputting = false
dataForm.value.dimList = findAddressPaths(pcasCodeList, dataForm.value.PCASName)
} else {
dataForm.value.isInputting = true
}
}
//输入框input事件
const pcasInputFn = (val) => {
if ( val && dataForm.value.PCASName ) {
dataForm.value.codeList = []
dataForm.value.isInputting = false
dataForm.value.dimList = findAddressPaths(pcasCodeList, val)
} else {
dataForm.value.isInputting = true
resetSelections([ "province", "city", "area", "street" ])
activeName.value = "first"
}
}
//重置选择
const resetSelections = (clearLevels) => {
// 根据传入的层级清除选项
if ( clearLevels.includes("province") ) {
dataForm.value.province = ""
dataForm.value.provinceName = "请选择"
dataForm.value.codeList = []
}
if ( clearLevels.includes("city") ) {
dataForm.value.city = ""
dataForm.value.cityName = "请选择"
dataForm.value.areaList = []
dataForm.value.codeList = [ dataForm.value.province ]
}
if ( clearLevels.includes("area") ) {
dataForm.value.areaName = "请选择"
dataForm.value.area = ""
dataForm.value.streetsList = []
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city ]
}
if ( clearLevels.includes("street") ) {
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, dataForm.value.area ]
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 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
}
//省市区模糊搜索算法
function findAddressPaths(data, searchText, limit = 30) {
let results = []
let subEntriesToCheck = [] // 存储子条目等待检查的列表
let normalizedSearchText = searchText.replace(/\//g, "") // 正规化搜索文本,移除斜杠
function getNormalizedPath(name) {
return name.replace(/\//g, "") // 移除路径中的斜杠用于搜索匹配
}
// 检查并添加符合条件的省级数据
for ( const province of data ) {
const normalizedProvinceName = getNormalizedPath(province.name)
if ( normalizedProvinceName.includes(normalizedSearchText) ) {
results.push({ name: province.name, code: province.code })
if ( results.length >= limit ) {
return results
}
}
// 将省下属的城市添加到待检查列表
if ( province.children ) {
for ( const city of province.children ) {
subEntriesToCheck.push({
fullName: province.name + "/" + city.name,
fullCode: province.code + "/" + city.code,
children: city.children
})
}
}
}
// 处理子级条目(城市或区县)
function processSubEntries(list) {
for ( const entry of list ) {
const normalizedFullName = getNormalizedPath(entry.fullName)
if ( normalizedFullName.includes(normalizedSearchText) ) {
results.push({ name: entry.fullName, code: entry.fullCode })
if ( results.length >= limit ) {
return
}
}
// 将该级下属的区县添加到待检查列表
if ( entry.children ) {
for ( const child of entry.children ) {
subEntriesToCheck.push({
fullName: entry.fullName + "/" + child.name,
fullCode: entry.fullCode + "/" + child.code,
children: child.children
})
}
}
}
}
// 循环处理所有待检查的子条目
do {
const currentLevelToCheck = [ ...subEntriesToCheck ]
subEntriesToCheck = []
processSubEntries(currentLevelToCheck)
} while ( results.length < limit && subEntriesToCheck.length > 0 )
return results
}
defineExpose({
dataForm
})
</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;
color: #1166fe;
}
}
}
}
.cityContent{
display: flex;
flex-wrap: wrap;
font-size: 14px;
.cityItem{
margin-right: 18px;
margin-bottom: 10px;
cursor: pointer;
&:hover{
color: #1166fe;
}
}
}
.aimContent{
height: 300px;
overflow-y: auto;
}
.dimItem{
margin: 4px 0;
cursor: pointer;
&:hover{
background: #1166fe;
color: #FFFFFF;
}
}
.active{
color: #1166fe !important;
}
</style>
这样就实现了这个页面了
交互逻辑太多
为了避免文章太长
直接上全部代码
<template>
<el-popover ref="popoverRef" :visible="popoverVisible" :width="dataForm.isInputting ? 460 : 300"
placement="bottom" trigger="click">
<template #reference>
<el-input v-model="dataForm.PCASName" placeholder="请选择省市区街道/乡镇"
@blur="inputBlurFn" @click="popoverVisible =true" @focus="inputFocusFn" @input="pcasInputFn"/>
</template>
<div v-if="dataForm.isInputting" v-click-outside="contentClick">
<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 && !dataForm.isSpecial" :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>
<div v-else class="aimContent">
<div v-if="dataForm.dimList.length > 0">
<div v-for="(item, index) in dataForm.dimList" :key="index" class="dimItem" @click="dimItem(item)">
{{ item.name }}
</div>
</div>
<div v-else>暂无数据</div>
</div>
</el-popover>
</template>
<script setup>
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 dataForm = ref({
citesList: [], //城市分组
areaList: [], //区县分组
streetsList: [], //街道乡镇分组
dimList: [], //模糊搜索省市区数据
codeList: [], //省市区数据code
province: "", //省code
city: "", //城市code
area: "", //区县code
street: "", //街道乡镇code
provinceName: "请选择", //省名称
cityName: "请选择",// 城市名称
areaName: "请选择", // 区县名称
streetName: "请选择", //街道名称
PCASName: "",//省市区街道名称
isSpecial: false,//是否是特别行政区
isInputting: true//是否是输入中
})
//弹出框是否显示
let popoverVisible = ref(false)
//省市区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) => {
popoverVisible.value = true
if ( 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 || []
}
}
//点击省
const provinceItemFn = (val) => {
popoverVisible.value = true
dataForm.value.provinceName = val.name
dataForm.value.PCASName = updatePCASName(val.name)
dataForm.value.codeList = [ val.code ]
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
} else {
activeName.value = "second"
dataForm.value.isSpecial = false
}
}
//点击城市
const cityItemFn = (val) => {
popoverVisible.value = true
dataForm.value.cityName = val.name
dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, val.name)
dataForm.value.city = val.code
dataForm.value.codeList = [ dataForm.value.province, val.code ]
dataForm.value.areaList = val.children || []
resetSelections([ "area", "street" ])
if ( dataForm.value.provinceName === "澳门特别行政区" || dataForm.value.provinceName === "香港特别行政区" ) {
dataForm.value.isSpecial = true
popoverVisible.value = false
} else {
activeName.value = "three"
dataForm.value.isSpecial = false
}
}
//点击区县
const areaItemFn = (val) => {
popoverVisible.value = true
dataForm.value.areaName = val.name
dataForm.value.PCASName = updatePCASName(dataForm.value.provinceName, dataForm.value.cityName, val.name)
dataForm.value.area = val.code
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, val.code ]
dataForm.value.streetsList = val.children
resetSelections([ "street" ])
activeName.value = "four"
}
//点击街道/乡镇
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
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, dataForm.value.area, val.code ]
popoverVisible.value = false
}
//点击模糊搜索结果
const dimItem = (val) => {
dataForm.value.PCASName = val.name
dataForm.value.codeList = val.code.split('/')
}
//省市区选择区域外内容点击事件
const contentClick = () => {
// if ( dataForm.value.codeList.length < 3 ) {
// resetSelections([ "province", "city", "area", "street" ])
// activeName.value = "first"
// dataForm.value.PCASName = ""
// }
popoverVisible.value = false
}
//输入框失去焦点事件
const inputBlurFn = ( (event) => {
if ( dataForm.value.codeList.length < 1 || dataForm.value.dimList.length < 1 ) {
resetSelections([ "province", "city", "area", "street" ])
activeName.value = "first"
dataForm.value.PCASName = ""
}
popoverVisible.value = false
// console.log("输入框失去焦点事件", dataForm.value.codeList)
} )
//输入框获取焦点事件
const inputFocusFn = (val) => {
if ( val && dataForm.value.PCASName ) {
dataForm.value.isInputting = false
dataForm.value.dimList = findAddressPaths(pcasCodeList, dataForm.value.PCASName)
} else {
dataForm.value.isInputting = true
}
}
//输入框input事件
const pcasInputFn = (val) => {
if ( val && dataForm.value.PCASName ) {
dataForm.value.codeList = []
dataForm.value.isInputting = false
dataForm.value.dimList = findAddressPaths(pcasCodeList, val)
} else {
dataForm.value.isInputting = true
resetSelections([ "province", "city", "area", "street" ])
activeName.value = "first"
}
}
//重置选择
const resetSelections = (clearLevels) => {
// 根据传入的层级清除选项
if ( clearLevels.includes("province") ) {
dataForm.value.province = ""
dataForm.value.provinceName = "请选择"
dataForm.value.codeList = []
}
if ( clearLevels.includes("city") ) {
dataForm.value.city = ""
dataForm.value.cityName = "请选择"
dataForm.value.areaList = []
dataForm.value.codeList = [ dataForm.value.province ]
}
if ( clearLevels.includes("area") ) {
dataForm.value.areaName = "请选择"
dataForm.value.area = ""
dataForm.value.streetsList = []
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city ]
}
if ( clearLevels.includes("street") ) {
dataForm.value.codeList = [ dataForm.value.province, dataForm.value.city, dataForm.value.area ]
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 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
}
//省市区模糊搜索算法
function findAddressPaths(data, searchText, limit = 30) {
let results = [];
let subEntriesToCheck = []; // 存储子条目等待检查的列表
let normalizedSearchText = searchText.replace(/\//g, ''); // 正规化搜索文本,移除斜杠
function getNormalizedPath(name) {
return name.replace(/\//g, ''); // 移除路径中的斜杠用于搜索匹配
}
// 检查并添加符合条件的省级数据
for (const province of data) {
const normalizedProvinceName = getNormalizedPath(province.name);
if (normalizedProvinceName.includes(normalizedSearchText)) {
results.push({ name: province.name, code: province.code });
if (results.length >= limit) {
return results;
}
}
// 将省下属的城市添加到待检查列表
if (province.children) {
for (const city of province.children) {
subEntriesToCheck.push({
fullName: province.name + "/" + city.name,
fullCode: province.code + "/" + city.code,
children: city.children
});
}
}
}
// 处理子级条目(城市或区县)
function processSubEntries(list) {
for (const entry of list) {
const normalizedFullName = getNormalizedPath(entry.fullName);
if (normalizedFullName.includes(normalizedSearchText)) {
results.push({ name: entry.fullName, code: entry.fullCode });
if (results.length >= limit) {
return;
}
}
// 将该级下属的区县添加到待检查列表
if (entry.children) {
for (const child of entry.children) {
subEntriesToCheck.push({
fullName: entry.fullName + "/" + child.name,
fullCode: entry.fullCode + "/" + child.code,
children: child.children
});
}
}
}
}
// 循环处理所有待检查的子条目
do {
const currentLevelToCheck = [...subEntriesToCheck];
subEntriesToCheck = [];
processSubEntries(currentLevelToCheck);
} while (results.length < limit && subEntriesToCheck.length > 0);
return results;
}
defineExpose({
dataForm
})
</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;
color: #1166fe;
}
}
}
}
.cityContent{
display: flex;
flex-wrap: wrap;
font-size: 14px;
.cityItem{
margin-right: 18px;
margin-bottom: 10px;
cursor: pointer;
&:hover{
color: #1166fe;
}
}
}
.aimContent{
height: 300px;
overflow-y: auto;
}
.dimItem{
margin: 4px 0;
cursor: pointer;
&:hover{
background: #1166fe;
color: #FFFFFF;
}
}
.active{
color: #1166fe !important;
}
</style>
这里我觉得有点冗余的是输入框输入地址和选择省市区乡镇的的联动效果,毕竟大部分人能选的话不会手输,如果不用的话直接禁用输入框就行,省下很多逻辑处理。
现在这这个组件刚写完
肯定涉及到父组件值的传入和子组件的值传出
以后再更新...