refactor: 登录页面 UI 新增邮箱登录模式
This commit is contained in:
parent
3f92660e9f
commit
a5a4cd4964
@ -6,10 +6,11 @@ const BASE_URL = '/auth';
|
|||||||
|
|
||||||
export interface LoginReq {
|
export interface LoginReq {
|
||||||
phone?: string;
|
phone?: string;
|
||||||
username: string;
|
email?: string;
|
||||||
password: string;
|
username?: string;
|
||||||
|
password?: string;
|
||||||
captcha: string;
|
captcha: string;
|
||||||
uuid: string;
|
uuid?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginRes {
|
export interface LoginRes {
|
||||||
|
156
continew-admin-ui/src/views/login/components/email-login.vue
Normal file
156
continew-admin-ui/src/views/login/components/email-login.vue
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<a-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="form"
|
||||||
|
:rules="rules"
|
||||||
|
layout="vertical"
|
||||||
|
size="large"
|
||||||
|
class="login-form"
|
||||||
|
>
|
||||||
|
<a-form-item field="email" hide-label>
|
||||||
|
<a-input
|
||||||
|
v-model="form.email"
|
||||||
|
:placeholder="$t('login.email.placeholder.email')"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="captcha" hide-label>
|
||||||
|
<a-input
|
||||||
|
v-model="form.captcha"
|
||||||
|
:placeholder="$t('login.email.placeholder.captcha')"
|
||||||
|
:max-length="6"
|
||||||
|
allow-clear
|
||||||
|
style="flex: 1 1"
|
||||||
|
/>
|
||||||
|
<a-button
|
||||||
|
class="captcha-btn"
|
||||||
|
:loading="captchaLoading"
|
||||||
|
:disabled="captchaDisable"
|
||||||
|
@click="handleSendCaptcha"
|
||||||
|
>
|
||||||
|
{{ captchaBtnName }}
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
<a-button class="btn" :loading="loading" type="primary" html-type="submit"
|
||||||
|
>{{ $t('login.button') }}(即将开放)
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { getCurrentInstance, ref, toRefs, reactive, computed } from 'vue';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useLoginStore } from '@/store';
|
||||||
|
import { LoginReq } from '@/api/auth/login';
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance() as any;
|
||||||
|
const { t } = useI18n();
|
||||||
|
const loginStore = useLoginStore();
|
||||||
|
const loading = ref(false);
|
||||||
|
const captchaLoading = ref(false);
|
||||||
|
const captchaDisable = ref(false);
|
||||||
|
const captchaTime = ref(60);
|
||||||
|
const captchaTimer = ref();
|
||||||
|
const captchaBtnNameKey = ref('login.captcha.get');
|
||||||
|
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
|
||||||
|
const data = reactive({
|
||||||
|
form: {} as LoginReq,
|
||||||
|
rules: {
|
||||||
|
email: [
|
||||||
|
{ required: true, message: t('login.email.error.required.email') },
|
||||||
|
],
|
||||||
|
captcha: [
|
||||||
|
{ required: true, message: t('login.email.error.required.captcha') },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { form, rules } = toRefs(data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置验证码
|
||||||
|
*/
|
||||||
|
const resetCaptcha = () => {
|
||||||
|
window.clearInterval(captchaTimer.value);
|
||||||
|
captchaTime.value = 60;
|
||||||
|
captchaBtnNameKey.value = 'login.captcha.get';
|
||||||
|
captchaDisable.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
const handleSendCaptcha = () => {
|
||||||
|
if (captchaLoading.value) return;
|
||||||
|
proxy.$refs.formRef.validateField('email', (valid: any) => {
|
||||||
|
if (!valid) {
|
||||||
|
captchaLoading.value = true;
|
||||||
|
captchaBtnNameKey.value = 'login.captcha.ing';
|
||||||
|
captchaLoading.value = false;
|
||||||
|
captchaDisable.value = true;
|
||||||
|
captchaBtnNameKey.value = `${t(
|
||||||
|
'login.captcha.get'
|
||||||
|
)}(${(captchaTime.value -= 1)}s)`;
|
||||||
|
captchaTimer.value = window.setInterval(() => {
|
||||||
|
captchaTime.value -= 1;
|
||||||
|
captchaBtnNameKey.value = `${t('login.captcha.get')}(${
|
||||||
|
captchaTime.value
|
||||||
|
}s)`;
|
||||||
|
if (captchaTime.value < 0) {
|
||||||
|
window.clearInterval(captchaTimer.value);
|
||||||
|
captchaTime.value = 60;
|
||||||
|
captchaBtnNameKey.value = t('login.captcha.get');
|
||||||
|
captchaDisable.value = false;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.login-form {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 32px 5px 0;
|
||||||
|
margin-top: 16px;
|
||||||
|
.arco-input-wrapper,
|
||||||
|
:deep(.arco-select-view-single) {
|
||||||
|
background-color: var(--color-bg-white);
|
||||||
|
border: 1px solid var(--color-border-3);
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.arco-input-wrapper.arco-input-error {
|
||||||
|
background-color: var(--color-danger-light-1);
|
||||||
|
border-color: var(--color-danger-light-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.captcha-btn {
|
||||||
|
height: 40px;
|
||||||
|
margin-left: 12px;
|
||||||
|
min-width: 98px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arco-btn-secondary:not(.arco-btn-disabled) {
|
||||||
|
background-color: #f6f8fa;
|
||||||
|
border: 1px solid #dde2e9;
|
||||||
|
color: #41464f;
|
||||||
|
}
|
||||||
|
.arco-btn-secondary:not(.arco-btn-disabled):hover {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 0 0 1px #05f, 0 2px 1px rgba(0, 0, 0, 0.15);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 22px;
|
||||||
|
margin: 20px 0 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -53,13 +53,17 @@
|
|||||||
const captchaDisable = ref(false);
|
const captchaDisable = ref(false);
|
||||||
const captchaTime = ref(60);
|
const captchaTime = ref(60);
|
||||||
const captchaTimer = ref();
|
const captchaTimer = ref();
|
||||||
const captchaBtnNameKey = ref('login.phone.captcha');
|
const captchaBtnNameKey = ref('login.captcha.get');
|
||||||
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
|
const captchaBtnName = computed(() => t(captchaBtnNameKey.value));
|
||||||
const data = reactive({
|
const data = reactive({
|
||||||
form: {} as LoginReq,
|
form: {} as LoginReq,
|
||||||
rules: {
|
rules: {
|
||||||
phone: [
|
phone: [
|
||||||
{ required: true, message: t('login.phone.error.required.phone') },
|
{ required: true, message: t('login.phone.error.required.phone') },
|
||||||
|
{
|
||||||
|
match: /^1[3-9]\d{9}$/,
|
||||||
|
message: t('login.phone.error.match.phone'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
captcha: [
|
captcha: [
|
||||||
{ required: true, message: t('login.phone.error.required.captcha') },
|
{ required: true, message: t('login.phone.error.required.captcha') },
|
||||||
@ -74,7 +78,7 @@
|
|||||||
const resetCaptcha = () => {
|
const resetCaptcha = () => {
|
||||||
window.clearInterval(captchaTimer.value);
|
window.clearInterval(captchaTimer.value);
|
||||||
captchaTime.value = 60;
|
captchaTime.value = 60;
|
||||||
captchaBtnNameKey.value = 'login.phone.captcha';
|
captchaBtnNameKey.value = 'login.captcha.get';
|
||||||
captchaDisable.value = false;
|
captchaDisable.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -86,21 +90,21 @@
|
|||||||
proxy.$refs.formRef.validateField('phone', (valid: any) => {
|
proxy.$refs.formRef.validateField('phone', (valid: any) => {
|
||||||
if (!valid) {
|
if (!valid) {
|
||||||
captchaLoading.value = true;
|
captchaLoading.value = true;
|
||||||
captchaBtnNameKey.value = 'login.phone.captcha.ing';
|
captchaBtnNameKey.value = 'login.captcha.ing';
|
||||||
captchaLoading.value = false;
|
captchaLoading.value = false;
|
||||||
captchaDisable.value = true;
|
captchaDisable.value = true;
|
||||||
captchaBtnNameKey.value = `${t(
|
captchaBtnNameKey.value = `${t(
|
||||||
'login.phone.reCaptcha'
|
'login.captcha.get'
|
||||||
)}(${(captchaTime.value -= 1)}s)`;
|
)}(${(captchaTime.value -= 1)}s)`;
|
||||||
captchaTimer.value = window.setInterval(() => {
|
captchaTimer.value = window.setInterval(() => {
|
||||||
captchaTime.value -= 1;
|
captchaTime.value -= 1;
|
||||||
captchaBtnNameKey.value = `${t('login.phone.reCaptcha')}(${
|
captchaBtnNameKey.value = `${t('login.captcha.get')}(${
|
||||||
captchaTime.value
|
captchaTime.value
|
||||||
}s)`;
|
}s)`;
|
||||||
if (captchaTime.value < 0) {
|
if (captchaTime.value < 0) {
|
||||||
window.clearInterval(captchaTimer.value);
|
window.clearInterval(captchaTimer.value);
|
||||||
captchaTime.value = 60;
|
captchaTime.value = 60;
|
||||||
captchaBtnNameKey.value = t('login.phone.reCaptcha');
|
captchaBtnNameKey.value = t('login.captcha.get');
|
||||||
captchaDisable.value = false;
|
captchaDisable.value = false;
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -12,26 +12,34 @@
|
|||||||
<div class="left-banner"></div>
|
<div class="left-banner"></div>
|
||||||
<div class="login-card">
|
<div class="login-card">
|
||||||
<div class="title"
|
<div class="title"
|
||||||
>{{ $t('login.welcome') }} {{ appStore.getTitle }}</div
|
>{{ $t('login.welcome') }} {{ appStore.getTitle }}</div
|
||||||
>
|
>
|
||||||
<a-tabs class="account-tab" default-active-key="1">
|
<EmailLogin v-if="isEmailLogin" />
|
||||||
<a-tab-pane key="1" :title="$t('login.account')"
|
<a-tabs v-else class="account-tab" default-active-key="1">
|
||||||
><AccountLogin
|
<a-tab-pane
|
||||||
/></a-tab-pane>
|
key="1"
|
||||||
<a-tab-pane key="2" :title="$t('login.phone')"
|
:title="$t('login.account')"
|
||||||
><PhoneLogin
|
>
|
||||||
/></a-tab-pane>
|
<AccountLogin />
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane
|
||||||
|
key="2"
|
||||||
|
:title="$t('login.phone')"
|
||||||
|
>
|
||||||
|
<PhoneLogin />
|
||||||
|
</a-tab-pane>
|
||||||
</a-tabs>
|
</a-tabs>
|
||||||
<div class="oauth">
|
<div class="oauth">
|
||||||
<a-divider class="text" orientation="center">{{
|
<a-divider class="text" orientation="center">{{
|
||||||
$t('login.other')
|
$t('login.other')
|
||||||
}}</a-divider>
|
}}</a-divider>
|
||||||
<div class="idps">
|
<div class="idps">
|
||||||
<a-tooltip content="邮箱登录(即将开放)" mini>
|
<div v-if="!isEmailLogin" class="mail app" @click="toggleLoginMode">
|
||||||
<div class="mail app">
|
<icon-email /> {{ $t('login.email.txt') }}
|
||||||
<icon-email /> {{ $t('login.email.txt') }}
|
</div>
|
||||||
</div>
|
<div v-else class="account app" @click="toggleLoginMode">
|
||||||
</a-tooltip>
|
<icon-user /> {{ $t('login.account.txt') }}
|
||||||
|
</div>
|
||||||
<a-tooltip content="Gitee(即将开放)" mini>
|
<a-tooltip content="Gitee(即将开放)" mini>
|
||||||
<a href="javascript: void(0);" class="app">
|
<a href="javascript: void(0);" class="app">
|
||||||
<svg
|
<svg
|
||||||
@ -65,7 +73,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div v-if="appStore.device === 'desktop'" class="footer">
|
||||||
<div class="beian">
|
<div class="beian">
|
||||||
<div class="below text" v-html="appStore.getCopyright"></div>
|
<div class="below text" v-html="appStore.getCopyright"></div>
|
||||||
</div>
|
</div>
|
||||||
@ -74,14 +82,23 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { useAppStore } from '@/store';
|
import { useAppStore } from '@/store';
|
||||||
import getFile from '@/utils/file';
|
import getFile from '@/utils/file';
|
||||||
|
import useResponsive from '@/hooks/responsive';
|
||||||
import AccountLogin from './components/account-login.vue';
|
import AccountLogin from './components/account-login.vue';
|
||||||
import PhoneLogin from './components/phone-login.vue';
|
import PhoneLogin from './components/phone-login.vue';
|
||||||
|
import EmailLogin from './components/email-login.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
useResponsive(true);
|
||||||
|
const isEmailLogin = ref(false);
|
||||||
|
|
||||||
|
const toggleLoginMode = () => {
|
||||||
|
isEmailLogin.value = !isEmailLogin.value;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@ -137,7 +154,7 @@
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5%;
|
top: 4.5%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -225,16 +242,31 @@
|
|||||||
.mail {
|
.mail {
|
||||||
min-width: 81px;
|
min-width: 81px;
|
||||||
width: 81px;
|
width: 81px;
|
||||||
|
}
|
||||||
|
.account {
|
||||||
|
min-width: 147px;
|
||||||
|
width: 147px;
|
||||||
|
}
|
||||||
|
.mail,
|
||||||
|
.account {
|
||||||
color: #41464f;
|
color: #41464f;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
svg {
|
}
|
||||||
color: #000;
|
.mail svg,
|
||||||
font-size: 16px;
|
.account svg {
|
||||||
margin-right: 10px;
|
font-size: 16px;
|
||||||
}
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.mail:hover,
|
||||||
|
.account:hover {
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
|
}
|
||||||
|
.mail svg:hover,
|
||||||
|
.account svg:hover {
|
||||||
|
color: rgb(var(--primary-6));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,20 @@ export default {
|
|||||||
'login.account.placeholder.captcha': 'Please enter captcha',
|
'login.account.placeholder.captcha': 'Please enter captcha',
|
||||||
'login.phone.placeholder.phone': 'Please enter phone',
|
'login.phone.placeholder.phone': 'Please enter phone',
|
||||||
'login.phone.placeholder.captcha': 'Please enter captcha',
|
'login.phone.placeholder.captcha': 'Please enter captcha',
|
||||||
'login.phone.captcha': 'Get captcha',
|
'login.email.placeholder.email': 'Please enter email',
|
||||||
'login.phone.captcha.ing': 'Sending...',
|
'login.email.placeholder.captcha': 'Please enter captcha',
|
||||||
'login.phone.reCaptcha': 'Resend captcha',
|
'login.captcha.get': 'Get captcha',
|
||||||
|
'login.captcha.ing': 'Sending...',
|
||||||
|
|
||||||
'login.account.error.required.username': 'Please enter username',
|
'login.account.error.required.username': 'Please enter username',
|
||||||
'login.account.error.required.password': 'Please enter password',
|
'login.account.error.required.password': 'Please enter password',
|
||||||
'login.account.error.required.captcha': 'Please enter captcha',
|
'login.account.error.required.captcha': 'Please enter captcha',
|
||||||
'login.phone.error.required.phone': 'Please enter phone',
|
'login.phone.error.required.phone': 'Please enter phone',
|
||||||
|
'login.phone.error.match.phone':
|
||||||
|
'Please enter the correct mobile phone number',
|
||||||
'login.phone.error.required.captcha': 'Please enter captcha',
|
'login.phone.error.required.captcha': 'Please enter captcha',
|
||||||
|
'login.email.error.required.email': 'Please enter email',
|
||||||
|
'login.email.error.required.captcha': 'Please enter captcha',
|
||||||
|
|
||||||
'login.captcha': 'Captcha',
|
'login.captcha': 'Captcha',
|
||||||
'login.rememberMe': 'Remember me',
|
'login.rememberMe': 'Remember me',
|
||||||
|
@ -10,15 +10,19 @@ export default {
|
|||||||
'login.account.placeholder.captcha': '请输入验证码',
|
'login.account.placeholder.captcha': '请输入验证码',
|
||||||
'login.phone.placeholder.phone': '请输入手机号',
|
'login.phone.placeholder.phone': '请输入手机号',
|
||||||
'login.phone.placeholder.captcha': '请输入验证码',
|
'login.phone.placeholder.captcha': '请输入验证码',
|
||||||
'login.phone.captcha': '获取验证码',
|
'login.email.placeholder.email': '请输入邮箱',
|
||||||
'login.phone.captcha.ing': '发送中...',
|
'login.email.placeholder.captcha': '请输入验证码',
|
||||||
'login.phone.reCaptcha': '重新发送',
|
'login.captcha.get': '获取验证码',
|
||||||
|
'login.captcha.ing': '发送中...',
|
||||||
|
|
||||||
'login.account.error.required.username': '请输入用户名',
|
'login.account.error.required.username': '请输入用户名',
|
||||||
'login.account.error.required.password': '请输入密码',
|
'login.account.error.required.password': '请输入密码',
|
||||||
'login.account.error.required.captcha': '请输入验证码',
|
'login.account.error.required.captcha': '请输入验证码',
|
||||||
'login.phone.error.required.phone': '请输入手机号',
|
'login.phone.error.required.phone': '请输入手机号',
|
||||||
|
'login.phone.error.match.phone': '请输入正确的手机号',
|
||||||
'login.phone.error.required.captcha': '请输入验证码',
|
'login.phone.error.required.captcha': '请输入验证码',
|
||||||
|
'login.email.error.required.email': '请输入邮箱',
|
||||||
|
'login.email.error.required.captcha': '请输入验证码',
|
||||||
|
|
||||||
'login.captcha': '验证码',
|
'login.captcha': '验证码',
|
||||||
'login.rememberMe': '记住我',
|
'login.rememberMe': '记住我',
|
||||||
|
Loading…
Reference in New Issue
Block a user