Vue 使用接口返回的背景图片和拼图图片进行滑动拼图验证

一、背景

前两天发了一篇 vue-monoplasty-slide-verify 滑动验证码插件使用及踩坑_vue-monoplasty-slide-verify 引用后不显示-CSDN博客

这两天项目又需要通过接口校验,接口返回了背景图片和拼图图片,于是在网上找了一篇帖子,vue + 图片滑动验证_基于vue图片滑动验证-CSDN博客

写的不错,代码拿过来就能用;但是感觉源码和vue-monoplasty-slide-verify的比较像,vue-monoplasty-slide-verify的icon图片拿过来直接就能在参考帖子里用。

二、获取验证码接口返回数据示例

三、校验接口返回数据示例

四、封装与后端配合的滑动组件

javascript 复制代码
//在参考帖基础上修改的
<template>
    <div>
        <el-dialog :visible.sync="showSlideVerify" title="向右滑动通过验证" :width=" w +48 + 'px'" :close-on-click-modal="false" :close-on-press-escape="false">
            <div class="slide-verify" :style="{width: w + 'px'}" id="slideVerify" onselectstart="return false;">
                <!-- 图片加载遮蔽罩 -->
                <div :class="{'slider-verify-loading': loadBlock}"></div>
                <canvas :width="w" :height="h" ref="canvas"></canvas>
                <div v-if="showRefresh" @click="refresh" class="slide-verify-refresh-icon"></div>
                <canvas :width="block_width" :height="h" ref="block" class="slide-verify-block"></canvas>
                <!-- container -->
                <div class="slide-verify-slider" :class="{'container-active': containerActive, 'container-success': containerSuccess, 'container-fail': containerFail}">
                    <div class="slide-verify-slider-mask" :style="{width: sliderMaskWidth}">
                        <!-- slider -->
                        <div @mousedown="sliderDown" @touchstart="touchStartEvent" @touchmove="touchMoveEvent" @touchend="touchEndEvent" class="slide-verify-slider-mask-item" :style="{left: sliderLeft}">
                            <div class="slide-verify-slider-mask-item-icon"></div>
                        </div>
                    </div>
                    <span class="slide-verify-slider-text">{{sliderText}}</span>
                </div>
            </div>
        </el-dialog>
    </div>
  </template>
<script>
import { getCanvasAndPuzzle, checkPuzzle } from '@/api/modules/setting'

