You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

330 lines
6.9 KiB

<template>
<div class="dropdown" ref="dropdownRef">
<!-- 下拉框触发器 -->
<div class="dropdown-toggle" @click="toggleMenu" :class="{ 'active': isOpen }">
<span class="placeholder" :style="{ color: selectedItem ? '#333' : '#A8ABB2' }">
{{ selectedItem || placeholder }}
</span>
<span class="arrow">
<el-icon>
<ArrowDown />
</el-icon>
</span>
</div>
<!-- 下拉菜单 -->
<div class="dropdown-menu" v-if="isOpen">
<!-- 搜索框 -->
<div class="search">
<input type="text" v-model="searchData" class="search-input" placeholder="查询" @focus="handleSearchFocus"
@blur="handleSearchBlur">
<el-icon class="search-icon">
<Search />
</el-icon>
<el-icon class="clear-icon" v-if="searchData" @click="clearSearch">
<CircleClose />
</el-icon>
</div>
<!-- 选项区域:按钮样式 -->
<div class="menuContent">
<button class="dropdown-item" v-for="(item, index) in filteredItems" :key="index" @click="handleSelect(item)"
:class="{ 'selected': selectedItem === item }">
{{ item }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watchEffect, onMounted } from 'vue';
const searchData = ref('')
const isOpen = ref(false)
const selectedItem = ref('')
const dropdownRef = ref(null)
const props = defineProps({
items: {
type: Array,
required: true,
default: () => []
},
placeholder: {
type: String,
default: '请选择支付方式'
},
modelValue: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue', 'change'])
// 切换下拉菜单
const toggleMenu = () => {
isOpen.value = !isOpen.value
}
// 清除搜索
const clearSearch = () => {
searchData.value = ''
}
// 选择选项
const handleSelect = (item) => {
selectedItem.value = item
isOpen.value = false
emit('update:modelValue', item)
emit('change', item)
}
// 点击外部关闭菜单
const handleClickOutside = (event) => {
if (dropdownRef.value && !dropdownRef.value.contains(event.target)) {
isOpen.value = false
}
}
// 监听搜索框的焦点和失焦事件
const handleSearchFocus = () => {
// 可以在这里添加额外的逻辑
}
const handleSearchBlur = () => {
// 可以在这里添加额外的逻辑
}
// 搜索过滤
const filteredItems = computed(() => {
if (!searchData.value) return props.items
return props.items.filter(item =>
item.toLowerCase().includes(searchData.value.toLowerCase())
)
})
// 挂载/卸载事件
onMounted(() => {
document.addEventListener('click', handleClickOutside)
return () => {
document.removeEventListener('click', handleClickOutside)
}
})
// 监听外部值变化
watchEffect(() => {
selectedItem.value = props.modelValue
})
</script>
<style scoped lang="scss">
// 下拉容器
.dropdown {
position: relative;
width: 268px;
font-family: 'Arial', sans-serif;
}
// 触发器控制展开/收起
.dropdown-toggle {
border: 1px solid #e5e7eb;
padding: 4px 12px;
/* 调整内边距以匹配按钮高度 */
height: 23px;
/* 调整高度以匹配按钮 */
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
border-radius: 6px;
transition: all 0.3s ease;
.placeholder {
flex: 1;
font-size: 14px;
line-height: 18px;
color: #A8ABB2;
}
}
// 展开状态边框+阴影高亮
.dropdown-toggle.active {
border-color: #678BFF;
box-shadow: 0 0 0 2px rgba(103, 139, 255, 0.1);
}
// 箭头图标展开时旋转
.arrow {
margin-left: 8px;
color: #999;
transition: transform 0.3s ease;
}
.dropdown-toggle.active .arrow {
transform: rotate(180deg);
}
// 下拉菜单主体
.dropdown-menu {
position: absolute;
top: 100%; // 紧贴触发器下方
left: 0;
width: 100%;
border: 1px solid #678BFF;
max-height: 300px;
background-color: #fff;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-radius: 0 0 8px 8px; // 仅底部两侧圆角
z-index: 1000;
margin-top: 15px; // 关键不要有上外边距尖角要紧贴触发器
overflow: visible;
&::before {
content: "";
position: absolute;
top: -8px;
left: 50%;
transform: translateX(-50%) scaleY(0.5);
width: 30px;
height: 16px;
background: #fff;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
z-index: 1001;
border: none; /* 移除原来的边框 */
}
&::after {
content: "";
position: absolute;
top: -9px; /* ::before 往下一点制造边框效果 */
left: 50%;
transform: translateX(-50%) scaleY(0.5);
width: 30px;
height: 16px;
background: #678BFF;
clip-path: polygon(0 100%, 100% 100%, 50% 0);
z-index: 1000;
}
}
// 搜索框区域
.search {
position: sticky;
top: 0;
background-color: #FFFFFF;
z-index: 1002;
padding: 10px 14px 0px 10px;
}
// 搜索输入框浅灰背景 + 图标定位
.search-input {
width: 100%;
height: 25px;
padding: 0 12px 0 5px;
/* 左侧留出图标空间 */
border: 1px solid #dcdfe6;
border-radius: 8px;
box-sizing: border-box;
background-color: #f8f9fa;
/* 浅灰背景匹配参考图 */
outline: none;
font-size: 12px;
transition: border-color 0.3s ease;
&::placeholder {
color: #909399;
}
&:hover {
border-color: #c0c4cc;
}
&:focus {
border-color: #678BFF;
}
}
// 搜索图标左侧定位
.search-icon {
position: absolute;
top: 62%;
left: 50px;
transform: translateY(-50%);
color: #909399;
z-index: 1003;
}
// 清除图标右侧定位 + hover效果
.clear-icon {
position: absolute;
top: 50%;
right: 20px;
transform: translateY(-50%);
color: #909399;
cursor: pointer;
z-index: 1003;
&:hover {
color: #606266;
}
}
// 选项容器调整滚动区域高度
.menuContent {
max-height: 200px;
/* 减去搜索框高度和尖角高度 */
overflow-y: auto;
padding: 8px;
padding: 10px 14px 12px 10px;
}
// 选项按钮无边框 + hover/选中效果
.dropdown-item {
width: 100%;
height: 25px;
padding: 5px 12px 5px 5px;
text-align: center;
cursor: pointer;
border: none;
border-radius: 8px;
background-color: #fff;
font-size: 12px;
margin: 5px 0;
color: #040A2D;
transition: all 0.2s ease;
&:hover {
background-color: #F3FAFE;
/* hover浅灰 */
}
&.selected {
background-color: #E5EBFE;
/* 选中浅蓝 */
color: #2741DE;
/* 选中文字蓝色 */
}
}
// 可选滚动条美化与原风格一致
.menuContent::-webkit-scrollbar {
width: 6px;
}
.menuContent::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.menuContent::-webkit-scrollbar-thumb {
background: #c0c4cc;
border-radius: 3px;
&:hover {
background: #909399;
}
}
</style>