Vue3.0 实现登录界面验证码效果(验证码图形插件GVerify)
Vue3.0 实现登录界面验证码效果(验证码图形插件GVerify)
·
要求:
1.当密码、验证码输入错误是,验证码会自动更新
2. 点击更新验证码
实现方式:
1.定义一个字母数组
/** 生成字母数组* */
function getAllLetter() {
var letterStr =
'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z';
return letterStr.split(',');
}
2.生成随机数
function randomNum(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
3.生成随机色
function randomColor(min, max) {
var r = randomNum(min, max);
var g = randomNum(min, max);
var b = randomNum(min, max);
return 'rgb(' + r + ',' + g + ',' + b + ')';
}
- 核心代码(验证码图形插件GVerify–插件代码)
function GVerify(options) {
// 创建一个图形验证码对象,接收options对象为参数
this.options = {
// 默认options参数值
id: '', // 容器Id
canvasId: 'verifyCanvas', // canvas的ID
width: '174', // 默认canvas宽度
height: '68', // 默认canvas高度
type: 'blend', // 图形验证码默认类型blend:数字字母混合类型、number:纯数字、letter:纯字母
code: '',
con: null,
};
if (Object.prototype.toString.call(options) == '[object Object]') {
// 判断传入参数类型
for (var i in options) {
// 根据传入的参数,修改默认参数值
this.options[i] = options[i];
}
} else {
this.options.id = options;
}
this.options.numArr =
'0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'.split(
',',
);
this.options.letterArr = getAllLetter();
this._init();
this.refresh();
}
GVerify.prototype = {
/** 版本号* */
version: '1.0.0',
/** 初始化方法* */
_init: function () {
// var con = document.getElementById(this.options.id);
var con = this.options.con;
var canvas = document.createElement('canvas');
this.options.width = con.offsetWidth > 0 ? con.offsetWidth : this.options.width;
this.options.height = con.offsetHeight > 0 ? con.offsetHeight : this.options.height;
canvas.id = this.options.canvasId;
canvas.width = this.options.width;
canvas.height = this.options.height;
canvas.style.cursor = 'pointer';
canvas.innerHTML = '您的浏览器版本不支持canvas';
con.appendChild(canvas);
},
/** 生成验证码* */
refresh: function () {
this.options.code = '';
var canvas = document.getElementById(this.options.canvasId);
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
} else {
return;
}
ctx.textBaseline = 'middle';
ctx.fillStyle = randomColor(180, 240);
ctx.fillRect(0, 0, this.options.width, this.options.height);
let txtArr;
if (this.options.type == 'blend') {
// 判断验证码类型
txtArr = this.options.numArr.concat(this.options.letterArr);
} else if (this.options.type == 'number') {
txtArr = this.options.numArr;
} else {
txtArr = this.options.letterArr;
}
for (var i = 1; i <= 4; i++) {
var txt = txtArr[randomNum(0, txtArr.length)];
this.options.code += txt;
ctx.font = randomNum(this.options.height / 2, this.options.height) + 'px SimHei'; // 随机生成字体大小
ctx.fillStyle = randomColor(50, 160); // 随机生成字体颜色
ctx.shadowOffsetX = randomNum(-3, 3);
ctx.shadowOffsetY = randomNum(-3, 3);
ctx.shadowBlur = randomNum(-3, 3);
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
var x = (this.options.width / 5) * i;
var y = this.options.height / 2;
var deg = randomNum(-30, 30);
/** 设置旋转角度和坐标原点* */
ctx.translate(x, y);
ctx.rotate((deg * Math.PI) / 180);
ctx.fillText(txt, 0, 0);
/** 恢复旋转角度和坐标原点* */
ctx.rotate((-deg * Math.PI) / 180);
ctx.translate(-x, -y);
}
/** 绘制干扰线* */
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = randomColor(40, 180);
ctx.beginPath();
ctx.moveTo(randomNum(0, this.options.width), randomNum(0, this.options.height));
ctx.lineTo(randomNum(0, this.options.width), randomNum(0, this.options.height));
ctx.stroke();
}
/** 绘制干扰点* */
for (let i = 0; i < this.options.width / 4; i++) {
ctx.fillStyle = randomColor(0, 255);
ctx.beginPath();
ctx.arc(randomNum(0, this.options.width), randomNum(0, this.options.height), 1, 0, 2 * Math.PI);
ctx.fill();
}
},
/** 获取验证码* */
GetCode: function () {
return this.options.code;
},
/** 验证验证码* */
validate: function (code) {
var code1 = code.toLowerCase();
var v_code = this.options.code.toLowerCase();
if (code1 == v_code) {
return true;
} else {
return false;
}
},
};
5.将以上(1、 2、 3、 4、)合并成一个js文件放到项目中便于调用
6.登录界面(template 部分)
<template>
<div class="root">
<div class="root_center">
<div style="width: 626px; height: 880px">
<div class="root_center_left" />
</div>
<div style="width: 854px; height: 880px">
<div class="root_center_right">
<p>
<img src="../../assets/image/login/组4793.png" />
</p>
<p class="font_size_style">*****信息管理系统</p>
<el-form ref="loginFormRef" :model="loginFormState" :rules="rules">
<el-form-item prop="account">
<span class="root_title" :style="root_name"> 账号: </span>
<el-input
v-model.trim="account"
class="root_input"
size="large"
type="user"
placeholder="请输入账号"
maxlength="11"
autocomplete="new-password"
:prefix-icon="User"
@focus="handleFocus('user')"
@blur="handleBlur('user')"
@keyup.enter.exact="handleLogin"
>
</el-input>
</el-form-item>
<el-form-item prop="password">
<span class="root_title" :style="root_pwa"> 密码: </span>
<el-input
v-model.trim="password"
class="root_input"
size="large"
type="password"
placeholder="请输入密码"
maxlength="12"
clearable
autocomplete="new-password"
:prefix-icon="Lock"
@focus="handleFocus('password')"
@blur="handleBlur('password')"
@keyup.enter.exact="handleLogin"
></el-input>
</el-form-item>
<el-form-item prop="code">
<span class="root_title" :style="root_pwa_code"> 验证码: </span>
<el-input
v-model.trim="code"
class="root_input_code"
size="large"
placeholder="请输入验证码"
:prefix-icon="Lock"
maxlength="4"
@focus="handleFocus('code')"
@blur="handleBlur('code')"
@keyup.enter.exact="handleLogin"
>
<template #append>
<div class="yzcode" :style="{ width: width + 'px', height: height + 'px' }" @click="OnRefresh()">
<p ref="picyzm"></p>
</div>
</template>
</el-input>
</el-form-item>
<br />
<el-form-item size="medium">
<el-button
v-debounce="2000"
class="root_but"
type="primary"
:loading="loginFormState.loading"
@click="handleLogin"
>登录</el-button
>
</el-form-item>
</el-form>
</div>
</div>
</div>
</div>
</template>
7.登录界面(js 部分)
<script setup>
import { ElInput, ElButton, ElForm, ElFormItem, ElLoading, ElMessage } from 'element-plus';
import { ref, reactive, toRefs, onMounted } from 'vue';
import { Lock, User } from '@element-plus/icons-vue';
import GVerify from './piccode.js';
import { encrypt } from '../../common/common-crypto';
import { getMenuInfoByUserIdForMap, getPersonalInformation, userLoginApi } from '../../apis/api_user';
import { KEY_TOKEN } from '../../apis';
import { useRouter } from 'vue-router';
import { useUserStore } from '../../stores/storeOther';
import { useMapStore } from '../../stores/map';
const props = defineProps({
width: {
type: Number,
default: 200,
},
height: {
type: Number,
default: 60,
},
});
let verifyCode = null;
const router = useRouter();
const $userStore = useUserStore();
const $mapStore = useMapStore();
const loginFormRef = ref();
const picyzm = ref(null);
const loginFormState = reactive({
account: '',
password: '',
loading: false,
code: '',
});
const { account, password, code } = toRefs(loginFormState);
const code_content = ref('');
const root_pwa = ref('color: #333333');
const root_name = ref('color: #333333');
const root_pwa_code = ref('color: #333333');
const rules = {
account: [{ required: true, message: '账号不能为空', trigger: 'blur' }],
password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
};
// 当有焦点时候触发
const handleFocus = (isName) => {
if (isName == 'user') {
root_name.value = 'color: #009cf9';
} else if (isName == 'password') {
root_pwa.value = 'color: #009cf9';
} else {
root_pwa_code.value = 'color: #009cf9';
}
};
// 失去焦点触发
const handleBlur = (isName) => {
if (isName == 'user') {
root_name.value = 'color: #333333';
} else if (isName == 'password') {
root_pwa.value = 'color: #333333';
} else {
root_pwa_code.value = 'color: #333333';
}
};
//刷新验证码
const OnRefresh = () => {
verifyCode.refresh();
const code = verifyCode.GetCode();
code_content.value = code;
};
onMounted(() => {
picyzm.value && picyzm.value.focus();
//初始化验证码
verifyCode = new GVerify({
type: 'blend',
height: props.height,
con: picyzm.value,
});
//获取验证码内容
const code = verifyCode.GetCode();
code_content.value = code;
});
/**
* @description:登录
* @param {*}
* @return {*}
*/
const handleLogin = () => {
loginFormRef.value.validate(async (valid) => {
if (valid) {
const loading = ElLoading.service({
lock: true,
text: '正在加载中...',
background: 'rgba(0, 0, 0, 0.7)',
});
if (code_content.value.toLowerCase() !== loginFormState.code.toLowerCase()) {
ElMessage({
type: 'warning',
message: '验证码输入不正确',
});
loading.close();
verifyCode.refresh();
const code = verifyCode.GetCode();
code_content.value = code;
return;
}
const random_start = PassWordRandom(7);
const random_end = PassWordRandom(7);
const password_value = encrypt(`${random_start}${loginFormState.password}${random_end}`);
const [err, data] = await userLoginApi({
username: loginFormState.account,
password: password_value,
});
if (!err) {
const { authorization } = data.data;
sessionStorage.setItem(KEY_TOKEN, authorization);
const [userErr, userInfo] = await getPersonalInformation();
if (userErr) {
ElMessage({
type: 'error',
message: '获取用户信息失败',
});
location.reload();
router.replace('/login');
return;
}
$userStore.SET_USER_INFO(userInfo.data);
$mapStore.SELECT_SUPERIOR_INDIVIDUAL_MAP();
$mapStore.SELECT_SUPERIOR_STAND_MAP();
$mapStore.SELECT_MAP_LAYER_ALL();
const [errMenuInfo, MenuInfo] = await getMenuInfoByUserIdForMap();
if (!errMenuInfo) {
$userStore.OBTAIN_BUSINESS_MAP(MenuInfo.data);
}
// !获取用户 信息
router.replace('/plant');
setTimeout(() => {
loading.close();
ElMessage({
type: 'success',
message: '登录成功',
});
}, 5000);
} else {
ElMessage.error(err);
loading.close();
verifyCode.refresh();
const code = verifyCode.GetCode();
code_content.value = code;
return;
}
}
});
};
const PassWordRandom = (count) => {
const str = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,0,1,2,3,4,5,6,7,8,9';
const arr = str.split(',');
let rand = '';
for (var i = 0; i < count; i++) {
rand += arr[Math.floor(Math.random() * 36)];
}
return rand;
};
8.登录界面(css 部分)
<style scoped lang="scss">
.root {
width: 100%;
height: 100%;
background-image: url('../../assets/image/login/login1.png');
background-size: 100% 100%;
opacity: 0.999;
user-select: none;
display: flex;
box-sizing: border-box;
justify-content: center;
align-items: center;
.root_center {
width: 1480px;
height: 880px;
border-radius: 24px;
overflow: hidden;
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.16);
display: flex;
justify-content: center;
align-items: center;
.el-row {
height: 100%;
.el-col {
height: 100%;
}
}
.root_center_left {
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.6);
background-image: url('../../assets/image/login/login2.png');
background-size: 100% 100%;
}
.root_center_right {
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.6);
text-align: center;
box-sizing: border-box;
padding: 25px 39px 104px 34px;
display: flex;
flex-direction: column;
overflow: hidden;
.font_size_style {
font-size: 30px;
font-weight: 700;
color: #009cf9;
margin-bottom: 54px;
margin-top: 0;
}
.root_title {
// font-size: 20px;
font-size: 18px;
margin-bottom: 5px;
}
.root_input_code {
font-size: 20px;
p {
margin: 0 !important;
}
:deep(input) {
height: 74px !important;
border-radius: 5px;
font-size: 18px;
}
:deep(.el-input__wrapper) {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
border-right-style: none;
box-shadow: none;
}
}
.root_input {
font-size: 18px;
:deep(input) {
height: 74px !important;
border-radius: 5px;
font-size: 20px;
}
:deep(.el-input__wrapper) {
border-radius: 5px;
border: 1px solid #dcdee5;
}
}
.root_but {
width: 100%;
height: 73px;
box-shadow: 0px 10px 20px 5px rgba(70, 113, 215, 0.3);
background-color: #009cf9;
font-size: 20px;
border-radius: 10px;
margin-top: 30px;
}
}
}
#picyzm {
height: 40px;
display: inline-block;
margin: 0 30px;
}
#verifyCodeDemo {
width: 100%;
display: flex;
margin-top: 200px;
justify-content: center;
}
#btn {
margin: 30px auto;
background-color: blue;
color: #fff;
border-radius: 5px;
border: 0;
width: 100px;
height: 40px;
}
}
.el-form-item:not(:last-child) {
display: block;
margin-bottom: 37px;
text-align: left;
}
.el-form-item:last-child {
margin-bottom: 0px !important;
}
:deep(.el-input-group__append) {
border-left-style: none;
background: #ffffff !important;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
padding: 0;
}
:deep(.el-form-item__error) {
font-weight: 600;
padding-top: 6px;
font-size: 12px;
}
</style>
9.最终效果
20230112_111105
更多推荐
所有评论(0)