前言

验证码输入是登录、注册、密码找回、支付确认里最常见的一类交互。这个场景看起来简单,真正做起来,体验差距却很明显。输入框类型选得不对,系统就不会把它识别成验证码场景,输入法优化、自动填充、无障碍提示这些能力都很难接得顺。

鸿蒙 6 在 API 20 里给文本输入组件补进了验证码输入模式 ONE_TIME_CODE。这项能力同时覆盖 TextInputTextAreaSearch,其中 TextInput 是最典型的落地入口。它的作用很直接,把验证码输入这件事从普通文本输入里单独拎出来,让系统知道这是一次性验证码场景,再围绕这个语义去做输入法和自动填充上的协同。

一、为什么验证码输入要单独设一种类型

过去很多项目直接拿普通输入框收验证码,功能上当然能用,但系统眼里它只是一个普通文本框。这样带来的问题也很现实。输入法未必会切到最合适的键盘,自动填充也不容易精准命中,无障碍场景下对输入意图的表达也不够明确。

API 20 把 ONE_TIME_CODE 放进输入类型枚举之后,验证码场景终于有了单独的语义标记。华为开发者文档和系统新增能力说明都已经把这一点写清楚了,ONE_TIME_CODE 属于验证码类型输入模式,在 API 20 起可用。对 TextInput 来说,它对应的是一个明确的 InputType 枚举成员。

这类语义化输入类型真正有价值的地方,在于它不只是改一个键盘样式。系统能据此判断,当前这个输入框承接的是验证码,而不是用户名、普通数字或密码。后面不管是自动填充、输入法联动,还是安全场景下的输入优化,前提都在这里。

二、处理基础接法

TextInput 这条线的接法很直接,输入框正常创建,然后把 type 设成 InputType.ONE_TIME_CODE。验证码通常有固定位数,所以 maxLength 也应该一起配上。6 位验证码是最常见的配置,4 位和 8 位也都很常见,关键是跟业务规则对齐。

下面这段代码就是最基础、最适合直接落进项目里的写法:

@Entry
@Component
struct VerifyCodeDemo {
  @State verificationCode: string = ''

  build() {
    Column({ space: 20 }) {
      TextInput({
        placeholder: '请输入验证码',
        text: this.verificationCode
      })
        .type(InputType.ONE_TIME_CODE)
        .maxLength(6)
        .onChange((value: string) => {
          this.verificationCode = value
        })
    }
    .width('100%')
    .padding(24)
  }
}

如果这个页面本身就是典型的验证码验证页,还可以把自动填充动画打开。enableAutoFillAnimation 也已经进了 TextInput 这条能力线,适合配合系统自动填充一起用,让验证码被系统填进来时,过渡更自然。

TextInput({
  placeholder: '请输入验证码',
  text: this.verificationCode
})
  .type(InputType.ONE_TIME_CODE)
  .maxLength(6)
  .enableAutoFillAnimation(true)
  .onChange((value: string) => {
    this.verificationCode = value
  })

如果页面还有手机号输入框,最顺的写法就是把手机号和验证码输入类型一起配清楚。手机号走 PhoneNumber,验证码走 ONE_TIME_CODE,两个输入场景分开,系统才能各自做对优化。

@Entry
@Component
struct LoginByCodePage {
  @State phoneNumber: string = ''
  @State verificationCode: string = ''

  build() {
    Column({ space: 16 }) {
      TextInput({ placeholder: '请输入手机号', text: this.phoneNumber })
        .type(InputType.PhoneNumber)
        .maxLength(11)
        .onChange((value: string) => {
          this.phoneNumber = value
        })

      TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
        .type(InputType.ONE_TIME_CODE)
        .maxLength(6)
        .onChange((value: string) => {
          this.verificationCode = value
        })
    }
    .padding(24)
  }
}

三、最佳实践

验证码输入体验真正拉开差距的地方,不在基础接入,在输入完成后的处理逻辑。

第一件事,是长度控制和自动提交。验证码一旦达到固定位数,通常就可以直接触发校验,不需要让用户再多点一次按钮。这个动作很适合放在 onChange 里做,但要注意只在长度满足时触发一次,不要每次输入都重复请求。

@State verificationCode: string = ''

private verifyCode(): void {
  // 执行验证码校验
}

TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
  .type(InputType.ONE_TIME_CODE)
  .maxLength(6)
  .onChange((value: string) => {
    this.verificationCode = value
    if (value.length === 6) {
      this.verifyCode()
    }
  })

