ANTD react 手机号(验证码)登陆 + 账号登陆(图形验证码)

                  

这种页面可能是大家常用的,但重写比较费时间,之前没有搜到完整的,在这里自己总结一下,方面复用

代码:

     <LoginForm
              formRef={formRef}
              initialValues={{
                autoLogin: false
              }}
              onFinish={async values => {
                await handleSubmit(values as LoginParams)
              }}
            >
              <Tabs activeKey={type} onChange={setType} style={{ color: '#999999', fontSize: '16px' }}>
                <Tabs.TabPane key='account' tab={'账号登陆'} />
                <Tabs.TabPane key='mobile' tab={'手机登陆'} />
              </Tabs>

账号登陆:

  {type === 'account' && (
                <>
                  <ProFormText
                    style={{ width: '60%' }}
                    name='account'
                    fieldProps={{
                      size: 'large',
                      prefix: <UserOutlined className={styles.prefixIcon} />
                    }}
                    placeholder={'用户名:admin'}
                    rules={[
                      {
                        required: true,
                        message: '请输入用户名'
                      }
                    ]}
                  />
                  <ProFormText.Password
                    name='password'
                    fieldProps={{
                      size: 'large',
                      prefix: <LockOutlined className={styles.prefixIcon} />
                    }}
                    placeholder={'输入密码'}
                    rules={[
                      {
                        required: true,
                        message: '请输入密码'
                      }
                    ]}
                  />
                  <div style={{ display: 'flex' }} className={styles.inputThree}>
                    <ProForm.Item
                      name='captimg'
                      rules={[
                        {
                          required: true,
                          message: '验证码不能为空!'
                        },
                        {
                          validator(_, value) {
                            if (!value) {
                              return new Promise((resolve, reject) => {
                                reject('')
                              })
                            } else {
                              const status = childRef.current.validate(value)
                              return status
                                ? Promise.resolve()
                                : Promise.reject(new Error('验证码不正确'))
                            }
                          }
                        }
                      ]}
                    >
                      <Input
                        style={{ width: '200px', height: '40px', paddingLeft: '30px' }}
                        width='xs'
                        placeholder={'请输入验证码'}
                        className='site-form-item-icon'
                      />
                    </ProForm.Item>
                    <CaptchaInput cRef={childRef} />
                    // 验证码画布组件,注意不能写到ProForm组件内,会有影响
                    <IconFont type='icon-yanzhengma' className={styles.verify} />
                  </div>
                </>
              )}

验证码画布组件:

import * as React from 'react'

const size = 4
const verifycode = {
  width: '32%',
  height: '40px',
  marginLeft: '5%',
  display: 'inline-block',
  top: '0',
  right: '0'
}

