写一个滑块组件

文章介绍了如何在React应用中使用腾讯云的滑块验证码SDK,包括动态引入JS,创建滑块验证码对象,以及处理验证成功后的afsCode。同时,提供了一个自定义的React组件`RiskCheck`,该组件实现了滑块验证的功能,包括加载、滑动验证和重试等状态的管理。
摘要由CSDN通过智能技术生成

引用腾讯云的滑块SDK

步骤1:动态引入验证码 JS

<script src="https://turing.captcha.qcloud.com/TCaptcha.js"></script>

new TencentCaptcha(CaptchaAppId, callback, options);

例子 

 const handleSubmit = async (values: API.LoginParams) => {
    let captchaId = "12346549879"; //腾讯滑块验证码appid
    //生成一个滑块验证码对象
    let captcha = new TencentCaptcha(captchaId, function (res: any) {
      if (res.ret === 0) {
        //验证模块
        afscodecheck(res.ticket, res.randstr);
      }
    });
    // 滑块显示
    captcha.show();

  };

通常腾讯云SDK接口成功后,会把这两个字段发给后端,然后后端会返回一个  afsCode 腾讯安全验证码服务(Anti Fraud Security Code)的缩写。 

再然后才是调登录接口,带着afsCode + 用户账户密码

参考:验证码 Web 客户端接入-接入指引-文档中心-腾讯云

----------------------

自定义

import React, { useState, useRef, useEffect } from 'react'
import { DoubleRightOutlined, CloseOutlined, CheckOutlined } from '@ant-design/icons'
import cx from 'classnames'
import './style'
import 'animate.css'

// function useEventListener(eventName, handler, element = window) {
//   // 创建一个 ref 来存储处理程序
//   const saveHandler = useRef<any>();

//   // 如果 handler 变化了,就更新 ref.current 的值。
//   // 这个让我们下面的 effect 永远获取到最新的 handler
//   useEffect(() => {
//     saveHandler.current = handler;
//   }, [handler]);

//   useEffect(
//     () => {
//       // 确保元素支持 addEventListener
//       const isSupported = element && element.addEventListener;
//       if (!isSupported) return;

//       // 创建事件监听调用存储在 ref 的处理方法
//       const eventListener = event => saveHandler.current(event);

//       // 添加事件监听
//       element.addEventListener(eventName, eventListener);

//       // 清除的时候移除事件监听
//       return () => {
//         element.removeEventListener(eventName, eventListener);
//       };
//     },
//     [eventName, element], // 如果 eventName 或 element 变化,就再次运行
//   );
// }

export interface IRiskCheckProps {
  xPoint: number
  yPoint: number
  imgId?: string
  imgUrl: string
  imgWidth?: number
  imgHeight?: number
  differ?: number
  shadowSize?: number
  successText?: React.ReactNode
  tipText?: React.ReactNode
  className?: string
  onStart?()
  onSuccess?(callback)
  onError?(callback)
  onReload?()
}

export enum RiskCheckStatus {
  LOADING = 0,
  READY = 1,
  SUCCESS = 2,
  ERROR = 3,
}

const createClipPath = (ctx, size: number, styleIndex: number) => {
  const styles = [
    [0, 0, 0, 0],
    [0, 0, 0, 1],
    [0, 0, 1, 0],
    [0, 0, 1, 1],
    [0, 1, 0, 0],
    [0, 1, 0, 1],
    [0, 1, 1, 0],
    [0, 1, 1, 1],
    [1, 0, 0, 0],
    [1, 0, 0, 1],
    [1, 0, 1, 0],
    [1, 0, 1, 1],
    [1, 1, 0, 0],
    [1, 1, 0, 1],
    [1, 1, 1, 0],
    [1, 1, 1, 1],
  ]
  const style = styles[styleIndex]
  const r = 0.1 * size
  ctx.save()
  ctx.beginPath()
  // left
  ctx.moveTo(r, r)
  ctx.lineTo(r, 0.5 * size - r)
  ctx.arc(r, 0.5 * size, r, 1.5 * Math.PI, 0.5 * Math.PI, style[0])
  ctx.lineTo(r, size - r)
  // bottom
  ctx.lineTo(0.5 * size - r, size - r)
  ctx.arc(0.5 * size, size - r, r, Math.PI, 0, style[1])
  ctx.lineTo(size - r, size - r)
  // right
  ctx.lineTo(size - r, 0.5 * size + r)
  ctx.arc(size - r, 0.5 * size, r, 0.5 * Math.PI, 1.5 * Math.PI, style[2])
  ctx.lineTo(size - r, r)
  // top
  ctx.lineTo(0.5 * size + r, r)
  ctx.arc(0.5 * size, r, r, 0, Math.PI, style[3])
  ctx.lineTo(r, r)

  ctx.clip()
  ctx.closePath()
}

