feat(登录页): 重新设计登录页面

- 采用居中卡片布局设计
- 左侧展示品牌区域和认证动画
- 右侧提供登录表单
- 使用灰白色背景提升视觉体验
- 添加响应式设计支持移动端
- 优化表单交互和动画效果
This commit is contained in:
gaoziman 2025-11-06 00:42:53 +08:00
parent c781657003
commit 6d96c5b947
3 changed files with 537 additions and 126 deletions

View File

@ -5,6 +5,7 @@ import {
Link,
Button,
Space,
Message,
} from '@arco-design/web-react';
import { FormInstance } from '@arco-design/web-react/es/Form';
import { IconLock, IconUser } from '@arco-design/web-react/icon';
@ -75,11 +76,12 @@ export default function LoginForm() {
return (
<div className={styles['login-form-wrapper']}>
<div className={styles['login-form-title']}>{t['login.form.title']}</div>
<div className={styles['login-form-sub-title']}>
{t['login.form.title']}
</div>
<div className={styles['login-form-title']}></div>
{errorMessage && (
<div className={styles['login-form-error-msg']}>{errorMessage}</div>
)}
<Form
className={styles['login-form']}
layout="vertical"
@ -91,39 +93,40 @@ export default function LoginForm() {
rules={[{ required: true, message: t['login.form.userName.errMsg'] }]}
>
<Input
prefix={<IconUser />}
placeholder={t['login.form.userName.placeholder']}
onPressEnter={onSubmitClick}
size="large"
/>
</Form.Item>
<Form.Item
field="password"
rules={[{ required: true, message: t['login.form.password.errMsg'] }]}
>
<Input.Password
prefix={<IconLock />}
placeholder={t['login.form.password.placeholder']}
onPressEnter={onSubmitClick}
size="large"
/>
</Form.Item>
<Space size={16} direction="vertical">
<div className={styles['login-form-password-actions']}>
<div className={styles['login-form-actions']}>
<Checkbox checked={rememberPassword} onChange={setRememberPassword}>
{t['login.form.rememberPassword']}
</Checkbox>
<Link>{t['login.form.forgetPassword']}</Link>
</div>
<Button type="primary" long onClick={onSubmitClick} loading={loading}>
<Button
type="primary"
long
size="large"
onClick={onSubmitClick}
loading={loading}
className={styles['login-btn']}
>
{t['login.form.login']}
</Button>
<Button
type="text"
long
className={styles['login-form-register-btn']}
>
{t['login.form.register']}
</Button>
</Space>
</Form>
</div>
);

View File

@ -1,8 +1,5 @@
import React, { useEffect } from 'react';
import Footer from '@/components/Footer';
import Logo from '@/assets/logo.svg';
import LoginForm from './form';
import LoginBanner from './banner';
import styles from './style/index.module.less';
function Login() {
@ -12,26 +9,53 @@ function Login() {
return (
<div className={styles.container}>
<div className={styles.logo}>
<Logo />
<div className={styles['logo-text']}>Arco Design Pro</div>
</div>
<div className={styles['login-card-wrapper']}>
{/* 左侧插画区域 */}
<div className={styles.banner}>
<div className={styles['banner-inner']}>
<LoginBanner />
{/* Logo */}
<div className={styles.logo}>
<span className={styles['logo-text']}>Coder Admin</span>
</div>
{/* 插画区域 */}
<div className={styles['illustration-wrapper']}>
<div className={styles.illustration}>
{/* 认证卡片 */}
<div className={styles['auth-card']}>
<div className={styles['auth-card-header']}>
<div className={styles['scan-line']}></div>
</div>
<div className={styles['auth-card-avatar']}>
<div className={styles['avatar-circle']}></div>
</div>
<div className={styles['auth-card-info']}>
<div className={styles['info-line']}></div>
<div className={styles['info-line-short']}></div>
</div>
<div className={styles['check-icon']}></div>
</div>
{/* 人物剪影 */}
<div className={styles['person-silhouette']}></div>
</div>
</div>
</div>
</div>
{/* 右侧登录表单区域 */}
<div className={styles.content}>
<div className={styles['content-inner']}>
<div className={styles['content-wrapper']}>
<LoginForm />
</div>
<div className={styles.footer}>
<Footer />
Copyright © 2024 Coder Admin. All Rights Reserved.
</div>
</div>
</div>
</div>
</div>
);
}
Login.displayName = 'LoginPage';
export default Login;

View File

@ -1,120 +1,504 @@
// 登录页面容器
.container {
display: flex;
height: 100vh;
.banner {
width: 550px;
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
}
.content {
flex: 1;
position: relative;
padding-bottom: 40px;
}
.footer {
align-items: center;
justify-content: center;
width: 100%;
position: absolute;
bottom: 0;
right: 0;
}
min-height: 100vh;
background: #f0f2f5;
padding: 40px 20px;
position: relative;
overflow: hidden;
}
.logo {
position: fixed;
top: 24px;
left: 22px;
display: inline-flex;
align-items: center;
// 登录卡片包装器
.login-card-wrapper {
display: flex;
background: #fff;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 30%);
overflow: hidden;
max-width: 1000px;
width: 100%;
position: relative;
z-index: 1;
&-text {
margin-left: 4px;
margin-right: 4px;
font-size: 20px;
color: var(--color-fill-1);
}
animation: fade-in-up 0.6s ease-out;
}
// 左侧插画区域
.banner {
flex: 0 0 500px;
display: flex;
justify-content: center;
align-items: center;
&-inner {
height: 100%;
flex: 1;
}
justify-content: center;
background: linear-gradient(135deg, #6b8aff 0%, #5470ff 100%);
position: relative;
overflow: hidden;
padding: 60px 40px;
}
.content {
.banner-inner {
width: 100%;
position: relative;
z-index: 2;
}
// Logo 区域
.logo {
display: flex;
justify-content: center;
align-items: center;
justify-content: center;
margin-bottom: 40px;
.logo-text {
font-size: 24px;
font-weight: 600;
color: #fff;
letter-spacing: 1px;
}
}
.carousel {
height: 100%;
// 插画区域
.illustration-wrapper {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
&-item {
.illustration {
position: relative;
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
gap: 40px;
}
&-title {
font-weight: 500;
font-size: 20px;
line-height: 28px;
color: var(--color-fill-1);
// 认证卡片
.auth-card {
width: 200px;
height: 260px;
background: rgba(255, 255, 255, 95%);
border-radius: 16px;
padding: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 15%);
position: relative;
animation: float 3s ease-in-out infinite;
&-header {
position: relative;
height: 60px;
border-radius: 8px;
background: linear-gradient(135deg, #e8eeff 0%, #f5f7ff 100%);
overflow: hidden;
margin-bottom: 16px;
.scan-line {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, #5470ff, transparent);
animation: scan 2s linear infinite;
}
}
&-sub-title {
margin-top: 8px;
&-avatar {
display: flex;
justify-content: center;
margin-bottom: 16px;
.avatar-circle {
width: 80px;
height: 80px;
border-radius: 50%;
background: linear-gradient(135deg, #6b8aff 0%, #5470ff 100%);
border: 4px solid rgba(84, 112, 255, 20%);
box-shadow: 0 4px 12px rgba(84, 112, 255, 30%);
}
}
&-info {
display: flex;
flex-direction: column;
gap: 8px;
.info-line {
height: 12px;
background: #e8eeff;
border-radius: 6px;
}
.info-line-short {
height: 12px;
width: 60%;
background: #e8eeff;
border-radius: 6px;
}
}
.check-icon {
position: absolute;
top: -12px;
right: -12px;
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: bold;
box-shadow: 0 4px 12px rgba(82, 196, 26, 40%);
animation: check-pulse 2s ease-in-out infinite;
}
}
// 人物剪影
.person-silhouette {
width: 120px;
height: 140px;
background: rgba(255, 255, 255, 30%);
border-radius: 60px 60px 80px 80px;
position: relative;
animation: float 3s ease-in-out infinite reverse;
&::before {
content: '';
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 50px;
border-radius: 50%;
background: rgba(255, 255, 255, 50%);
}
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60px;
height: 8px;
border-radius: 50%;
background: rgba(0, 0, 0, 10%);
filter: blur(4px);
}
}
// 右侧表单区域
.content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 50px;
background: #fff;
position: relative;
}
.content-wrapper {
width: 100%;
max-width: 400px;
}
// 登录表单
.login-form-wrapper {
width: 100%;
}
.login-form-title {
font-size: 24px;
font-weight: 600;
color: #1d2129;
margin-bottom: 32px;
text-align: center;
}
.login-form-error-msg {
padding: 12px 16px;
background: #ffece8;
border: 1px solid #ffccc7;
border-radius: 8px;
color: #f53f3f;
font-size: 14px;
line-height: 22px;
color: var(--color-text-3);
}
&-image {
margin-top: 30px;
width: 320px;
}
margin-bottom: 20px;
animation: shake 0.5s ease;
}
.login-form {
&-wrapper {
width: 320px;
:global {
.arco-form-item {
margin-bottom: 20px;
}
&-title {
font-size: 24px;
font-weight: 500;
color: var(--color-text-1);
line-height: 32px;
.arco-input-wrapper,
.arco-input-password {
border-radius: 8px;
height: 44px;
transition: all 0.3s ease;
&:hover {
border-color: #5470ff;
}
}
&-sub-title {
font-size: 16px;
line-height: 24px;
color: var(--color-text-3);
.arco-input-wrapper.arco-input-focus,
.arco-input-password.arco-input-focus {
border-color: #5470ff;
box-shadow: 0 0 0 2px rgba(84, 112, 255, 10%);
}
&-error-msg {
height: 32px;
line-height: 32px;
color: rgb(var(--red-6));
.arco-input {
height: 44px;
font-size: 14px;
}
}
}
&-password-actions {
.login-form-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
:global {
.arco-checkbox-text {
color: #4e5969;
font-size: 14px;
}
&-register-btn {
color: var(--color-text-3) !important;
.arco-link {
color: #5470ff;
font-size: 14px;
&:hover {
color: #6b8aff;
}
}
}
}
.login-btn {
height: 44px;
font-size: 16px;
font-weight: 500;
background: linear-gradient(135deg, #5470ff 0%, #6b8aff 100%);
border: none;
border-radius: 8px;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(84, 112, 255, 40%);
background: linear-gradient(135deg, #6b8aff 0%, #7d9aff 100%);
}
&:active {
transform: translateY(0);
}
&:global(.arco-btn-primary):not(:global(.arco-btn-disabled)) {
background: linear-gradient(135deg, #5470ff 0%, #6b8aff 100%);
&:hover {
background: linear-gradient(135deg, #6b8aff 0%, #7d9aff 100%);
}
}
}
// 底部版权
.footer {
margin-top: 40px;
text-align: center;
font-size: 12px;
color: #86909c;
}
// 动画定义
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
@keyframes scan {
0% {
transform: translateY(0);
}
100% {
transform: translateY(60px);
}
}
@keyframes check-pulse {
0%,
100% {
transform: scale(1);
box-shadow: 0 4px 12px rgba(82, 196, 26, 40%);
}
50% {
transform: scale(1.1);
box-shadow: 0 4px 20px rgba(82, 196, 26, 60%);
}
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
10%,
30%,
50%,
70%,
90% {
transform: translateX(-5px);
}
20%,
40%,
60%,
80% {
transform: translateX(5px);
}
}
// 响应式设计
@media (max-width: 1024px) {
.login-card-wrapper {
flex-direction: column;
max-width: 500px;
}
.banner {
flex: 0 0 auto;
padding: 40px 30px;
}
.illustration-wrapper {
min-height: auto;
}
.auth-card {
width: 150px;
height: 200px;
padding: 15px;
&-header {
height: 40px;
}
&-avatar {
margin-bottom: 12px;
.avatar-circle {
width: 60px;
height: 60px;
}
}
.check-icon {
width: 32px;
height: 32px;
font-size: 18px;
}
}
.person-silhouette {
width: 80px;
height: 100px;
&::before {
width: 35px;
height: 35px;
}
}
.content {
padding: 40px 30px;
}
}
@media (max-width: 768px) {
.container {
padding: 20px;
}
.login-card-wrapper {
max-width: 100%;
}
.banner {
padding: 30px 20px;
}
.logo {
margin-bottom: 30px;
.logo-text {
font-size: 20px;
}
}
.illustration-wrapper {
min-height: 250px;
}
.auth-card {
width: 120px;
height: 160px;
}
.person-silhouette {
width: 60px;
height: 80px;
}
.content {
padding: 30px 20px;
}
.login-form-title {
font-size: 20px;
}
}