Vuetify VOtpInput 组件深度指南:用 v-otp-input 构建 MFA 一次性密码输入
2026/9/19 10:03:23 网站建设 项目流程

Vuetify VOtpInput 组件深度指南:用 v-otp-input 构建 MFA 一次性密码输入

【免费下载链接】vuetify🐉 Vue Component Framework项目地址: https://gitcode.com/gh_mirrors/vu/vuetify

本指南围绕 Vuetify 的VOtpInputv-otp-input)组件展开,系统讲解其在邮箱/短信一次性密码(OTP)、多因素认证(MFA)场景中的完整用法。你将掌握组件的全部核心 props(长度、掩码、合并、字符模式限制等)、finish事件与v-model的数据流、fields插槽自定义布局、以及隐藏输入框背后的 grapheme 分段与 IME 输入处理等源码级原理,可直接用于构建生产级验证码界面。

组件定位与典型场景

OTP(One-Time Password)输入框是 MFA 认证流程的标准交互形式:服务端通过邮件或短信向用户下发一次性验证码,用户在若干字符槽中逐位输入。v-otp-input正是为这一场景设计的组件,官方文档(otp-input.md)将其定位为 “used for MFA authentication via input field”。

它具备以下开箱即用的能力:

  • 渲染可配置数量的字符字段,默认 6 位;
  • 自动处理键盘导航(方向键、退格键)、整段粘贴与浏览器/系统自动填充(autocomplete="one-time-code");
  • 支持密码掩码、字符模式限制(数字/字母/混合/Unicode)、加载与错误状态;
  • 在填满全部字段时通过finish事件通知外部。

基础用法

最简单的使用方式与任何表单输入组件一致,仅需一行:

<v-otp-input></v-otp-input>

配合v-model双向绑定当前已输入的字符串,并监听finish事件判断用户是否输完:

<template> <v-otp-input v-model="otp" @finish="verifyOtp" ></v-otp-input> </template> <script setup> const otp = ref('') function verifyOtp (value) { console.log('OTP 已输入完毕:', value) // 调用后端接口校验验证码 } </script>

官方使用示例(usage.vue)展示了通过配置面板实时调整lengthplaceholderdisabledloadingpatternfocus-allvariant等参数的效果,是快速理解各 props 交互差异的起点。

组件解剖:单一隐藏输入 + 多个视觉字段

v-otp-input的 DOM 结构与传统“多个独立输入框”的实现完全不同。从源码(VOtpInput.tsx)可以看到,其渲染结构为:

| 结构层 | 说明 | | - | - | |.v-otp-input容器 | 持有全部视觉字段与隐藏输入框,居中布局 | |.v-otp-input__content| 字段与分隔符的排列区域,可容纳fieldsdividerloaderdefault插槽 | |.v-otp-input__field| 由VField渲染的单个字符槽(对应官方文档 Anatomy 中的 “Field”) | |.v-otp-input__input| 一个透明的<input type="text">,承载真实的键盘交互、粘贴与自动填充 |

关键设计是:整个组件只有唯一一个真实输入框,它被定位到容器底部、仅有 1px 大小、文字与光标透明(样式见 VOtpInput.sass)。而在触屏设备(any-pointer: coarse)上,该隐藏输入框会扩展铺满整个区域,让移动端键盘、IME 候选框与系统自动填充栏正常工作。

每个字符槽则由VOtpField内部的VField渲染,并根据槽位数据展示三种内容之一:

  1. 输入中的字符(或掩码后的);
  2. IME 组合中的临时字符(.v-otp-input__composition);
  3. 虚拟光标(.v-otp-input__caret,闪烁动画)或占位符。

槽位数据由useOtpInputslotscomputed 计算得出(useOtpInput.ts),每个槽包含charcompositionCharplaceholderCharisActivehasFakeCaret五个字段。

Props 详解

v-otp-input支持大部分v-field的 props,并遵循与其他输入组件一致的密度、尺寸与焦点设计模式。其 props 定义见 VOtpInput.tsx。

length:字符位数

length决定渲染多少个字符槽,默认值为6,支持数字或字符串形式传入:

<v-otp-input length="4"></v-otp-input>

源码中通过toRef(() => Number(props.length))统一转为数字。注意length同时约束了隐藏输入框可接受的最大字符数(maxLength以槽数为准,而非代码单元数),因此输入内容永远不会超过槽位数量。

autofocus 与 focus-all:聚焦行为

官方文档将两个焦点相关 props 并列说明:

  • autofocus:组件挂载并进入视口后自动聚焦。实现上借助useIntersectionObserver(VOtpInput.tsx),即只有元素真正可见时才触发聚焦,避免页面加载瞬间抢焦点;
  • focus-all:聚焦时高亮全部字段。VOtpField通过判断isFocused && focusAll && !isActive给非活动槽添加v-otp-input__field--highlighted类(VOtpField.tsx),其描边会以 50% 透明度呈现(见 VOtpInput.sass)。

error:错误状态

error将组件整体置入错误态,用于展示校验失败。它被透传给内部的VFieldprovideDefaults统一注入,见 VOtpInput.tsx),因此错误边框、错误色等样式与v-text-field等输入组件完全一致。示例见 prop-error.vue。

