- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
导读
本文基于 Quasar Framework 官方文档docs/src/pages/vue-components/input.md编写,系统讲解 UI 组件库中 QInput 的完整使用方式:从五种互斥的设计风格(filled/outlined/standout/borderless)、文本输入与原生属性透传,到业界少见的强大 Mask 掩码系统(含自定义 token、反向填充、多掩码动态切换),再到内部/外部/异步表单验证、无障碍支持与原生表单提交。读完本文,你将掌握 QInput 在真实业务表单(电话号码、金额、序列号、身份证等格式受限输入)中的完整落地方案,并能结合源码理解其底层行为。
快速了解 QInput
QInput 是 Quasar 框架中用于捕获用户文本输入的核心表单组件,位于ui/src/components/input/QInput.js。它与原生<input>类似使用v-model双向绑定,同时内置了错误与验证支持,并提供丰富的样式、颜色与类型组合。
从源码看,QInput 的modelValue类型为String、Number或FileList(SSR/SSG 下不含 FileList,见 QInput.js),意味着它天然覆盖了普通文本、数字和文件选择三种场景。组件基于 QField 构建:QInput.js通过useField(见ui/src/composables/private.use-field/use-field.js)复用了 QField 的框架、标签、错误提示、插槽等全部能力,因此 QInput 的所有属性与行为都可以视为"QField 框架 + 原生 input/textarea + 掩码/防抖等增强"的组合。
组件暴露的方法(见 QInput.json):
| 方法 | 说明 |
|---|---|
focus() | 聚焦底层原生 input 元素 |
blur() | 让底层原生 input 失去焦点 |
select() | 选中输入框文本 |
getNativeElement() | 获取原生 input/textarea DOM 元素(已废弃,请改用nativeEl) |
nativeEl是响应式计算属性(computed),同样返回底层原生元素。组件 API 的完整类型定义可查看 types/api/QInput.d.ts 或直接阅读QInput.json。
设计(Design)
QInput 支持五种主要视觉设计。需要注意的是,这五种设计彼此互斥,一个 QInput 只能使用其中一种,不能混用。
- Standard(标准):默认设计,输入框底部有一条下划线。
- Filled(填充):输入框带有填充背景色。
- Outlined(描边):输入框四周有边框轮廓。
- Standout(突出):背景在聚焦/悬停时变化,常用于 QToolbar 中集成搜索框等场景(见 StandoutToolbar.vue)。
- Borderless(无边框):不绘制边框、不改变背景色,用于将输入框无缝嵌入其他组件中。
颜色(Coloring)
QInput 支持通过color属性改变主题色,同时 Quasar 的dark布尔属性可强制切换暗色模式(见 Dark.vue)。颜色主题遵循 Quasar 全局色彩体系(primary、secondary、accent 等),可参考 ui/src/css/index.sass 中的主题变量定义。
Rounded 与 Square
rounded属性仅在Filled、Outlined、Standout三种设计下生效,用于将输入框四角变圆(见 Rounded.vue)。square属性同样只在上述三种设计下有意义,用于去掉圆角、强制直角边框(见 SquareBorders.vue)。
Force dark mode
通过dark属性可以强制 QInput 使用暗色样式,而不管当前应用是否处于暗色主题,适用于局部区域深色化场景。
基础功能
原生属性透传
QInput 上所有不在其 props 列表中的属性都会被自动透传给内部的原生元素(<input>或<textarea>),例如autocomplete、placeholder等。这是通过inheritAttrs: false加手动合并 attrs 实现的(见 QInput.js),透传时还会自动带上tabindex、maxlength、disabled、readonly等映射属性。这意味着你可以直接使用 HTML 原生规范中的属性,无需额外封装。原生属性完整清单可参考 MDN 的 input 与 textarea 文档。
Clearable 一键清空
添加clearable属性后,当有输入内容时会在输入框尾部出现一个清空图标,点击后将 model 重置为null。从源码看,清空动作调用onClear()(QInput.js),它会取消所有待执行的防抖/惰性更新并删除临时值,然后通过 QField 的onClear机制重置显示。
输入类型(type)
type属性支持渲染原生等价输入类型,取值包括text、password、textarea、email、search、tel、file、number、url、time、date、datetime-local(见 QInput.json)。
[!WARNING] 各浏览器对输入类型的支持与行为完全取决于浏览器自身渲染,与 Quasar 核心代码无关。
[!TIP] 某些输入类型(如
date、time)始终会渲染浏览器控件。如果同时使用label,建议配合stack-label让标签始终悬浮在上方,否则标签会与原生控件重叠。
数字类型
使用数字输入时,应同时使用v-model.number(注意.number修饰符)与type="number":
<q-input v-model.number="age" type="number" label="Age" />源码中对number类型做了专门处理:输入过程中先暂存字符串形式的临时值(temp.value),在update:modelValue发出时 Vue 的.number修饰符会将其转换为数字;同时floatingLabel计算属性对number类型会检查Number.isFinite,避免空值/非法值时标签异常悬浮(QInput.js)。
文件类型
[!TIP] 需要文件选择时,通常更推荐使用 QFile 或 QUploader 组件,而非 QInput 的
type="file"。
[!WARNING]切勿对
type="file"的 QInput 使用v-model!浏览器安全策略不允许为文件输入设置 value。因此你只能读取它(通过@update:model-value事件),无法写入。源码中对此有专门分支:当type === 'file'时,onInput直接 emite.target.files(FileList)而非字符串值(QInput.js),且渲染时使用useFileFormDomProps提供 DOM 属性而非 value(QInput.js)。
Textarea 文本域
将type设为textarea(或使用autogrow)即可渲染为多行文本域。文本域的行距随字体大小变化,因此通过input-class/input-style调整字号时行距会自动保持均匀。
当内容增长时,可使用autogrow属性让文本域随内容自动增高。底层实现有两种路径(见 use-autogrow.js):
- CSS 路径:浏览器支持
field-sizing: content时直接交给 CSS 处理(性能最优); - JS 测量路径:不支持时,将 textarea 高度折叠为 1px、读取
scrollHeight再写回为内联高度,并用requestAnimationFrame合并每帧内的多次触发,同时处理 Firefox 的 overflow 与滚动位置保留等边界情况。
前缀与后缀(Prefix and suffix)
通过prefix与suffix属性(或对应的prepend/append插槽)可在输入框前后附加静态文本,适合单位(如$、kg)、货币符号等场景,见 PrefixSuffix.vue。
自定义标签(Custom label)
使用label插槽可完全自定义标签的展示形式,例如在标签内嵌入 QTooltip:
<q-input label-slot filled> <template #label> <q-icon name="info" size="14px" /> 用户名 </template> </q-input>[!TIP] 使用
label插槽时必须设置label-slot属性;若需要在插槽元素上交互(如 QTooltip 悬停),请为元素添加all-pointer-eventsclass。
Shadow text 阴影文本
shadowText属性可以在输入内容之后显示一段"影子"文字,用于预览提示完整格式,例如输入金额时提示小数点后位数。源码中通过getShadowControl()渲染一个不可见副本加影子文字的叠加层(QInput.js),该功能对type="file"不生效。
Slots 中的 submit 按钮
[!WARNING] 当把
type="submit"的 QBtn 放入 QField / QInput / QSelect 的before、after、prepend或append插槽时,必须在该 QBtn 上额外添加@click监听器来调用表单提交方法。这些插槽内的 click 事件不会向上传播到父元素,因此仅靠原生 form submit 无法触发。
防抖(Debounce)
当你在 watch model 时执行昂贵操作(如请求远程校验),希望用户先输入完成再触发更新,而不是每次按键都更新 model。此时使用debounce属性(单位毫秒):
<q-input v-model="search" debounce="500" label="Search" />源码中防抖通过emitTimer = setTimeout(emitValueFn, props.debounce)实现,防抖期间把用户输入暂存在temp.value中以保持界面即时反馈,定时器到点后才发出update:modelValue(QInput.js)。同时源码还处理了一个易被忽视的细节:当 model 已被外部重置时,会取消仍在途中的防抖发射(cancelPendingValueEmission()),避免过期中间值污染模型(use-mask.js)。
惰性更新(v-model.lazy)
使用v-model.lazy修饰符时,model 只在用户完成编辑时更新(原生change事件或输入框失焦时),而不是每次按键都更新,与原生<input>的行为一致。注意:使用v-model.lazy时debounce属性会被忽略。源码中emitValueFn在 lazy 模式下保持待定状态,直到onChange(change 事件)或onFinishEditing(失焦)时被调用(QInput.js)。另外,v-model.trim与v-model.number由 Vue 本身处理。
加载状态(Loading state)
QInput 支持loading属性,开启后显示加载指示器,常用于异步校验或远程数据回填场景(见 LoadingState.vue)。
Mask 掩码
mask属性可以强制/辅助用户输入特定格式,这是 QInput 最强大的功能之一,实现位于 use-mask.js。
[!WARNING] Mask 仅在
type为text(默认)、search、url、tel或password时可用。源码通过getIsTypeText()检查类型并据此决定是否启用掩码逻辑(use-mask.js)。
[!WARNING]与
maxlength的冲突:Mask 本身已按槽位限制输入长度,因此不要与maxlength组合使用。原生maxlength统计的是完整显示值(含字面符与填充字符),任何小于掩码全长的值都会过早阻断输入;而配合fill-mask时显示值始终是掩码全长,这样的maxlength会直接锁死整个输入框。
默认掩码 Token
| Token | 说明 |
|---|---|
# | 数字 |
S | 字母 a-z,大小写不敏感 |
N | 字母数字,字母大小写不敏感 |
A | 字母,自动转换为大写 |
a | 字母,自动转换为小写 |
X | 字母数字,字母自动转换为大写 |
x | 字母数字,字母自动转换为小写 |
这些 token 在 use-mask.js 中定义:A/a/X/x带有transform函数(toLocaleUpperCase/toLocaleLowerCase),pattern用于单字符匹配,negate用于取反匹配。源码还针对纯 ASCII 的三种 pattern 做了零分配的性能优化(patternTesters,use-mask.js),其它 pattern 才回退到正则。
除自定义字符串外,mask还内置了命名掩码(见 use-mask.js):
| 名称 | 展开的掩码 |
|---|---|
date | ####/##/## |
datetime | ####/##/## ##:## |
time | ##:## |
fulltime | ##:##:## |
phone | (###) ### - #### |
card | #### #### #### #### |
基本用法示例(摘自 MaskBasic.vue):
<q-input filled v-model="id" label="Special ID" mask="###/##" hint="Mask: ###/##" /> <q-input filled v-model="phone" label="Phone" mask="(###) ### - ####" /> <q-input filled v-model="serialNumber" label="Serial number" mask="AAAA - #### - #### - SSS" />填充掩码(fill-mask)
默认掩码在未填满时只显示已输入的部分。fill-mask属性(布尔值或字符串)会用指定字符(传字符串时取第一个字符,默认_)填充掩码的剩余槽位:
<q-input v-model="date" mask="date" fill-mask="0" label="Date" />注意源码中一个关键边界处理:当填充字符恰好满足某个 token(如fill-mask="0"对#token),仅凭渲染值无法区分"填充"与"真实数据",因此 use-mask 维护了innerValueDataLen(填充前数据长度)来精确判定每个位置是否承载真实数据,避免每次按键都错误吞掉/多出一个填充字符(use-mask.js)。
光标与按键行为
Mask 会接管光标移动(跨越掩码字面符)以及BACKSPACE/DELETE的边界逻辑。你可以通过阻止其keydown事件来禁用掩码自身的按键处理——但注意这同时会取消浏览器对该键的原生处理,例如@keydown.left.prevent会让光标停留在原处而非跳过字面符,重新定位光标需要自己实现。底层由onMaskedKeydown实现(use-mask.js),它维护了 Shift 多选锚点(selectionAnchor)与四种方向移动(moveCursor.left/right/leftReverse/rightReverse)。
unmasked-value 原始值模式
如果你希望强制用户按格式输入,但 model 中保存的是去除格式的原始值,则使用unmasked-value属性:
<q-input v-model="phone" mask="phone" unmasked-value label="Phone" />此时 model 保存纯数字(如0212345678),显示层仍带格式。源码在updateMaskValue中对unmasked-value使用unmaskValue(preMasked)在填充之前去掩码(use-mask.js)。
reverse-fill-mask 反向填充
reverse-fill-mask强制用户从掩码末尾开始填充,且允许输入长度不固定(不足部分不占位):
<q-input v-model="price" mask="#.##" fill-mask="#" reverse-fill-mask label="Price" />源码专门实现了maskValueReverse从右侧向左装配数据,并处理了"字面符在左侧数据落地前不提前输出"等细节(use-mask.js)。
多掩码动态切换
当一个字段需要接受多种格式时(例如电话号码可能是 8 位或 9 位本地号码),可将mask绑定到计算属性,根据值的长度选择掩码。两个关键细节保证可靠性:
- 使用
unmasked-value,让纯数字长度(而非当前掩码字面符)驱动切换决策; - 给较短的掩码末尾多留一个 token 槽位,使跨越阈值的那个数字能够被输入——一旦输入,计算属性切换掩码并重新排版,光标保持在原数据位置。
实现示例可查看 MaskMultiple.vue。源码中掩码切换会保留光标前的数据字符数量(dataBeforeCaret),切换后在新的布局中重新锚定光标(use-mask.js)。
自定义掩码 Token(v2.18.4+)
mask-tokens属性允许在默认 token 基础上新增自定义 token,甚至可以覆盖部分/全部默认 token。自定义 token 的语法必须与默认 token 一致:pattern(必填,匹配单个字符的正则字符串)、negate(必填,不匹配的正则字符串)、transform(可选,字符转换函数)。
示例(摘自 MaskCustomTokens.vue):
<q-input filled v-model="id" label="Special ID" mask="AA-CC-XX-CC" :mask-tokens="customTokens" />const customTokens = { C: { pattern: '[0-4a-eA-E]', negate: '[^0-4a-eA-E]', transform: v => v.toLocaleUpperCase() }, X: { pattern: '[5-8]', negate: '[^5-8]' } // 覆盖默认的 X }源码中mask-tokens通过getTokenMap解析并与默认 token map 合并({ ...DEFAULT_TOKEN_MAP, ...customTokens }),且对mask-tokens做了深度监听,变更时自动重算掩码(use-mask.js)。
使用第三方掩码处理器
你也可以轻松接入任意第三方掩码处理器。思路是利用 QField 的control插槽替换内部控件。以 v-money 指令为例,从标准 QInput:
<q-input filled v-model="price" label="Price with 2 decimals" mask="#.##" fill-mask="#" reverse-fill-mask hint="Mask: #.00" input-class="text-right" />改为 QField + 原生 input + v-money 指令:
<q-field filled v-model="price" label="Price with v-money directive" hint="Mask: $ #,###.00 #" > <template #control="{ id, floatingLabel, modelValue, emitValue }"> <input :id="id" class="q-field__input text-right" :value="modelValue" @change="e => emitValue(e.target.value)" v-money="moneyFormatForDirective" v-show="floatingLabel" /> </template> </q-field>moneyFormatForDirective: { decimal: '.', thousands: ',', prefix: '$ ', suffix: ' #', precision: 2, masked: false /* doesn't work with directive */ }或使用 money 组件:
<q-field filled v-model="price" label="Price with v-money component" hint="Mask: $ #,###.00 #" > <template #control="{ id, floatingLabel, modelValue, emitValue }"> <money :id="id" class="q-field__input text-right" :model-value="modelValue" @update:model-value="emitValue" v-bind="moneyFormatForComponent" v-show="floatingLabel" /> </template> </q-field>moneyFormatForComponent: { decimal: '.', thousands: ',', prefix: '$ ', suffix: ' #', precision: 2, masked: true }control插槽暴露的id(用于 label 关联)、floatingLabel(标签是否悬浮)、modelValue、emitValue是接入第三方控件的关键协议,这正是 QField 将"框架渲染"与"控件渲染"解耦的体现。
验证(Validation)
内部验证(:rules)
通过:rules属性验证 QInput:传入内建规则数组或自定义校验器。自定义校验器是一个函数,校验成功返回true,失败返回错误消息字符串:
value => condition || errorMessage // 示例: value => value.includes('Hello') || 'Field must contain word Hello'[!TIP] 出于性能考虑,默认情况下规则的变化不会触发重新验证,直到 model 变化。若希望规则变化时也触发验证,使用
reactive-rules布尔属性。代价是性能开销(仅在确实需要时使用!),并且可以通过把规则写成计算属性(而不是在模板中内联)来略微缓解。
调用 QInput 的resetValidation()方法可重置验证状态(示例见 ValidationRequired.vue):
<q-input ref="inputRef" filled v-model="model" label="Required Field" :rules="[val => !!val || 'Field is required']" /> <q-btn label="Reset Validation" @click="inputRef.resetValidation()" />[!WARNING]原生约束与 rules 是两套体系:原生 HTML 约束(如
url类型,或透传到原生 input 的pattern/required属性)只会在原生表单提交时由浏览器强制执行。程序化的validate()方法(QInput 自身或外层 QForm)只评估 rules,不检查原生约束。因此任何需要被validate()捕获的约束都应同时写成规则,例如:rules="['email']"。注意 QInput 提供内建的常用规则字符串,如url等,可直接复用。
lazy-rules 触发时机
设置lazy-rules后,验证在字段失焦时触发(readonly字段也触发,只有disabled字段豁免);错误显示期间,每次变更都会重新验证,一旦值合法错误立即清除。字段内部打开的菜单或对话框(如append插槽中的 QPopupProxy)在其打开期间保持字段聚焦,不算失焦。
若lazy-rules设置为字符串ondemand,则验证仅在手动调用组件validate()方法或外层 QForm 提交时触发,适合完全由表单控制的场景。
异步规则(Async rules)
规则支持异步:使用 async/await 或直接返回 Promise。如果异步验证进行中值发生变化或字段失焦,字段会在异步验证结束后自动重新验证,确保显示的结论始终匹配当前值。
[!TIP] 建议将异步规则与
debounce属性配合使用,避免每次按键都立即触发异步规则,造成性能损耗。
示例模式:
const rules = [ async val => { const ok = await checkUnique(val) return ok || '该值已被占用' } ]外部验证(External validation)
也可以完全使用外部验证,只传入error和error-message属性(需启用bottom-slots来显示错误消息):
<q-input filled v-model="model" :error="hasError" error-message="用户名已存在" bottom-slots />[!TIP] 根据需求,你可以连接 Regle(官方推荐方案)或其他验证库到 QInput。
错误消息的显示槽位也可以自定义(见 ValidationSlots.vue),例如在错误文本旁加图标或改变布局。
无障碍(Accessibility,v2.25+)
QInput 在 QField 框架内渲染原生<input>(或<textarea>),因此 QField 的无障碍章节所述内容全部适用:
- 通过生成的 SSR 安全 id 建立 label 关联;
- 错误消息以
role="alert"播报,并通过aria-invalid/aria-errormessage/aria-describedby从控件引用; - 清空按钮可通过键盘操作。
在此基础上,label属性会额外作为aria-label暴露到原生元素(自行设置的aria-label/aria-labelledby优先级更高);disable与readonly会映射为原生disabled与readonly属性(因此 readonly 输入框保留在 Tab 顺序中,聚焦时显示聚焦态)。其余原生属性(placeholder、autocomplete、inputmode等)均透传到原生元素。这与 QInput.js 中inputAttrs的组装逻辑完全一致。
原生表单提交
当 QInput 用于带action与method的原生表单时(例如 Quasar 配合 ASP.NET 控制器),必须指定name属性,否则 formData 中不会包含该字段(如果需要提交它的话):
<form action="/submit" method="post"> <q-input name="username" v-model="username" label="Username" /> </form>源码通过useFormInputNameAttr为控件提供 name 属性(QInput.js),未设置name时该字段不会进入 formData。
进阶:结合源码理解输入管线
了解 QInput 的底层工作流有助于排查疑难问题。核心输入管线如下:
- 输入事件:用户键入触发
onInput,非 file 类型时读取e.target.value; - 暂存与发射:
emitValue(val)根据修饰符决定发射策略——debounce走定时器、v-model.lazy挂起等待 change/blur、其余立即发射(QInput.js); - 掩码介入:有 mask 时走
updateMaskValue,完成去掩码、重装配、光标定位与unmasked-value转换; - IME 输入法合成:通过
qComposing标记跳过掩码重写,避免中文等输入法合成期间值被篡改(QInput.js); - 失焦收尾:
onFinishEditing清空暂存值、取消待发射的防抖定时器并同步显示值。
对应的测试覆盖可参考 QInput.test.js 与 use-mask.test.js,其中包含大量针对光标移动、填充字符边界、反向填充、防抖竞态等场景的回归用例。
结语
QInput 远不止是一个"带样式的 input":它由 QField 框架提供一致的布局/标签/错误体系,自带防抖、惰性更新、加载态与无障碍支持,更内置了一套可自定义 token、可反向填充、可动态切换的生产级掩码引擎。无论是简单的登录表单,还是复杂的电话号码、金额、序列号输入,QInput 都能在保持原生输入体验的同时完成格式约束与验证闭环。结合 input.md 官方文档、docs/src/examples/QInput 目录下的 37 个可运行示例,以及 use-mask.js 等源码,你可以进一步验证并扩展本文涉及的所有能力。
- 前端
- UI组件
- 跨平台
【免费下载链接】quasar
Quasar Framework - Build high-performance VueJS user interfaces in record time
相关推荐
Quasar QDate 组件完全指南:从基础用法到波斯日历、无障碍与表单集成的实战手册
Quasar QDate 组件完全指南:从基础用法到波斯日历、无障碍与表单集成的实战手册 导读 :本文以 Quasar Framework 官方文档 QDate
前端UI组件跨平台react-native-elements Input 组件完全指南:从基础用法到表单交互实战
react native elements Input 组件完全指南:从基础用法到表单交互实战 导读 本文围绕 react native elements 的
UI组件移动开发前端Element(Vue 2.0)Radio 单选框组件完全指南:从基础用法到源码实现
Element(Vue 2.0)Radio 单选框组件完全指南:从基础用法到源码实现 Element 的 Radio 组件族( el radio 、 el ra
前端UI组件设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考