<template> 
 | 
  <view class="tn-verification-code-class tn-verification-code"> 
 | 
    <view class="tn-code__container"> 
 | 
      <input class="tn-code__input" :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxLength" @input="getValue" /> 
 | 
      <view v-for="(item, index) in loopCharArr" :key="index"> 
 | 
        <view 
 | 
          class="tn-code__item" 
 | 
          :class="[{ 
 | 
            'tn-code__item--breathe': breathe && charArrLength === index, 
 | 
            'tn-code__item__box': mode === 'box', 
 | 
            'tn-code__item__box--active': mode === 'box' && charArrLength === index 
 | 
          }]" 
 | 
          :style="[itemStyle(index)]" 
 | 
        > 
 | 
          <view 
 | 
            v-if="mode !== 'middleLine'" 
 | 
            class="tn-code__item__line tn-code__item__line--placeholder" 
 | 
            :style="[placeholderLineStyle(index)]" 
 | 
          ></view> 
 | 
          <view 
 | 
            v-if="mode === 'middleLine' && charArrLength <= index" 
 | 
            class="tn-code__item__line tn-code__item__line--middle" 
 | 
            :class="[{ 
 | 
              'tn-code__item__line--bold': bold, 
 | 
              'tn-code__item--breathe': breathe && charArrLength === index, 
 | 
              'tn-code__item__line--active': charArrLength === index 
 | 
            }]" 
 | 
            :style="[lineStyle(index)]" 
 | 
          ></view> 
 | 
          <view 
 | 
            v-if="mode === 'bottomLine'" 
 | 
            class="tn-code__item__line tn-code__item__line--bottom" 
 | 
            :class="[{ 
 | 
              'tn-code__item__line--bold': bold, 
 | 
              'tn-code__item--breathe': breathe && charArrLength === index, 
 | 
              'tn-code__item__line--active': charArrLength === index 
 | 
            }]" 
 | 
            :style="[lineStyle(index)]" 
 | 
          ></view> 
 | 
          <block v-if="!dotFill"> 
 | 
            <text>{{ charArr[index] ? charArr[index] : '' }}</text> 
 | 
          </block> 
 | 
          <block v-else> 
 | 
            <text class="tn-code__item__dot">{{ charArr[index] ? '●' : '' }}</text> 
 | 
          </block> 
 | 
        </view> 
 | 
      </view> 
 | 
    </view> 
 | 
  </view> 
 | 
</template> 
 | 
  
 | 