export default {
    name: 'SlideVerify',
    props: {
        sliderText: {
            type: String,
            default: '向右滑动'
        },
        showRefresh: {
            type: Boolean,
            default: true
        }
    },
    data () {
        return {
            showSlideVerify: false,
            containerActive: false, // container active class
            containerSuccess: false, // container success class
            containerFail: false, // container fail class

            nonceStr: '',

            canvasCtx: null,
            blockCtx: null,
            block: null,
            block_src: undefined,
            block_y: undefined,
            block_width: undefined,
            block_height: undefined,
            block_radius: undefined,

            w: 0,
            h:0,
            img: undefined,

            originX: undefined,
            originY: undefined,
            isMouseDown: false,
            trail: [],
            sliderLeft: 0, // block right offset
            sliderMaskWidth: 0, // mask width,
            success: false, // Bug Fixes 修复了验证成功后还能滑动
            loadBlock: true, // Features 图片加载提示,防止图片没加载完就开始验证
            timestamp: null
        }
    },
    methods: {
        show () {
            Object.assign(this.$data, {
                success: false,
                containerActive: false,
                containerSuccess: false,
                containerFail: false,

                nonceStr: '',

                w: 0,
                h: 0,
                img: undefined,
                
                loadBlock: true,
                block_src: undefined,
                block_y: undefined,
                block_width: undefined,
                block_height: undefined,
                block_radius: undefined,

                sliderLeft: 0,
                sliderMaskWidth: 0
            });
            this.getCanvasAndPuzzle();
        },
        getCanvasAndPuzzle () { //从接口获取滑动组件背景图片、图片宽高与拼图图片、拼图宽高、block_radius、垂直方向上的位置
            getCanvasAndPuzzle().then(res => {
                Object.assign(this.$data, {
                    nonceStr: res.data.nonceStr, //根据接口的校验逻辑,校验时需传递给后台
                    w: res.data.canvasWidth,
                    h: res.data.canvasHeight,
                    img: res.data.canvasSrc,
                    block_src: res.data.blockSrc,
                    block_y: res.data.blockY,
                    block_width: res.data.blockWidth,
                    block_height: res.data.blockHeight,
                    block_radius: res.data.blockRadius,
                    showSlideVerify: true
                });
                this.$nextTick(() => {
                    this.init();
                });
            }).catch(error => {
                this.$message.error(error);
            });
        },
        init () {
            this.initDom();
            this.initImg();
            this.bindEvents();
        },
        initDom () {
            this.block = this.$refs.block;
            this.canvasCtx = this.$refs.canvas.getContext('2d');
            this.blockCtx = this.block.getContext('2d');
        },
        initImg () {
            let _this = this, image = new Image(), blockImage = new Image();
            image.src = this.img;
            blockImage.src = this.block_src;
            image.onload = () => {
                _this.canvasCtx.drawImage(image, 0, 0, _this.w, _this.h);
            };
            blockImage.onload = () => {
                _this.loadBlock = false;
                // 第二个、三个参数分别是被拖动的拼图在水平、垂直方向的偏移量
                _this.blockCtx.drawImage(blockImage, 0, _this.block_y, _this.block_width, _this.block_height);

            };
        },
        sliderDown (event) {
            if (this.success) return;
            this.originX = event.clientX;
            this.originY = event.clientY;
            this.isMouseDown = true;
            this.timestamp = + new Date();
        },
        touchStartEvent (e) {
            if (this.success) return;
            this.originX = e.changedTouches[0].pageX;
            this.originY = e.changedTouches[0].pageY;
            this.isMouseDown = true;
            this.timestamp = + new Date();
        },
        bindEvents () {
            document.addEventListener('mousemove', (e) => {
                if (!this.isMouseDown) return false;
                const moveX = e.clientX - this.originX;
                const moveY = e.clientY - this.originY;
                if (moveX < 0 || moveX + 38 >= this.w) return false;
                this.sliderLeft = moveX + 'px';
                let blockLeft = (this.w - 40 - 20) / (this.w - 40) * moveX;
                this.block.style.left = blockLeft + 'px';
                this.containerActive = true;
                this.sliderMaskWidth = moveX + 'px';
                this.trail.push(moveY);
            });
            document.addEventListener('mouseup', (e) => {
                if (!this.isMouseDown) return false;
                this.isMouseDown = false;
                if (e.clientX === this.originX) return false;
                this.containerActive = false;
                this.timestamp = + new Date() - this.timestamp;

                this.verify();
            });
        },
        touchMoveEvent (e) {
            if (!this.isMouseDown) return false;
            const moveX = e.changedTouches[0].pageX - this.originX;
            const moveY = e.changedTouches[0].pageY - this.originY;
            if (moveX < 0 || moveX + 38 >= this.w) return false;
            this.sliderLeft = moveX + 'px';
            let blockLeft = (this.w - 40 - 20) / (this.w - 40) * moveX;
            this.block.style.left = blockLeft + 'px';

            this.containerActive = true;
            this.sliderMaskWidth = moveX + 'px';
            this.trail.push(moveY);
        },
        touchEndEvent (e) {
            if (!this.isMouseDown) return false;
            this.isMouseDown = false;
            if (e.changedTouches[0].pageX === this.originX) return false;
            this.containerActive = false;
            this.timestamp = + new Date() - this.timestamp;
            this.verify();
        },
        verify () {
//将拼图水平方向拖动量传递给接口,接口来校验是否拼好返回校验结果,我也不是很理解这个操作,但没办法,就让这样做,只能自己网上找例子,然后改出来了
            checkPuzzle({
                nonceStr: this.nonceStr,
                value: parseInt(this.block.style.left) //被拖动拼图在水平方向上的移动量
            }).then(res => {
                if(res.code===-1 || !res.data) {//校验通过,res.data的值为true,否则为false
                    this.containerFail = true;
                    this.$message.error(res.message);
                    this.refresh();
                } else {
                    this.$emit('verify-success');
                    Object.assign(this.$data, {
                        containerSuccess: true,
                        success: true,
                        showSlideVerify: false
                    });
                }
            }).catch(error => {
                this.$message.error(error);
            });
        },
        refresh () {
            let { w, h, block_width } = this;
            this.canvasCtx.clearRect(0, 0, w, h);
            this.blockCtx.clearRect(0, 0, block_width, h);
            this.block.style.left = 0;
            Object.assign(this.$data, {
                success: false,
                containerActive: false,
                containerSuccess: false,
                containerFail: false,

                nonceStr: '',
                img: undefined,
                loadBlock: true,
                sliderLeft: 0,
                sliderMaskWidth: 0
            });
            this.getCanvasAndPuzzle();
        }
    }
}
</script>
<style scoped>
.slide-verify {
    position: relative;
}
  