variant:外观变体

variant支持与v-fieldv-text-field相同的全部变体,默认值为outlined。可用的典型值包括filledplainsolosolo-filledsolo-invertedunderlinedoutlined等。示例见 prop-variant.vue。

masked:掩码显示

masked隐藏已输入的字符,效果类似type="password",每个字符显示为。与密码框不同的是,它仍可配合type="number"将键盘限制为数字键盘。源码中isMasked = masked || type === 'password'(useOtpInput.ts),即type="password"也会自动触发掩码。示例见 prop-masked.vue。

loader:加载状态

loader在组件进入加载态时显示加载指示器。其实现是在内容上方叠加一个VOverlaycontained模式),默认渲染一个 24px 的VProgressCircular(indeterminate 无限旋转),也可通过loader插槽自定义。loading支持布尔值与字符串(此时字符串作为加载圈颜色)。示例见 prop-loader.vue。

merged:合并分组

merged将所有字段渲染为一个连续连接的组:共享统一的圆角与投影,字段之间无间隙,边框相接(通过margin-inline-start: -1px重叠轮廓线,聚焦或悬停时恢复边框宽度以突出当前槽,见 VOtpInput.sass)。示例见 prop-merged.vue。

pattern:字符模式限制

pattern限制可接受的字符。它支持以下预设值与自定义正则:

| 预设值 | 对应正则(useOtpInput.ts) | 说明 | | - | - | - | |numeric|/[0-9]/| 仅数字 | |alpha|/[a-zA-Z]/| 仅英文字母 | |alphanumeric|/[a-zA-Z0-9]/| 字母与数字 | |unicode-alpha|/\p{L}/u| 任意语言的字母(含 CJK、西里尔、带重音字符) | |unicode-alphanumeric|/[\p{L}\p{N}]/u| 任意语言的字母与数字 |

关键行为:

  • type="number"时,即使未显式设置pattern,也会自动默认numeric
  • 也可传入自定义RegExp对象,例如仅允许大写字母::pattern="/[A-Z]/"
  • 过滤基于 grapheme(字素簇)而非单个代码点,因此 emoji、ZWJ 序列、肤色修饰符都能作为一个整体被校验;
  • inputMode会根据生效的 pattern 自动切换:numeric模式返回numeric,否则返回text,从而在移动端自动弹出对应键盘。

示例见 prop-pattern.vue,其中分别演示了numericalpha与自定义大写正则/[A-Z]/三种情况。

divider:字段间分隔符

divider接受一个字符串,渲染在每个字段之间的分隔位置:

<v-otp-input divider="-"></v-otp-input>

