一、背景
前两天发了一篇 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与组件文件位置
八、效果图