效果如下
代码如下
<template>
<div class="text-sm font-medium text-green-700">{{ placeholder }}</div>
<div class="relative w-full max-w-md mx-auto">
<button @click="toggleDropdown"
class="w-full px-4 py-2 text-left text-green-800 bg-green-50 border-2 rounded-lg focus:outline-none transition-all duration-300 ease-in-out"
:class="[isOpen ? 'border-green-500 ring-2 ring-green-400' : 'border-green-300', { 'text-green-400': !selectedOption }]">
{{ selectedOption || placeholder }}
<ChevronDownIcon
class="absolute right-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-green-600 transition-transform duration-300"
:class="{ 'rotate-180': isOpen }" />
</button>
<transition enter-active-class="transition duration-100 ease-out" enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100" leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100" leave-to-class="transform scale-95 opacity-0">
<ul v-if="isOpen"
class="absolute z-10 w-full mt-1 bg-white border-2 border-green-300 rounded-lg shadow-lg max-h-60 overflow-auto">
<!-- 搜索框 -->
<li class="px-4 py-2">
<input type="text" v-model="searchQuery" placeholder="搜索..."
class="w-full px-2 py-1 border-2 border-green-300 rounded-lg focus:outline-none" />
</li>
<!-- 显示过滤后的选项 -->
<li v-for="option in filteredOptions" :key="option.value" @click="selectOption(option)"
class="px-4 py-2 cursor-pointer text-green-800 hover:bg-green-100 transition-colors duration-200"
:class="{ 'bg-green-200': option.value === modelValue }">
{{ option.label }}
</li>
</ul>
</transition>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ChevronDownIcon } from 'lucide-vue-next'
const props = defineProps({
modelValue: {
type: [String, Number],
default: ''
},
options: {
type: Array,
required: true
},
placeholder: {
type: String,
default: 'Select an option'
},
label: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:modelValue'])
const isOpen = ref(false)
const searchQuery = ref('') // 搜索框的输入值
const selectedOption = computed(() => {
const option = props.options.find(opt => opt.value === props.modelValue)
return option ? option.label : ''
})
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
const closeDropdown = () => {
setTimeout(() => {
isOpen.value = false
}, 200)
}
const selectOption = (option) => {
emit('update:modelValue', option.value)
isOpen.value = false
}
// 计算过滤后的选项
const filteredOptions = computed(() => {
if (!searchQuery.value) return props.options
return props.options.filter(option =>
option.label.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
</script>
<style scoped>
/* 样式调整:使搜索框和下拉菜单美观 */
input {
width: 100%;
padding: 0.5rem;
margin-bottom: 0.5rem;
border-radius: 0.375rem;
border: 1px solid #ddd;
font-size: 1rem;
color: #333;
}
input:focus {
border-color: #4CAF50;
}
</style>