从源码(VOtpInput.tsx)可见,分隔符出现在索引非 0 的字段之前,由VOtpSeparator渲染,同时根元素会加上v-otp-input--divided类并放宽内容最大宽度($otp-input-divided-content-max-width)。示例见 prop-divider.vue。

其他透传 props

组件还透传了v-field的部分 props 与通用输入 props:

  • 视觉相关colorbgColorbaseColorroundedthemedisabledloadingclassstyle
  • 输入相关label(默认$vuetify.input.otp,会作为隐藏输入框的aria-label,便于无障碍访问)、placeholder(显示于空槽位的占位字符)、typetext/password/number,默认number)、modelValue(默认空字符串);
  • 密度与尺寸densityheightwidthmax-width等(来自makeDensityPropsmakeDimensionProps)。

事件与组件实例方法

组件仅发出三个事件(VOtpInput.tsx):

| 事件 | 载荷 | 触发时机 | | - | - | - | |update:modelValue| 字符串 | 每次输入变化 | |finish| 完整字符串 | 输入长度恰好等于length时(watch 判定val.length === length.value) | |update:focused| 布尔 | 焦点变化 |

同时,通过模板 ref 可调用组件实例方法(见 VOtpInput.tsx):

  • focus():聚焦隐藏输入框;
  • blur():失焦;
  • reset():清空全部内容、选区与组合状态。

Slots:插槽体系

divider 插槽

divider插槽用于自定义字段间的分隔内容,并接收分隔符的index作为插槽 prop(从 1 开始计数):

<v-otp-input> <template #divider="{ index }"> <v-icon>{{ index % 2 ? 'mdi-minus' : 'mdi-dot' }}</v-icon> </template> </v-otp-input>

插槽实现见 slot-divider.vue。当使用插槽而非字符串时,dividerprop 可以为空,插槽内容会取代默认字符串。

fields 插槽与三个子组件

fields插槽是最强大的扩展点,它允许完全重排字段结构。结合三个子组件可以构建任意布局:

  • v-otp-field:渲染单个字符槽,必须传入:index(对应length中的槽位序号)。它必须位于v-otp-input内部,否则会抛出VOtpField must be used inside VOtpInput错误(VOtpField.tsx);
  • v-otp-group:将多个字段包成一组,merged属性可独立控制该组是否合并(默认继承父级v-otp-inputmerged,见 VOtpGroup.tsx)。合并时组会计算子节点数量并分配 flex 权重;
  • v-otp-separator:显示在字段/组之间的视觉分隔元素,默认插槽可接收任意内容(文本、图标等),本质是createSimpleFunctional('v-otp-input__divider')生成的轻量组件(VOtpSeparator.tsx)。

例如经典的 “3-3” 分组验证码(示例见 misc-custom-layout.vue):

<v-otp-input v-model="otp"> <template #fields> <v-otp-group merged> <v-otp-field :index="0"></v-otp-field> <v-otp-field :index="1"></v-otp-field> <v-otp-field :index="2"></v-otp-field> </v-otp-group> <v-otp-separator>-</v-otp-separator> <v-otp-group merged> <v-otp-field :index="3"></v-otp-field> <v-otp-field :index="4"></v-otp-field> <v-otp-field :index="5"></v-otp-field> </v-otp-group> </template> </v-otp-input>

同一个示例中还演示了 “2-4 混合”布局:前两个字段为独立(非合并)槽,中间用v-icon图标作分隔符,后四个字段合并为组。

loader 与 default 插槽

  • loader:自定义加载指示器内容,取代默认的VProgressCircular
  • default:渲染在内容区域末尾,可用于放置辅助提示文案。

实战场景示例

官方文档提供了三个贴近真实业务的示例:

卡片内的验证码

v-misc-card.vue 将v-otp-input放入v-card,配合标题、说明文字与提交按钮,构成完整的 MFA 弹窗卡片。适合在登录、支付确认等场景中直接复用。

移动端短信验证码

misc-mobile.vue 模拟移动端短信验证码交互,展示了在窄屏与触屏设备上(隐藏输入框铺满组件区域)的输入体验,以及发送验证码按钮与倒计时等常见配套元素。

