小麦子 发表于 2024-12-18 14:27:09

vue6格验证码




根据项目需求,可将发送验证码功能抽离成单独的组件使用。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="screen-orientation" content="portrait">
<meta name="full-screen" content="no">
<meta name="x5-orientation" content="portrait">
<meta name="x5-fullscreen" content="false">
<meta name="viewport" content="initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no,width=device-width,viewport-fit=cover">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<meta name="format-detection" content="telephone=no">
<title>vue-6格验证码-支持自动填充</title>
<script src="https://www.jq22.com/demo/vue6pwd202209080005/js/rem.js"></script>
<link rel="stylesheet" href="https://www.jq22.com/demo/vue6pwd202209080005/css/index.css">
</head>
<body>
<div id="wrapper" v-cloak>
    <div class="content">
      <h2>{{ this.phone | mobileToStar }}</h2>
      <div class="input-box code">
      <div class="input-content" @keydown="keydown" @keyup="keyup" @paste="paste" @mousewheel="mousewheel" @input="inputEvent">
          <input
            ref="firstinput"
            v-model.trim.number="input"
            class="code-text"
            max="9"
            min="0"
            maxlength="6"
            data-index="0"
            type="tel"
            :class="{'btnLight': iptFocus || input}"
            @focus="handleFocus(1)"
            @blur="handleBlur(1)"
          >
          <input v-model.trim.number="input" class="code-text" max="9" min="0" maxlength="6" data-index="1" type="tel" :class="{'btnLight': iptFocus || input}" @focus="handleFocus(2)" @blur="handleBlur(2)">
          <input v-model.trim.number="input" class="code-text" max="9" min="0" maxlength="6" data-index="2" type="tel" :class="{'btnLight': iptFocus || input}" @focus="handleFocus(3)" @blur="handleBlur(3)">
          <input v-model.trim.number="input" class="code-text" max="9" min="0" maxlength="6" data-index="3" type="tel" :class="{'btnLight': iptFocus || input}" @focus="handleFocus(4)" @blur="handleBlur(4)">
          <input v-model.trim.number="input" class="code-text" max="9" min="0" maxlength="6" data-index="4" type="tel" :class="{'btnLight': iptFocus || input}" @focus="handleFocus(5)" @blur="handleBlur(5)">
          <input v-model.trim.number="input" class="code-text" max="9" min="0" maxlength="6" data-index="5" type="tel" :class="{'btnLight': iptFocus || input}" @focus="handleFocus(6)" @blur="handleBlur(6)">
      </div>
      </div>
      <div class="form-button">
      <button
          v-show="showTime"
          type="button"
          class="btn-submit"
          @click="sendCaptcha"
      >重新发送</button>

      <button
          v-show="!showTime"
          type="button"
          class="btn-submit active"
      ><span>重新发送({{ count }}s)</span></button>
      </div>
    </div>
    <!-- <Captcha ref="captchaLogin" @complete="handleLogin" />
      handleLogin(value) {
      this.loginForm.smsCode = value.join('')
      },
      handleClear() {
      this.loginForm.smsCode = ''
      this.$refs['captchaLogin'].clear()
      this.$refs['captchaLogin'].focus()
      },
    -->