const RiskCheck: React.FC<IRiskCheckProps> = (props) => {
  const [crlStatus, setCrlStatus] = useState<RiskCheckStatus>(RiskCheckStatus.LOADING)
  const [currentX, setCurrentX] = useState<number>(0)
  const [isMoviable, setIsMoviable] = useState<boolean>(false)
  let oldX = 0
  const fragmentSize = props.shadowSize
  const RiskBoxCanvas = useRef(null)
  const RiskShadowCanvas = useRef(null)

  const boxRef = useRef(null)
  const saveHandler = useRef<any>()

  useEffect(() => {
    renderImg()
  }, [props.imgUrl])

  const renderImg = () => {
    setCrlStatus(RiskCheckStatus.LOADING)

    const objImg = new Image()

    objImg.addEventListener('load', () => {
      const { xPoint, yPoint } = props
      const ctxShadow = (RiskShadowCanvas.current as HTMLCanvasElement).getContext('2d')
      const ctxBox = (RiskBoxCanvas.current as HTMLCanvasElement).getContext('2d')

      const styleIndex = Math.floor(Math.random() * 16)

      createClipPath(ctxBox, fragmentSize, styleIndex)
      createClipPath(ctxShadow, fragmentSize, styleIndex)

      const x = (xPoint / props.imgWidth) * objImg.width
      const y = (yPoint / props.imgHeight) * objImg.height

      const transformSize = (fragmentSize / props.imgWidth) * objImg.width
      ctxBox.drawImage(objImg, x, y, transformSize, transformSize, 0, 0, fragmentSize, fragmentSize)

      ctxShadow.fillStyle = 'rgba(0, 0, 0, 1)'
      ctxShadow.fill()

      ctxShadow.restore()
      ctxBox.restore()

      setCrlStatus(RiskCheckStatus.READY)
    })

    objImg.src = props.imgUrl
  }

  const onMoveStart = (e: React.MouseEvent) => {
    if (crlStatus !== RiskCheckStatus.READY) {
      return
    }
    e.persist()
    // 记录滑动开始时的绝对坐标x
    setIsMoviable(true)
    props.onStart()
  }

  const onMoving = (e: React.MouseEvent) => {
    if (crlStatus !== RiskCheckStatus.READY || !isMoviable) {
      return
    }
    e.persist()
    const distance = e.clientX - boxRef.current.getBoundingClientRect().left - fragmentSize
    let currX = oldX + distance

    const minX = 0
    const maxX = props.imgWidth - fragmentSize
    currX = currX < minX ? 0 : currX > maxX ? maxX : currX
    setCurrentX(currX)
  }

  const onMoveEnd = () => {
    if (crlStatus !== RiskCheckStatus.READY || !isMoviable) {
      return
    }
    oldX = currentX
    setIsMoviable(false)

    const isMatch = Math.abs(currentX - props.xPoint) < props.differ
    if (isMatch) {
      setCrlStatus(RiskCheckStatus.SUCCESS)
      setCurrentX(props.xPoint)
      props.onSuccess(onReset)
      // this.props.onMatch()
    } else {
      setCrlStatus(RiskCheckStatus.ERROR)
      setTimeout(() => {
        onReset()
        props.onError(onReset)
      }, 1000)
    }
  }

  // 如果 handler 变化了,就更新 ref.current 的值。
  // 这个让我们下面的 effect 永远获取到最新的 handler
  useEffect(() => {
    saveHandler.current = onMoveEnd
  }, [onMoveEnd])

  useEffect(
    () => {
      // 确保元素支持 addEventListener
      const isSupported = window && window.addEventListener
      if (!isSupported) return

      // 创建事件监听调用存储在 ref 的处理方法
      const eventListener = (event) => saveHandler.current(event)

      // 添加事件监听
      window.addEventListener('mouseup', eventListener)

      // 清除的时候移除事件监听
      return () => {
        window.removeEventListener('mouseup', eventListener)
      }
    },
    [onMoveEnd], // 如果 eventName 或 element 变化,就再次运行
  )

  // useEventListener('mouseup', onMoveEnd);

  const onReset = () => {
    const timer = setTimeout(() => {
      oldX = 0
      setCurrentX(0)
      setCrlStatus(RiskCheckStatus.READY)
      clearTimeout(timer)
    }, 0)
  }

  const onReload = () => {
    if (crlStatus !== RiskCheckStatus.READY && crlStatus !== RiskCheckStatus.SUCCESS) {
      return
    }
    const ctxShadow = (RiskShadowCanvas.current as HTMLCanvasElement).getContext('2d')
    const ctxBox = (RiskBoxCanvas.current as HTMLCanvasElement).getContext('2d')

    // 清空画布
    ctxShadow.clearRect(0, 0, fragmentSize, fragmentSize)
    ctxBox.clearRect(0, 0, fragmentSize, fragmentSize)

    setIsMoviable(false)
    setCrlStatus(RiskCheckStatus.LOADING)
    props.onReload()
  }
  return (
    <div
      className={cx(
        'god-riskcheck-container',
        crlStatus === RiskCheckStatus.ERROR ? 'animate__animated animate__shakeX' : '',
        props.className,
      )}
      ref={boxRef}
    >
      <div
        className="god-riskcheck-img-container"
        style={{
          width: props.imgWidth,
          height: props.imgHeight,
          backgroundImage: `url("${props.imgUrl}")`,
        }}
      >
        <canvas
          className="god-riskcheck-canvas"
          style={{
            left: currentX + 'px',
            top: props.yPoint + 'px',
            zIndex: 20,
          }}
          ref={RiskBoxCanvas}
          width={fragmentSize}
          height={fragmentSize}
        ></canvas>
        <canvas
          className="god-riskcheck-canvas"
          ref={RiskShadowCanvas}
          width={fragmentSize}
          height={fragmentSize}
          style={{
            left: props.xPoint + 'px',
            top: props.yPoint + 'px',
            zIndex: 10,
          }}
        ></canvas>
      </div>

      <div
        className="god-riskcheck-slider"
        style={{ width: props.imgWidth }}
        onMouseMove={onMoving}
        onMouseLeave={onMoveEnd}
      >
        <div className="god-riskcheck-slider-tip">{props.tipText}</div>
        <div
          className={cx(
            'god-riskcheck-slider-button',
            RiskCheckStatus.ERROR === crlStatus && 'god-riskcheck-error',
          )}
          onMouseDown={onMoveStart}
          onMouseUp={onMoveEnd}
          style={{ left: currentX + 'px' }}
        >
          {crlStatus === RiskCheckStatus.READY && <DoubleRightOutlined />}
          {crlStatus === RiskCheckStatus.ERROR && <CloseOutlined />}
          {crlStatus === RiskCheckStatus.SUCCESS && <CheckOutlined />}
        </div>
      </div>
    </div>
  )
}