第二件事,是错误状态要收得干净。验证码输错之后,提示要清楚,但不能一直挂着不消失。用户一旦重新输入,错误提示最好立刻清空,这样反馈会更连贯。showError 本身就是 TextInput 的现成能力,适合直接拿来做这层状态。

@State verificationCode: string = ''
@State errorMessage: string = ''

TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
  .type(InputType.ONE_TIME_CODE)
  .maxLength(6)
  .showError(this.errorMessage)
  .onChange((value: string) => {
    this.verificationCode = value
    this.errorMessage = ''
  })

第三件事,是倒计时和重新获取按钮要跟输入框节奏一致。验证码输入框只是流程的一部分,真正完整的交互还包括获取验证码、倒计时、重发控制和提交动作。页面如果只把输入框做对了,其他链路还很生硬,体验还是会断。

下面这段代码就是一个更接近真实业务页面的骨架:

@Entry
@Component
struct SmsLoginPage {
  @State phoneNumber: string = ''
  @State verificationCode: string = ''
  @State countdown: number = 0
  @State errorMessage: string = ''

  private canSendCode(): boolean {
    return this.phoneNumber.length === 11 && this.countdown === 0
  }

  private canLogin(): boolean {
    return this.phoneNumber.length === 11 && this.verificationCode.length === 6
  }

  private requestVerificationCode(): void {
    this.countdown = 60
    // 这里补发码逻辑和倒计时逻辑
  }

  private performLogin(): void {
    // 这里补登录逻辑
  }

  build() {
    Column({ space: 20 }) {
      TextInput({ placeholder: '请输入手机号', text: this.phoneNumber })
        .type(InputType.PhoneNumber)
        .maxLength(11)
        .onChange((value: string) => {
          this.phoneNumber = value
        })

      Row({ space: 12 }) {
        TextInput({ placeholder: '请输入验证码', text: this.verificationCode })
          .type(InputType.ONE_TIME_CODE)
          .maxLength(6)
          .layoutWeight(1)
          .showError(this.errorMessage)
          .enableAutoFillAnimation(true)
          .onChange((value: string) => {
            this.verificationCode = value
            this.errorMessage = ''
            if (value.length === 6) {
              this.performLogin()
            }
          })

        Button(this.countdown > 0 ? `${this.countdown}s` : '获取验证码')
          .enabled(this.canSendCode())
          .onClick(() => {
            this.requestVerificationCode()
          })
      }

      Button('登录')
        .width('100%')
        .enabled(this.canLogin())
        .onClick(() => {
          this.performLogin()
        })
    }
    .padding(24)
  }
}

四、容易踩坑的点

第一个坑,是把验证码输入框继续当普通数字输入框用。验证码看起来大多是数字,但语义并不一样。Number 只表达数字输入,ONE_TIME_CODE 表达的是一次性验证码场景。项目里如果目标是做短信验证码、邮箱验证码、支付确认码,这两个类型不要混着用。

第二个坑,是把验证码当密码框处理。验证码输入通常需要用户可见,便于确认输入结果,所以不适合直接套密码隐藏逻辑。安全防护重点应该放在日志、内存和流程控制上,而不是把验证码显示成星号。

第三个坑,是忽略无障碍语义。验证码输入场景对屏幕阅读器来说,最好有明确提示。输入框的 placeholder 和 accessibilityText 应该把“验证码”“位数”“数字输入”这些关键信息说清楚,别让无障碍用户只能猜。

TextInput({ placeholder: '6位数字验证码', text: this.verificationCode })
  .type(InputType.ONE_TIME_CODE)
  .maxLength(6)
  .accessibilityText('验证码输入框,请输入6位数字验证码')

第四个坑,是兼容性判断做得太晚。ONE_TIME_CODE 是 API 20 新增能力,如果项目需要兼容更低版本系统,最好在输入组件适配层就把这件事统一处理掉。新版本走 ONE_TIME_CODE,旧版本再回退到普通数字输入。这样页面层不需要知道太多细节,后面维护会轻很多。

总结

鸿蒙 6 API 20 给 TextInput 补进来的 ONE_TIME_CODE,看起来只是多了一个输入类型,真正改变的是验证码输入这类场景终于有了明确的系统语义。系统知道这是验证码输入之后,输入法优化、自动填充协同、无障碍提示这些能力才有了稳定的基础。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