功能
模拟了纯前端的邮箱登录逻辑
还没有连接后端的发送邮件的服务
后续计划,再做一个邮箱、密码登录的界面
然后把这两个一块连接上后端
技术介绍
主要介绍绘制图形人机验证乃个
使用的是canvas,在源码里就有
界面控制主要就是用 表格、表单(其实表单都没怎么用到)、v-if、文本输入框和按钮。
效果图
代码
Auth.vue
<template>
<div>
<form @submit.prevent="handleSubmit">
<table>
<!-- 邮箱输入框 -->
<tr v-show="!showEmailVerification">
<td><label for="email">邮箱</label></td>
<td colspan="2"><input type="email" id="email" v-model="formData.email" placeholder="请输入邮箱" required></td>
</tr>
<!-- 人机校验 -->
<tr v-show="!showEmailVerification">
<td><button type="button" @click="refreshCaptcha"><canvas ref="myCanvas" width="90" height="25"></canvas></button></td>
<td><input type="text" id="captcha" v-model="userInputCaptchaText" placeholder="请输入验证码" required></td>
</tr>
<tr v-show="!showEmailVerification">
<td></td>
<td><button type="button" @click="sendEmail">人机验证</button></td>
</tr>
<!-- 邮箱认证 -->
<tr v-show="showEmailVerification">
<td><label for="emailVerificationCode">验证码</label></td>
<td colspan="2"><input type="text" id="emailVerificationCode" v-model="formData.emailVerificationCode" placeholder="请输入邮箱验证码" required></td>
</tr>
<tr v-show="showEmailVerification">
<td></td>
<td colspan="2"><button type="button" @click="verifyEmailVerificationCode">提交验证码</button></td>
</tr>
<!-- 消息通知栏 -->
<tr>
<td colspan="2">{{ message }}</td>
</tr>
</table>
</form>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
import useFormValidation from './formValidation';
const {
formData,
userInputCaptchaText,
showEmailVerification,
message,
refreshCaptcha,
sendEmail,
verifyEmailVerificationCode,
} = useFormValidation();
// Initialize captcha on component mount
onMounted(refreshCaptcha);
function handleSubmit() {
// 可以在这里添加表单提交逻辑
}
</script>
<style scoped>
table, th, td {
border: 1px solid black;
border-collapse: collapse;
}
</style>
captcha.js
export function drawCaptcha(ctx, width, height) {
// 填充背景色
ctx.fillStyle = '#f2f2f2';
ctx.fillRect(0, 0, width, height);
// 设置字符集
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
const charCount = 4; // 验证码字符数
let captchaText = '';
// 随机生成验证码文本
for (let i = 0; i < charCount; i++) {
captchaText += chars.charAt(Math.floor(Math.random() * chars.length));
}
// 设置字体样式
ctx.font = '24px Arial';
ctx.fillStyle = '#333';
ctx.textBaseline = 'middle';
// 绘制字符
for (let i = 0; i < captchaText.length; i++) {
const x = i * 24;
const y = height / 2;
const angle = (Math.random() - 0.5) * 0.6; // 随机旋转角度
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle);
ctx.fillText(captchaText.charAt(i), 0, 0);
ctx.restore();
}
// 添加干扰线条
for (let i = 0; i < 10; i++) {
ctx.strokeStyle = getRandomColor();
ctx.beginPath();
ctx.moveTo(Math.random() * width, Math.random() * height);
ctx.lineTo(Math.random() * width, Math.random() * height);
ctx.stroke();
}
// 添加噪点
for (let i = 0; i < 50; i++) {
ctx.fillStyle = getRandomColor();
ctx.beginPath();
ctx.arc(Math.random() * width, Math.random() * height, 1, 0, Math.PI * 2);
ctx.fill();
}
return captchaText;
}
function getRandomColor() {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
return `rgb(${r},${g},${b})`;
}
formValidation.js
import { ref } from 'vue';
import { drawCaptcha } from './captcha';
import axios from 'axios';
export default function useFormValidation() {
const formData = ref({
email: '',
emailVerificationCode: ''
});
const userInputCaptchaText = ref('');
let captchaText = '';
const isValid = ref(false);
const showEmailVerification = ref(false);
const loginSuccess = ref(false);
const message = ref('');
function refreshCaptcha() {
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
captchaText = drawCaptcha(ctx, canvas.width, canvas.height);
}
function sendEmail() {
if (!isValidEmail(formData.value.email)) {
message.value = '请输入有效的邮箱地址';
return;
}
if (isValidCaptcha(userInputCaptchaText.value, captchaText)) {
isValid.value = true;
showEmailVerification.value = true;
message.value = '请查收邮箱验证码';
// 发送邮件验证码的逻辑可以放在这里
sendVerificationCode(formData.value.email);
} else {
message.value = '验证码错误,请重新输入';
refreshCaptcha();
userInputCaptchaText.value = '';
isValid.value = false;
showEmailVerification.value = false;
}
}
const sendVerificationCode = async (email) => {
try {
const response = await axios.post('http://localhost:8080/api/register', { email }, {
headers: {
'Content-Type': 'application/json'
}
});
console.log('Verification code sent successfully:', response.data);
} catch (error) {
console.error('Error sending verification code:', error);
}
};
// 校验邮箱验证码
function verifyEmailVerificationCode() {
if (isValidEmailVerificationCode(formData.value.emailVerificationCode)) {
loginSuccess.value = true;
message.value = '邮箱验证码校验通过,登录成功!';
} else {
message.value = '邮箱验证码错误,请重新输入';
}
}
const isValidEmailVerificationCode = async (code) => {
console.log(code);
try {
const response = await axios.post('http://localhost:8080/api/checkEmail', { code }, {
headers: {
'Content-Type': 'application/json'
}
});
console.log('Verification code check result:', response.data);
return response.data;
} catch (error) {
console.error('Error checking verification code:', error);
message.value = '校验邮箱验证码时发生错误';
return false;
}
}
return {
formData,
userInputCaptchaText,
isValid,
showEmailVerification,
loginSuccess,
message,
refreshCaptcha,
sendEmail,
verifyEmailVerificationCode
};
}
function isValidEmail(email) {
const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
return emailRegex.test(email);
}
function isValidCaptcha(inputCaptcha, generatedCaptcha) {
return inputCaptcha.toLowerCase() === generatedCaptcha.toLowerCase();
}