</div>
<script type="text/javascript" src="https://www.jq22.com/jquery/jquery-1.10.2.js"></script>
<script type="text/javascript" src="https://www.jq22.com/demo/vue6pwd202209080005/js/vue.min.js"></script>
<script>
    // problem: 点击短信通知栏的“复制”后,6位验证码会自动显示在软键盘左上角,点击一下即完成填充,某些 android 手机不生效,这个事件是 inputEvent,是先填充内容再进行处理的,但是输入框的 input maxlength 是1,如果更改该 maxlength 值为6可以实现填充,但是,如果从中间输入,最后一个输入框会输入最多6个值,输入完成后自动填充了最后一个输入框的6个值了。
    // 如果有更好的解决办法,欢迎留言交流!

    /*
      toast 提示:绑定在window上,使用方法:showMessage('提示信息', time)
    */
    window.showMessage = function( $msg, $time, callback ) {
      $time = $time === 0 ? 0 : ($time || 1000);
      var oDiv = document.createElement("div");
      oDiv.setAttribute("id", "toastTip");
      // var oBody = document.getElementsByClassName('wrapper');
      var oBody = document.getElementsByTagName('body');
      oBody.append(oDiv);
      $('#toastTip').text( $msg );
      $('#toastTip').fadeIn();
      setTimeout(function() {
      callback()
      $('#toastTip').fadeOut(function() {
          $('#toastTip').remove();
      });
      }, $time);
    }

    var mainVM = new Vue({
      el: "#wrapper",
      data: {
      pasteResult: [], // 存放粘贴进来的数字
      iptFocus: [],
      phone: 18800001111,
      smsCode: '',
      countryCode: '+86',
      showTime: true,
      count: '', // 初始化次数
      timer: null,
      },
      filters: {
      /**
       * @param {string} 18310746666
       * @returns {string} 183****6666 前3后4,中间4星
       */
      mobileToStar (value) {
          if (!value) return null
          let reg = '', str = ''
          if (!isNaN(value)) value = value.toString()
          let len = value.length
          if (len === 4) {
            reg = /^({1})({2})({1})$/
            str = value.replace(reg, '$1**$3')
          } else if (len === 5) {
            reg = /^({1})({3})({1})$/
            str = value.replace(reg, '$1***$3')
          } else if (len > 5 && len < 21) { // 隐藏中间4位
            var num = Math.floor(len / 2) - 2
            str = value.substr(0, num) + '****' + value.substr(num + 4)
          } else if (len > 20) {
            str = value.substr(0, 6) + '****' + value.substr(len - 6)
          }
          return str
      }
      },
      computed: {
      input() {
          return this.pasteResult.length === 6 ? this.pasteResult : ['', '', '', '', '', '']
      }
      },
      mounted() {
                                document.getElementById('wrapper').style.display = 'block';
      // 这里如果是从上一页过来可以使用 session 取出上一页传递过来的 phone
      // 可以从上一页发送验证码,也可以在此页面发送验证码
      if (this.validMobile()) {
          const TIME_COUNT = 60 // 更改倒计时时间
          if (!this.timer) {
            this.count = TIME_COUNT
            this.showTime = false
            this.timer = setInterval(() => {
            if (this.count > 1 && this.count <= TIME_COUNT) {
                this.count--
            } else {
                this.showTime = true
                clearInterval(this.timer) // 清除定时器
                this.timer = null
            }
            }, 1000)
          }
      }

      this.focus()
      this.$on('complete', this.handleComplete)
      },
      methods: {
      sendCaptcha() { // 发送验证码
          var phonePre = this.countryCode.replace(/^\+/, '')
          if (this.validMobile()) {
            this.handleClear()
            const TIME_COUNT = 60 // 更改倒计时时间
            if (!this.timer) {
            this.count = TIME_COUNT
            this.showTime = false
            this.timer = setInterval(() => {
                if (this.count > 1 && this.count <= TIME_COUNT) {
                  this.count--
                } else {
                  this.showTime = true
                  clearInterval(this.timer) // 清除定时器
                  this.timer = null
                }
            }, 1000)
            }
          }
      },
      handleComplete(value) {
          this.smsCode = value.join('')
          if (this.validCaptcha()) {
            showMessage('登录', 1800)
          }
      },
      handleClear() {
          this.smsCode = ''
          this.clear()
          this.focus()
      },
      // 验证手机号
      validMobile(hasToast) {
          const reg = /^1{9}$/
          if (reg.test(this.phone)) {
            return true
          } else {
            this.errorMsg = '请输入正确的手机号'
            if (hasToast) {
            // this.$toast(this.errorMsg)
            showMessage(this.errorMsg, 1800)
            }
            return false
          }
      },
      // 验证验证码
      validCaptcha() {
          var reg = /\d{6}/
          if (reg.test(this.smsCode)) {
            return true
          } else {
            this.errorMsg = '请输入正确的验证码'
            let that = this
            showMessage(this.errorMsg, 1800, () => {
            this.handleClear()
            });
            // this.$toast({
            //   message: that.errorMsg,
            //   onClose() {
            //   that.handleClear()
            //   }
            // })
            return false
          }
      },
      
      focus() {
          this.$nextTick(() => {
            this.$refs.firstinput.focus()
          })
      },
      clear() {
          this.pasteResult = []
      },
      handleBlur(index) {
          this.iptFocus = false
          this.$set(this.iptFocus, index, this.iptFocus)
      },
      handleFocus(index) {
          this.iptFocus = true
          this.$set(this.iptFocus, index, this.iptFocus)
      },
      // inputEvent (e) {
      //   var index = e.target.dataset.index * 1;
      //   var el = e.target;
      //   this.$set(this.input, index, el.value.slice(0, 1))
      // },
      inputEvent(e) {
          var index = e.target.dataset.index * 1
          var el = e.target
   
          var elVal = el.value
          var elStr = elVal.toString()
      
          if (elStr.length === 6 ) {
            var reg = /^\d{6}/
            if (reg.test(elStr)) {
            let v = elStr.match(reg)
            v = v
            this.pasteResult = [...v]
            this.$emit('complete', this.input)
            return false
            } else {
            this.pasteResult = elStr.split('')
            document.activeElement.blur()
            this.$emit('complete', this.input)
            }
          }
   
          var val = el.value.slice(0, 1)
          if (val) {
            this.$set(this.input, index, val)
          } else {
            if (this.input === '') {
            this.$set(this.input, index, '0')
            setTimeout(() => {
                this.$set(this.input, index, '')
            }, 0)
            }
          }
      },
      keydown(e) {
          var index = e.target.dataset.index * 1
          var el = e.target
          if (e.key === 'Backspace') {
            if (this.input.length > 0) {
            this.$set(this.input, index, '')
            } else {
            if (el.previousElementSibling) {
                el.previousElementSibling.focus()
                this.$set(this.input, index - 1, '')
            }
            }
          } else if (e.key === 'Delete') {
            if (this.input.length > 0) {
            this.$set(this.input, index, '')
            } else {
            if (el.nextElementSibling) {
                this.$set(this.input, index = 1, '')
            }
            }
            if (el.nextElementSibling) {
            el.nextElementSibling.focus()
            }
          } else if (e.key === 'Home') {
            el.parentElement.children && el.parentElement.children.focus()
          } else if (e.key === 'End') {
            el.parentElement.children && el.parentElement.children.focus()
          } else if (e.key === 'ArrowLeft') {
            if (el.previousElementSibling) {
            el.previousElementSibling.focus()
            }
          } else if (e.key === 'ArrowRight') {
            if (el.nextElementSibling) {
            el.nextElementSibling.focus()
            }
          } else if (e.key === 'ArrowUp') {
            if (this.input * 1 < 9) {
            this.$set(this.input, index, (this.input * 1 + 1).toString())
            }
          } else if (e.key === 'ArrowDown') {
            if (this.input * 1 > 0) {
            this.$set(this.input, index, (this.input * 1 - 1).toString())
            }
          }
      },
      keyup(e) {
          var index = e.target.dataset.index * 1
          var el = e.target
          var elVal = el.value
          var elStr = elVal.toString()
         
          if (elStr.length === 6 ) {
            var reg = /^\d{6}/
            if (reg.test(elStr)) {
            let v = elStr.match(reg)
            v = v
            this.pasteResult = [...v]
            this.$emit('complete', this.input)
            return false
            } else {
            this.pasteResult = elStr.split('')
            document.activeElement.blur()
            this.$emit('complete', this.input)
            }
          }
   
          var val = el.value.slice(0, 1)
          // if (/Digit|Numpad/i.test(e.code)) {
          // digit数字;numpad是指整个小键盘的所有键
          if ((/Digit|Numpad/i.test(e.code) || e.isTrusted) && val) {
            // this.$set(this.input, index, e.code.replace(/Digit|Numpad/i, ''));
            el.nextElementSibling && el.nextElementSibling.focus()
            if (index === 5) {
            // 注意:android ios keyup事件不同,android获取值是keydown
            if (this.input.join('').length === 6) {
                document.activeElement.blur()
                this.$emit('complete', this.input)
            }
            } else {
            if (this.input.join('').length === 6) {
                document.activeElement.blur()
                this.$emit('complete', this.input)
            }
            }
          } else {
            if (this.input === '') {
            // this.$set(this.input, index, '');
            this.$set(this.input, index, '0')
            setTimeout(() => {
                this.$set(this.input, index, '')
            }, 0)
            }
          }
      },
      mousewheel(e) {
          var index = e.target.dataset.index
          if (e.wheelDelta > 0) {
            if (this.input * 1 < 9) {
            this.$set(this.input, index, (this.input * 1 + 1).toString())
            }
          } else if (e.wheelDelta < 0) {
            if (this.input * 1 > 0) {
            this.$set(this.input, index, (this.input * 1 - 1).toString())
            }
          } else if (e.key === 'Enter') {
            if (this.input.join('').length === 6) {
            document.activeElement.blur()
            this.$emit('complete', this.input)
            }
          }
      },
      paste(e) {
          e.clipboardData.items.getAsString(str => {
            if (str.toString().length === 6) {
            this.pasteResult = str.split('')
            document.activeElement.blur()
            this.$emit('complete', this.input)
            }
          })
      }
      }
    })
</script>
</body>
</html>

页: [1]
查看完整版本: vue6格验证码