4 changed files with 338 additions and 3 deletions
@ -0,0 +1,48 @@ |
|||||
|
@import (reference) '~antd/es/style/themes/index'; |
||||
|
|
||||
|
.container { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
height: 100vh; |
||||
|
overflow: auto; |
||||
|
background: @layout-body-background; |
||||
|
} |
||||
|
|
||||
|
.lang { |
||||
|
width: 100%; |
||||
|
height: 40px; |
||||
|
line-height: 44px; |
||||
|
text-align: right; |
||||
|
:global(.ant-dropdown-trigger) { |
||||
|
margin-right: 24px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
flex: 1; |
||||
|
padding: 32px 0; |
||||
|
} |
||||
|
|
||||
|
@media (min-width: @screen-md-min) { |
||||
|
.container { |
||||
|
background-image: url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr'); |
||||
|
background-size: cover; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 32px 0 24px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
margin-left: 8px; |
||||
|
color: rgba(0, 0, 0, 0.2); |
||||
|
font-size: 24px; |
||||
|
vertical-align: middle; |
||||
|
cursor: pointer; |
||||
|
transition: color 0.3s; |
||||
|
|
||||
|
&:hover { |
||||
|
color: @primary-color; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,290 @@ |
|||||
|
import Footer from '@/components/Footer'; |
||||
|
import { login } from '@/services/ant-design-pro/api'; |
||||
|
import { getFakeCaptcha } from '@/services/ant-design-pro/login'; |
||||
|
import { |
||||
|
AlipayCircleOutlined, |
||||
|
LockOutlined, |
||||
|
MobileOutlined, |
||||
|
TaobaoCircleOutlined, |
||||
|
UserOutlined, |
||||
|
WeiboCircleOutlined, |
||||
|
} from '@ant-design/icons'; |
||||
|
import { |
||||
|
LoginForm, |
||||
|
ProFormCaptcha, |
||||
|
ProFormCheckbox, |
||||
|
ProFormText, |
||||
|
} from '@ant-design/pro-components'; |
||||
|
import { FormattedMessage, history, SelectLang, useIntl, useModel } from '@umijs/max'; |
||||
|
import { Alert, message, Tabs } from 'antd'; |
||||
|
import React, { useState } from 'react'; |
||||
|
import styles from './index.less'; |
||||
|
|
||||
|
const LoginMessage: React.FC<{ |
||||
|
content: string; |
||||
|
}> = ({ content }) => { |
||||
|
return ( |
||||
|
<Alert |
||||
|
style={{ |
||||
|
marginBottom: 24, |
||||
|
}} |
||||
|
message={content} |
||||
|
type="error" |
||||
|
showIcon |
||||
|
/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const Login: React.FC = () => { |
||||
|
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({}); |
||||
|
const [type, setType] = useState<string>('account'); |
||||
|
const { initialState, setInitialState } = useModel('@@initialState'); |
||||
|
|
||||
|
const intl = useIntl(); |
||||
|
|
||||
|
const fetchUserInfo = async () => { |
||||
|
const userInfo = await initialState?.fetchUserInfo?.(); |
||||
|
if (userInfo) { |
||||
|
await setInitialState((s) => ({ |
||||
|
...s, |
||||
|
currentUser: userInfo, |
||||
|
})); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const handleSubmit = async (values: API.LoginParams) => { |
||||
|
try { |
||||
|
// 登录
|
||||
|
const msg = await login({ ...values, type }); |
||||
|
if (msg.status === 'ok') { |
||||
|
const defaultLoginSuccessMessage = intl.formatMessage({ |
||||
|
id: 'pages.login.success', |
||||
|
defaultMessage: '登录成功!', |
||||
|
}); |
||||
|
message.success(defaultLoginSuccessMessage); |
||||
|
await fetchUserInfo(); |
||||
|
const urlParams = new URL(window.location.href).searchParams; |
||||
|
history.push(urlParams.get('redirect') || '/'); |
||||
|
return; |
||||
|
} |
||||
|
console.log(msg); |
||||
|
// 如果失败去设置用户错误信息
|
||||
|
setUserLoginState(msg); |
||||
|
} catch (error) { |
||||
|
const defaultLoginFailureMessage = intl.formatMessage({ |
||||
|
id: 'pages.login.failure', |
||||
|
defaultMessage: '登录失败,请重试!', |
||||
|
}); |
||||
|
console.log(error); |
||||
|
message.error(defaultLoginFailureMessage); |
||||
|
} |
||||
|
}; |
||||
|
const { status, type: loginType } = userLoginState; |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<div className={styles.lang} data-lang> |
||||
|
{SelectLang && <SelectLang />} |
||||
|
</div> |
||||
|
<div className={styles.content}> |
||||
|
<LoginForm |
||||
|
logo={<img alt="logo" src="/logo.svg" />} |
||||
|
title="Ant Design" |
||||
|
subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })} |
||||
|
initialValues={{ |
||||
|
autoLogin: true, |
||||
|
}} |
||||
|
actions={[ |
||||
|
<FormattedMessage |
||||
|
key="loginWith" |
||||
|
id="pages.login.loginWith" |
||||
|
defaultMessage="其他登录方式" |
||||
|
/>, |
||||
|
<AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.icon} />, |
||||
|
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={styles.icon} />, |
||||
|
<WeiboCircleOutlined key="WeiboCircleOutlined" className={styles.icon} />, |
||||
|
]} |
||||
|
onFinish={async (values) => { |
||||
|
await handleSubmit(values as API.LoginParams); |
||||
|
}} |
||||
|
> |
||||
|
<Tabs activeKey={type} onChange={setType}> |
||||
|
<Tabs.TabPane |
||||
|
key="account" |
||||
|
tab={intl.formatMessage({ |
||||
|
id: 'pages.login.accountLogin.tab', |
||||
|
defaultMessage: '账户密码登录', |
||||
|
})} |
||||
|
/> |
||||
|
<Tabs.TabPane |
||||
|
key="mobile" |
||||
|
tab={intl.formatMessage({ |
||||
|
id: 'pages.login.phoneLogin.tab', |
||||
|
defaultMessage: '手机号登录', |
||||
|
})} |
||||
|
/> |
||||
|
</Tabs> |
||||
|
|
||||
|
{status === 'error' && loginType === 'account' && ( |
||||
|
<LoginMessage |
||||
|
content={intl.formatMessage({ |
||||
|
id: 'pages.login.accountLogin.errorMessage', |
||||
|
defaultMessage: '账户或密码错误(admin/ant.design)', |
||||
|
})} |
||||
|
/> |
||||
|
)} |
||||
|
{type === 'account' && ( |
||||
|
<> |
||||
|
<ProFormText |
||||
|
name="username" |
||||
|
fieldProps={{ |
||||
|
size: 'large', |
||||
|
prefix: <UserOutlined className={styles.prefixIcon} />, |
||||
|
}} |
||||
|
placeholder={intl.formatMessage({ |
||||
|
id: 'pages.login.username.placeholder', |
||||
|
defaultMessage: '用户名: admin or user', |
||||
|
})} |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: ( |
||||
|
<FormattedMessage |
||||
|
id="pages.login.username.required" |
||||
|
defaultMessage="请输入用户名!" |
||||
|
/> |
||||
|
), |
||||
|
}, |
||||
|
]} |
||||
|
/> |
||||
|
<ProFormText.Password |
||||
|
name="password" |
||||
|
fieldProps={{ |
||||
|
size: 'large', |
||||
|
prefix: <LockOutlined className={styles.prefixIcon} />, |
||||
|
}} |
||||
|
placeholder={intl.formatMessage({ |
||||
|
id: 'pages.login.password.placeholder', |
||||
|
defaultMessage: '密码: ant.design', |
||||
|
})} |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: ( |
||||
|
<FormattedMessage |
||||
|
id="pages.login.password.required" |
||||
|
defaultMessage="请输入密码!" |
||||
|
/> |
||||
|
), |
||||
|
}, |
||||
|
]} |
||||
|
/> |
||||
|
</> |
||||
|
)} |
||||
|
|
||||
|
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />} |
||||
|
{type === 'mobile' && ( |
||||
|
<> |
||||
|
<ProFormText |
||||
|
fieldProps={{ |
||||
|
size: 'large', |
||||
|
prefix: <MobileOutlined className={styles.prefixIcon} />, |
||||
|
}} |
||||
|
name="mobile" |
||||
|
placeholder={intl.formatMessage({ |
||||
|
id: 'pages.login.phoneNumber.placeholder', |
||||
|
defaultMessage: '手机号', |
||||
|
})} |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: ( |
||||
|
<FormattedMessage |
||||
|
id="pages.login.phoneNumber.required" |
||||
|
defaultMessage="请输入手机号!" |
||||
|
/> |
||||
|
), |
||||
|
}, |
||||
|
{ |
||||
|
pattern: /^1\d{10}$/, |
||||
|
message: ( |
||||
|
<FormattedMessage |
||||
|
id="pages.login.phoneNumber.invalid" |
||||
|
defaultMessage="手机号格式错误!" |
||||
|
/> |
||||
|
), |
||||
|
}, |
||||
|
]} |
||||
|
/> |
||||
|
<ProFormCaptcha |
||||
|
fieldProps={{ |
||||
|
size: 'large', |
||||
|
prefix: <LockOutlined className={styles.prefixIcon} />, |
||||
|
}} |
||||
|
captchaProps={{ |
||||
|
size: 'large', |
||||
|
}} |
||||
|
placeholder={intl.formatMessage({ |
||||
|
id: 'pages.login.captcha.placeholder', |
||||
|
defaultMessage: '请输入验证码', |
||||
|
})} |
||||
|
captchaTextRender={(timing, count) => { |
||||
|
if (timing) { |
||||
|
return `${count} ${intl.formatMessage({ |
||||
|
id: 'pages.getCaptchaSecondText', |
||||
|
defaultMessage: '获取验证码', |
||||
|
})}`;
|
||||
|
} |
||||
|
return intl.formatMessage({ |
||||
|
id: 'pages.login.phoneLogin.getVerificationCode', |
||||
|
defaultMessage: '获取验证码', |
||||
|
}); |
||||
|
}} |
||||
|
name="captcha" |
||||
|
rules={[ |
||||
|
{ |
||||
|
required: true, |
||||
|
message: ( |
||||
|
<FormattedMessage |
||||
|
id="pages.login.captcha.required" |
||||
|
defaultMessage="请输入验证码!" |
||||
|
/> |
||||
|
), |
||||
|
}, |
||||
|
]} |
||||
|
onGetCaptcha={async (phone) => { |
||||
|
const result = await getFakeCaptcha({ |
||||
|
phone, |
||||
|
}); |
||||
|
if (result === false) { |
||||
|
return; |
||||
|
} |
||||
|
message.success('获取验证码成功!验证码为:1234'); |
||||
|
}} |
||||
|
/> |
||||
|
</> |
||||
|
)} |
||||
|
<div |
||||
|
style={{ |
||||
|
marginBottom: 24, |
||||
|
}} |
||||
|
> |
||||
|
<ProFormCheckbox noStyle name="autoLogin"> |
||||
|
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" /> |
||||
|
</ProFormCheckbox> |
||||
|
<a |
||||
|
style={{ |
||||
|
float: 'right', |
||||
|
}} |
||||
|
> |
||||
|
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> |
||||
|
</a> |
||||
|
</div> |
||||
|
</LoginForm> |
||||
|
</div> |
||||
|
<Footer /> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Login; |
||||
Loading…
Reference in new issue