Vue3.0 PC端滑块拼图验证,配合后端验证

2023-11-16

Vue3.0 PC端滑块拼图验证,配合后端验证

简介

最近因为产品需要实现一个滑块拼图验证,而且需要配合后端进行验证,不想接入第三方SDK,所以自己手写了一个,主要是配合element plus 实现UI大致框架,背景图片和拼图都是通过后端接口获取,通过CryptoJS对滑块滑动距离以及当前拼图唯一标识和秘钥进行加密处理,配合后端进行验证。 使用requestAnimationFrame 优化性能 为了防止滑块卡顿,有问题欢迎评论交流,另外还有小程序版本的 下次发布

安装CryptoJS库

yarn add crypto-js 或 npm i crypto-js

效果图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

后端接口返回数据

获取图片的接口
在这里插入图片描述

背景图和拼图都是base64格式的

验证接口看后端怎么写

实现代码

<template>
  <a-modal
    :visible="store.state.isVerify"
    :footer="null"
    :maskClosable="false"
    :closable="false"
    width="350px"
    :body-style="{ padding: '20px' }"
    @cancel="onClose"
    centered
  >
    <div class="image-body">
      <div class="verify-title">
        请先完成以下安全验证:
      </div>
        <a-spin :spinning="loading" tip="加载中...">
          <div  v-if="loading" class="image-div"></div>
        </a-spin>
        <div v-if="!loading&&bkImage&&slideImage" class="image-div">
          <ElImage class="image-bk" :src="bkImage"></ElImage>
          <ElImage
            :style="{ marginLeft: marginLeft + 'px' }"
            class="image-slide"
            :src="slideImage"
          ></ElImage>
        </div>
      <div class="image-slide-div">
        <div class="image-slide-text">
          <span class="image-slide-tips">
            {{ showTips ? "向右拖动滑块完成拼图" : "&nbsp;" }}
          </span>
        </div>
        <div :style="slideStyle" class="slide-div">
          <ElButton
            :style="slideButtonStyle"
            @mousedown="handleDrag"
            class="slide-button"
            :round="false"
            type="primary"
            plain
          >
          <div class="iconbox">
              <i
              class="iconfont icon-jiantou"
              v-if="result === 'default'"
              style="color:#52ccba;font-size: 16px"
              >
              </i>
              <i class="iconfont icon-duigou" v-if="result === 'success'" style="color: #fff;font-size: 28px"></i>
              <i class="iconfont icon-a-shanchu_huaban1" v-if="result === 'error'" style="color: rgb(246, 185, 186);"></i>
            </div>
          </ElButton>
        </div>
      </div>
    </div>
  </a-modal>
