vue登录人机校验--文字点选验证

  1. 点击登录,出现文字点选验证模态框
    login.vue文件
<template>
	<!-- 文字验证 -->
    <div class="verify" v-if="isVerify === true">
    	<!-- 嵌入子组件 -->
        <verify @isOkShow="okOk" :infos="infos" />
    </div>
</template>
<script setup>
import verify from './verify.vue' // 文字验证
const isVerify = ref(false) // 是否开启文字验证
</script>
<style scoped>
.verify {
    z-index: 5;
}

.verify::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: #3c3b3b;
    opacity: 0.5;
    /* 半透明黑色 */
    z-index: -1;
    /* 将遮罩层的层级放到最底层 */
}
</style>
  1. 子组件verify.vue文件
<template>
	<div class="app" v-cloak v-if="isOkShow === true">
		<div class="verify-container" :style="{ width: `${width}px` }">
			<div class="refresh" @click="reset">
				<svg t="1637315258145" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
					p-id="2420" width="20" height="20">
					<path
						d="M960 416V192l-73.056 73.056a447.712 447.712 0 0 0-373.6-201.088C265.92 63.968 65.312 264.544 65.312 512S265.92 960.032 513.344 960.032a448.064 448.064 0 0 0 415.232-279.488 38.368 38.368 0 1 0-71.136-28.896 371.36 371.36 0 0 1-344.096 231.584C308.32 883.232 142.112 717.024 142.112 512S308.32 140.768 513.344 140.768c132.448 0 251.936 70.08 318.016 179.84L736 416h224z"
						p-id="2421" fill="#8a8a8a"></path>
				</svg>
			</div>
			<div class="pic">
				<canvas class="canvas" ref="canvas" :width="width" :height="height" @click="createPointer"></canvas>
				<span class="pointer" v-for="(item, index) in pointer" :style="{ left: `${item.x}px`, top: `${item.y}px` }">
					<i>{{ index + 1 }}</i>
				</span>
			</div>
			<div :class="['toolbar', state]">
				<p v-if="state === 'fail'">验证失败</p>
				<p v-else-if="state === 'success'">验证通过</p>
				<p v-else>请顺序点击【<span v-for="(item, index) in tips" :key="index">{{ item.character }}</span></p>
			</div>
		</div>
	</div>
</template>

