| <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> |