Web 端实现"输入验证码"的效果

379 阅读1分钟

项目中用到了,有一些难点, 重新实现,记录一下

1.代码

template

<template>
  <div class="container">
    <div class="bind-phone">
     <div class="bind-phone-title">{{ title }}</div>
     <div class="bind-phone-content">
      <swiper :options="swiperOption" ref="swiper">
        <swiper-slide>
          <div class="choose-area">
            <div class="choose-area-item" @click="selectItem(item, index)"
            v-for="(item, index) in list" :key="index">
              <div class="left">
                <img :src="require(`../assets/img/${currenCodeIndex === index ? 
                'selected': 'unselect'}.png`)"> <span>{{ item.area }}</span>
              </div>
              <div class="right">{{ item.code }}</div>
            </div>
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="input-hone-number-content">
            <div class="bind-phone-input-number">
              <div class="select-code" @click="selectCode">
                <span>{{ currentCode }} </span><img src="../assets/img/arrow.png">
              </div>
              <div class="input-box">
                <input
                  type="text"
                  v-model="phone"
                  placeholder="输入手机号"
                  autofocus='autofocus'
                >
                <img src="../assets/img/close.png" v-if="phone.length >= 8" 
                @click="phone = ''">
              </div>
            </div>
            <div :class="['get-verify-code', { 'get-verify-code-available': 
            phone.length >= 8}]" @click="getVerifyCode">
              获取验证码
            </div>
          </div>
        </swiper-slide>
        <swiper-slide>
          <div class="verify-code-content">
            <div class="input-box">
              <div :class="['input-box-item', {'input-box-item-active':
              +verifyCode.length === item -1} ]" v-for="item in 6" :key="item">
                {{ verifyCode.length >= item ? verifyCode[item - 1] : '' }}
              </div>
              <input
                class="input-verify-code"
                type="text"
                v-model="verifyCode"
                autofocus='autofocus'
                ref="inputVerifyCode"
                inputMode="numeric"
                autoComplete="one-time-code"
                maxLength="6"
                pattern="[0-9]*"
                :class="`${ isAnd ? '': 'hide-cursor'}`"
              >
            </div>
            <div class="verify-code-status">验证码错误</div>
            <div class="confirm-verify-code" @click="getVerifyCode">{{ timeLeft ?
            `重新发送 (${timeLeft}s)` : '重新发送'}}</div>
          </div>
        </swiper-slide>
      </swiper>
     </div>
    </div>
  </div>
</template>

js

<script>
export default {
  data() {
    return {
      swiperOption: {
        loop: false,
        allowTouchMove: true,
        initialSlide: 1,
      },
      currentCode: '+86',
      currenCodeIndex: 0,
      phone: '',
      title: '绑定领奖手机号',
      list: [
        { area: '中国' , code: '+86'},
        { area: '印尼', code: '+84'}
      ],
      verifyCode: '',
      timeLeft: 0, // 60秒倒计时
      verifyCodeText: '验证码已发送'
    }
  },
  methods: {
    focusInput() {
      this.$refs.inputVerifyCode.focus();
    },
    selectCode() {
      this.$refs.swiper.swiper.slidePrev();
    },
    selectItem(item, index) {
      if (this.selected) return
      this.selected = true // 选中后短时间内,300ms不能切换
      this.currentCode = item.code
      this.currenCodeIndex = index
      setTimeout(() => {
        this.$refs.swiper.swiper.slideNext();
        this.selected = false 
      }, 300)
    },
    getVerifyCode() {
      // 第二个getVerifyCode如果在倒计时不能进行发送
      if (this.timeLeft) return
      this.timeLeft = 60
      // 开始调用获取验证码的接口,成功之后
      // 倒计时
      this.countDown();
      this.$refs.swiper.swiper.slideNext();
      setTimeout(()=>{
        // 首次加载的时候input-verify-code会focus,但是切换的时候并没有加载需要手动focus
        this.focusInput();
      },200)
    },
    countDown() {
      clearTimeout(this.countDown.timer);
      if (this.timeLeft > 0) {
        this.countDown.timer = setTimeout(() => {
          this.timeLeft--;
          this.countDown();
        }, 1000);
      }
    },
    // 检验验证码是否正确
    checkVerifyCode() {
      if(this.checkVerifyCode.requesting) return ;
      this.checkVerifyCode.requesting = true;
      // 数据请求
      // 如果成功刷新页面的绑定的接口,显示绑定的状态
      // 如果不成功
      // this.verifyCodeText = '验证码错误'
      // promise.finally之后 this.checkVerifyCode.requesting = false
    }
  },
  computed:{
    isAnd() {
      return navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Adr') > -1
    }
  },
  watch: {
    // 如果大于11个数,后面的不能输入
    phone(val) {
      if (!/^\d{0,11}$/.test(val)) {
        this.$nextTick(() => {
          this.phone = val.replace(/\D/g, "").slice(0, 11);
        });
      }
    },
    // 通过对verifyCode的监控来判断用户输入的是否正确,如果大于6,只获取前6
    verifyCode(val) {
      // 如果是6个直接请求
      if (/^\d{6}$/.test(val)) {
        // 直接请求
        this.checkVerifyCode();
        return;
      }
      // 如果大于6个变成6个,还会触发一次上面的请求
      if (!/^\d{0,6}$/.test(val)) {
        this.$nextTick(() => {
          this.verifyCode = val.replace(/\D/g, "").slice(0, 6);
        });
      }
    },

  }
}
</script>

css

<style lang="scss" scoped>
.container {
  display: flex;
  align-items: center;
  justify-content: center;
  background: pink;
  width: 100%;
  height: 100vh;
}
.bind-phone {
  width: 315px;
  height: 284px;
  background: #FFFFFF;
  border-radius: 15px;
  overflow: hidden;
  .bind-phone-title {
    margin-top: 40px;
  }
  .bind-phone-content {
    .input-hone-number-content {
      margin-top: 40px;
      display: flex;
      flex-direction: column;
      align-items: center;
      .bind-phone-input-number {
        width: 275px;
        height: 50px;
        background: #F8FAFB;
        border-radius: 28px;
        overflow: hidden;
        display: flex;
        align-items: center;
        .select-code {
          text-align: left;
          margin-left: 20px;
          width: 100px;
          display: flex;
          align-items: center;
          img {
            width: 15px;
            height: 15px;
            vertical-align: middle;
          }
          span {
            vertical-align: middle;
          }
        }
        .input-box {
          position: relative;
        }
        .input-box img {
          position: absolute;
          right: 30px;
          top: 6px;
          width: 16px;
          height: 16px;
        }
        input {
          background: #F8FAFB;
          outline:none; // 去掉border样式1
          caret-color:#171717; // 光标的颜色
          height: 24px;
          border: none; // 去掉border样式2
          font-size: 17px;// 光标的大小
        }
        input::input-placeholder {
          color: #2C2C2C; // 设定placeholder颜色
        } 
      }
      .get-verify-code {
        width: 275px;
        height: 50px;
        background: #F8FAFB;
        border-radius: 30px;
        text-align: center;
        line-height: 50px;
        margin-top: 30px;
        color: #DFE5E9;
      }
      .get-verify-code-available {
        background-color: pink;
        color: black;
      }
    }
    .choose-area-item {
      display: flex;
      padding: 0px 30px;
      height: 40px;
      line-height: 40px;
      margin-top: 40px;
      .left {
        flex: 1;
        text-align: left;
        img {
          vertical-align: middle;
          width: 18px;
          height: 18px;
        }
        span {
          vertical-align: middle;
        }
      }
    }
    .verify-code-content {
      margin-top: 40px;
      padding: 0px 20px;
      position: relative;
      .input-box {
        display: flex;
        justify-content: space-between;
        .input-box-item {
          border-bottom: 2px solid #F2F3F6;
          width: 34px;
          height: 30px;
          position: relative;
          user-select: none;
          font-size: 20px;
          &.input-box-item-active:after{
            position: absolute;
            top: -3px;
            left: 2px;
            content: "";
            width: 2px;
            height: 25px;
            background: pink;
            border-radius: 1px;
            animation: cursor 0.9s infinite;
          }
        }
      
        .input-verify-code {
          outline:none; // 去掉border样式1
          border: none; // 去掉border样式2
          position: absolute;
          top: -25px;
          left:18px ;
          height: 25px;
          width: 280px;
          background-color: transparent;
          color: transparent;
          -webkit-tap-highlight-color: rgba(0,0,0,0); /*点击高亮的颜色*/
        }
      }
      .confirm-verify-code {
        width: 275px;
        height: 50px;
        background: pink;
        border-radius: 30px;
        text-align: center;
        line-height: 50px;
      }
      .hide-cursor {
        text-indent: -999em; /*文本向左缩进*/
        margin-right: -100%; /*输入框光标起始点向右移*/
        width: 200%; /*输入框增大一倍*/
        opacity: 0;
      }
      .verify-code-status {
        margin-top: 15px;
      }
      .confirm-verify-code {
        margin-top: 30px;
      }
    }
    @keyframes cursor {
      0%,
      100% {
        opacity: 0;
      }
      50% {
        opacity: 1;
      }
    }
  }
}
</style>

2.实现效果

测试14.gif

3.要注意的点

1.验证码的框通过6个div实现,输入操作通过无边框的input实现,对输入的数据做6个数字的处理,按照 verifyCode.length >= item ? verifyCode[item - 1] : ''的规则填入到div中
2.切换到输入验证码页面应该进行focus操作
3.ios上光标的兼容性问题
4.输入验证码调接口,通过watch来实现

如何引入移动端swiper,参考swiper,vue-awesome-swiper在移动端的使用

有问题一起交流哈~