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.
 
 
 
 

321 lines
6.7 KiB

<template>
<div class="dropdown" ref="dropdownRef">
<!-- 下拉框触发器 -->
<div class="dropdown-toggle" @click="toggleMenu" :class="{ 'active': isOpen,'disabled': props.disabled }">
<div class="search">
<input type="text" class="search-input" :placeholder="placeholder" :value="isOpen ? searchData : selectedItem"
@focus="handleSearchFocus" @blur="handleSearchBlur" @input="handleInput">
<el-icon class="clear-icon" v-if="searchData" @click="clearSearch">
<CircleClose />
</el-icon>
</div>
<span class="arrow">
<el-icon>
<ArrowDown />
</el-icon>
</span>
</div>
<!-- 下拉菜单 -->
<div class="dropdown-menu" v-if="isOpen">
<!-- 选项区域:按钮样式 -->
<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, watch } 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: ''
},
disabled: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['update:modelValue', 'change'])
// 切换下拉菜单
const toggleMenu = () => {
if (props.disabled) return
isOpen.value = !isOpen.value
searchData.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())
)
})
const handleInput = (e) => {
searchData.value = e.target.value
}
// 挂载/卸载事件
onMounted(() => {
selectedItem.value = props.modelValue;
document.addEventListener('click', handleClickOutside)
return () => {
document.removeEventListener('click', handleClickOutside)
}
})
// 监听外部值变化
watch(() => props.modelValue, (newVal) => {
selectedItem.value = newVal;
}, { immediate: true });
</script>
<style scoped lang="scss">
// 下拉容器
.dropdown {
position: relative;
width: 268px;
font-family: 'Arial', sans-serif;
}
// 触发器控制展开/收起
.dropdown-toggle {
border: 1px solid #e5e7eb;
/* 调整内边距以匹配按钮高度 */
height: 31px;
/* 调整高度以匹配按钮 */
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;
}
// 搜索框区域
.search {
width: 100%;
height: 100%;
position: sticky;
}
// 搜索输入框
.search-input {
width: 100%;
height: 90%;
/* 左侧留出图标空间 */
box-sizing: border-box;
border: none;
border-radius: 6px;
padding-left: 11px;
outline: none;
font-size: 14px;
transition: border-color 0.3s ease;
&::placeholder {
color: #999999;
opacity: 1;
font-style: normal;
font-weight: 350;
line-height: 22px;
}
}
.clear-icon {
position: absolute;
top: 62%;
right: 20px;
transform: translateY(-50%);
color: #909399;
cursor: pointer;
z-index: 1003;
&:hover {
color: #606266;
}
}
}
// 展开状态边框+阴影高亮
.dropdown-toggle.active {
border-color: #678BFF;
box-shadow: 0 0 0 2px rgba(103, 139, 255, 0.1);
}
// 箭头图标展开时旋转
.arrow {
margin-right: 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;
min-height: 50px;
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: -11px;
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: -12px;
/* ::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;
}
}
// 选项容器调整滚动区域高度
.menuContent {
max-height: 200px;
/* 减去搜索框高度和尖角高度 */
overflow-y: auto;
padding: 8px;
padding: 10px 14px 12px 10px;
}
// 选项按钮无边框 + hover/选中效果
.dropdown-item {
width: 100%;
height: 27px;
padding: 5px 12px 5px 5px;
text-align: center;
cursor: pointer;
border: none;
border-radius: 10px;
background-color: #fff;
font-size: 12px;
font-style: normal;
font-weight: 700;
line-height: 20px;
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>