|
|
<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>
|