<script >
import { ElMessage } from "element-plus";
export default {
	props: {
		infos: Object
	},
	data() {
		return {
			bgImg: null,  // 背景图
			ctx: null, // 背景画笔
			fontArr: [], // 显示的字符
			tips: [], // 提示文字
			pointer: [], // 点击序号
			state: '', // success fail active
			timeIns: null,
			width: 320,
			height: 120,
			fontStr: '赵钱孙李周吴郑王朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐',
			fontNum: 5,
			checkNum: 3,
			accuracy: 15,
			images: [
				'https://images.pexels.com/photos/2418973/pexels-photo-2418973.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
				'https://images.pexels.com/photos/5187070/pexels-photo-5187070.jpeg?auto=compress&cs=tinysrgb&w=600&lazy=load',
				'https://images.pexels.com/photos/3510717/pexels-photo-3510717.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
				'https://ts1.cn.mm.bing.net/th?id=OIP-C.7-6n_pwnAPz_IkgyRuRI2wHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=1.4&pid=3.1&rm=2'
			],
			isOkShow: true
		}
	},
	mounted() {
		this.init()
	},

	beforeDestroy() {
		clearTimeout(this.timeIns)
	},
	methods: {
		init() {
			this.ctx = this.$refs['canvas'].getContext('2d');

			this.getImg();
		},
		getImg() {
			const img = document.createElement('img');
			const imagesLen = this.images.length;
			const randomIndex = Math.floor(Math.random() * imagesLen);
			img.crossOrigin = "Anonymous";
			img.src = this.images[randomIndex];
			this.bgImg = img;

			img.onload = () => {
				this.draw();
			}
		},
		draw() {
			// 绘制背景图
			this.ctx.drawImage(this.bgImg, 0, 0, this.width, this.height);
			for (let i = 0; i < this.fontNum; i++) {
				const character = this.getRandomCharacter();
				const fontSize = this.randomNum(20, this.height * 1 / 4);
				const fontWeight = Math.random() > 0.5 ? 'bold' : 'normal';
				const fontStyle = Math.random() > 0.5 ? 'italic' : 'normal';
				const fontFamily = Math.random() > 0.5 ? 'sans-serif' : 'serif'
				const x = this.width / this.fontNum * i + 10;
				const y = Math.random() * (this.height - fontSize);

				this.ctx.fillStyle = this.randomColor(0, 255);
				this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
				this.ctx.textBaseline = 'top';
				this.ctx.fillText(character, x, y);

				this.fontArr.push({
					character,
					// fontSize,
					x,
					y
				})
			}

			for (let i = 0; i < this.checkNum; i++) {
				const randomIndex = Math.floor(Math.random() * this.fontArr.length);
				const character = this.fontArr.splice([randomIndex], 1)[0];
				this.tips.push(character);
				// console.log(character, this.fontArr)
			}
		},
		// 获取随机字符
		getRandomCharacter() {
			const fontStrLen = this.fontStr.length;
			const randomIndex = Math.floor(Math.random() * fontStrLen);
			const character = this.fontStr.charAt([randomIndex]);

			// debugger
			const isSome = this.fontArr.some(item => {
				return item.character === character;
			})
			if (isSome) {
				console.log(`>>>${character}已存在>>>`)
				return this.getRandomCharacter();
			} else {
				return character;
			}
		},
		randomColor(min, max) {
			let r = this.randomNum(min, max)
			let g = this.randomNum(min, max)
			let b = this.randomNum(min, max)
			return 'rgb(' + r + ',' + g + ',' + b + ')'
		},
		randomNum(min, max) {
			return Math.floor(Math.random() * (max - min) + min)
		},
		createPointer(e) {
			const canvasRect = this.$refs.canvas.getBoundingClientRect();

			const x = e.offsetX - 20;
			const y = e.offsetY - 20;

			if (this.pointer.length < this.tips.length) {
				this.pointer.push({ x, y });
				// this.verify()
				this.state = 'active'
			}
			if (this.pointer.length === this.tips.length) {
				const isPass = this.verify();
				if (isPass) { // 验证成功
					this.state = 'success';
					this.isOkShow = false
					this.reset()
					sessionStorage.setItem('token', JSON.stringify(this.infos.token))
					sessionStorage.setItem('userInfo', JSON.stringify(this.infos.info))
					this.$router.push('/home')
					ElMessage({ type: "success", message: "登录成功" });
				} else {
					this.state = 'fail';
					// 如果失败则1000毫秒后重置
					this.timeIns = setTimeout(() => {
						this.reset()
					}, 1000)
				}
			}
		},

		// 判断精度
		verify() {
			const result = this.pointer.every((item, index) => {
				const _left = item.x > this.tips[index].x - this.accuracy;
				const _right = item.x < this.tips[index].x + this.accuracy;
				const _top = item.y > this.tips[index].y - this.accuracy;
				const _bottom = item.y < this.tips[index].y + this.accuracy;
				return _left && _right && _top && _bottom;
			})
			return result;
		},
		// 重置
		reset() {
			this.fontArr = [];
			this.tips = [];
			this.pointer = [];
			this.state = '';
			this.ctx.clearRect(0, 0, this.width, this.height);
			this.getImg();
		}
	}
}



</script>


<style scoped>
.app {
	z-index: 5;
	padding: 0;
	margin: 0;
	position: relative;
	top: 221px;
	right: 160px;
}

.verify-container {
	border: 1px solid #e4e4e4;
	margin: 20px auto;
	position: relative;
	overflow: hidden;
	user-select: none;
}

.pic {
	position: relative;
}

.canvas {
	display: block;
}

.pointer {
	background: #1abd6c;
	border-radius: 50%;
	padding: 15px;
	position: absolute;
}

.pointer i {
	color: #fff;
	font-style: normal;
	font-size: 12px;
	display: flex;
	justify-content: center;
	align-items: center;
	position: absolute;
	left: 0;
	top: 0;
	right: 0;
	bottom: 0;
}

.toolbar {
	width: 100%;
	height: 40px;
	border: 1px solid #e4e4e4;
	background: #f7f7f7;
	color: #666;
	font-size: 14px;
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
}

.toolbar.active {
	color: #fff;
	background: #1991FA;
	border: 1px solid #1991FA;
}

.toolbar.success {
	color: #fff;
	background: #52CCBA;
	border: 1px solid #52CCBA;
}

.toolbar.fail {
	color: #fff;
	background: #f57a7a;
	border: 1px solid #f57a7a;
}

.refresh {
	display: flex;
	position: absolute;
	right: 10px;
	top: 10px;
	z-index: 2;
	cursor: pointer;
}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值