/* 图片加载样式 */
.slider-verify-loading {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.9);
    z-index: 999;
    animation: loading 1.5s infinite;
}

@keyframes loading {
    0% {
        opacity: 0.7;
    }
    100% {
        opacity: 9;
    }
}
  
.slide-verify-block {
    position: absolute;
    left: 0;
    top: 0;
}
  
.slide-verify-refresh-icon {
    position: absolute;
    right: 0;
    top: 0;
    width: 34px;
    height: 34px;
    cursor: pointer;
    background: url('./imgs/icon.png') 0 -437px;
    background-size: 34px 471px;
}

.slide-verify-slider {
    position: relative;
    text-align: center;
    width: 100%;
    height: 40px;
    line-height: 40px;
    /* margin-top: 15px; */
    background: #f7f9fa;
    color: #45494c;
    border: 1px solid #e4e7eb;
}

.slide-verify-slider-mask {
    position: absolute;
    left: 0;
    top: 0;
    height: 40px;
    border: 0 solid #1991fa;
    background: #d1e9fe;
}

.slide-verify-slider-mask-item {
    position: absolute;
    top: 0;
    left: 0;
    width: 40px;
    height: 40px;
    background: #fff;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
    cursor: pointer;
    transition: background 0.2s linear;
}

.slide-verify-slider-mask-item:hover {
    background: #1991fa;
}

.slide-verify-slider-mask-item:hover .slide-verify-slider-mask-item-icon {
    background-position: 0 -13px;
}

.slide-verify-slider-mask-item-icon {
    position: absolute;
    top: 15px;
    left: 13px;
    width: 14px;
    height: 12px;
    background: url('./imgs/icon.png') 0 -26px;
    background-size: 34px 471px;
}
.container-active .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #1991fa;
}

.container-active .slide-verify-slider-mask {
    height: 38px;
    border-width: 1px;
}

.container-success .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #52ccba;
    background-color: #52ccba !important;
}

.container-success .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #52ccba;
    background-color: #d2f4ef;
}

.container-success .slide-verify-slider-mask-item-icon {
    background-position: 0 0 !important;
}

.container-fail .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #f57a7a;
    background-color: #f57a7a !important;
}

.container-fail .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #f57a7a;
    background-color: #fce1e1;
}

.container-fail .slide-verify-slider-mask-item-icon {
    top: 14px;
    background-position: 0 -82px !important;
}

.container-active .slide-verify-slider-text,
.container-success .slide-verify-slider-text,
.container-fail .slide-verify-slider-text {
    display: none;
}
</style>

五、 接口

意义不太大,要根据项目实际情况来的

六、CSS 代码里用到的图片资源

https://download.csdn.net/download/hrcsdn13/89709217

七、CSS与组件文件位置

八、效果图

相关推荐
怕浪猫5 小时前
FDE 如何正确解决现场项目工程性的问题
前端·后端·ai编程
西瓜太郎12345 小时前
外层模型卡片与分组详情成功率不一致,应该怎么排查?
前端·typescript·go·软件工程·react
GreenTea6 小时前
vLLM 与 SGLang KV Cache 底层实现机制深度调研报告
前端·后端·算法
毕业设计7038 小时前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
xifangge20258 小时前
AGENTS.md 怎么写?涵盖 Java、Python、Vue、Go 的 8 套开箱即用模板
java·vue.js·python
几何心凉8 小时前
没有万能模型:我用 AiiOnly Token Plan,把多款大模型的长处拼进同一个项目
前端
kyriewen8 小时前
Claude Code 的额度今天缩水了17%——官方公告上写的是"永久提高25%"
前端·ai编程·claude
雪芽蓝域zzs9 小时前
vue解构平铺VS对象包裹
前端·javascript·vue.js
风骏时光牛马10 小时前
从零到实战,完整AI学习路线
前端
三十而立洋11 小时前
深入理解 Monorepo:子包安装依赖
前端·前端工程化