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

2 篇文章 0 订阅
1 篇文章 0 订阅

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

简介

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

安装CryptoJS库

yarn add crypto-js 或 npm i crypto-js

效果图

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

后端接口返回数据

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

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

验证接口看后端怎么写

实现代码

<template>
  <a-modal :visible="store.isVerify" :footer="null" :maskClosable="false" :closable="true"
    :body-style="{ padding: '20px' }" @cancel="onClose" :width="370" style="top: 208px">
    <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">
        <img class="image-bk" :src="bkImage" />
        <img :style="{ marginLeft: marginLeft + 'px' }" class="image-slide" :src="slideImage" />
      </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">
          <a-button :style="slideButtonStyle" @mousedown="handleDrag" class="slide-button" type="primary">
            <div class="iconbox">
              <span class="iconfont icon-jiantou" v-if="result === 'default'" style="color:#10b2bc;font-size: 16px">
                &#xe6a0;</span>
              <span class="iconfont" v-if="result === 'success'" style="color: #fff;font-size: 28px">&#xebe6;</span>
              <span class="iconfont" v-if="result === 'error'" style="color: rgb(246, 185, 186);">&#xe64e;</span>
            </div>
          </a-button>
        </div>
      </div>
    </div>
  </a-modal>
</template>
<script setup>
import { message } from 'ant-design-vue'
import { userGetCaptcha, userVerifyCaptcha } from "@/api/user";
import { useMianStore } from "@/store/index";
import CryptoJS from 'crypto-js'

const store = useMianStore()
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
  userGetCaptcha().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.SET_ISVERIFY(false)
}
/**
 * 改变拖动时改变
 */
function dragChangeSildeStyle () {
  slideStyleJson.background = 'rgba(25,145,250,0.5)'
  slideStyleJson.transition = null
  slideButtonStyleJson.transition = 'margin-left ease 0.5s'
}
/**
 * 验证成功
 */

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

function handleSuccess () {
  result.value = 'success'
  slideStyleJson.background = '#d2f4ef'
  slideButtonStyleJson.background = '#10b2bc'
  slideButtonStyleJson.color = 'white'
  slideButtonStyleJson.border = '1px solid #10b2bc'
  message.success('验证成功')
  setTimeout(() => {
    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.transition = 'margin-left ease 0.5s'
  slideButtonStyleJson.color = 'white'
  slideButtonStyleJson.border = '1px solid #f57a7a'
  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 0.5s ease'
  slideButtonStyleJson.transition = 'margin-left 0.5s ease'
  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 userVerifyCaptcha(params)
    if (res.code === 200) {
      // 成功
      handleSuccess()
    } else {
      // 失败
      handleError()
    }

  }
  document.addEventListener('mousemove', handleMove)
  document.addEventListener('mouseup', handleUp)
  requestAnimationFrame(() => {
    handleMove(c)
  })
}
// 移除监听事件
onUnmounted(() => {
  document.removeEventListener('mousemove', handleDrag)
  document.removeEventListener('mouseup', handleDrag)
})
</script>
<style lang="scss" 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;
  padding-bottom: 20px;

  .verify-title {
    font-size: 14px;
    color: #333;
    margin-bottom: 10px;
  }

  .image-div {
    // position: relative;
    width: 310px;
    height: 155px;
    background: rgb(153, 216, 197);
    box-shadow: 0 0 4px #ccc;

    .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: -38px;

      .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: #10b2bc;
          border-color: #10b2bc;
          color: white;

          .icon-jiantou {
            color: #fff !important;
          }
        }
      }
    }
  }
}
</style>

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值