Skip to content

Commit

Permalink
refactor: 重构登录页面 UI 以适配多因子认证、第三方登录等场景
Browse files Browse the repository at this point in the history
  • Loading branch information
Charles7c committed Sep 27, 2023
1 parent d356a6a commit d40d5b4
Show file tree
Hide file tree
Showing 13 changed files with 659 additions and 397 deletions.
1 change: 1 addition & 0 deletions continew-admin-ui/src/api/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { UserState } from '@/store/modules/login/types';
const BASE_URL = '/auth';

export interface LoginReq {
phone?: string;
username: string;
password: string;
captcha: string;
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion continew-admin-ui/src/hooks/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function useUser() {
const logout = async (logoutTo?: string) => {
await loginStore.logout();
const currentRoute = router.currentRoute.value;
Notification.success(t('login.form.logout.success'));
Notification.success(t('login.logout.success'));
router.push({
name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
query: {
Expand Down
210 changes: 210 additions & 0 deletions continew-admin-ui/src/views/login/components/account-login.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<template>
<a-form
ref="formRef"
:model="form"
:rules="rules"
layout="vertical"
size="large"
class="login-form"
@submit="handleLogin"
>
<a-form-item field="username" hide-label>
<a-input
v-model="form.username"
:placeholder="$t('login.account.placeholder.username')"
:max-length="64"
allow-clear
/>
</a-form-item>
<a-form-item field="password" hide-label>
<a-input-password
v-model="form.password"
:placeholder="$t('login.account.placeholder.password')"
:max-length="32"
/>
</a-form-item>
<a-form-item field="captcha" hide-label>
<a-input
v-model="form.captcha"
:placeholder="$t('login.account.placeholder.captcha')"
:max-length="4"
allow-clear
style="flex: 1 1"
/>
<img
:src="captchaImgBase64"
:alt="$t('login.captcha')"
class="captcha"
@click="getCaptcha"
/>
</a-form-item>
<div class="remember-me">
<a-checkbox
:model-value="loginConfig.rememberMe"
@change="setRememberMe as any"
>
{{ $t('login.rememberMe') }}
</a-checkbox>
</div>
<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 } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStorage } from '@vueuse/core';
import { useLoginStore } from '@/store';
import { LoginReq } from '@/api/auth/login';
import { ValidatedError } from '@arco-design/web-vue';
import { encryptByRsa } from '@/utils/encrypt';
import { useRouter } from 'vue-router';
const { proxy } = getCurrentInstance() as any;
const { t } = useI18n();
const router = useRouter();
const loginStore = useLoginStore();
const loading = ref(false);
const captchaImgBase64 = ref();
const loginConfig = useStorage('login-config', {
rememberMe: true,
username: 'admin', // 演示默认值
password: 'admin123', // 演示默认值
// username: debug ? 'admin' : '', // 演示默认值
// password: debug ? 'admin123' : '', // 演示默认值
});
const data = reactive({
form: {
username: loginConfig.value.username,
password: loginConfig.value.password,
captcha: '',
uuid: '',
} as LoginReq,
rules: {
username: [
{ required: true, message: t('login.account.error.required.username') },
],
password: [
{ required: true, message: t('login.account.error.required.password') },
],
captcha: [
{ required: true, message: t('login.account.error.required.captcha') },
],
},
});
const { form, rules } = toRefs(data);
/**
* 获取验证码
*/
const getCaptcha = () => {
loginStore.getImgCaptcha().then((res) => {
form.value.uuid = res.data.uuid;
captchaImgBase64.value = res.data.img;
});
};
getCaptcha();
/**
* 登录
*
* @param errors 表单验证错误
* @param values 表单数据
*/
const handleLogin = ({
errors,
values,
}: {
errors: Record<string, ValidatedError> | undefined;
values: Record<string, any>;
}) => {
if (loading.value) return;
if (!errors) {
loading.value = true;
loginStore
.login({
username: values.username,
password: encryptByRsa(values.password) || '',
captcha: values.captcha,
uuid: values.uuid,
})
.then(() => {
const { redirect, ...othersQuery } = router.currentRoute.value.query;
router.push({
name: (redirect as string) || 'Workplace',
query: {
...othersQuery,
},
});
const { rememberMe } = loginConfig.value;
const { username } = values;
loginConfig.value.username = rememberMe ? username : '';
proxy.$notification.success(t('login.success'));
})
.catch(() => {
getCaptcha();
form.value.captcha = '';
})
.finally(() => {
loading.value = false;
});
}
};
/**
* 记住我
*
* @param value 是否记住我
*/
const setRememberMe = (value: boolean) => {
loginConfig.value.rememberMe = value;
};
</script>

<style lang="less" scoped>
.login-form {
box-sizing: border-box;
padding: 0 5px;
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 {
width: 111px;
height: 36px;
margin: 0 0 0 5px;
cursor: pointer;
}
.remember-me {
display: flex;
justify-content: space-between;
.arco-checkbox {
padding-left: 0;
}
}
.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>
87 changes: 0 additions & 87 deletions continew-admin-ui/src/views/login/components/banner.vue

This file was deleted.

Loading

0 comments on commit d40d5b4

Please sign in to comment.