要做的功能 示意图是这样的,因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件
所以只能自己分装 组件
当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样
毕竟是 三个 数组 省份 城市 区域
我直接粘贴组件代码了
<template>
<div class="cascader-compoents-container">
<div class="city-select" @click.stop="toggleCascader">
<a-input
v-model:value="cascaderName"
:style="{ width: width + 'px', 'text-align': 'left' }"
placeholder="选择城市"
/>
<DownOutlined
style="
font-size: 12px;
color: rgba(0, 0, 0, 0.25);
position: absolute;
right: 11px;
top: 10px;
"
/>
</div>
<div class="city-select-content" v-if="cascaderShow" v-click-outside="handleClose">
<div class="province" :style="{ width: width / 2 + 'px' }" v-if="province.length > 0">
<div class="item" v-for="item in province" :key="item.value" @click="onSelected(item, 0)">
<div>{{ item.label }}</div>
<RightOutlined style="font-size: 12px; color: rgba(0, 0, 0, 0.25)" />
</div>
</div>
<div class="city" :style="{ width: width / 2 + 'px' }" v-if="city.length > 0">
<div class="item" v-for="item in city" :key="item.value" @click="onSelected(item, 1)">
<div>{{ item.label }}</div>
<RightOutlined style="font-size: 12px; color: rgba(0, 0, 0, 0.25)" />
</div>
</div>
<div class="district" :style="{ width: width / 2 + 'px' }" v-if="district.length > 0">
<div class="item" v-for="item in district" :key="item.value" @click="onSelected(item, 2)">
<div>{{ item.label }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, toRefs, watch } from 'vue'
import * as addressApi from '@/api/address'
const props = defineProps({
width: {
type: [Number, String],
default: 300,
},
placeholder: {
type: String,
default: '选择城市',
},
modelValue: {
type: Array,
default: () => [],
},
})
const { width, modelValue, placeholder } = toRefs(props)
const cascaderName = ref('')
const values = ref([])
const selectedId = ref([...modelValue.value])
const province = ref([])
const city = ref([])
const district = ref([])
const cascaderShow = ref(false)
const emits = defineEmits(['update:modelValue', 'onSelected'])
const updateCascaderName = () => {
cascaderName.value = selectedId.value.map(item => item.label).join(' / ') || ''
}
// 监听 modelValue 变化以实现回显
watch(
modelValue,
async newVal => {
selectedId.value = [...newVal]
updateCascaderName()
},
{ immediate: true },
)
const vClickOutside = {
mounted(el: HTMLElement, binding: any) {
el.clickOutsideEvent = (e: Event) => {
if (!(el === e.target || el.contains(e.target as Node))) {
binding.value(e)
}
}
document.body.addEventListener('click', el.clickOutsideEvent)
},
unmounted(el: HTMLElement) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
}
const toggleCascader = () => {
cascaderShow.value = !cascaderShow.value
}
const handleClose = () => {
cascaderShow.value = false
}
// 添加点击外部关闭逻辑
const handleClickOutside = (e: MouseEvent) => {
// const container = document.querySelector(`.cascader-compoents-container`)
if (!e.currentTarget?.contains(e.target as Node)) {
cascaderShow.value = false
}
}
const getCityData = async (parentId: any, index: any) => {
try {
let { state, data, message: msg } = await addressApi.getChannelAreaList({ parentId })
if (state === 200) {
if (index == 0) {
province.value = data.map((item: any) => {
return {
value: item.id,
label: item.name,
// children: [],
}
})
district.value = []
} else if (index == 1) {
city.value = data.map((item: any) => {
return {
value: item.id,
label: item.name,
}
})
} else if (index == 2) {
district.value = data.map((item: any) => {
return {
value: item.id,
label: item.name,
}
})
}
}
} catch (error) {
message.error('网络请求连接失败~')
}
}
const onSelected = (item, index) => {
console.log(item, index, 'item, index')
if (index == 0) {
city.value = []
district.value = []
selectedId.value[0] = item
}
if (index == 1) {
selectedId.value[1] = item
}
if (index == 2) {
selectedId.value[2] = item
cascaderShow.value = false
}
cascaderName.value = selectedId.value.map(item => item.label).join(' / ')
emits('onSelected', selectedId.value)
emits('update:modelValue', [...selectedId.value])
getCityData(item.value, index + 1)
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
getCityData(0, 0)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style lang="less" scoped>
.cascader-compoents-container {
position: relative;
.city-select {
position: relative;
}
.city-select-content {
position: absolute;
display: flex;
justify-content: space-around;
left: 5px;
top: 40px;
z-index: 99;
.province,
.city,
.district {
background-color: #fff;
height: 200px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
overflow-y: auto;
border-radius: 5px;
padding: 5px 0;
transition: all, 0.3s;
.item {
display: flex;
font-size: 13px;
justify-content: space-between;
align-items: center;
padding: 0 10px;
height: 35px;
line-height: 30px;
cursor: pointer;
transition: all, 0.3s;
&:hover {
background-color: #f0f0f0;
}
}
}
.city,
.district {
margin-left: 10px;
}
}
}
</style>
父组件中使用
<CascaderCom @onSelected="getPcd1" v-model="selectedPcd2"></CascaderCom>
@getPcd1 获取选择的省份城市 区域的 结果 自定义函数
selectedPcd2 v-model 绑定的值 [] 有三个对象 分别对应 省 市 县 [{value:,label:},{value:,label:},{value:,label:}]
在vue中 v-model 本来就是 modelValue 的语法糖
这里组件内用到了 监听 modelValue 的 时间 修改 组件内的 选择的数组
这样能够解决 回显问题 因为 如果没有这个v-model 的 使用 父组件传递的回显数组 将不能够很快捷的在子组件中处理
这是我对于 项目业务逻辑中的相关的使用来进行的封装组件 此项目多处使用了 类似样式 而且接口都一样 所以我把接口也放到了组件里 这样能够随机能用 当然 这个接口 原则上不太适合在组件中使用
所以我展现组件的目的
1.自己能够随时使用
2.其他技术人可以参考我的逻辑
3.可以给其他前端人 带来逻辑 学习
4.本人不才 望指教