
Quasar QSelect 组件完全指南从单选/多选到过滤、懒加载与无障碍支持【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar导读QSelect 是 Quasar Framework 中功能最全面的表单选择组件之一支持单选与多选两种模式内置弹出菜单桌面端/ 对话框移动端双形态切换、选项过滤、模糊搜索、新值创建、HTML 渲染、虚拟滚动与完整的键盘导航和 WAI-ARIA 无障碍支持。本文以 select.md 官方文档为骨架结合ui/src/components/select/QSelect.js源码、QSelect.json API 定义与docs/src/examples/QSelect/下的 43 个官方示例系统讲解 QSelect 的模型机制、选项数据形态、过滤与自动补全、懒加载、性能优化以及无障碍细节帮助你直接写出可投入生产环境的 QSelect 表单。QSelect 概览与适用边界QSelect 提供两种选择类型单选single与多选multiple。组件会打开一个菜单用于展示选择列表和操作对于较长的列表还可以启用过滤器。注意如果你需要的是按钮式下拉dropdown button而不是输入框式下拉请使用 Button Dropdown 组件而不是 QSelect。QSelect 的完整属性、插槽、事件与方法定义见 QSelect.json其实现位于 QSelect.js约 2000 行并混入了use-field继承 QField 全部字段能力、use-virtual-scroll选项列表虚拟滚动、use-form表单提交与use-hover悬停打开v2.28等组合式 API。外观设计Design外观模式总览QSelect 继承了 QField 的四种主体外观[!WARNING] 同一个 QSelect只能使用filled、outlined、standout、borderless四种主体设计之一它们互斥不能叠加使用。四种外观及叠加装饰的完整示例见 DesignOverview.vue在字段内部使用前置/后置图标、头像等装饰内容的做法见 Decorators.vue通过color属性整体着色的示例见 Coloring.vue。可清空Clearableclearable属性会在字段尾部追加一个清空图标用户点击后模型被重置为null。官方示例 Clearable.vue 中第二个 QSelect 展示了等价的手动实现方式。禁用与只读Disable / readonlydisable与readonly均可让 QSelect 不可交互示例见 DisableReadonly.vue。二者在无障碍语义上有明确差异见下文无障碍章节readonly控件仍保持可聚焦并标注aria-readonlytrue而disable控件通过原生disabled属性退出 Tab 顺序但仍留在可访问性树中并被宣告为不可用。Slots 中的 QBtn 与 submit 类型[!WARNING] 当你在 QField、QInput 或 QSelect 的before、after、prepend、append插槽中放置typesubmit的 QBtn 时必须同时在该 QBtn 上添加click监听器来调用提交表单的方法——这些插槽中的 click 事件不会冒泡到父级元素。菜单过渡动画transition-show/transition-hide/transition-duration属性控制菜单或对话框的显示/隐藏过渡默认分别为fade、fade、300毫秒。[!WARNING] 使用options-cover属性时过渡动画不生效。官方示例 MenuTransitions.vue 展示了数种过渡效果完整的过渡列表见 Transitions 文档。选项列表展示形态Menu vs Dialog默认情况下QSelect 在桌面端以菜单menu展示选项在移动端以对话框dialog展示。可以通过behavior属性强制指定行为取值default | menu | dialog见 QSelect.json对话框模式会在其控件内渲染一个Close按钮文案取自 Quasar 语言包用户不必点击遮罩即可关闭。该按钮继承color属性可通过q-select__dialog-closeCSS 类进一步定制或用hide-dialog-close属性v2.28彻底移除。[!WARNING] 在 iOS 上菜单行为可能产生问题尤其是与use-input组合使用时。建议使用条件式behavior:behavior$q.platform.is.ios ? dialog : menu即仅在 iOS 上使用对话框模式。两种形态的示例分别见 BehaviorMenu.vue 与 BehaviorDialog.vue。悬停展开Hoverv2.28hover属性让选项在指针悬停到选择框上时自动展开并在指针离开选择框和选项菜单后关闭。配套属性hover-delay指针进入控件到选项显示之间的延迟毫秒hover-hide-delay指针在控件与菜单之间往返/重新进入的宽限期毫秒在宽限期内不会关闭。点击/触摸和键盘交互仍按常规方式切换选项因此无悬停能力的触摸设备自然回退到点击行为这也意味着点击一个已悬停展开的 QSelect 会将其关闭。唯一例外是如果点击发生在选项仍在入场动画期间则该点击会聚焦选择框并保持菜单打开——一次移动即点击的手势不会关闭它刚刚打开的东西。悬停触发的展开不会聚焦选择框因此不触发focus/blur也不会因为指针仅仅掠过而触发懒校验规则只有当用户真正点击或 Tab 进入时选择框才被聚焦升级为常规展开此后指针离开不再关闭。[!WARNING]hover只在选项以菜单形态展示时生效对behaviordialog无效在移动平台上除非显式使用behaviormenu否则同样无效。在源码中悬停监听被条件性地挂到 QMenu 上props.hover为真时传入onPointerenter: onHoverContentEnter与onPointerleave: hoverHide见 QSelect.jshoverShown标志位标记当前弹层是否由悬停触发用于决定聚焦与focus/blur的发射行为QSelect.js。相关行为测试见 QSelect.test.js覆盖打开、关闭、宽限期与动画期间的点击例外。完整示例见 Hover.vue。模型The model[!CAUTION]单选的模型可以是任何类型String、Object……而多选的模型必须是数组。单选与多选的对比示例ModelSingleMultiple.vue多选、计数器与最大值限制示例ModelMultipleCounter.vue其中counter显示已选数量max-values2限制最多选择 2 项模型的最终内容还会受emit-value属性影响详见下文The options章节。在源码中innerValue计算属性会把模型归一化为数组处理并在此处执行map-options的标签映射QSelect.js。选项The options选项数据类型options属性默认为空数组[]支持两种形态见 QSelect.json// 字符串数组 [Tesla, iPhone] // 对象数组labelString、value任意类型、可选 disableBoolean [{ label: Tesla, value: car }, { label: iPhone, value: phone }]字符串选项示例OptionString.vue对象选项示例OptionObject.vue影响模型emit-value 与 map-optionsemit-value启用后模型保存的是所选项的value字段而非整个选项对象。默认行为是发射整个选项。该属性只在选项为对象形态时才有意义。map-options启用后模型可以只保存valueQSelect 会将其与选项数组比对以确定显示标签。存在性能开销仅在确实必要时使用——例如当模型已包含整个对象即自带 label时就不需要它。OptionEmitValue.vueOptionMapOptions.vue源码中map-options的映射通过getOption(v, cache)完成并维护了一个innerValueCache缓存来降低映射开销QSelect.jsgetEmittingOptionValue()/getOptionValue()/getOptionLabel()/isOptionDisabled()等方法则统一封装了emit-value、option-value、option-label、option-disable的取值逻辑见 QSelect.json。自定义选项属性名Custom prop names默认情况下QSelect 从选项对象中读取label、value、disable和sanitize四个键。你可以通过以下属性覆盖option-label标签字段名String或返回标签的函数Functionoption-value值字段名或函数option-disable禁用字段名或函数[!WARNING] 如果自定义属性使用函数务必检查选项是否为null——这些函数既会用于列表中的选项也会用于已选中的选项。官方示例 OptionCustomProps.vue 同时演示了字符串字段名与函数两种写法函数写法对非对象选项返回null/- Null -兜底。底层实现见getPropValueFnQSelect.js当用户传入函数时直接使用否则按字段名取值选项为null或非对象时回退为返回选项本身。自定义选项渲染option 插槽选项列表使用虚拟滚动渲染[!WARNING] 因为选项列表使用虚拟滚动渲染如果你为一个选项渲染多个元素除第一个元素外的所有元素都必须添加q-virtual-scroll--with-prev类。基础自定义选项OptionSlot.vue在#optionscope插槽中使用QItem并务必通过v-bindscope.itemProps绑定到你的条目上——否则选项将丢失roleoption、idaria-activedescendant的目标、aria-selected与位置属性详见无障碍章节。在选项中加入 QToggle 的示例OptionQToggle.vue官方注释指出可能性是无限的。option插槽的作用域字段来自 QSelect.json包括index选项索引、opt选项本身、label标签、selected是否选中、focused是否聚焦、toggleOption(opt)增删选项、setOptionIndex(index)设为聚焦、itemProps传给 QItem 的计算属性以及html内容是否为 HTML。无选项时的展示默认情况下当没有选项时菜单不出现。你可以自定义此场景纯文本消息使用no-option-label属性v2.28自定义内容使用no-option插槽优先级高于属性作用域暴露inputValue当前输入框文本见 QSelect.json。示例见 OptionNoneSlot.vue。懒加载选项Lazy loading配合filter事件可以实现选项懒加载——这也意味着options属性在首次渲染时不是必填的。官方示例 OptionLazyLoad.vue 中options初始为nullfilterFn在 2 秒后通过update(() { options.value stringOptions })注入数据并配套filter-abort处理中断。[!TIP]选项加载期间默认加载 spinner 会占据下拉图标的位置使字段保持恒定宽度。因此当使用hide-dropdown-icon时默认 spinner 根本不会显示否则字段宽度会跳动若此时仍需要内联指示器请提供loading插槽。当模型已持有值且使用map-options时在选项加载完成前没有任何东西可供映射字段会显示原始值。从 Quasar v2.28 起QSelect 在这种情况下会自动请求选项它调用你的filter处理器一次传入空搜索字符串且不打开菜单使正确标签无需任何用户交互即可显示。模型值稍后到达时例如从服务器加载的记录同样适用。如果加载的选项不包含该值则不会再次请求。若不想启用此行为例如父组件自行加载选项时可用no-option-prefetch属性退出。源码层面prefetchUnmappedOptions()实现了这一自动预取QSelect.js当选项尚未加载virtualScrollLength.value 0且模型值不是自带标签的完整对象时调用filter(, true, resetInputValue)第三个参数keepClosed为true即不打开菜单触发一次预取。filter函数内部通过filterId令牌保证只有最新一次过滤请求的回调会被采纳并处理filter-abortQSelect.js。滚动到底部动态加载Dynamic loading通过监听virtual-scroll事件可以在滚动到达末尾时追加加载新选项function onScroll({ to, ref: compRef }) { const lastIndex options.value.length - 1 if (loading.value ! true nextPage.value lastPage to lastIndex) { loading.value true setTimeout(() { nextPage.value nextTick(() { compRef.refresh() // 通知虚拟滚动刷新 loading.value false }) }, 500) } }完整实现见 OptionsDynamic.vue对 10 万条数据按每页 50 条增量加载。覆盖模式与禁用 Tab 确认options-cover展开的菜单覆盖整个组件与use-input不兼容原因显而易见。示例OptionCover.vue。disable-tab-selectionv2.17阻止 Tab 键确认当前高亮的选项。示例DisableTabSelection.vue。显示值The display value默认情况下选中的值以单行不换行渲染空间不足时以省略号截断。如果需要重新定制其样式例如允许换行请针对q-select__selected-valueCSS 类v2.28。自定义显示值DisplayCustomValue.vue以 QChip 形式显示DisplayChips.vueChips 同时承担移除功能chip 的移除图标会把该选项移出选择当内部输入框为空时Backspace键也能做到同样的事。如果只允许通过选项列表改变选择用no-chip-remove属性v2.28同时禁用这两者——该属性不影响clearable的行为见 QSelect.json。示例DisplayChipsNoRemove.vue。selected/selected-item插槽示例DisplaySelectedItemSlot.vue。selected-item的作用域提供index、opt、selected、html、removeAtIndex(index)、toggleOption(opt)、tabindex见 QSelect.json示例中通过removescope.removeAtIndex(scope.index)实现 chip 移除。过滤与自动补全Filtering and autocompleteuse-input 与原生属性透传use-input启用后QSelect 内部会出现一个真实的input用户可输入文本进行过滤/自动补全/添加新值。所有不在 QSelect 属性列表中的属性都会被透传到这个原生 input 上例如autocomplete、placeholder等原生 input 属性参考见 MDN 文档。[!TIP]无障碍即使不使用use-input这些属性也会被应用到可聚焦控件上。这对aria-label或aria-labelledby尤其有用——它们设置屏幕阅读器为选择框宣告的可访问名称优先级高于从label属性派生的名称。过滤相关示例过滤选项InputFilterOptions.vuefilterfilterFn配合update()回调input-debounce0关闭防抖基础过滤BasicFiltering.vue至少 2 个字符才过滤InputFilterMin.vue文本自动补全TextAutocomplete.vue懒过滤输入防抖InputFilterLazy.vue过滤后选择选项InputFilterAfter.vueinput-debounce默认值为500毫秒同时影响filter事件的触发节奏QSelect.json。filter事件带三个参数inputValue、doneFn(callbackFn, afterFn)、abortFn()——afterFn在 QSelect 完成更新后被调用并接收组件实例引用详见 QSelect.json。创建新值Create new values[!TIP] 以下只是帮你起步的几个示例并非 QSelect 能力的穷举。此功能通常与use-input属性搭配使用才有意义。要启用新值创建需要二选一指定new-value-mode属性和/或监听new-value事件。如果两者都用监听new-value的目的就只是在你自定义的场景中覆盖new-value-mode。new-value-mode 属性取值与行为源码中的校验器见 QSelect.js值行为add添加值即使是重复值add-unique仅当不重复时添加toggle若值不在模型中则添加否则移除使用该属性时通常不必再监听new-value事件除非有需要覆盖行为的特定场景。示例CreateNewValueMode.vue三个 QSelect 分别演示三种模式均搭配use-input、use-chips、multiple、hide-dropdown-icon、input-debounce0。new-value 事件new-value事件携带待添加的值和一个done回调。done回调有两个可选参数要添加的值行为取值与new-value-mode相同指定时会覆盖该属性——如果它被使用了。默认行为未使用new-value-mode时是即使重复也添加。调用done()不带参数只会清空输入框内容不会以任何方式改动模型。事件参数定义见 QSelect.json。监听new-valueCreateListener.vue只添加唯一值CreateListenerUnique.vue结合菜单与过滤过滤并将新值加入菜单FilteringAddsToMenu.vue——createValue中将新值 push 进stringOptions并调用done(val, toggle)。过滤新值但不加入菜单FilteringNoAddToMenu.vue——只有输入至少 3 个字符且不在已有选项中的值才通过done(val, add-unique)加入模型。从输入生成多个值FilteringAddMultiple.vue——将输入按[,;|]分隔、trim、去空后批量加入模型最后调用done(null)清空输入框并直接设置model.value。消毒与 HTML 渲染Sanitization默认情况下所有选项包括已选中的都会被消毒即禁止以 HTML 形式显示。如果你确实需要选项渲染 HTML 且信任其内容有以下几种方式强制菜单选项以 HTML 渲染将可信选项的html键设为true针对特定可信选项或设置 QSelect 的options-html属性针对全部选项。显示值以 HTML 渲染的条件设置了display-value-html属性或未使用display-value且满足以下任一条件设置了options-html任一已选选项的html键为true。[!WARNING] 如果使用selected或selected-item插槽则显示值的消毒由你负责——display-value-html属性不再生效。[!WARNING]options-html与display-value-html可能导致 XSS 攻击务必确保内容经过消毒见 [QSelect.json](https://link.gitcode.com/i/b903a94fea1511d7591ce1d2c12c13d1#L61-L65, L139-L143)options-html在使用option插槽时不生效。源码中的判定逻辑见needsHtmlFn与valueAsHtmlQSelect.jsoptionsHtml为真时全部按 HTML 处理否则逐项检查opt.html true。示例HtmlOptions.vue、HtmlDisplayValue.vue。渲染性能Render performance选项数量对渲染性能影响不大除非在大量选项上使用map-options。列表内置无限滚动虚拟滚动用户滚动时会增量渲染后续选项。官方 RenderPerf.vue 示例直接加载了100,000 条字符串选项。[!TIP]组合式 API要在大批量选项下获得最佳性能不要用ref()/computed()/reactive()等包裹传给options属性的数组让 Vue 跳过对该列表的响应式转换。选项式 API同理可用Object.freeze(items)冻结数组后再传入让 Vue 跳过响应式处理。虚拟滚动的默认条目尺寸由options-dense决定dense 模式 24px常规 48px也可用virtual-scroll-item-size覆盖QSelect.js。混入的use-virtual-scroll组合式 API 提供了切片计算、填充与滚动事件处理QSelect.js。正因为列表是虚拟滚动的无障碍树中同一时刻只存在部分选项因此 QSelect 为每个选项维护aria-setsize与aria-posinset以宣告其在完整集合中的真实位置。键盘导航Keyboard navigation选择框聚焦时按键行为Enter、Arrow Down或未设置use-input时的Space打开选项列表ShiftTabuse-chips已设置且未设置no-chip-remove在 QChip 间向后导航选中 QChip 后Tab向前导航EnterQChip 被选中时从选择中移除该选项Backspace移除最后一个选项除非该选项被禁用use-input时输入框应为空Backspace设置了clearable单选清空模型置为null多选移除最后添加的值Tab或未设置use-chips/ 第一个 QChip 被选中时的ShiftTab导航到页面下一个/上一个可聚焦元素键入文本0-9或A-Z未设置use-input建立搜索缓冲1.5 秒未键入新键则重置用于在选项标签中搜索首键多次键入时选中当前聚焦项之后的下一个以该字母开头的选项否则从当前聚焦项开始匹配模糊匹配——标签须以首字母开头且包含所有键入字母对于大多数按键可以通过阻止 QSelect 的keydown事件来取消其默认动作例如keydown.enter.prevent可阻止Enter打开选项列表。例外是Esc——其关闭选项列表的处理绑定在keyup事件上无法以此方式取消。选项列表打开时按键行为Arrow Up/Arrow Down在列表中向上/向下导航到达首尾时会环绕Page Up/Page Down向上/向下翻一页Home/End跳到列表开头/末尾仅当未使用use-input或输入框为空时Enter或未设置use-input时的Space或未设置multiple且未设置disable-tab-selection时的Tab单选选中该选项并关闭列表multiple与disable-tab-selection均未设置时多选切换该选项的选中状态。例外创建新值时你输入的文本优先于打开列表/过滤后自动高亮镜像当前值的那个选项——键入的文本会被作为新值提交而你通过方向键导航或悬停到的选项仍会被选中无障碍Accessibilityv2.25QSelect 遵循 WAI-ARIA combobox 模式。焦点目标始终是一个rolecombobox的input——use-input时为真实的过滤输入框否则为持有已选显示值的只读输入框屏幕阅读器直接读取当前值。它无论字段处于何种状态都会渲染因此始终携带可访问名称、当前值以及 label 的for所指向的idreadonly的 QSelect 保持可聚焦显示聚焦态并在到达时发射focus/blur标注aria-readonlytruedisabled的 QSelect 保留在可访问性树中宣告为不可用但通过原生disabled属性退出 Tab 顺序两者都无法打开弹层。combobox 还携带aria-expanded反映弹层状态、aria-controls引用选项列表仅在弹层确实存在选项时才存在避免引用不存在的元素、aria-activedescendant跟踪高亮选项、aria-autocompleteuse-input允许键入文本过滤时为list否则为none。按照模式要求弹层打开期间焦点从不离开此输入框——列表由它驱动按键细节见上文键盘导航。这些属性的构建见源码comboboxAttrs计算属性QSelect.js。弹层内容是listbox始终携带aria-multiselectable按multiple属性为true/false其选项携带aria-selected以及aria-setsize、aria-posinset因为列表虚拟滚动DOM 中同时只存在切片这些属性让屏幕阅读器仍能宣告每个选项在完整集合中的真实位置。当选项以对话框渲染时见上文选项列表展示形态对话框内的控件承担同样的 combobox 契约对话框的Close按钮紧随其后处于 Tab 顺序中可用Enter/Space激活并显示浏览器原生焦点指示器。无论对话框以何种方式关闭Close 按钮、Esc、点击遮罩或在非multiple模式下选中选项焦点都会回到 QSelect 控件唯有一个刻意的例外在移动平台上use-input的 QSelect 只在键盘发起的关闭时恢复焦点以免把刚收起的虚拟键盘再次召唤出来。注意iOS 只有开启系统全键盘访问Full Keyboard Access后Tab才能到达按钮包括此按钮及页面上其他按钮Esc则不受限制。label属性兼任 combobox 的aria-label在 QSelect 上设置的aria-label或aria-labelledby优先级更高因为它们被应用到可聚焦控件上见上文use-input 与原生属性透传中的无障碍提示。两项插槽相关责任由你承担使用option插槽时必须v-bindscope.itemProps否则选项丢失roleoption、id、aria-selected与位置属性见自定义选项渲染放在before-options/after-options插槽中的内容位于 listbox 之外弹层打开期间无法通过键盘触达请避免在其中放置交互元素插槽描述见 QSelect.json。标签关联与错误宣告继承自字段框架——详见 QField 的无障碍章节。原生表单提交Native form submit当 QSelect 用于带有action与method的原生表单时例如 Quasar 与 ASP.NET 控制器搭配必须为 QSelect 指定name属性否则 formData 不会包含它如果预期包含的话。注意所有值都会被转换为字符串这是原生行为因此不要使用 Object 值。源码中该名称通过useFormInputNameAttr注入QSelect.js。完整示例见 NativeForm.vueq-form submitonSubmit classq-gutter-md q-select namepreferred_genre v-modelpreferred :optionsoptions filled clearable labelPreferred genre / q-select nameaccepted_genres v-modelaccepted multiple :optionsoptions filled clearable labelAccepted genres / q-btn labelSubmit typesubmit colorprimary / /q-formfunction onSubmit(evt) { const formData new FormData(evt.target) // 遍历 formData.entries() 即可看到 name 对应的值 }常用实例方法速查QSelect 通过 ref 暴露了若干实例方法完整定义见 QSelect.json可在组合式 API 或模板 ref 中调用方法说明showPopup()/hidePopup()聚焦并打开弹层 / 隐藏弹层add(opt, unique)向模型添加选项unique控制是否必须唯一toggleOption(opt, keepOpen)增删选项keepOpen为真时不关闭菜单且不清空过滤removeAtIndex(index)移除指定索引的已选项getOptionIndex()/setOptionIndex(index)读取/设置菜单中聚焦的选项索引-1 表示无moveOptionSelection(offset, skipInputValue)按偏移量移动选项焦点filter(value)以指定字符串过滤选项updateMenuPosition()重新计算菜单位置updateInputValue(value, noFilter)更新use-input的输入框值可选不触发过滤isOptionSelected(opt)/isOptionDisabled(opt)判断选项是否被选中 / 被禁用getOptionValue(opt)/getOptionLabel(opt)/getEmittingOptionValue(opt)获取选项的值 / 标签 / 发射值考虑emit-value小结QSelect 在单个组件中整合了选择、过滤、搜索、创建、展示与无障碍能力模型层面有emit-value/map-options的灵活数据形态数据层面支持字符串与对象选项、自定义属性名、懒加载与滚动分页交互层面覆盖菜单/对话框双形态、悬停展开v2.28、完整键盘导航与 WAI-ARIA combobox 模式性能层面依托虚拟滚动可轻松承载 10 万级选项。建议在项目中直接参考 官方示例目录43 个可运行示例与 QSelect.json 的 API 定义并结合本仓库的 QSelect.test.js 了解各属性的预期行为边界。【免费下载链接】quasarQuasar Framework - Build high-performance VueJS user interfaces in record time项目地址: https://gitcode.com/gh_mirrors/qu/quasar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考