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

Vue3.0 PC端滑块拼图验证,配合后端验证,背景图片和拼图都是通过后端接口获取,通过CryptoJS对滑块滑动距离以及当前拼图唯一标识和秘钥进行加密处理,配合后端进行验证
安装CryptoJS库

yarn add crypto-js 或 npm i crypto-js
image.png

后端接口返回数据,背景图和拼图都是base64格式的


image.png
<template>
  <!-- :visible="store.isVerify" -->
  <a-modal
    :open="true"
    :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) => {
  const res = {
    data: {
      token: "asadjl",
      secret_key: "dsacsac",
      slider_img_base_64:'xxx',
      original_img_base_64: 'xxx'
        
    },
  };
  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,
    };
    console.log(params, "params");
    // const res = await userVerifyCaptcha(params);
    const res = {
      code: 200,
    };
    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>

用到的图片地址:
https://necaptcha.nosdn.127.net/1408a0ca90b8420684d00878c2c84fde@2x.png
https://necaptcha.nosdn.127.net/c5e34ba0cc704eb8ba013d9ff22355ee@2x.jpg
在线图片转Base64字符
https://www.zhihuilib.com/general/imagetobase64

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容