LuoSong
LuoSong
Published on 2023-09-16 / 36 Visits
0
0

Taro小程序图片滑动验证

Taro小程序图片滑动验证

环境:taro v3.6.8taro-ui 3.1.0-beta.5

简介

组件适用于以base64为图片格式的滑动验证方式(示例以后台返回图片大小300*150px为基准)。

关闭验证码逻辑没有写进去只有一个测试性的操作入口,按照个人逻辑进行改造和调整样式参数即可。

组件逻辑:从后端获取背景图、拼图(残缺部分)、拼图据背景图上方的距离以及其他验证所需的参数,父组件调用子组件渲染。子组件内部也可调用父组件刷新逻辑对相关数据进行更改,用户操作完成后在父组件中验证操作,主要验证移动的X距离。父组件与后端交互判断,成功后关闭,不成功刷新子组件再操作。

db4a15d8d64a4af189c8e2cbe5cfba82.png

目录结构

|-- root
	|-- src
	|--	|-- components
	|		|-- ImageCode
	|			|-- index.jsx
	|			|-- index.scss
	|-- |-- page
			|-- hello
				|-- index.config.js
				|-- index.jsx
				|-- index.scss

图片滑块验证组件

components/ImageCode/index.jsx

import { View, Image, MovableView, MovableArea } from '@tarojs/components';
import './index.scss'
import { useState } from 'react'
import { useEffect } from 'react';
const ImageCode = (props) => {
  const {
    imageTop,
    pin,
    bak,
    onRefresh,
    onCheck,
    onClose,
  } = props;
  // MovableView为非受控组件,需要记录数值作为初始化时改变的参数
  const [sliderX, setSliderX] = useState(0);
  const [sliderTemp, setSliderTemp] = useState(0);
  useEffect(() => {
    setSliderTemp(0);
    setSliderX(0);
  }, [imageTop])
  const flushCode = () => {
    onRefresh();
    setSliderTemp(0);
    setSliderX(0);
  }
  const onCloseView = () => {
    onClose();
  }
  return (
    <View className='imageCodeBox'>
      <View className='headBox'>
        <View>请完成安全验证</View>
        <View className='actionBox'>
          <View className='flushBox' onClick={() => flushCode()}>刷新</View>
          <View onClick={() => onCloseView()}>X</View>
        </View>
      </View>
      <View className='imageContentBox'>
        <Image className='imageContent' src={bak} />
        <View className='smegma'></View>
      </View>
      <View className='dividerBox'></View>
      <MovableArea className='moveArea'>
        <MovableView
          className='moveView'
          onTouchEnd={(e) => {
            setSliderX(sliderTemp);
            onCheck(sliderTemp); // 送检
          }}
          inertia={false}
          direction='horizontal'
          onChange={(e) => setSliderTemp(e.detail.x)}
          friction={4}
          x={sliderX}
        >
          <View className='puzzleBox' style={{ marginTop: ${imageTop}px }}>
            <Image className='puzzle' src={pin} />
          </View>
          <View className='sliderBox'>
            <View className='slider'>→</View>
          </View>
        </MovableView>
      </MovableArea>
    </View>
  )
}
export default ImageCode;

components/ImageCode/index.scss

// 单位使用大写PX可以强制使用px,不参与变化
.imageCodeBox {
  width: 100%;
  padding: 10PX calc(50% - 150PX);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  .headBox {
    display: flex;
    justify-content: space-between;
    width: 100%;
    height: 30PX;
    .actionBox {
      display: flex;
      justify-content: space-between;
      width: 50PX;
      .flushBox {
        margin-right: 6PX;
      }
    }
  }
  .imageContentBox {
    width: 300PX;
    height: 150PX;
    position: relative;
    .imageContent {
      display: inline-block;
      width: 100%;
      height: 100%;
    }
    .smegma {
      position: absolute;
      top: 0;
      width: 100%;
      height: 100%;
      z-index: 10002;
    }
  }
  .dividerBox {
    width: 100%;
    height: 10PX;
    z-index: 10002;
  }
  .moveArea {
    width: 100%;
    height: 30PX;
    background-color: #F7F7F7;
    position: relative;
    border: 0.5PX solid #eee;
    box-sizing: border-box;
    .moveView {
      width: 50PX;
      height: 190PX;
      position: absolute;
      bottom: 0;
      top: -160PX;
      z-index: 9999;
      border-radius: 6PX;
      color: #fff;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      align-items: center;
      .puzzleBox {
        width: 100%;
        height: 50PX;
        .puzzle {
          display: inline-block;
          width: 100%;
          height: 50PX;
        }
      }
      .sliderBox {
        width: 100%;
        height: 30PX;
        z-index: 10000;
        padding: 0 5PX;
        box-sizing: border-box;
        .slider {
          width: 100%;
          height: 30PX;
          border-radius: 4PX;
          background-color: #fff;
          z-index: 10001;
          color: #000;
          text-align: center;
          line-height: 28PX;
          box-sizing: border-box;
          border: 1PX solid #eee;
        }
      }
    }
  }
}

父组件调用

pages/hello/index.jsx

import { View, Image, MovableView, MovableArea } from '@tarojs/components'
import './index.scss'
import { AtButton } from 'taro-ui'
import { useState, useEffect } from 'react'
import ImageCode from '../../components/ImageCode'
export default function Hello() {
  const [imageTop, setImageTop] = useState(0); // 拼图与背景图相对定位的top值
  const [pin, setPin] = useState("");
  const [bak, setBak] = useState("");
  useEffect(() => {
  	// 网络初始获取验证码图片数据
  	... // 网络请求
  	setPin("");
    setBak("");
    setImageTop(19);
  }, [])
  const resetCode = () => {
    // 网络请求获取新的验证码相关参数
    ... // 网络请求
    setPin("");
    setBak("");
    setImageTop(29);
  }
  const checkCode = (num) => {
    console.log(num);
    // 做校验,成功关闭验证框,失败刷新
    // closeCodeView();
    // resetCode();
  }
  const closeCodeView = () => {
  	console.log("C-C-V");
  }
  return (
    <View className='hello'>
      <ImageCode imageTop={imageTop} pin={pin} bak={bak} onRefresh={() => resetCode()} onCheck={(num) => checkCode(num)} onClose={() => closeCodeView()}></ImageCode>
      <AtButton onClick={() => resetCode()}>复位</AtButton> // 实验性复位
    </View >
  )
}


Comment