多位验证码组件Captcha

效果图:
在这里插入图片描述

index.js

import React from 'react'
import styles from './index.less'
import { Input } from 'antd'
import PropTypes from 'prop-types'
import { remove, throttle } from 'lodash'
// 回撤 方向键左 方向键右 对应keycode 分别是 8 ,37 ,39
const BACK_SPACE = 8
const LEFT = 37
const RIGHT = 39

class Captcha extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      values: [], // 验证码的值
    }
    // throttle 目的是阻止chrome85版本+微软中文输入法联合造成的input number重复输入两次
    this.throttleHandleInputValue = throttle(this.handleInputValue, 100).bind(this)
  }

  /**
   * 清洗arr数据
   * @param arr
   * @returns {{size: number, values: *}}
   */
  cleanArray(arr) {
    let values = arr
    let empty = remove(values, function (n) {
      return !n
    })
    empty.forEach(item => {
      values.push('')
    })
    return { values: values, size: values.length - empty.length }
  }

  //验证码光标后移
  handleInputValue = (e, index) => {
    const { size, getValue } = this.props
    const { value = '' } = e.target
    if (!/^\d{1}$/.test(value)) {
      return
    }
    let values = [].concat(this.state.values)
    values[index] = value.slice(0, 1)
    let cleanArray = this.cleanArray(values)
    this.setState({ values: cleanArray.values })
    if (value && index < size - 1) {
      this[`focus${cleanArray.size}`].input.focus()
    }
    getValue(values.join(''))
  }
  //删除验证码
  handleDel = (e, index) => {
    const { getValue } = this.props
    const isBackSpaceKey = e.keyCode === BACK_SPACE
    const isLeftKey = e.keyCode === LEFT
    const isRightKey = e.keyCode === RIGHT
    if (isBackSpaceKey && e.target.value.length === 0) {
      this.reFocus(e, 'prev')
      let values = [].concat(this.state.values)
      values.splice(index, 1)
      values.push('')
      this.setState({ values })
      getValue(values.join(''))
    } else if (isBackSpaceKey && e.target.value.length === 1) {

      // todo:优化,让光标无论如何都在数值的右边
      if (e.target.selectionStart === 0) {
        // 调整光标位置
        e.target.selectionStart = 1
        e.target.selectionEnd = 1
      } else {
        // 回删
        this.reFocus(e, 'prev')
        let values = [].concat(this.state.values)
        values.splice(index, 1)
        values.push('')
        this.setState({ values })
        getValue(values.join(''))
      }
    } else if (isLeftKey) {
      this.reFocus(e, 'prev')
    } else if (isRightKey) {
      this.reFocus(e, 'next')
    }
  }
  /**
   * focus 到上一个或下一个input
   * @param e
   * @param type <string>(prev|next)
   */
  reFocus = (e, type) => {
    let ele = e.target.parentElement
    if (type === 'prev') {
      ele = ele.previousElementSibling && ele.previousElementSibling.querySelector('input')
    } else { //'next'
      ele = ele.nextElementSibling && ele.nextElementSibling.querySelector('input')
    }
    if (ele && ele.tagName.toLowerCase() === 'input') {
      ele.focus()
    }
  }

  componentWillReceiveProps(nextProps, nextContext) {
    if (nextProps.emptyValue && !this.props.emptyValue) {
      let values = new Array(this.props.size).fill('')
      this.setState({ values })
      this.props.getValue(values.join(''))
    }
  }

  render() {
    const { values } = this.state
    const { maxLength, size, wrapperStyle = {}, itemStyle = {} } = this.props
    let inputArray = new Array(size).fill('')
    return (
      <div className={styles.wrapper} style={wrapperStyle}>
        {
          inputArray.map((item, index) => {
            return <div className={styles.checkInputItem} style={itemStyle}>
              <Input
                ref={ref => {
                  this[`focus${index}`] = ref
                }}
                type={'tel'} // type 由number 改为tel,以允许selectionStart的使用
                value={values[index]}
                maxLength={maxLength}
                onKeyDown={maxLength ? e => this.handleDel(e, index) : null}
                onChange={e => {
                  e.persist()
                  this.throttleHandleInputValue(e, index)
                }}
              />
            </div>
          })
        }
      </div>
    )
  }

}

Captcha.propTypes = {
  size: PropTypes.number,// 验证码位数
  maxLength: PropTypes.number, //单格验证码长度
  wrapperStyle: PropTypes.object, // 整体样式
  itemStyle: PropTypes.object, // 单项样式
  getValue: PropTypes.func, //外部获取组件内的验证码
  emptyValue: PropTypes.bool, //是否清空当前验证码内容
}
Captcha.defaultProps = {
  size: 6,
  maxLength: 1,
  wrapperStyle: {},
  itemStyle: {},
  getValue: () => {},
  emptyValue: false,
}

export default Captcha


index.less

.wrapper {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  width: 100%;
  padding-right: 8px;
  padding-top: 4px;

  input::-webkit-outer-spin-button,
  input::-webkit-inner-spin-button {
    -webkit-appearance: none;
  }

  input[type="number"] {
    -moz-appearance: textfield;
  }

  .checkInputItem {
    //flex: 1
  }

  :global {
    .ant-input {
      padding: 0;
      text-align: center;
      border: unset;
      border-bottom: 1px #d9d9d9 solid;
      border-radius: unset;
      font-size: 16px;
      font-family: PingFangSC-Semibold, PingFang SC, Sans-serif;
      font-weight: 600;
      color: #1A1A1A;
    }

    .ant-input:focus {
      border-bottom: 1px #1890FF solid;
      box-shadow: unset;
    }

    .ant-input:hover {
      border-bottom: 1px #1890FF solid;
      box-shadow: unset;
    }
  }
}

使用


 <FormItem>
        <div className={styles.formItemMessage}>
          <FormattedMessage id={'login.captcha.placeholder'}
                            defauleMessage={'Captcha'}/>
        </div>
        <Row gutter={8}
             style={{ display: 'flex', alignItems: 'center' }}>
          <Col span={17}>
            {getFieldDecorator('captcha', {
              initialValue: '',
              rules: [{
                required: true,
                message: <FormattedMessage id={'login.captcha'}
                                           defaultMessage={'Please input Code!'}/>,
              }, {
                validator: (rule, value, callback) => {
                  if (value && value.length !== 6) {
                    callback(<FormattedMessage id={'login.captcha.error'}
                                               defaultMessage={'Captcha type error!'}/>)
                  }
                  callback()
                },
              }],
            })(
              <Captcha getValue={(value) => {
                this.props.form.setFieldsValue({ captcha: value })
              }} itemStyle={{ width: 30 }}/>,
            )}
          </Col>
          <Col span={8}>
            <Button
              disabled={count && count !== -1}
              className={`${styles.getCaptcha} ${count && count !== -1 && styles.getCaptchaDisabled}`}
              onClick={this.onGetCaptcha}
            >
              {count && count !== -1 ? `${count}s ${isCN ? '后可重发' : 'Resend'}` :
                (count === -1 ? <FormattedMessage id={'login.getCode'} defaultMessage={'Get Code'}/> :
                  <FormattedMessage id={'login.resend'} defaultMessage={'Resend'}/>)}
            </Button>
          </Col>
        </Row>
      </FormItem>
      

通过getValue 将验证码组件的值传出去


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值