验证码组件
<template>
<div class="s-canvas">
<canvas id="s-canvas" :width="contentWidth" :height="contentHeight"></canvas>
</div>
</template>
<script>
export default {
name: 'SIdentify',
props: {
identifyCode: {
type: String,
default: '1234',
},
fontSizeMin: {
type: Number,
default: 28,
},
fontSizeMax: {
type: Number,
default: 40,
},
backgroundColorMin: {
type: Number,
default: 180,
},
backgroundColorMax: {
type: Number,
default: 240,
},
colorMin: {
type: Number,
default: 50,
},
colorMax: {
type: Number,
default: 160,
},
lineColorMin: {
type: Number,
default: 40,
},
lineColorMax: {
type: Number,
default: 180,
},
dotColorMin: {
type: Number,
default: 0,
},
dotColorMax: {
type: Number,
default: 255,
},
contentWidth: {
type: Number,
default: 112,
},
contentHeight: {
type: Number,
default: 38,
},
},
methods: {
// 生成一个随机数
randomNum(min, max) {
return Math.floor(Math.random() * (max - min) + min);
},
// 生成一个随机的颜色
randomColor(min, max) {
var r = this.randomNum(min, max);
var g = this.randomNum(min, max);
var b = this.randomNum(min, max);
return 'rgb(' + r + ',' + g + ',' + b + ')';
},
drawPic() {
var canvas = document.getElementById('s-canvas');
var ctx = canvas.getContext('2d');
ctx.textBaseline = 'bottom';
// 绘制背景
ctx.fillStyle = this.randomColor(this.backgroundColorMin, this.backgroundColorMax);
ctx.fillRect(0, 0, this.contentWidth, this.contentHeight);
// 绘制文字
for (let i = 0; i < this.identifyCode.length; i++) {
this.drawText(ctx, this.identifyCode[i], i);
}
this.drawLine(ctx);
this.drawDot(ctx);
},
drawText(ctx, txt, i) {
ctx.fillStyle = this.randomColor(this.colorMin, this.colorMax);
ctx.font = this.randomNum(this.fontSizeMin, this.fontSizeMax) + 'px SimHei';
var x = (i + 1) * (this.contentWidth / (this.identifyCode.length + 1));
var y = this.randomNum(this.fontSizeMax, this.contentHeight - 5);
var deg = this.randomNum(-30, 30);
// 修改坐标原点和旋转角度
ctx.translate(x, y);
ctx.rotate((deg * Math.PI) / 270);
ctx.fillText(txt, 0, 0);
// 恢复坐标原点和旋转角度
ctx.rotate((-deg * Math.PI) / 270);
ctx.translate(-x, -y);
},
drawLine(ctx) {
// 绘制干扰线
for (let i = 0; i < 2; i++) {
ctx.strokeStyle = this.randomColor(this.lineColorMin, this.lineColorMax);
ctx.beginPath();
ctx.moveTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight));
ctx.lineTo(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight));
ctx.stroke();
}
},
drawDot(ctx) {
// 绘制干扰点
for (let i = 0; i < 20; i++) {
ctx.fillStyle = this.randomColor(0, 255);
ctx.beginPath();
ctx.arc(this.randomNum(0, this.contentWidth), this.randomNum(0, this.contentHeight), 1, 0, 2 * Math.PI);
ctx.fill();
}
},
},
watch: {
identifyCode() {
this.drawPic();
},
},
mounted() {
this.drawPic();
},
};
</script>
<style lang="scss" scoped>
.s-canvas {
display: flex;
}
.s-canvas canvas {
margin-top: 0;
margin-left: 0;
}
</style>
使用
<template>
<a-row :gutter="[16, 16]">
<a-col :span="8" :push="7" class="text-center mt-24">
<a-typography-title :level="3">{{ APP_TITLE }}</a-typography-title>
<a-typography-title :level="4">管理端</a-typography-title>
</a-col>
</a-row>
<a-row :gutter="[16, 16]" class="mt-8">
<a-col :span="6" :push="8">
<a-card>
<a-typography-title :level="4" class="text-center">登录</a-typography-title>
<a-form
:layout="formState.layout"
:model="formState"
:rules="rules"
name="basic"
:label-col="{ span: 0 }"
:wrapper-col="{ span: 24 }"
autocomplete="off"
@finish="onFinish"
@finishFailed="onFinishFailed"
ref="formRef"
>
<a-form-item label="" name="username">
<a-input v-model:value="formState.username" size="large" placeholder="请输入用户名">
<template #prefix> <UserOutlined class="site-form-item-icon" /> </template>
</a-input>
</a-form-item>
<a-form-item label="" name="password">
<a-input-password v-model:value="formState.password" size="large" placeholder="请输入密码">
<template #prefix>
<LockOutlined class="site-form-item-icon" />
</template>
</a-input-password>
</a-form-item>
<a-form-item label="" name="code">
<a-input v-model:value="formState.code" size="large" placeholder="请输入验证码">
<template #prefix>
<CodeOutlined class="site-form-item-icon" />
</template>
<template #addonAfter>
<IdentifyCode :identifyCode="state.identifyCode" @click="refreshCode"></IdentifyCode>
</template>
</a-input>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 0, span: 24 }">
<a-button type="primary" size="large" class="w-[calc(100%+0rem)]" html-type="submit">立即登录</a-button>
</a-form-item>
</a-form>
<a-row>
<a-col :span="24" class="text-right">
<a-typography-link :href="APP_PREVIEW_URL" target="_blank"> 大屏预览 </a-typography-link>
</a-col>
</a-row>
</a-card>
</a-col>
</a-row>
</template>
<script setup lang="ts">
import { APP_TITLE, APP_PREVIEW_URL } from '@/constant';
import { UserOutlined, LockOutlined, CodeOutlined } from '@ant-design/icons-vue';
import { useGlobalStore } from '@/store/global';
import { Rule } from 'ant-design-vue/es/form';
import { validatePass } from '@/utils/validate';
const globalStore = useGlobalStore();
const state = reactive({
redirect: '/',
// 图片验证码
identifyCode: '',
// 验证码规则
identifyCodes: '3456789ABCDEFGHGKMNPQRSTUVWXYabcdefghijklmnopqrstuvwxyz',
});
// const reload: any = inject('reload');
// nextTick(() => {
// debugger;
// reload();
// });
interface FormState {
layout: string;
username: string;
password: string;
remember: boolean;
code: string;
}
const formState = reactive<FormState>({
layout: 'horizontal',
username: '',
password: '',
remember: true,
code: '',
});
const validateCode = (rule: any, value: any, callback: any) => {
if (!value) {
callback(new Error('请输入验证码'));
} else if (value !== state.identifyCode) {
callback(new Error('验证码错误'));
} else {
callback();
}
};
const rules: Record<string, Rule[]> = {
username: [{ required: true, message: '请输入用户名!', trigger: 'blur' }],
password: [{ required: true, validator: validatePass, trigger: 'blur' }],
code: [{ required: true, validator: validateCode, trigger: 'blur' }],
};
const onFinish = (values: any) => {
const { username, password } = values;
globalStore.Login({
username,
password,
});
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
/**
* 刷新验证码
*/
const refreshCode = () => {
state.identifyCode = '';
makeCode(state.identifyCodes, 4);
};
/**
* 生成随机验证码
* @param o 验证规则
* @param l 验证码位数
*/
const makeCode = (o: string, l: number) => {
for (let i = 0; i < l; i++) {
state.identifyCode += state.identifyCodes[Math.floor(Math.random() * (state.identifyCodes.length - 0) + 0)];
}
};
const formRef = ref('');
onMounted(() => {
if (formState.username === '') {
// formRef.username.focus();
} else if (formState.password === '') {
// formRef.password.focus();
}
refreshCode();
});
</script>
<style scoped>
:deep .ant-input-group-addon {
padding: 0;
}
</style>