h5 自动注入验证码

4 阅读1分钟
 <div class="input-box-wrap">
 // 主要是用来获取自动填入的验证码(核心)
  //id="single-factor-code-text-field"
    //    autocomplete="one-time-code"
      //  inputMode="numeric"
      <input
        type="text"
        pattern="[0-9]*"
        id="single-factor-code-text-field"
        autocomplete="one-time-code"
        inputMode="numeric"
        v-maxlength="4"
        ref="hideInputRef"
        @input="handleHideInput"
      />
    </div>
    // 四个验证码数字
    <div class="vcode-inputs" v-if="vcodeSended" @click="handleClickVCode">
      <input v-for="(item, index) in 4" :key="item" type="number"
      pattern="\d*" v-maxlength="1" ref="vcode"
      @keyup.prevent="vcodekeydown($event, index)"
  >
<script>
export default {
  data () {
    return {
      ac: null
    }
  },
  methods: {
    handleClickVCode () {
      this.$refs.hideInputRef.focus()
    },
    handleHideInput () {
      const code = this.$refs.hideInputRef.value
      if (code && code.length >= 4) {
        this.fillcode(code)
      }
    },
    getCodeFrom (success) {
      if ('OTPCredential' in window) {
        // DOMContentLoaded 事件会等待 DOM树准备好
        this.ac = new AbortController()
        navigator.credentials.get({
          otp: { transport: ['sms'] },
          signal: this.ac.signal
        }).then(otp => {
          success && success(otp.code)
        }).catch(err => {
          this.showToast(JSON.stringify(err))
        })
      }
    },
    async sendCode () {
      try {
        this.showLoading = true
        const { $api, phone } = this
        await $api.vcode(phone, this.verifyParams)
        this.vcode = ''
        this.phoneLocked = true
        this.vcodeSended = true
        this.countdown()
        this.$nextTick(() => {
          const vcodeInputs = this.$refs.vcode
          vcodeInputs.forEach(item => {
            item.value = ''
          })
          // vcodeInputs[0].focus()
          this.$refs.hideInputRef.focus()
          this.getCodeFrom((code) => {
            this.fillcode(code)
          })
        })
      } catch (error) {
        this.showToast(error.error)
      } finally {
        this.showLoading = false
      }
    },
    fillcode (code) {
      if (code && code.length >= 4) {
        this.$refs.hideInputRef.value = code
        const codeArr = code.split('')
        const codeArr4 = codeArr.slice(0, 4)
        const vcodeInputs = this.$refs.vcode
        vcodeInputs.forEach((item, index) => {
          if (codeArr4[index]) {
            item.value = codeArr4[index]
            this.$$(vcodeInputs[index]).addClass('on')
          }
        })
        vcodeInputs[0].focus()
        this.vcode = code
        if (code && code.length >= 4) {
          this.signIn()
        }
        if (this.ac) this.ac.abort()
      }
    },
    async signIn () {
      const { vcode, phone, $api, $account, backPath, $listeners, $f7router } = this
      if (this.verifyPhone() && this.verifyCode()) {
        try {
          this.showLoading = true
          await $api.login({
            phone,
            code: vcode
          })
          this.$stat('all_login_succ')
          $account.retrieveAccount().then(res => {
            if (backPath) {
              $f7router.navigate(backPath, { clearPreviousHistory: true })
            } else if ($listeners.success) {
              this.$app.ready(app => {
                if (app.router.currentRoute.url) {
                  app.router.refreshPage()
                } else {
                  window.location.reload()
                }
                this.$emit('success')
              })
            } else {
              $f7router.navigate('/', { clearPreviousHistory: true })
            }
          })
        } catch (error) {
          this.showToast(error.error || error.msg)
        } finally {
          this.showLoading = false
        }
      }
    },
    verifyCode () {
      return this.vcode.length > 3
    },
    verifyPhone () {
      const isValid = /^1([38][0-9]|4[5-9]|5[0-3,5-9]|66|7[0-8]|9[89])[0-9]{8}$/.test(this.phone)
      if (!isValid) {
        this.showToast('手机号码格式错误')
      }
      return isValid
    },
    modifyPhone () {
      this.phoneLocked = false
      this.$nextTick(() => {
        this.$$(this.$refs.phoneInput).focus()
      })
    },
    vcodekeydown (event, index) {
      const vcodeInputs = this.$refs.vcode
      if ((event.which >= 48 && event.which <= 57) || event.which === 229) {
        let value = event.key
        if (Number.isNaN(parseInt(value))) {
          value = event.target.value
        }
        vcodeInputs[index].value = value
        if (index < 3 && value > -1) {
          this.$$(vcodeInputs[index + 1]).focus()
        } else {
          this.vcode = vcodeInputs.map(item => item.value).join('')
          if (this.vcode.length > 3) {
            this.signIn()
          }
        }
        this.$$(vcodeInputs[index]).addClass('on')
      } else if (event.which === 8) {
        vcodeInputs[index].value = ''
        if (index > 0) {
          this.$$(vcodeInputs[index - 1]).focus()
        }
        this.vcode = ''
        this.$$(vcodeInputs[index]).removeClass('on')
      }
    },
    showToast (text) {
      this.$f7.toast.create({
        text,
        position: 'center',
        cssClass: 'alk-login-toast',
        closeTimeout: 2000
      }).open()
    },
    countdown () {
      const timer = new this.$utils.Timer(1000, {
        start: true,
        loop: true
      }, () => {
        this.countdownSeconds = this.countdownSeconds - 1
        if (this.countdownSeconds === 0) {
          timer.stop()
          this.countdownSeconds = 60
        }
      })
    },
    reset () {
      this.phone = ''
      this.vcode = ''
      this.showLoading = false
      this.agreementChecked = false
      this.phoneLocked = false
      this.vcodeSended = false
      this.countdownSeconds = 60
      this.verifyParams = {}
      if (this.ac) {
        this.ac.abort()
        this.ac = null
      }
    },
    useVerify () {
      if (!this.verifyPhone() || this.vcodeDisabled) {
        return false
      }
    }
  }
}
</script>