<template>
<div class="img-verify">
<!-- 画布,绑定一个点击事件,用于刷新验证码 -->
<canvas ref="verify" :width="width" :height="height" @click="handleDraw"></canvas>
</div>
</template>
<script>
import { reactive, onMounted, ref, toRefs } from "vue";
export default {
setup() {
const verify = ref(null);
const state = reactive({
str: "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", // 字符串
width: 110,
height: 40,
imgCode: "", // 初始化验证码为空
});
onMounted(() => {
if (verify.value) {
// 初始化绘制图片验证码
state.imgCode = draw();
}
});
// 点击图片重新绘制
const handleDraw = () => {
state.imgCode = draw();
};
// 随机数
const randomNum = (min: number, max: number): number => {
return Math.floor(Math.random() * (max - min) + min);
};
// 随机颜色
const randomColor = (min: number, max: number) => {
const r = randomNum(min, max);
const g = randomNum(min, max);
const b = randomNum(min, max);
return `rgb(${r},${g},${b})`;
};
// 定义一个绘制验证码的函数 draw
const draw = () => {
// 获取 canvas 上下文,用于绘图
const ctx = verify.value.getContext("2d");
// 生成背景颜色,设置为浅色调(RGB范围在200-230之间)
ctx.fillStyle = randomColor(200, 230);
// 填充整个 canvas 背景区域
ctx.fillRect(0, 0, state.width, state.height);
// 初始化验证码字符串变量 imgCode
let imgCode = "";
// 遍历4次以绘制4个随机字符
for (let i = 0; i < 4; i++) {
// 随机选取字符集中的一个字符
const text = state.str[randomNum(0, state.str.length)];
// 将该字符添加到验证码字符串中
imgCode += text;
// 设置随机字体大小(18px - 40px)
const fontSize = randomNum(18, 40);
// 设置随机旋转角度(-30度至30度)
const deg = randomNum(-30, 30);
// 设置字体样式、大小和颜色
ctx.font = `${fontSize}px Simhei`;
ctx.textBaseline = "top";
ctx.fillStyle = randomColor(80, 150);
// 保存当前画布状态,以便进行平移、旋转等操作而不影响后续绘制
ctx.save();
// 对每个字符进行坐标平移,并根据旋转角度进行旋转
ctx.translate(30 * i + 15, 15);
ctx.rotate((deg * Math.PI) / 180);
// 在当前位置绘制填充的文本(偏移量防止超出边框)
ctx.fillText(text, -15 + 5, -15);
// 恢复之前保存的画布状态,撤销平移和旋转
ctx.restore();
}
// 绘制5条随机干扰线,颜色设置为较浅色系
for (let i = 0; i < 5; i++) {
ctx.beginPath();
ctx.moveTo(randomNum(0, state.width), randomNum(0, state.height));
ctx.lineTo(randomNum(0, state.width), randomNum(0, state.height));
ctx.strokeStyle = randomColor(180, 230);
ctx.closePath(); // 这一行实际上在绘制直线时是可选的
ctx.stroke(); // 实际上绘制出干扰线
}
// 绘制40个随机位置的小圆点作为干扰元素,颜色同样设置为较浅色系
for (let i = 0; i < 40; i++) {
ctx.beginPath();
// 创建一个圆形路径,指定圆心坐标与半径
ctx.arc(randomNum(0, state.width), randomNum(0, state.height), 1, 0, 2 * Math.PI);
ctx.closePath(); // 同样,在绘制封闭图形如圆时是可选的
// 设置小圆点的颜色
ctx.fillStyle = randomColor(150, 200);
// 填充该圆形路径,形成实心小圆点
ctx.fill();
}
// 返回生成的验证码字符串
return imgCode;
};
return {
...toRefs(state), // toRefs为了防止结构数据丢失响应性
verify,
handleDraw,
};
},
};
</script>
<style>
/* 设置鼠标悬停样式 */
.img-verify canvas {
cursor: pointer;
}
</style>
在其他文件中引用这个图片验证码:
HHTML:
<!-- 模版,点击刷新验证码 -->
<div class="imgCode">
<VueImgVerify ref="verifyRef" />
</div>
JS:
// 引入组件
import VueImgVerify from '@/components/VueImgVerify.vue';
// 便于拿到 verifyRef 组件内的实例属性
const verifyRef = ref<any>(null);
// 这里是我项目中点击注册时会触发的验证,具体以自己的代码来写
const onSubmit = async () => {
// 使用 nextTick 确保组件渲染后再访问 verifyRef
await nextTick();
// 确保 verifyRef 不为 null
if (verifyRef.value) {
// 生成的图片验证码的文字等于验证码组件生成的验证码
state.imgCode = verifyRef.value.imgCode || "";
// 如果验证码组件生成的验证码的小写 != 用户输入的验证码的小写,则提示错误
if (
verifyRef.value.imgCode.toLowerCase() != state.verify.toLowerCase()
) {
console.log("verifyRef.value.imgCode", verifyRef.value.imgCode);
showFailToast("验证码错误");
console.log("Generated Captcha:", verifyRef.value.imgCode.toLowerCase());
console.log("User Input Captcha:", state.verify.toLowerCase());
return;
}
// 验证码匹配成功,注册=>注册成功
// 发请求,将state.nickname,state.username,state.password数据传给后端
await axios.post('/register', {
nickname: state.nickname,
username: state.username,
password: state.password,
isVip: state.isVip,
userType: state.userType
});
showSuccessToast('注册成功!');
// 1s后跳转到登录页面
setTimeout(() => {
router.push('/login');
}, 1000);
} else {
// 处理 verifyRef 为 null 的情况
console.error("verifyRef is null");
}
};