账户验证

misc-verify.vue 演示完整的账户验证流程:接收验证码 → 输入 → 点击验证。可结合finish事件自动触发校验,配合errorprop 展示服务端校验失败信息。

源码级原理:useOtpInput 与输入管线

组件的全部输入逻辑收敛在useOtpInputcomposable(useOtpInput.ts)中,理解它可以解释许多看似“魔法”的行为:

1. Grapheme(字素簇)分段组件用Intl.Segmenter将文本按字素切分,槽位边界基于字素而非 UTF-16 代码单元。这意味着 emoji、ZWJ 序列、组合字符(如肤色修饰符)各占一个槽位,选区换算在“字素空间”与“代码单元空间”之间双向转换,保证setSelectionRange与槽位渲染始终一致。

2. 字符过滤与截断所有写入路径(setValueinsert)都经过filter(按生效 pattern 过滤非法字素)与clampGraphemes(按length截断)。onBeforeinput还会在插入前拦截非法字符(VOtpInput.tsx),实现“输不进去”而非“输进去再删”。

3. 粘贴与自动填充onPaste读取剪贴板纯文本、去首尾空白、过滤非法字符后插入当前选区(VOtpInput.tsx)。隐藏输入框声明了autocomplete="one-time-code",让 iOS/Android 与桌面浏览器的 OTP 自动填充(短信提取码)能直接写入组件。

4. IME 与组合输入组件完整处理compositionstart/update/end事件:组合中的内容先显示在compositionChar槽位(斜体下划线样式,见 VOtpInput.sass),compositionend后才提交到模型。对 CJK 输入法,组合内容通过覆盖层渲染,避免与隐藏输入框的选区互相干扰;同时通过IME_SCRIPT_RE识别汉字、假名、谚文等脚本,兼容死键与三星预测输入等非 CJK 组合场景。

5. 选区同步组件监听原生selectionchange事件将系统选区同步为“至少覆盖一个槽”的选区(syncSelection),保证任意时刻都有一个槽处于 active 状态;方向键移动按字素计算,Shift+方向键扩展选区支持多槽批量删除。

6. RTL 支持组件接入useRtl:根输入框方向切换为rtl,方向键移动方向自动取反(VOtpInput.tsx),分隔符与合并组的样式同样使用逻辑属性(margin-inline-start等)适配镜像布局。

样式定制

组件的视觉样式集中在 VOtpInput.sass,可通过 _variables.scss 中的 Sass 变量覆盖,例如:

  • $otp-input-content-gap:字段/分隔符间距,默认0.5rem
  • $otp-input-content-height:内容区高度,默认64px(随 density 变化);
  • $otp-input-content-max-width:内容区最大宽度,默认320px(启用divider时为360px);
  • $otp-input-field-font-size:字段字体大小,默认1.25rem
  • $otp-input-divider-margin:分隔符外边距,默认0 8px

示例中加载圈的尺寸与颜色也可通过loader插槽或loading字符串值调整。

测试保障

组件行为由浏览器级测试覆盖(VOtpInput.spec.browser.tsx),涉及输入、粘贴、键盘导航、焦点与事件触发等场景。在自行扩展或修复该组件时,可运行对应测试验证行为不回归。

小结

v-otp-input是一个“内部结构复杂、对外 API 简洁”的组件:对使用者而言,只需v-modellengthpatternfinish即可完成 90% 的 MFA 验证码需求;对需要定制体验的团队,fields插槽配合v-otp-field/v-otp-group/v-otp-separator三个子组件可以构建任意分组布局,而useOtpInput中基于 grapheme 的输入管线保证了多语言、IME、粘贴与自动填充场景下的输入一致性。更多组合方式可继续查阅 VOtpInput 源码目录与 示例目录。

【免费下载链接】vuetify🐉 Vue Component Framework项目地址: https://gitcode.com/gh_mirrors/vu/vuetify

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询