Vue+Quasar q-select多选组件选中后残留输入文本解决方法
问题描述
项目基于Vue3 + Quasar框架开发,需要实现带服务端自动补全能力的多选选择器:用户输入检索文本时,组件自动向服务端发送请求,拉取匹配输入内容的可选值供用户多选。
现有实现代码如下:
模板部分
<template> <div class="autocomplete"> <q-select filled label="Search" v-model="searchTerm" use-input use-chips multiple @filter="filterFn" @input-value="inputValue" @filter-abort="abortFilterFn" :options="searchResult" style="width: 395px" :option-label="" option-value="text" > <template v-slot:no-option> <q-item> <q-item-section class="text-grey"> No results </q-item-section> </q-item> </template> </q-select> <div> <back-button @click="go" data-test="btn-back" forwardicon="arrowright" /> </div> </template>
脚本部分
<script> import { defineComponent, ref, computed } from "vue"; import { apiProductService } from "../../models/"; import apiEndPoints from "../../models/api"; import BackButton from "../ui/"; import { useRouter } from "vue-router"; import { useSearchStore } from "../../store/"; import _ from "lodash"; import { debounce } from "quasar"; export default defineComponent({ name: "search-bar", components: { BackButton }, setup(props, context) { const router = useRouter(); let searchTerm = ref(null); let searchResult = ref([]); const search = useSearchStore(); function filterFn(val, update, abort) { if (searchResult.value.length > 0) { update(); return; } abortFilterFn(); document.addEventListener('keypress', function(e) { true }); } function abortFilterFn() { // console.log('delayed filter aborted') } const inputValue = computed(() => debounce(suggestions, 0).bind(this)); function suggestions(val) { if (val) { getSearchData(val); document.addEventListener('keypress', function(e) { true }); } else { searchResult.value = []; } } const getSearchData = async (val) => { await apiProductService( apiEndPoints.GetSearchSuggestions.method, apiEndPoints.GetSearchSuggestions.url, { q: val, top: 5, suggester: "sg", } ).then((response) => { searchResult.value = response?.data?.suggestions || []; }); }; return { searchTerm, searchResult, filterFn, inputValue }; }, }); </script>
异常现象
输入检索词(例如tes)时,可以正常获取后端返回的匹配结果,但点击选中目标选项后,用户手动输入的检索文本会被错误保存为多选组件的已选值,异常效果如下:
需要实现选中选项后自动清除残留输入文本的效果。
问题根因
- 未单独维护输入框的临时检索值,输入内容与已选值逻辑耦合,Quasar的
q-select在use-input + multiple模式下不会主动清空输入框的临时输入内容,需要手动控制 @filter逻辑错误,本地过滤逻辑会将用户输入的临时文本加入可选列表,导致输入内容被识别为可选项- 防抖逻辑写法错误,将防抖函数包裹在
computed中并使用bind(this)不符合Vue3组合式API的写法规范,事件触发时序异常 - 未监听选项选中事件,没有在选中动作触发后执行清空输入的逻辑
- 代码中冗余的全局键盘事件监听无实际作用,还可能引发意料之外的逻辑问题
option-label属性绑定为空字符串,会导致选项文本渲染逻辑异常- 多选模式下
searchTerm初始值设为null不符合组件要求,易引发值绑定异常
修复方案
- 新增独立的响应式变量存储输入框临时文本,通过
v-model:input-value双向绑定输入值,与存储已选结果的searchTerm完全隔离 - 修正
filter逻辑,直接终止本地过滤,所有选项完全由服务端接口返回的searchResult提供,避免临时输入文本被加入选项列表 - 调整防抖函数写法,直接在setup中生成防抖检索方法,防抖时间设置为300ms避免频繁发请求
- 监听选项新增事件,选中选项后立刻清空临时输入值、重置搜索结果列表
- 修正
option-label属性,与option-value对应绑定接口返回的text字段 - 删除所有冗余的无效键盘监听代码,补全缺失的页面跳转方法
修复后的完整代码如下:
<template> <div class="autocomplete"> <q-select filled label="Search" v-model="searchTerm" use-input use-chips multiple @filter="filterFn" @filter-abort="abortFilterFn" :options="searchResult" style="width: 395px" option-label="text" option-value="text" v-model:input-value="inputText" @add="onOptionSelected" > <template v-slot:no-option> <q-item> <q-item-section class="text-grey"> No results </q-item-section> </q-item> </template> </q-select> <div> <back-button @click="go" data-test="btn-back" forwardicon="arrowright" /> </div> </template> <script> import { defineComponent, ref } from "vue"; import { apiProductService } from "../../models/"; import apiEndPoints from "../../models/api"; import BackButton from "../ui/"; import { useRouter } from "vue-router"; import { useSearchStore } from "../../store/"; import { debounce } from "quasar"; export default defineComponent({ name: "search-bar", components: { BackButton }, setup(props, context) { const router = useRouter(); // 已选结果,多选模式初始值为空数组 const searchTerm = ref([]); // 服务端返回的搜索结果列表 const searchResult = ref([]); // 输入框临时检索文本 const inputText = ref(''); const search = useSearchStore(); // 终止本地过滤,完全使用服务端返回的选项 function filterFn(val, update, abort) { abort(); } function abortFilterFn() { // 过滤中止逻辑可按需扩展 } // 选中选项后清空输入框和临时搜索结果 function onOptionSelected() { inputText.value = ''; searchResult.value = []; } // 防抖请求搜索建议,300ms间隔避免频繁触发接口 const fetchSuggestions = debounce(async (val) => { if (!val.trim()) { searchResult.value = []; return; } const response = await apiProductService( apiEndPoints.GetSearchSuggestions.method, apiEndPoints.GetSearchSuggestions.url, { q: val, top: 5, suggester: "sg", } ); searchResult.value = response?.data?.suggestions || []; }, 300); // 监听输入值变化触发检索 function onInputChange(val) { inputText.value = val; fetchSuggestions(val); } // 返回按钮跳转逻辑 function go() { router.back(); } return { searchTerm, searchResult, inputText, filterFn, abortFilterFn, onOptionSelected, onInputChange, go }; }, }); </script>
补充:如果需要支持自定义新标签(即用户输入的内容不在接口返回选项中时,也允许作为已选项添加),可以给q-select添加
use-new-value属性;不需要该能力时保持上述代码即可,此时只有接口返回的匹配选项能被选中,不会出现输入文本残留的问题。
内容的提问来源于stack exchange,提问作者Javascript user
相关产品推荐
相关产品推荐