export default ({ cRef }) => {
  const [options, setOptions] = React.useState({
    id: 'verifycode', // 容器Id
    canvasId: 'verifyCanvas', // canvas的ID
    width: 150, // 默认canvas宽度
    height: 47, // 默认canvas高度
    type: 'blend', // 图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
    code: '',
    numArr: '0,1,2,3,4,5,6,7,8,9'.split(','),
    letterArr: getAllLetter() })

  React.useImperativeHandle(cRef, () => ({
    validate: (value: any) => {
      const vcode = value?.toLowerCase()
      const v_code = options.code?.toLowerCase()
      if (vcode === v_code) {
        return true
      } else {
        return false
      }
    }
  }))

  React.useEffect(() => {
    _init()
    refresh()
  })

  function _init() {
    const con = document.getElementById(options.id)
    const canvas: any = document.createElement('canvas')
    options.width = con.offsetWidth > 0 ? con.offsetWidth : 150
    options.height = con.offsetHeight > 0 ? con.offsetHeight : 47
    canvas.id = options.canvasId
    canvas.width = options.width
    canvas.height = options.height
    canvas.style.cursor = 'pointer'
    canvas.innerHTML = '您的浏览器版本不支持canvas'
    con.appendChild(canvas)
    canvas.onclick = function() {
      refresh()
    }
  }

  function refresh() {
    options.code = ''
    const canvas: any = document.getElementById(options.canvasId)
    let ctx = null
    if (canvas.getContext) {
      ctx = canvas.getContext('2d')
    } else {
      return
    }
    ctx.clearRect(0, 0, options.width, options.height)
    ctx.textBaseline = 'middle'

    ctx.fillStyle = randomColor(180, 240)
    ctx.fillStyle = 'rgba(206, 244, 196)'// 背景色
    ctx.fillRect(0, 0, options.width, options.height)

    if (options.type == 'blend') { // 判断验证码类型
      var txtArr = options.numArr.concat(options.letterArr)
    } else if (options.type == 'number') {
      var txtArr = options.numArr
    } else {
      var txtArr = options.letterArr
    }

    for (let i = 1; i <= size; i++) {
      const txt = txtArr[randomNum(0, txtArr.length)]
      options.code += txt
      ctx.font = randomNum(options.height / 2, options.height) + 'px SimHei' // 随机生成字体大小
      ctx.fillStyle = randomColor(50, 160) // 随机生成字体颜色
      // ctx.fillStyle = "rgb(46, 137, 255)";//固定字体颜色
      ctx.shadowOffsetX = randomNum(-3, 3)
      ctx.shadowOffsetY = randomNum(-3, 3)
      ctx.shadowBlur = randomNum(-3, 3)
      ctx.shadowColor = 'rgba(0, 0, 0, 0.3)'
      const x = options.width / (size + 1) * i
      const y = options.height / 2
      const deg = randomNum(-30, 30)
      /** 设置旋转角度和坐标原点**/
      ctx.translate(x, y)
      ctx.rotate(deg * Math.PI / 180)
      ctx.fillText(txt, 0, 0)
      /** 恢复旋转角度和坐标原点**/
      ctx.rotate(-deg * Math.PI / 180)
      ctx.translate(-x, -y)
    }
    /** 绘制干扰线**/
    for (let i = 0; i < 4; i++) {
      ctx.strokeStyle = randomColor(40, 180)
      ctx.beginPath()
      ctx.moveTo(randomNum(0, options.width), randomNum(0, options.height))
      ctx.lineTo(randomNum(0, options.width), randomNum(0, options.height))
      ctx.stroke()
    }
    // 绘制干扰点
    // for (let i = 0; i < 100; i++) {
    //   ctx.fillStyle = randomColor(0, 255)
    //   ctx.beginPath()
    //   ctx.arc(randomNum(0, options.width), randomNum(0, options.height), 1, 0, 2 * Math.PI)
    //   ctx.fill()
    // }
  }

  /** 生成字母数组**/
  function getAllLetter() {
    const letterStr = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z'
    return letterStr.split(',')
  }
  /** 生成一个随机数**/
  function randomNum(min: number, max: number) {
    return Math.floor(Math.random() * (max - min) + min)
  }
  /** 生成一个随机色**/
  function randomColor(min: number, max: number) {
    const r = randomNum(min, max)
    const g = randomNum(min, max)
    const b = randomNum(min, max)
    return 'rgb(' + r + ',' + g + ',' + b + ')'
  }

  return (
    <div id='verifycode' style={verifycode} />
  )
}





手机号登陆

方法:
  const sendFn = () => {
    (formRef.current as any).validateFields(['mobile']).then((value) => {
      pchildref.current._childFn()
      loginCodeRun.run({
        mobile: value.mobile,
        event: 'admin:login_by_mobile'
      })
    }).catch(() => {
      return
    })
  }



渲染:   

 {type === 'mobile' && (
                <>
                  <ProFormText
                    fieldProps={{
                      size: 'large',
                      prefix: <MobileOutlined className={styles.prefixIcon} />
                    }}
                    name='mobile'
                    placeholder={'请输入手机号'}
                    rules={[
                      {
                        required: true,
                        message: '请输入手机号'
                      },
                      {
                        pattern: /^1\d{10}$/,
                        message: '手机号格式错误'
                      }
                    ]}
                  />
                  <div style={{ display: 'flex' }} className={styles.inputThree}>
                    <ProForm.Item
                      name='captcha'
                      rules={[
                        {
                          required: true,
                          message: '请输入验证码'
                        }
                      ]}
                    >
                      <Input
                        style={{ width: '200px', height: '40px', paddingLeft: '30px' }}
                        width='xs'
                        placeholder={'请输入验证码'}
                        className='site-form-item-icon'
                      />
                    </ProForm.Item>
                    <div onClick={sendFn}>
                      <CountDown ref={pchildref} codeStyle={codeStyle} />  
                    // 倒计时计数器组件
                    </div>
                    <IconFont type='icon-yanzhengma' className={styles.verify} />
                  </div>
                  <ProForm.Item />
                </>
              )}

倒计时计数器组件


import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'
import { Button } from 'antd'

type selfProps = {
  codeStyle: object;
};

const CountDown: React.FC<selfProps> = (props, ref) => {
  const intervalRef = useRef<any>(null)
  const [count, changeCount] = useState(0)
  const { codeStyle } = props // 样式

  // 组件卸载时清除计时器
  useEffect(() => {
    return () => {
      clearInterval(intervalRef.current)
    }
  }, [])

  useEffect(() => {
    if (count === 59) {
      intervalRef.current = setInterval(() => {
        changeCount((preCount) => preCount - 1)
      }, 1000)
    } else if (count === 0) {
      clearInterval(intervalRef.current)
    }
  }, [count])

  // 暴露的子组件方法,给父组件调用
  useImperativeHandle(ref, () => {
    return {
      _childFn() {
        changeCount(59)
      }
    }
  })

  return (
    <Button disabled={!!count} style={ codeStyle }>
      {count ? `${count} s后重新发送` : '发送验证码'}
    </Button>
  )
}

export default forwardRef(CountDown)

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值