<script> 
 | 
  export default { 
 | 
    name: 'tn-verification-code', 
 | 
    props: { 
 | 
      // 验证码的值 
 | 
      value: { 
 | 
        type: [String, Number], 
 | 
        default: '' 
 | 
      }, 
 | 
      // 最大输入长度 
 | 
      maxLength: { 
 | 
        type: Number, 
 | 
        default: 4 
 | 
      }, 
 | 
      // 显示模式 
 | 
      // box -> 盒子 bottomLine -> 底部横线 middleLine -> 中间横线 
 | 
      mode: { 
 | 
        type: String, 
 | 
        default: 'box' 
 | 
      }, 
 | 
      // 用圆点填充空白位置 
 | 
      dotFill: { 
 | 
        type: Boolean, 
 | 
        default: false 
 | 
      }, 
 | 
      // 字体加粗 
 | 
      bold: { 
 | 
        type: Boolean, 
 | 
        default: false 
 | 
      }, 
 | 
      // 字体大小 
 | 
      fontSize: { 
 | 
        type: [String, Number], 
 | 
        default: '' 
 | 
      }, 
 | 
      // 激活时颜色 
 | 
      activeColor: { 
 | 
        type: String, 
 | 
        default: '' 
 | 
      }, 
 | 
      // 未激活时颜色 
 | 
      inactiveColor: { 
 | 
        type: String, 
 | 
        default: '' 
 | 
      }, 
 | 
      // 输入框宽度,单位rpx 
 | 
      inputWidth: { 
 | 
        type: Number, 
 | 
        default: 80 
 | 
      }, 
 | 
      // 当前激活的item带呼吸效果 
 | 
      breathe: { 
 | 
        type: Boolean, 
 | 
        default: true 
 | 
      }, 
 | 
      // 自动获取焦点 
 | 
      focus: { 
 | 
        type: Boolean, 
 | 
        default: false 
 | 
      }, 
 | 
      // 隐藏原生键盘,当使用自定义键盘的时候设置该参数未true即可 
 | 
      disabledKeyboard: { 
 | 
        type: Boolean, 
 | 
        default: false 
 | 
      } 
 | 
    }, 
 | 
    computed: { 
 | 
      // 拆分要显示的字符 
 | 
      charArr() { 
 | 
        return this.valueModel.split('') 
 | 
      }, 
 | 
      // 当前输入字符的长度 
 | 
      charArrLength() { 
 | 
        return this.charArr.length 
 | 
      }, 
 | 
      // 输入框的个数 
 | 
      loopCharArr() { 
 | 
        return new Array(this.maxLength) 
 | 
      }, 
 | 
      itemStyle() { 
 | 
        return (index) => { 
 | 
          let style = {} 
 | 
          style.fontWeight = this.bold ? 'bold' : 'normal' 
 | 
          if (this.fontSize) { 
 | 
            style.fontSize = this.fontSize + 'rpx' 
 | 
          } 
 | 
          if (this.inputWidth) { 
 | 
            style.width = this.inputWidth + 'rpx' 
 | 
            style.height = this.inputWidth + 'rpx' 
 | 
            style.lineHeight = this.inputWidth + 'rpx' 
 | 
          } 
 | 
          if (this.inactiveColor) { 
 | 
            style.color = this.inactiveColor 
 | 
            style.borderColor = this.inactiveColor 
 | 
          } 
 | 
          if (this.mode === 'box' && this.charArrLength === index) { 
 | 
            style.borderColor = this.activeColor 
 | 
          } 
 | 
          return style 
 | 
        } 
 | 
      }, 
 | 
      placeholderLineStyle() { 
 | 
        return (index) => { 
 | 
          let style = {} 
 | 
          style.display = this.charArrLength === index ? 'block' : 'none' 
 | 
          if (this.inputWidth) { 
 | 
            style.height = (this.inputWidth * 0.5) + 'rpx' 
 | 
          } 
 | 
          return style 
 | 
        } 
 | 
      }, 
 | 
      lineStyle() { 
 | 
        return (index) => { 
 | 
          let style = {} 
 | 
          if (this.inactiveColor) { 
 | 
            style.backgroundColor = this.inactiveColor 
 | 
          } 
 | 
          if (this.charArrLength === index && this.activeColor) { 
 | 
            style.backgroundColor = this.activeColor 
 | 
          } 
 | 
          return style 
 | 
        } 
 | 
      } 
 | 
    }, 
 | 
    watch: { 
 | 
      value: { 
 | 
        handler(val) { 
 | 
          // 转换为字符串 
 | 
          val = String(val) 
 | 
          // 截掉超出的部分 
 | 
          this.valueModel = val.substring(0, this.maxLength) 
 | 
        }, 
 | 
        immediate: true 
 | 
      } 
 | 
    }, 
 | 
    data() { 
 | 
      return { 
 | 
        valueModel: '' 
 | 
      } 
 | 
    }, 
 | 
    methods: { 
 | 
      // 获取填写的值 
 | 
      getValue(e) { 
 | 
        const { 
 | 
          value 
 | 
        } = e.detail 
 | 
        this.valueModel = value 
 | 
        // 判断输入的长度是否超出了maxlength的值 
 | 
        if (String(value).length > this.maxLength) return 
 | 
        // 未达到maxlength之前,触发change事件,否则触发finish事件 
 | 
        this.$emit('change', value) 
 | 
        this.$emit('input', value) 
 | 
        if (String(value).length == this.maxLength) { 
 | 
          this.$emit('finish', value) 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
  } 
 | 
</script> 
 | 
  
 | 
<style lang="scss" scoped> 
 | 
   
 | 
  .tn-verification-code { 
 | 
    text-align: center; 
 | 
     
 | 
    .tn-code { 
 | 
      &__container { 
 | 
        display: flex; 
 | 
        flex-direction: row; 
 | 
        justify-content: center; 
 | 
        flex-wrap: wrap; 
 | 
        position: relative; 
 | 
      } 
 | 
       
 | 
      &__input { 
 | 
        position: absolute; 
 | 
        top: 0; 
 | 
        left: -100%; 
 | 
        width: 200%; 
 | 
        height: 100%; 
 | 
        text-align: left; 
 | 
        z-index: 9; 
 | 
        opacity: 0; 
 | 
        background: none; 
 | 
      } 
 | 
       
 | 
      &__item { 
 | 
        position: relative; 
 | 
        width: 80rpx; 
 | 
        height: 80rpx; 
 | 
        line-height: 80rpx; 
 | 
        display: flex; 
 | 
        flex-direction: row; 
 | 
        align-items: center; 
 | 
        justify-content: center; 
 | 
        margin: 10rpx 10rpx; 
 | 
        font-size: 60rpx; 
 | 
        font-weight: bold; 
 | 
        color: #838383; 
 | 
         
 | 
        &--breathe { 
 | 
          animation: breathe 2s infinite ease; 
 | 
        } 
 | 
         
 | 
        &__box { 
 | 
          border: 2rpx solid #AAAAAA; 
 | 
          border-radius: 6rpx; 
 | 
           
 | 
          &--active { 
 | 
            animation-timing-function: ease-in-out; 
 | 
            animation-duration: 1500ms; 
 | 
            animation-iteration-count: infinite; 
 | 
            animation-direction: alternate; 
 | 
            overflow: hidden; 
 | 
            border: 2rpx solid #01BEFF; 
 | 
          } 
 | 
        } 
 | 
         
 | 
        &__line { 
 | 
          position: absolute; 
 | 
          top: 50%; 
 | 
          left: 50%; 
 | 
          transform: translate(-50%, -50%); 
 | 
          background-color: #AAAAAA; 
 | 
           
 | 
          &--bold { 
 | 
            height: 4px !important; 
 | 
          } 
 | 
           
 | 
          &--placeholder { 
 | 
            display: none; 
 | 
            width: 2rpx; 
 | 
            height: 40rpx; 
 | 
          } 
 | 
           
 | 
          &--middle, &--bottom { 
 | 
            width: 80%; 
 | 
            height: 2px; 
 | 
            border-radius: 2px; 
 | 
          } 
 | 
          &--bottom { 
 | 
            top: auto !important; 
 | 
            bottom: 0; 
 | 
            transform: translateX(-50%) !important; 
 | 
          } 
 | 
           
 | 
          &--active { 
 | 
            background-color: #01BEFF !important; 
 | 
          } 
 | 
        } 
 | 
         
 | 
        &__dot { 
 | 
          font-size: 34rpx; 
 | 
          line-height: 34rpx; 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
  } 
 | 
   
 | 
  @keyframes breathe { 
 | 
    0% { 
 | 
      opacity: 0.3; 
 | 
    } 
 | 
     
 | 
    50% { 
 | 
      opacity: 1; 
 | 
    } 
 | 
     
 | 
    100% { 
 | 
      opacity: 0.3; 
 | 
    } 
 | 
  } 
 | 
</style> 
 |