前端重构
This commit is contained in:
parent
5f66394e7c
commit
fad3767b79
|
|
@ -1,5 +1,5 @@
|
||||||
import { createAdminService } from "@/services/index";
|
|
||||||
import { setUserAuthority } from "@/services/kratos/user";
|
import { setUserAuthority } from "@/services/kratos/user";
|
||||||
|
import { jsonInBlacklist } from "@/services/kratos/jwt";
|
||||||
import {
|
import {
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
|
|
@ -14,8 +14,6 @@ import React from "react";
|
||||||
import { flushSync } from "react-dom";
|
import { flushSync } from "react-dom";
|
||||||
import HeaderDropdown from "../HeaderDropdown";
|
import HeaderDropdown from "../HeaderDropdown";
|
||||||
|
|
||||||
const adminService = createAdminService();
|
|
||||||
|
|
||||||
export type GlobalHeaderRightProps = {
|
export type GlobalHeaderRightProps = {
|
||||||
menu?: boolean;
|
menu?: boolean;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|
@ -56,7 +54,10 @@ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({
|
||||||
* 退出登录
|
* 退出登录
|
||||||
*/
|
*/
|
||||||
const loginOut = async () => {
|
const loginOut = async () => {
|
||||||
await adminService.Logout({});
|
const res = await jsonInBlacklist();
|
||||||
|
if (res.code !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { search, pathname } = window.location;
|
const { search, pathname } = window.location;
|
||||||
const urlParams = new URL(window.location.href).searchParams;
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
const searchParams = new URLSearchParams({
|
const searchParams = new URLSearchParams({
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
/**
|
/**
|
||||||
* KRA - Login Page
|
* KRA - Login Page
|
||||||
* User authentication with captcha support
|
* Modern login page with gradient background and card design
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Form, Input, Button, message, App } from 'antd';
|
import { Form, Input, Button, App } from 'antd';
|
||||||
import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons';
|
import { UserOutlined, LockOutlined, SafetyOutlined } from '@ant-design/icons';
|
||||||
import { history, Helmet, useModel } from '@umijs/max';
|
import { history, Helmet, useModel } from '@umijs/max';
|
||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
|
|
@ -13,7 +13,7 @@ import { useUserStore } from '@/models/user';
|
||||||
import { convertToMenuData } from '@/utils/dynamicRouter';
|
import { convertToMenuData } from '@/utils/dynamicRouter';
|
||||||
import Logo from '@/components/Logo/Logo';
|
import Logo from '@/components/Logo/Logo';
|
||||||
import BottomInfo from '@/components/BottomInfo/BottomInfo';
|
import BottomInfo from '@/components/BottomInfo/BottomInfo';
|
||||||
import { createStyles } from 'antd-style';
|
import { createStyles, keyframes } from 'antd-style';
|
||||||
import Settings from '../../../../config/defaultSettings';
|
import Settings from '../../../../config/defaultSettings';
|
||||||
|
|
||||||
interface LoginFormData {
|
interface LoginFormData {
|
||||||
|
|
@ -29,61 +29,140 @@ interface CaptchaData {
|
||||||
openCaptcha: boolean;
|
openCaptcha: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 浮动动画
|
||||||
|
const float = keyframes`
|
||||||
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||||
|
50% { transform: translateY(-20px) rotate(5deg); }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const float2 = keyframes`
|
||||||
|
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
||||||
|
50% { transform: translateY(-15px) rotate(-5deg); }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const pulse = keyframes`
|
||||||
|
0%, 100% { opacity: 0.4; transform: scale(1); }
|
||||||
|
50% { opacity: 0.6; transform: scale(1.05); }
|
||||||
|
`;
|
||||||
|
|
||||||
const useStyles = createStyles(({ token }) => ({
|
const useStyles = createStyles(({ token }) => ({
|
||||||
container: {
|
container: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
},
|
},
|
||||||
|
// 左侧品牌区域 - 深色渐变背景
|
||||||
leftSection: {
|
leftSection: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
display: 'none',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)',
|
||||||
|
position: 'relative',
|
||||||
|
overflow: 'hidden',
|
||||||
|
'@media (min-width: 992px)': {
|
||||||
|
display: 'flex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// 浮动装饰圆圈
|
||||||
|
floatingCircle1: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '300px',
|
||||||
|
height: '300px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, rgba(99, 102, 241, 0.3) 0%, rgba(139, 92, 246, 0.1) 100%)',
|
||||||
|
top: '-50px',
|
||||||
|
left: '-50px',
|
||||||
|
animation: `${float} 8s ease-in-out infinite`,
|
||||||
|
},
|
||||||
|
floatingCircle2: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '200px',
|
||||||
|
height: '200px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.25) 0%, rgba(99, 102, 241, 0.1) 100%)',
|
||||||
|
bottom: '100px',
|
||||||
|
left: '20%',
|
||||||
|
animation: `${float2} 10s ease-in-out infinite`,
|
||||||
|
},
|
||||||
|
floatingCircle3: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '150px',
|
||||||
|
height: '150px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, rgba(139, 92, 246, 0.2) 0%, rgba(59, 130, 246, 0.1) 100%)',
|
||||||
|
top: '30%',
|
||||||
|
right: '10%',
|
||||||
|
animation: `${pulse} 6s ease-in-out infinite`,
|
||||||
|
},
|
||||||
|
floatingCircle4: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100px',
|
||||||
|
height: '100px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
background: 'linear-gradient(135deg, rgba(59, 130, 246, 0.3) 0%, rgba(99, 102, 241, 0.15) 100%)',
|
||||||
|
bottom: '-20px',
|
||||||
|
right: '25%',
|
||||||
|
animation: `${float} 12s ease-in-out infinite`,
|
||||||
|
},
|
||||||
|
// 品牌内容
|
||||||
|
brandContent: {
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 10,
|
||||||
|
textAlign: 'center' as const,
|
||||||
|
color: '#fff',
|
||||||
|
padding: '0 48px',
|
||||||
|
},
|
||||||
|
brandTitle: {
|
||||||
|
fontSize: '42px',
|
||||||
|
fontWeight: 700,
|
||||||
|
marginBottom: '16px',
|
||||||
|
background: 'linear-gradient(135deg, #fff 0%, #a5b4fc 100%)',
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
backgroundClip: 'text',
|
||||||
|
},
|
||||||
|
brandSubtitle: {
|
||||||
|
fontSize: '16px',
|
||||||
|
color: 'rgba(255, 255, 255, 0.7)',
|
||||||
|
lineHeight: 1.6,
|
||||||
|
maxWidth: '400px',
|
||||||
|
margin: '0 auto',
|
||||||
|
},
|
||||||
|
// 右侧表单区域
|
||||||
|
rightSection: {
|
||||||
|
width: '100%',
|
||||||
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
background: '#fff',
|
background: '#f8fafc',
|
||||||
position: 'relative',
|
padding: '24px',
|
||||||
zIndex: 10,
|
'@media (min-width: 992px)': {
|
||||||
},
|
width: '480px',
|
||||||
rightSection: {
|
minWidth: '480px',
|
||||||
width: '50%',
|
|
||||||
height: '100%',
|
|
||||||
background: '#194bfb',
|
|
||||||
display: 'none',
|
|
||||||
'@media (min-width: 768px)': {
|
|
||||||
display: 'block',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
rightBanner: {
|
// 登录卡片
|
||||||
|
loginCard: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
maxWidth: '400px',
|
||||||
objectFit: 'cover',
|
|
||||||
},
|
|
||||||
oblique: {
|
|
||||||
position: 'absolute',
|
|
||||||
height: '130%',
|
|
||||||
width: '60%',
|
|
||||||
background: '#fff',
|
background: '#fff',
|
||||||
transform: 'rotate(-12deg)',
|
borderRadius: '16px',
|
||||||
marginLeft: '-200px',
|
padding: '40px 32px',
|
||||||
zIndex: 5,
|
boxShadow: '0 4px 24px rgba(0, 0, 0, 0.08)',
|
||||||
},
|
|
||||||
formWrapper: {
|
|
||||||
width: '100%',
|
|
||||||
maxWidth: '384px',
|
|
||||||
padding: '48px 24px',
|
|
||||||
zIndex: 999,
|
|
||||||
},
|
},
|
||||||
logoWrapper: {
|
logoWrapper: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginBottom: '16px',
|
marginBottom: '24px',
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
textAlign: 'center' as const,
|
textAlign: 'center' as const,
|
||||||
fontSize: '32px',
|
fontSize: '28px',
|
||||||
fontWeight: 700,
|
fontWeight: 700,
|
||||||
marginBottom: '8px',
|
marginBottom: '8px',
|
||||||
color: token.colorText,
|
color: token.colorText,
|
||||||
|
|
@ -92,10 +171,29 @@ const useStyles = createStyles(({ token }) => ({
|
||||||
textAlign: 'center' as const,
|
textAlign: 'center' as const,
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
color: token.colorTextSecondary,
|
color: token.colorTextSecondary,
|
||||||
marginBottom: '36px',
|
marginBottom: '32px',
|
||||||
},
|
},
|
||||||
|
// 表单样式
|
||||||
formItem: {
|
formItem: {
|
||||||
marginBottom: '24px',
|
marginBottom: '20px',
|
||||||
|
},
|
||||||
|
inputWrapper: {
|
||||||
|
'& .ant-input-affix-wrapper': {
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '12px 16px',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#6366f1',
|
||||||
|
},
|
||||||
|
'&:focus, &.ant-input-affix-wrapper-focused': {
|
||||||
|
borderColor: '#6366f1',
|
||||||
|
boxShadow: '0 0 0 3px rgba(99, 102, 241, 0.1)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .ant-input': {
|
||||||
|
fontSize: '15px',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
captchaWrapper: {
|
captchaWrapper: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
@ -106,21 +204,44 @@ const useStyles = createStyles(({ token }) => ({
|
||||||
},
|
},
|
||||||
captchaImage: {
|
captchaImage: {
|
||||||
width: '120px',
|
width: '120px',
|
||||||
height: '40px',
|
height: '48px',
|
||||||
borderRadius: '6px',
|
borderRadius: '8px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
background: '#c3d4f2',
|
background: 'linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%)',
|
||||||
|
transition: 'transform 0.2s ease',
|
||||||
|
'&:hover': {
|
||||||
|
transform: 'scale(1.02)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
submitBtn: {
|
submitBtn: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '44px',
|
height: '48px',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
|
fontWeight: 600,
|
||||||
|
borderRadius: '8px',
|
||||||
|
background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
|
||||||
|
border: 'none',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
background: 'linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)',
|
||||||
|
transform: 'translateY(-1px)',
|
||||||
|
boxShadow: '0 4px 12px rgba(99, 102, 241, 0.4)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
initBtn: {
|
initBtn: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '44px',
|
height: '48px',
|
||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
|
fontWeight: 500,
|
||||||
|
borderRadius: '8px',
|
||||||
marginTop: '12px',
|
marginTop: '12px',
|
||||||
|
border: '1px solid #e2e8f0',
|
||||||
|
color: token.colorTextSecondary,
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '#6366f1',
|
||||||
|
color: '#6366f1',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
bottomInfo: {
|
bottomInfo: {
|
||||||
position: 'absolute' as const,
|
position: 'absolute' as const,
|
||||||
|
|
@ -133,19 +254,22 @@ const useStyles = createStyles(({ token }) => ({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: '12px',
|
gap: '16px',
|
||||||
},
|
},
|
||||||
linkIcon: {
|
linkIcon: {
|
||||||
width: '32px',
|
width: '36px',
|
||||||
height: '32px',
|
height: '36px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'transform 0.2s',
|
transition: 'all 0.3s ease',
|
||||||
|
opacity: 0.8,
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'scale(1.1)',
|
transform: 'scale(1.15)',
|
||||||
|
opacity: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
const Login: React.FC = () => {
|
const Login: React.FC = () => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
@ -164,7 +288,6 @@ const Login: React.FC = () => {
|
||||||
const fetchCaptcha = useCallback(async () => {
|
const fetchCaptcha = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await captcha();
|
const res = await captcha();
|
||||||
// request.ts 的 responseInterceptors 已经解包,直接使用 res
|
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
setCaptchaData({
|
setCaptchaData({
|
||||||
captchaId: res.data.captchaId || '',
|
captchaId: res.data.captchaId || '',
|
||||||
|
|
@ -196,17 +319,14 @@ const Login: React.FC = () => {
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
const { token, user } = res.data;
|
const { token, user } = res.data;
|
||||||
|
|
||||||
// 保存 token 和用户信息
|
|
||||||
setToken(token);
|
setToken(token);
|
||||||
setUserInfo(user);
|
setUserInfo(user);
|
||||||
|
|
||||||
// 更新全局状态,重新获取用户信息和菜单
|
|
||||||
const [userInfo, menus] = await Promise.all([
|
const [userInfo, menus] = await Promise.all([
|
||||||
initialState?.fetchUserInfo?.(),
|
initialState?.fetchUserInfo?.(),
|
||||||
initialState?.fetchMenus?.(),
|
initialState?.fetchMenus?.(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 转换菜单数据
|
|
||||||
const menuData = menus ? convertToMenuData(menus) : [];
|
const menuData = menus ? convertToMenuData(menus) : [];
|
||||||
|
|
||||||
flushSync(() => {
|
flushSync(() => {
|
||||||
|
|
@ -220,14 +340,12 @@ const Login: React.FC = () => {
|
||||||
|
|
||||||
messageApi.success('登录成功!');
|
messageApi.success('登录成功!');
|
||||||
|
|
||||||
// 跳转到用户默认首页或重定向页面
|
|
||||||
const urlParams = new URL(window.location.href).searchParams;
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
const redirect = urlParams.get('redirect');
|
const redirect = urlParams.get('redirect');
|
||||||
|
|
||||||
if (redirect) {
|
if (redirect) {
|
||||||
history.push(redirect);
|
history.push(redirect);
|
||||||
} else {
|
} else {
|
||||||
// 使用用户角色的默认路由
|
|
||||||
const defaultRouter = user?.authority?.defaultRouter || 'dashboard';
|
const defaultRouter = user?.authority?.defaultRouter || 'dashboard';
|
||||||
history.push(`/${defaultRouter}`);
|
history.push(`/${defaultRouter}`);
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +365,6 @@ const Login: React.FC = () => {
|
||||||
const handleCheckInit = async () => {
|
const handleCheckInit = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await checkDB();
|
const res = await checkDB();
|
||||||
// request.ts 的 responseInterceptors 已经解包,直接使用 res
|
|
||||||
if (res.code === 0 && res.data) {
|
if (res.code === 0 && res.data) {
|
||||||
const checkResult = res.data as { needInit?: boolean };
|
const checkResult = res.data as { needInit?: boolean };
|
||||||
if (checkResult.needInit) {
|
if (checkResult.needInit) {
|
||||||
|
|
@ -267,18 +384,31 @@ const Login: React.FC = () => {
|
||||||
<title>登录 - {Settings.title || 'Kratos Admin'}</title>
|
<title>登录 - {Settings.title || 'Kratos Admin'}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
|
{/* 左侧品牌区域 */}
|
||||||
<div className={styles.leftSection}>
|
<div className={styles.leftSection}>
|
||||||
<div className={styles.oblique} />
|
<div className={styles.floatingCircle1} />
|
||||||
|
<div className={styles.floatingCircle2} />
|
||||||
|
<div className={styles.floatingCircle3} />
|
||||||
|
<div className={styles.floatingCircle4} />
|
||||||
|
|
||||||
<div className={styles.formWrapper}>
|
<div className={styles.brandContent}>
|
||||||
|
<h1 className={styles.brandTitle}>Kratos Admin</h1>
|
||||||
|
<p className={styles.brandSubtitle}>
|
||||||
|
基于 React 19 + Go Kratos 微服务框架构建的现代化全栈管理平台,
|
||||||
|
提供高效、安全、可扩展的企业级解决方案
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 右侧登录表单 */}
|
||||||
|
<div className={styles.rightSection}>
|
||||||
|
<div className={styles.loginCard}>
|
||||||
<div className={styles.logoWrapper}>
|
<div className={styles.logoWrapper}>
|
||||||
<Logo size="large" />
|
<Logo size="large" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className={styles.title}>Kratos Admin</h1>
|
<h2 className={styles.title}>欢迎回来</h2>
|
||||||
<p className={styles.subtitle}>
|
<p className={styles.subtitle}>请输入您的账号信息登录系统</p>
|
||||||
基于 React 19 + Go Kratos 的全栈管理平台
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
|
|
@ -288,28 +418,28 @@ const Login: React.FC = () => {
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
className={styles.formItem}
|
className={`${styles.formItem} ${styles.inputWrapper}`}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入用户名' },
|
{ required: true, message: '请输入用户名' },
|
||||||
{ min: 5, message: '用户名至少5个字符' },
|
{ min: 5, message: '用户名至少5个字符' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<UserOutlined />}
|
prefix={<UserOutlined style={{ color: '#94a3b8' }} />}
|
||||||
placeholder="请输入用户名"
|
placeholder="请输入用户名"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
className={styles.formItem}
|
className={`${styles.formItem} ${styles.inputWrapper}`}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入密码' },
|
{ required: true, message: '请输入密码' },
|
||||||
{ min: 6, message: '密码至少6个字符' },
|
{ min: 6, message: '密码至少6个字符' },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
prefix={<LockOutlined />}
|
prefix={<LockOutlined style={{ color: '#94a3b8' }} />}
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
@ -320,21 +450,15 @@ const Login: React.FC = () => {
|
||||||
className={styles.formItem}
|
className={styles.formItem}
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: '请输入验证码' },
|
{ required: true, message: '请输入验证码' },
|
||||||
{
|
{ pattern: /^\d+$/, message: '验证码须为数字' },
|
||||||
pattern: /^\d+$/,
|
{ min: captchaData.captchaLength, message: `请输入${captchaData.captchaLength}位验证码` },
|
||||||
message: '验证码须为数字',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
min: captchaData.captchaLength,
|
|
||||||
message: `请输入${captchaData.captchaLength}位验证码`,
|
|
||||||
},
|
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div className={styles.captchaWrapper}>
|
<div className={styles.captchaWrapper}>
|
||||||
<Input
|
<Input
|
||||||
prefix={<SafetyOutlined />}
|
prefix={<SafetyOutlined style={{ color: '#94a3b8' }} />}
|
||||||
placeholder="请输入验证码"
|
placeholder="请输入验证码"
|
||||||
className={styles.captchaInput}
|
className={`${styles.captchaInput} ${styles.inputWrapper}`}
|
||||||
/>
|
/>
|
||||||
{captchaData.picPath && (
|
{captchaData.picPath && (
|
||||||
<img
|
<img
|
||||||
|
|
@ -349,7 +473,7 @@ const Login: React.FC = () => {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item style={{ marginBottom: '12px' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
|
|
@ -360,7 +484,7 @@ const Login: React.FC = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item style={{ marginBottom: 0 }}>
|
||||||
<Button
|
<Button
|
||||||
type="default"
|
type="default"
|
||||||
onClick={handleCheckInit}
|
onClick={handleCheckInit}
|
||||||
|
|
@ -373,14 +497,7 @@ const Login: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.rightSection}>
|
{/* 底部信息 */}
|
||||||
<img
|
|
||||||
src="/login_right_banner.jpg"
|
|
||||||
alt="banner"
|
|
||||||
className={styles.rightBanner}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.bottomInfo}>
|
<div className={styles.bottomInfo}>
|
||||||
<BottomInfo>
|
<BottomInfo>
|
||||||
<div className={styles.links}>
|
<div className={styles.links}>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue