| import Footer from '@/components/Footer'; |
| import { getCaptchaImg, login } from '@/services/system/auth'; |
| 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 { useEmotionCss } from '@ant-design/use-emotion-css'; |
| import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max'; |
| import { Alert, Col, message, Row, Tabs, Image } from 'antd'; |
| import Settings from '../../../../config/defaultSettings'; |
| import React, { useEffect, useState } from 'react'; |
| import { flushSync } from 'react-dom'; |
| import { clearSessionToken, setSessionToken } from '@/access'; |
| |
| const ActionIcons = () => { |
| const langClassName = useEmotionCss(({ token }) => { |
| return { |
| marginLeft: '8px', |
| color: 'rgba(0, 0, 0, 0.2)', |
| fontSize: '24px', |
| verticalAlign: 'middle', |
| cursor: 'pointer', |
| transition: 'color 0.3s', |
| '&:hover': { |
| color: token.colorPrimaryActive, |
| }, |
| }; |
| }); |
| |
| return ( |
| <> |
| <AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} /> |
| <TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} /> |
| <WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} /> |
| </> |
| ); |
| }; |
| |
| const Lang = () => { |
| const langClassName = useEmotionCss(({ token }) => { |
| return { |
| width: 42, |
| height: 42, |
| lineHeight: '42px', |
| position: 'fixed', |
| right: 16, |
| borderRadius: token.borderRadius, |
| ':hover': { |
| backgroundColor: token.colorBgTextHover, |
| }, |
| }; |
| }); |
| |
| return ( |
| <div className={langClassName} data-lang> |
| {SelectLang && <SelectLang />} |
| </div> |
| ); |
| }; |
| |
| 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>({code: 200}); |
| const [type, setType] = useState<string>('account'); |
| const { initialState, setInitialState } = useModel('@@initialState'); |
| const [captchaCode, setCaptchaCode] = useState<string>(''); |
| const [uuid, setUuid] = useState<string>(''); |
| |
| const containerClassName = useEmotionCss(() => { |
| return { |
| display: 'flex', |
| flexDirection: 'column', |
| height: '100vh', |
| overflow: 'auto', |
| backgroundImage: |
| "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')", |
| backgroundSize: '100% 100%', |
| }; |
| }); |
| |
| const intl = useIntl(); |
| |
| const getCaptchaCode = async () => { |
| const response = await getCaptchaImg(); |
| const imgdata = `data:image/png;base64,${response.img}`; |
| setCaptchaCode(imgdata); |
| setUuid(response.uuid); |
| }; |
| |
| const fetchUserInfo = async () => { |
| const userInfo = await initialState?.fetchUserInfo?.(); |
| if (userInfo) { |
| flushSync(() => { |
| setInitialState((s) => ({ |
| ...s, |
| currentUser: userInfo, |
| })); |
| }); |
| } |
| }; |
| |
| const handleSubmit = async (values: API.LoginParams) => { |
| try { |
| // 登录 |
| const response = await login({ ...values, uuid }); |
| if (response.code === 200) { |
| const defaultLoginSuccessMessage = intl.formatMessage({ |
| id: 'pages.login.success', |
| defaultMessage: '登录成功!', |
| }); |
| const current = new Date(); |
| const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); |
| console.log('login response: ', response); |
| setSessionToken(response?.token, response?.token, expireTime); |
| message.success(defaultLoginSuccessMessage); |
| await fetchUserInfo(); |
| console.log('login ok'); |
| const urlParams = new URL(window.location.href).searchParams; |
| history.push(urlParams.get('redirect') || '/'); |
| return; |
| } else { |
| console.log(response.msg); |
| clearSessionToken(); |
| // 如果失败去设置用户错误信息 |
| setUserLoginState({ ...response, type }); |
| getCaptchaCode(); |
| } |
| } catch (error) { |
| const defaultLoginFailureMessage = intl.formatMessage({ |
| id: 'pages.login.failure', |
| defaultMessage: '登录失败,请重试!', |
| }); |
| console.log(error); |
| message.error(defaultLoginFailureMessage); |
| } |
| }; |
| const { code } = userLoginState; |
| const loginType = type; |
| |
| useEffect(() => { |
| getCaptchaCode(); |
| }, []); |
| |
| return ( |
| <div |
| className={containerClassName} |
| style={{ |
| backgroundImage: |
| "url('https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1500&q=80')", |
| backgroundSize: 'cover', |
| backgroundPosition: 'center', |
| minHeight: '100vh', |
| position: 'relative', |
| overflow: 'hidden', |
| }} |
| > |
| <Helmet> |
| <title> |
| {intl.formatMessage({ |
| id: 'menu.login', |
| defaultMessage: '登录页', |
| })} |
| - {Settings.title} |
| </title> |
| </Helmet> |
| <Lang /> |
| {/* 星空粒子特效层 */} |
| <div |
| style={{ |
| position: 'absolute', |
| inset: 0, |
| zIndex: 0, |
| pointerEvents: 'none', |
| background: 'radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%)', |
| }} |
| > |
| {/* 可以集成粒子库如 tsParticles 或者简单用 CSS 动画 */} |
| <svg width="100%" height="100%"> |
| {[...Array(60)].map((_, i) => ( |
| <circle |
| key={i} |
| cx={Math.random() * 1600} |
| cy={Math.random() * 900} |
| r={Math.random() * 1.5 + 0.5} |
| fill="#fff" |
| opacity={Math.random() * 0.8 + 0.2} |
| /> |
| ))} |
| </svg> |
| </div> |
| <div |
| style={{ |
| flex: '1', |
| padding: '32px 0', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'center', |
| minHeight: '100vh', |
| position: 'relative', |
| zIndex: 1, |
| }} |
| > |
| <LoginForm |
| contentStyle={{ |
| minWidth: 320, |
| maxWidth: 400, |
| background: 'rgba(25, 34, 54, 0.92)', |
| borderRadius: 16, |
| boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.37)', |
| border: '1px solid rgba(255,255,255,0.18)', |
| padding: '32px 32px 24px 32px', |
| color: '#fff', |
| }} |
| // logo={ |
| // <img |
| // alt="logo" |
| // src="/planet-logo.svg" |
| // style={{ |
| // width: 64, |
| // height: 64, |
| // filter: 'drop-shadow(0 0 8px #fff8) drop-shadow(0 0 16px #6cf)', |
| // marginBottom: 8, |
| // }} |
| // /> |
| // } |
| title={ |
| <span style={{ color: '#fff', fontWeight: 700, fontSize: 28, letterSpacing: 2 }}> |
| PT星球 |
| </span> |
| } |
| subTitle={ |
| <span style={{ color: '#b3c7f9', fontSize: 16 }}> |
| 探索你的专属星球,畅享PT世界 |
| </span> |
| } |
| initialValues={{ |
| autoLogin: true, |
| }} |
| // actions={[ |
| // <FormattedMessage |
| // key="loginWith" |
| // id="pages.login.loginWith" |
| // defaultMessage="其他登录方式" |
| // />, |
| // <ActionIcons key="icons" />, |
| // ]} |
| onFinish={async (values) => { |
| await handleSubmit(values as API.LoginParams); |
| }} |
| > |
| <Tabs |
| activeKey={type} |
| onChange={setType} |
| centered |
| items={[ |
| { |
| key: 'account', |
| label: ( |
| <span style={{ color: '#fff' }}> |
| {intl.formatMessage({ |
| id: 'pages.login.accountLogin.tab', |
| defaultMessage: '账户密码登录', |
| })} |
| </span> |
| ), |
| }, |
| { |
| key: 'mobile', |
| label: ( |
| <span style={{ color: '#fff' }}> |
| {intl.formatMessage({ |
| id: 'pages.login.phoneLogin.tab', |
| defaultMessage: '手机号登录', |
| })} |
| </span> |
| ), |
| }, |
| ]} |
| style={{ marginBottom: 24 }} |
| /> |
| |
| {code !== 200 && loginType === 'account' && ( |
| <LoginMessage |
| content={intl.formatMessage({ |
| id: 'pages.login.accountLogin.errorMessage', |
| defaultMessage: '账户或密码错误(admin/admin123)', |
| })} |
| /> |
| )} |
| {type === 'account' && ( |
| <> |
| <ProFormText |
| name="username" |
| initialValue="admin" |
| fieldProps={{ |
| size: 'large', |
| prefix: <UserOutlined style={{ color: '#6cf' }} />, |
| style: { background: 'rgba(255,255,255,0.08)', color: '#fff' }, |
| }} |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.username.placeholder', |
| defaultMessage: '用户名: admin', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.username.required" |
| defaultMessage="请输入用户名!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| <ProFormText.Password |
| name="password" |
| initialValue="admin123" |
| fieldProps={{ |
| size: 'large', |
| prefix: <LockOutlined style={{ color: '#6cf' }} />, |
| style: { background: 'rgba(255,255,255,0.08)', color: '#fff' }, |
| }} |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.password.placeholder', |
| defaultMessage: '密码: admin123', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.password.required" |
| defaultMessage="请输入密码!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| <Row> |
| <Col flex={3}> |
| <ProFormText |
| style={{ |
| float: 'right', |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| }} |
| name="code" |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.captcha.placeholder', |
| defaultMessage: '请输入验证', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.searchTable.updateForm.ruleName.nameRules" |
| defaultMessage="请输入验证啊" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| </Col> |
| <Col flex={2}> |
| <Image |
| src={captchaCode} |
| alt="验证码" |
| style={{ |
| display: 'inline-block', |
| verticalAlign: 'top', |
| cursor: 'pointer', |
| paddingLeft: '10px', |
| width: '100px', |
| borderRadius: 8, |
| boxShadow: '0 0 8px #6cf8', |
| background: '#fff', |
| }} |
| preview={false} |
| onClick={() => getCaptchaCode()} |
| /> |
| </Col> |
| </Row> |
| </> |
| )} |
| |
| {code !== 200 && loginType === 'mobile' && ( |
| <LoginMessage content="验证码错误" /> |
| )} |
| {type === 'mobile' && ( |
| <> |
| <ProFormText |
| fieldProps={{ |
| size: 'large', |
| prefix: <MobileOutlined style={{ color: '#6cf' }} />, |
| style: { background: 'rgba(255,255,255,0.08)', color: '#fff' }, |
| }} |
| 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 style={{ color: '#6cf' }} />, |
| style: { background: 'rgba(255,255,255,0.08)', color: '#fff' }, |
| }} |
| 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) { |
| return; |
| } |
| message.success('获取验证码成功!验证码为:1234'); |
| }} |
| /> |
| </> |
| )} |
| <div |
| style={{ |
| marginBottom: 24, |
| color: '#b3c7f9', |
| }} |
| > |
| <ProFormCheckbox> |
| <a |
| style={{ |
| float: 'right', |
| color: '#fff', |
| fontSize: 14, |
| }}> |
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" /> |
| </a> |
| </ProFormCheckbox> |
| <a |
| style={{ |
| float: 'right', |
| color: '#6cf', |
| }} |
| > |
| <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> |
| </a> |
| </div> |
| </LoginForm> |
| </div> |
| <Footer /> |
| </div> |
| ); |
| }; |
| |
| export default Login; |