feat: 新增登录验证码
Former-commit-id: 9a297d1986ed6ea92b39abc0538cfe29be0636a1
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import { AxiosPromise } from 'axios';
|
import { AxiosPromise } from 'axios';
|
||||||
import { LoginData, LoginResult } from './types';
|
import { CaptchaResult, LoginData, LoginResult } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录API
|
* 登录API
|
||||||
@@ -25,3 +25,15 @@ export function logoutApi() {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码
|
||||||
|
*/
|
||||||
|
export function getCaptchaApi(): AxiosPromise<CaptchaResult> {
|
||||||
|
return request({
|
||||||
|
url: '/api/v1/auth/captcha',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,11 +5,21 @@ export interface LoginData {
|
|||||||
/**
|
/**
|
||||||
* 用户名
|
* 用户名
|
||||||
*/
|
*/
|
||||||
username: string;
|
username?: string;
|
||||||
/**
|
/**
|
||||||
* 密码
|
* 密码
|
||||||
*/
|
*/
|
||||||
password: string;
|
password?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码缓存key
|
||||||
|
*/
|
||||||
|
verifyCodeKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码
|
||||||
|
*/
|
||||||
|
verifyCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,3 +43,18 @@ export interface LoginResult {
|
|||||||
*/
|
*/
|
||||||
tokenType?: string;
|
tokenType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码响应
|
||||||
|
*/
|
||||||
|
export interface CaptchaResult {
|
||||||
|
/**
|
||||||
|
* 验证码缓存key
|
||||||
|
*/
|
||||||
|
verifyCodeKey: string;
|
||||||
|
/**
|
||||||
|
* 验证码图片Base64字符串
|
||||||
|
*/
|
||||||
|
verifyCodeBase64: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
9
src/assets/icons/verify_code.svg
Normal file
9
src/assets/icons/verify_code.svg
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<svg t="1655050462467" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2131"
|
||||||
|
width="200" height="200">
|
||||||
|
<path
|
||||||
|
d="M917.6 267.2c-36.1-2.5-72.4-9.3-103.6-19.3-10.1-3-20.2-6.4-30.3-10-21.4-6.3-50.5-18.8-83.6-36.6-0.4-0.2-0.7-0.4-1.1-0.6-7.8-4.2-15.7-8.7-23.8-13.4-10.9-6.3-21.7-12.9-32.5-19.9-0.4-0.3-0.8-0.5-1.2-0.8-7.7-5-15.5-10.2-23.1-15.5-5-3.4-10-7.1-15-10.7-3.8-2.8-7.5-5.3-11.3-8.2-27.4-20.5-54.5-43.5-79.9-68.3-25.4 24.8-52.5 47.8-79.9 68.3-3.7 2.8-7.5 5.4-11.3 8.2-5 3.6-10 7.3-15 10.7-7.7 5.4-15.4 10.5-23.1 15.5-0.4 0.3-0.8 0.5-1.2 0.8-10.8 6.9-21.6 13.6-32.5 19.9-8.1 4.7-16 9.2-23.8 13.4-0.3 0.2-0.7 0.4-1 0.6-33 17.8-62.2 30.3-83.6 36.6-10.1 3.6-20.2 7-30.3 10-31.1 10-67.4 16.8-103.6 19.3h0.1c1.1 16.2 2.1 37.7 3.4 60.9h0.7c6.1 86.8 23.5 210.2 49.7 282.8 1.2 3.2 2.2 6.5 3.3 9.6 0.6 1.5 1.2 2.8 1.8 4.3 62.8 162.1 171.9 280.1 303 323.4v0.4c17.3 5.7 31.9 9.3 43.5 11.5 11.5-2.2 26.1-5.8 43.5-11.5v-0.4C687 905 796.1 787 858.9 624.8c0.6-1.5 1.2-2.8 1.8-4.3 1.2-3.1 2.2-6.4 3.3-9.6 26.2-72.5 43.6-196 49.7-282.8h0.7c1.1-23.3 2.2-44.7 3.2-60.9z m-47.4 41.9l-0.5 9.5c-0.5 2.2-0.9 4.4-1 6.6C863 406 847 525.7 821.3 596.7c-0.7 1.9-1.4 3.9-2 5.8-0.4 1.2-0.8 2.5-1.4 4.1-0.5 1.2-1 2.5-1.4 3.4C758.1 760.8 657.7 869.3 541 907.8c-1.9 0.6-3.7 1.4-5.5 2.2-7.9 2.5-15.7 4.6-23.2 6.3-7.5-1.7-15.2-3.8-23.1-6.3-1.8-0.9-3.6-1.6-5.5-2.2-116.7-38.5-217.1-147-275.4-297.5-0.5-1.2-0.9-2.4-1.7-4.1-0.4-1.2-0.8-2.4-1.3-3.6-0.7-2-1.3-3.9-1.9-5.6-25.8-71.2-41.7-191-47.4-271.7-0.2-2.3-0.5-4.5-1-6.6l-0.5-9.3c-0.1-1.5-0.2-3-0.2-4.5 24.6-3.8 48.4-9.3 70-16.2 10.1-3 20.4-6.4 31.4-10.4 25.2-7.6 56.5-21.2 90.5-39.6 0.6-0.3 1.2-0.6 1.7-0.9 8.2-4.4 16.7-9.2 24.8-14 10.7-6.1 22-13 34.5-21.1 0.4-0.2 1-0.6 1.3-0.8 8.2-5.3 16.4-10.8 24.1-16.2 4.5-3.1 9.1-6.4 13.7-9.7l2.4-1.8 4-2.9c2.6-1.9 5.2-3.7 7.5-5.5 17.9-13.4 35.3-27.5 52-42.1 16.7 14.7 34 28.7 51.8 42 2.6 1.9 5.1 3.8 7.7 5.6l4.3 3.1 1.5 1.1c4.8 3.5 9.6 6.9 14 9.9 8.1 5.7 16.3 11.2 23.7 16l2.1 1.3c12.4 8 23.7 14.9 34.1 20.8 8.6 5 17 9.8 25 14.1 0.4 0.2 1 0.5 1.5 0.8 34.2 18.4 65.6 32.1 90.9 39.7 11 3.9 21.3 7.3 30.6 10.1 22.1 7.1 46.1 12.6 70.8 16.5 0.1 1.5 0.1 3 0 4.4z"
|
||||||
|
p-id="2132"></path>
|
||||||
|
<path
|
||||||
|
d="M710.6 411.2L476.1 651.6l-120-123c-8.3-8.5-21.8-8.5-30.1 0s-8.3 22.3 0 30.9L461.1 698c4.2 4.3 9.6 6.4 15.1 6.4 5.4 0 10.9-2.1 15-6.4l249.5-255.7c8.3-8.5 8.3-22.3 0-30.9-8.3-8.7-21.8-8.7-30.1-0.2z"
|
||||||
|
p-id="2133"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -10,10 +10,7 @@ export default {
|
|||||||
username: 'Username',
|
username: 'Username',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
code: 'Verification Code',
|
verifyCode: 'Verify Code',
|
||||||
copyright: '',
|
|
||||||
icp: '',
|
|
||||||
thirdPartyLogin: 'third-party login'
|
|
||||||
},
|
},
|
||||||
// 导航栏国际化
|
// 导航栏国际化
|
||||||
navbar: {
|
navbar: {
|
||||||
|
|||||||
@@ -10,10 +10,7 @@ export default {
|
|||||||
username: '用户名',
|
username: '用户名',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
login: '登 录',
|
login: '登 录',
|
||||||
code: '请输入验证码',
|
verifyCode: '验证码'
|
||||||
copyright: '',
|
|
||||||
icp: '',
|
|
||||||
thirdPartyLogin: '第三方登录'
|
|
||||||
},
|
},
|
||||||
navbar: {
|
navbar: {
|
||||||
dashboard: '首页',
|
dashboard: '首页',
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<el-form
|
<el-form ref="loginFormRef" :model="loginData" :rules="loginRules" class="login-form">
|
||||||
ref="loginFormRef"
|
|
||||||
:model="loginData"
|
|
||||||
:rules="loginRules"
|
|
||||||
class="login-form"
|
|
||||||
>
|
|
||||||
<div class="flex text-white items-center py-4">
|
<div class="flex text-white items-center py-4">
|
||||||
<span class="text-2xl flex-1 text-center">{{ $t('login.title') }}</span>
|
<span class="text-2xl flex-1 text-center">{{ $t('login.title') }}</span>
|
||||||
<lang-select style="color: #fff" />
|
<lang-select style="color: #fff" />
|
||||||
@@ -15,51 +10,40 @@
|
|||||||
<div class="p-2 text-white">
|
<div class="p-2 text-white">
|
||||||
<svg-icon icon-class="user" />
|
<svg-icon icon-class="user" />
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<el-input class="flex-1" ref="username" size="large" v-model="loginData.username"
|
||||||
class="flex-1"
|
:placeholder="$t('login.username')" name="username" />
|
||||||
ref="username"
|
|
||||||
size="large"
|
|
||||||
v-model="loginData.username"
|
|
||||||
:placeholder="$t('login.username')"
|
|
||||||
name="username"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-tooltip
|
<el-tooltip :disabled="isCapslock === false" content="Caps lock is On" placement="right">
|
||||||
:disabled="isCapslock === false"
|
|
||||||
content="Caps lock is On"
|
|
||||||
placement="right"
|
|
||||||
>
|
|
||||||
<el-form-item prop="password">
|
<el-form-item prop="password">
|
||||||
<span class="p-2 text-white">
|
<span class="p-2 text-white">
|
||||||
<svg-icon icon-class="password" />
|
<svg-icon icon-class="password" />
|
||||||
</span>
|
</span>
|
||||||
<el-input
|
<el-input class="flex-1" v-model="loginData.password" placeholder="密码"
|
||||||
class="flex-1"
|
:type="passwordVisible === false ? 'password' : 'input'" size="large" name="password" @keyup="checkCapslock"
|
||||||
v-model="loginData.password"
|
@keyup.enter="handleLogin" />
|
||||||
placeholder="密码"
|
|
||||||
:type="passwordVisible === false ? 'password' : 'input'"
|
|
||||||
size="large"
|
|
||||||
name="password"
|
|
||||||
@keyup="checkCapslock"
|
|
||||||
@keyup.enter="handleLogin"
|
|
||||||
/>
|
|
||||||
<span class="mr-2" @click="passwordVisible = !passwordVisible">
|
<span class="mr-2" @click="passwordVisible = !passwordVisible">
|
||||||
<svg-icon
|
<svg-icon :icon-class="passwordVisible === false ? 'eye' : 'eye-open'" class="text-white cursor-pointer" />
|
||||||
:icon-class="passwordVisible === false ? 'eye' : 'eye-open'"
|
|
||||||
class="text-white cursor-pointer"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
|
|
||||||
<el-button
|
<!-- 验证码 -->
|
||||||
size="default"
|
<el-form-item prop="verifyCode">
|
||||||
:loading="loading"
|
<span class="p-2 text-white">
|
||||||
type="primary"
|
<svg-icon icon-class="verify_code" />
|
||||||
class="w-full"
|
</span>
|
||||||
@click.prevent="handleLogin"
|
<el-input v-model="loginData.verifyCode" auto-complete="off" :placeholder="$t('login.verifyCode')" class="w-[60%]"
|
||||||
>{{ $t('login.login') }}
|
@keyup.enter="handleLogin" />
|
||||||
|
|
||||||
|
<div class="captcha">
|
||||||
|
<img :src="captchaBase64" @click="getCaptcha" />
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
|
||||||
|
<el-button size="default" :loading="loading" type="primary" class="w-full" @click.prevent="handleLogin">{{
|
||||||
|
$t('login.login') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
||||||
<!-- 账号密码提示 -->
|
<!-- 账号密码提示 -->
|
||||||
@@ -81,6 +65,7 @@ import { useUserStore } from '@/store/modules/user';
|
|||||||
|
|
||||||
// API依赖
|
// API依赖
|
||||||
import { LocationQuery, LocationQueryValue, useRoute } from 'vue-router';
|
import { LocationQuery, LocationQueryValue, useRoute } from 'vue-router';
|
||||||
|
import { getCaptchaApi } from '@/api/auth';
|
||||||
import { LoginData } from '@/api/auth/types';
|
import { LoginData } from '@/api/auth/types';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -90,15 +75,17 @@ const loginFormRef = ref(ElForm);
|
|||||||
|
|
||||||
const loginData = ref<LoginData>({
|
const loginData = ref<LoginData>({
|
||||||
username: 'admin',
|
username: 'admin',
|
||||||
password: '123456'
|
password: '123456',
|
||||||
});
|
});
|
||||||
|
|
||||||
const loginRules = {
|
const loginRules = {
|
||||||
username: [{ required: true, trigger: 'blur' }],
|
username: [{ required: true, trigger: 'blur' }],
|
||||||
password: [{ required: true, trigger: 'blur', validator: validatePassword }]
|
password: [{ required: true, trigger: 'blur', validator: validatePassword }],
|
||||||
|
// verifyCode: [{ required: true, trigger: 'blur' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
const passwordVisible = ref(false);
|
const passwordVisible = ref(false);
|
||||||
|
const captchaBase64 = ref()
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|
||||||
@@ -119,7 +106,18 @@ function checkCapslock(e: any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录
|
* 验证码
|
||||||
|
*/
|
||||||
|
function getCaptcha() {
|
||||||
|
getCaptchaApi().then(({ data }) => {
|
||||||
|
const { verifyCodeBase64, verifyCodeKey } = data;
|
||||||
|
loginData.value.verifyCodeKey = verifyCodeKey;
|
||||||
|
captchaBase64.value = verifyCodeBase64;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
*/
|
*/
|
||||||
function handleLogin() {
|
function handleLogin() {
|
||||||
loginFormRef.value.validate((valid: boolean) => {
|
loginFormRef.value.validate((valid: boolean) => {
|
||||||
@@ -150,9 +148,26 @@ function handleLogin() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getCaptcha();
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.captcha {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 48px;
|
||||||
|
width: 120px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -167,6 +182,7 @@ function handleLogin() {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
background: rgba(0, 0, 0, 0.1);
|
background: rgba(0, 0, 0, 0.1);
|
||||||
@@ -175,17 +191,20 @@ function handleLogin() {
|
|||||||
|
|
||||||
.el-input {
|
.el-input {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|
||||||
// 子组件 scoped 无效,使用 :deep
|
// 子组件 scoped 无效,使用 :deep
|
||||||
:deep(.el-input__wrapper) {
|
:deep(.el-input__wrapper) {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
caret-color: #fff;
|
caret-color: #fff;
|
||||||
|
|
||||||
&:-webkit-autofill {
|
&:-webkit-autofill {
|
||||||
box-shadow: 0 0 0 1000px transparent inset !important;
|
box-shadow: 0 0 0 1000px transparent inset !important;
|
||||||
-webkit-text-fill-color: #fff !important;
|
-webkit-text-fill-color: #fff !important;
|
||||||
@@ -202,5 +221,6 @@ function handleLogin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user