</template>
<script>
import { onMounted, ref, computed, reactive, nextTick } from 'vue'
import { Modal, Spin, message } from 'ant-design-vue'
import $http from '@/utils/request/api'
import { ElImage, ElButton } from 'element-plus'
import { useStore } from 'vuex'
import { useRoute, useRouter } from 'vue-router'
import CryptoJS from 'crypto-js'
export default {
  components: {
    ElImage,
    ElButton,
    'a-modal': Modal,
    'a-spin': Spin
  },
  setup () {
    const route = useRoute()
    const router = useRouter()
    const store = useStore()
    const loading = ref(false)
    // 验证码背景图片
    const bkImage = ref('')
    // 验证码滑块图片
    const slideImage = ref('')
    const secretKey = ref('')
    const currentToken = ref('')
    // 是否显示提示文字
    const showTips = ref(true)
    // 验证码滑块图片移动量
    const marginLeft = ref(0)
    // 验证码状态
    const result = ref('default')
    // 滑动背景样式
    const slideStyleJson = reactive({})
    // 滑块按钮样式
    const slideButtonStyleJson = reactive({})
    const slideStyle = computed(() => {
      return slideStyleJson
    })
    const slideButtonStyle = computed(() => {
      return slideButtonStyleJson
    })

    function loadImage () {
      loading.value = true
      $http.getSlider().then((res) => {
        loading.value = !loading.value
        bkImage.value = res.data.original_img_base_64
        slideImage.value = res.data.slider_img_base_64
        secretKey.value = res.data.secret_key
        currentToken.value = res.data.token
      })
    }
    function onClose () {
      store.commit('SET_IS_VERIFY', false)
    }
    /**
     * 改变拖动时改变
     */
    function dragChangeSildeStyle () {
      slideStyleJson.background = 'rgba(25,145,250,0.5)'
      slideStyleJson.transition = null
      slideButtonStyleJson.transition = null
    }
    /**
     * 验证成功
     */

    const reload = () => {
      store.commit('SET_IS_RELOAD', true)
      nextTick(() => {
        store.commit('SET_IS_RELOAD', false)
      })
    }

    function handleSuccess () {
      result.value = 'success'
      slideStyleJson.background = '#d2f4ef'
      slideButtonStyleJson.background = '#52ccba'
      slideButtonStyleJson.color = 'white'
      // slideButtonStyleJson.transform = 'translateX(0px)'
      slideButtonStyleJson.border = '1px solid #52ccba'
      message.success('验证成功')
      //这里写自己的代码,以下代码只是参考(可自己封装)
      setTimeout(() => {
        if (route.path.split('/').includes('detail')) {
          router.go(0)
        } else {
          reload()
        }
        onClose()
      }, 400)
    }
    /**
     * 验证失败
     */
    function handleError () {
      result.value = 'error'
      slideStyleJson.background = 'rgba(245,122,122,0.5)'
      slideButtonStyleJson.background = '#f57a7a'
      slideButtonStyleJson.transition = 'transform 0.5s'
      slideButtonStyleJson.color = 'white'
      slideButtonStyleJson.border = '1px solid #f57a7a'
      message.error('验证失败请重试')
       //这里写自己的代码,以下代码只是参考(可自己封装)
      setTimeout(() => {
        handleReset()
      }, 300)
    }
    /**
     * 重置验证码
     */
    function handleReset () {
      result.value = 'default'
      marginLeft.value = 0
      slideStyleJson.width = '0px'
      slideButtonStyleJson.marginLeft = '0px'
      slideButtonStyleJson.transform = 'translateX(0px)'
      slideButtonStyleJson.color = null
      slideButtonStyleJson.border = null
      slideButtonStyleJson.background = null
      // slideStyleJson.transition = 'all ease 0.5s'
      slideButtonStyleJson.transition = 'margin-left 0.5s'
      showTips.value = true
      loadImage()
    }

    onMounted(() => {
      loadImage()
    })
    // 添加移动事件
    function handleDrag (c) {
      let moveX = 0; let offset = 0; const y = 5
      const clickX = c.clientX
      dragChangeSildeStyle()
      showTips.value = false
      const handleMove = (e) => {
        moveX = e.clientX
        offset = Math.min(Math.max(moveX - clickX, 0), 260)
        slideStyleJson.width = offset + 'px'
        slideButtonStyleJson.transform = `translateX(${offset}px)`
        marginLeft.value = offset
      }
      const handleUp = async () => {
        document.removeEventListener('mousemove', handleMove)
        document.removeEventListener('mouseup', handleUp)
        // 校验验证码
        // aes加密
        const key = CryptoJS.enc.Utf8.parse(secretKey.value)
        const srcs = CryptoJS.enc.Utf8.parse(JSON.stringify({ x: offset, y: y }))
        const encrypted = CryptoJS.AES.encrypt(srcs, key, {
          mode: CryptoJS.mode.ECB,
          padding: CryptoJS.pad.Pkcs7
        })
        // 将ciphertext字符串转二进制
        const params = {
          point_json: CryptoJS.enc.Base64.stringify(encrypted.ciphertext),
          token: currentToken.value
        }
        const res = await $http.verifyCodeCheck(params)
        if (res.code === 0) {
          // 成功
          handleSuccess()
        } else {
          // 失败
          handleError()
        }
      }
      document.addEventListener('mousemove', handleMove)
      document.addEventListener('mouseup', handleUp)
      requestAnimationFrame(() => {
        handleMove(c)
      })
    }

    return {
      onClose,
      loadImage,
      bkImage,
      loading,
      slideImage,
      marginLeft,
      handleDrag,
      slideStyle,
      slideButtonStyle,
      result,
      showTips,
      store
    }
  }
}
</script>
<style lang="less" scoped>
.iconbox{
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.iconfont{
  font-size: 20px;
  color: #333;
}
.image-body {
  margin: 0 auto;
  width: 310px;
  margin-top: 10px;
  .verify-title{
    font-size: 14px;
    color: #333;
    margin-bottom: 10px;
  }
  .image-div {
    width: 310px;
    height: 155px;
    background: rgb(153, 216, 197);
    .image-bk {
      width: 310px;
      height: 155px;
      z-index: 1;
      position: absolute;
    }
    .image-slide {
      width: 50px;
      height: 155px;
      position: absolute;
      z-index: 2;
    }
  }
  .image-slide-div {
    width: 310px;
    height: 39px;
    margin-top: 15px;
    position: relative;
    .image-slide-text {
      text-align: center;
      background: #eef1f8;
      border: 1px solid #ebebeb;
      .image-slide-tips {
        display: inline-block;
        font-size: 14px;
        color: #b7bcd1;
        line-height: 36px;
        height: 36px;
        text-align: center;
      }
    }
    .slide-div {
      width: 0px;
      height: 38px;
      margin-top: -39px;
      .slide-button {
        width: 50px;
        height: 38px;
        border: none;
        border-left: 1px solid;
        border-right: 1px solid;
        border-bottom: 1px solid;
        border-color: #ebebeb;
        box-shadow: 0 0 4px #ccc;
        background: white;
        cursor: pointer;
        &:hover {
          background: #52ccba;
          border-color: #52ccba;
          color: white;
          .icon-jiantou{
            color: #fff!important;
          }
        }
      }
    }
  }
}
</style>

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue3.0 PC端滑块拼图验证,配合后端验证 的相关文章

随机推荐

  • 手写嵌入式操作系统(基于stm8单片机)

    include
  • maven学习总结

    众所周知 maven的两大作用是项目构建和依赖管理 除此之外 基于多模块项目 maven常用的功能还有模块化管理 项目构建 Maven是一个构建工具 可以根据项目中的配置文件 pom xml 来自动执行项目的构建过程 它可以将源代码编译 运
  • win10 win7局域网、AD域内共享文件夹方法

    第一 确保访问电脑和被访问电脑同在域中 可右击此电脑 属性 域 查看 第二 确保防火墙关闭 如图均已关闭 第三 选择要共享的文件夹 右击 属性 共享 高级共享 全新 Everyone或指定个人 第四 分享地址 即 本机IP地址 win r输
  • 惠普 g5 服务器 centos安装系统,hp 380G5 安装centos 7

    最近给服务器升级操作系统 发现hp的老机器安装centos 7时不能识别硬盘 原因 hp的服务器G5 使用的是CCISS driver 新的机器使用的是HPSA driver RHEL7 已经移除了 cciss 的支持 处理 安装时候 修改
  • 常数据成员、常成员函数

    定义常数据成员 类型 const 对象名 或者 const 类型 对象名 例如 const clock c1 9 9 9 或者 clock const c2 10 10 10 常对象的几条特殊规则 1 常对象 不能被赋值 2 常对象 不能访
  • 【Pytorch Lighting】第 7 章:半监督学习

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 2、halcon+利用光流场检测运动的物体

    这个事例是应用optical flow mg这个算子来在一个图像序列中计算其光溜 并且分割其运动物体 dev update off 把程序窗口 变量窗口 显示窗体变为off状态 dev close window 关闭显示窗口 read im
  • Bert Estimator input_fn 函数调用逻辑

    目录 Bert Estimator input fn 函数调用逻辑 Bert Estimator input fn 函数调用逻辑 网上有很多讲 Bert 源码的 本身代码难度不大 主要两个重点 一个是数据集的处理 以满足 masked LM
  • 整理极客冠军方案

    总结 通常套路就是 1 先观察整体的数据分布 数据集数量 标签数量 标注框数量 数据集数量 数量大 大模型训练缓慢 标签数量 各类的分布情况 gt 分布是否平衡 focal损失函数 长尾分布 样本分布不均衡 mosaic 小目标相对较多 m
  • springboot MongoDB 主从 多数据源

    上一篇 我写了关于用一个map管理mongodb多个数据源 每个数据源 只有单例 的内容 springboot mongodb 配置多数据源 临到部署到阿里云的测试环境 发现还需要考虑一下主从的问题 阿里云买的数据库 不是说让你无感知的 而
  • curl 支持 http2

    源码安装 安装 nghttp2 git clone https github com tatsuhiro t nghttp2 git cd nghttp2 autoreconf i automake autoconf configure m
  • Android Notifaction 从4.0到7.0

    Android Notifaction 从4 0到7 0 图1 4 0 通知样式 图2 5 0 6 0 通知样式 图3 7 0 通知样式 兼容 这么多版本 如何兼容 没关系 交给 android support v7 app Notific
  • 几种RS485隔离通讯的方案介绍

    RS485具有抗干扰能力强 成本低等特点 广泛应用于工业智能仪表 通讯设备等各个领域 RS485电路可以分为非隔离型和隔离型 隔离型电路是在非隔离型电路的基础上增加隔离性能 使得电路具有更强的抗干扰性和系统稳定性 今天介绍几种隔离RS485
  • 说说我亲历的京东发展史

    最近好久没有写博客了 响应CSDN的号召 有奖征文 我亲历的京东发展史 所以我也来从个人观点来说说对京东的一些看法 京东从某种程度上来说 确实改变了我们的生活 使我们生活变得更加丰富多彩 通过阅读 京东技术解密 看到京东的愿景是 让生活变得
  • 依据ASCII排序:有关map.entrySet()的一些心得

    List
  • java的main是主线程吗_main函数是主线程吗

    1 线程的概念 线程是程序最基本的运行单位 而进程不能运行 所以能运行的 是进程中的线程 2 线程是如何创建起来的 进程仅仅是一个容器 包含了线程运行中所需要的数据结构等信息 一个进程创建时 操作系统会创建一个线程 这就是主线程 而其他的从
  • AI 与智能化 API 治理的探索实践

    7月 Eolink 受邀参加 QECon 2023 全球软件质量 效能大会 北京站 Eolink CEO 刘昊臻 发表了主题为 AI 与智能化 API 治理的探索实践 的演讲 分享 Eolink 在 API 全生命周期中治理实践与 AI 结
  • Qt工具相关--无法Debug调试的原因

    第一个原因 缺少对应的调试器 第二个原因 调试器的位数没有对应 这些基本原因都可以在项目 构建套件里面看到 逐项检查 耐心分析问题原因是工程师的基本功 第二次遇到类似问题 解决效率务必大幅度提升 这个一道及格线
  • javascript atob()函数和 btoa()函数-Base64的编码与解码

    在 JavaScript 中 有两个函数被分别用来处理解码和编码 base64 字符串 atob ASCII to Base64 btoa Base64 to ASCII atob 函数能够解码通过base 64编码的字符串数据 相反地 b
  • Vue3.0 PC端滑块拼图验证,配合后端验证

    Vue3 0 PC端滑块拼图验证 配合后端验证 简介 最近因为产品需要实现一个滑块拼图验证 而且需要配合后端进行验证 不想接入第三方SDK 所以自己手写了一个 主要是配合element plus 实现UI大致框架 背景图片和拼图都是通过后端