
本文详解 Vue 3 中实现搜索输入框 + 下拉选项 + 多选添加功能的关键技巧,重点解决因 @blur 导致下拉项无法点击的问题,并提供结构清晰、事件安全的完整实现方案。
本文详解 vue 3 中实现搜索输入框 + 下拉选项 + 多选添加功能的关键技巧,重点解决因 `@blur` 导致下拉项无法点击的问题,并提供结构清晰、事件安全的完整实现方案。
在 Vue 3 中构建带搜索过滤的下拉选择组件时,一个常见痛点是:为在点击输入框外部时自动收起下拉列表而使用 @blur,却意外导致下拉项(位于 外部)的 @click 事件失效——因为失焦触发过早,选项尚未被点击。根本原因在于:@blur 绑定在 上时,焦点一旦离开输入框(包括点击下拉项),optionsVisible 立即设为 false,DOM 被销毁,点击事件甚至无法触发。
✅ 正确解法是将 @blur 移至包裹容器,使输入框与下拉列表同属一个“焦点上下文”:
<template><div tabindex="0">
<input type="text" v-model="input" placeholder="Search fruits..." true><div class="item fruit" v-if="optionsVisible" v-for="fruit in filteredList()" :key="fruit">
<p>{{ fruit }}</p>
</div>
<div class="item error" v-if="input && !filteredList().length">
<p>No results found!</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill6464" title="Auth0 Vue"><img
src="https://img.php.cn/upload/skill/000/000/081/179094790260840.jpg" alt="Auth0 Vue" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill6464" title="Auth0 Vue" class="overflowclass">Auth0 Vue</a>
<p class="overflowclass">在 Vue.js 3 中添加身份验证(登录、注销、用户会话、受保护路由),集成 @auth0/auth0-vue SDK,配合 Vite 使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill6464" title="Auth0 Vue" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div>
</div>
</div>
<div class="selected">Selected: {{ selected }}</div>
</template>
关键改进说明:
- 使用 包裹整个搜索区域,使其可获得焦点;
- tabindex="0" 确保该容器能响应 blur 事件(否则无焦点能力,@blur 不生效);
- 移除 上的 @blur,改由外层容器统一控制显示/隐藏;
- @click 直接传入 fruit 值(而非依赖 e.srcElement),更语义化、更可靠。
对应的 <script setup> 需同步优化:</script>
<script setup> import { ref } from 'vue' const input = ref('') const optionsVisible = ref(false) const selected = ref([]) // ✅ 安全的 blur 处理:仅当焦点真正离开整个容器时才隐藏 const handleBlur = (e) => { // 可选:防误触 — 检查焦点是否落在下拉项内(增强鲁棒性) const isClickingInside = e.relatedTarget?.closest('.fruit') if (!isClickingInside) { optionsVisible.value = false } } // ✅ 直接接收 fruit 字符串,避免 DOM 依赖 const select = (fruit) => { if (!selected.value.includes(fruit)) { selected.value.push(fruit) } input.value = '' // 可选:选中后清空输入框 optionsVisible.value = false } const fruits = ['apple', 'banana', 'orange'] const filteredList = () => { if (!input.value.trim()) return [] return fruits.filter(fruit => fruit.toLowerCase().includes(input.value.toLowerCase()) ) } </script>⚠️ 注意事项:
- 不要滥用 @click.stop:它会阻止事件冒泡,但无法解决 blur 时机问题;本方案通过合理 DOM 结构和事件委托规避该需求;
- 避免重复添加:select() 中加入 !selected.value.includes(fruit) 判断,防止同一选项多次加入;
- 用户体验优化:@focus 确保聚焦时始终显示选项;选中后自动清空输入并收起下拉,提升交互流畅度;
- 无障碍友好:tabindex="0" 支持键盘导航,配合 @keydown 可进一步支持方向键选择(进阶扩展点)。
此方案结构清晰、事件边界明确,既保障了下拉项的可点击性,又实现了精准的失焦收起逻辑,是 Vue 3 中构建搜索选择器的推荐实践。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