RiskCheck.defaultProps = {
  // 图片路径
  imgUrl: '',
  // 图片宽高
  imgWidth: 200,
  imgHeight: 100,
  // 生成的阴影位置,基于左上角
  xPoint: 100,
  yPoint: 30,

  // 位置匹配允许的误差值
  differ: 5,
  // 移动块的大小
  shadowSize: 60,

  // 最外层容器class
  className: '',
  // 提示语
  tipText: '按住滑块, 拖动拼图',
  // 废弃
  successText: '恭喜你,你打败了地球上100%的人!',

  onStart: () => {},
  onError: () => {},
  onReload: () => {},
  onSuccess: () => {},
}

export default RiskCheck

微信小程序中的 Swiper 组件一个用于创建轮播图或者滑动列表的常用模块,它允许你轻松地实现滚动效果。要在小程序中使用 Swiper,你需要按照以下步骤操作: 1. 导入 Swiper 组件:在需要使用 Swiper 的页面的 WXML 文件中,通过 `import` 或者 `<import>` 标签导入组件库,如: ```html <import src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js" /> <view> <wxs:import name="swiper" src="/path/to/your/components/swiper.swiper.wxml" /> <!-- 使用自定义组件路径 --> </view> ``` 2. 定义 Swiper 组件:在 WXML 中,你可以创建一个新的 Swiper 组件并配置其属性,例如显示多少张图片、切换速度等。示例如下: ```html <swiper indicator-dots="{{showDots}}" interval="{{intervalTime}}" autoplay="{{autoplay}}"> <block wx:for="{{items}}"> <swiper-item> <image src="{{item.src}}" style="width: 100%; height: 100%" /> </swiper-item> </block> </swiper> ``` 其中,`items` 是包含所有滑动内容的数组,`indicator-dots` 控制是否显示指示点,`interval-time` 设置切换时间间隔,`autoplay` 是否自动播放。 3. 配置 JavaScript 脚本:在对应的 JS 文件中,设置 Swiper 的数据以及事件处理。例如,初始化 Swiper 和控制滑动的行为: ```javascript Page({ data: { items: [ {src: 'image1.jpg'}, {src: 'image2.jpg'}, {src: 'image3.jpg'} ], showDots: true, intervalTime: 2000, autoplay: true }, onLoad() { this.createSwiper(); }, createSwiper() { wx.createSelectorQuery().select('#swiper').boundingClientRect((rect) => { this.setData({ swiperOption: { width: rect.width, height: rect.height, indicators: this.data.showDots ? ['dot'] : [], autoplay: this.data.autoplay, interval: this.data.intervalTime } }); let mySwiper = new wx.Swiper({ ...this.data.swiperOption, indicatorDots: this.data.showDots, // 更多选项... }); }); } }) ``` 4. CSS 样式调整:如果需要,你还可以添加一些 CSS 样式来自定义 Swiper 的样式,比如改变滑块的大小、位置等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值