| 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'; |
| import './index.less'; |
| import { registerUser } from '@/services/bt/index'; |
| |
| 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: 'hidden', |
| 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: |
| "linear-gradient(120deg, #232526 0%, #414345 100%), url('https://images.unsplash.com/photo-1462331940025-496dfbfc7564?auto=format&fit=crop&w=1500&q=80')", |
| backgroundBlendMode: 'overlay', |
| 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%)', |
| }} |
| > |
| <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, |
| }} |
| > |
| <div |
| style={{ |
| background: 'rgba(30, 34, 54, 0.96)', |
| borderRadius: 20, |
| boxShadow: '0 8px 32px 0 rgba(31, 38, 135, 0.37)', |
| border: '1px solid rgba(255,255,255,0.12)', |
| padding: '36px 24px 28px 24px', |
| minWidth: 340, |
| maxWidth: 420, |
| width: '100%', |
| color: '#fff', |
| backdropFilter: 'blur(8px)', |
| position: 'relative', |
| transition: 'max-width 0.2s cubic-bezier(.4,2,.6,1)', |
| overflow: 'hidden', |
| }} |
| > |
| <div style={{ textAlign: 'center', marginBottom: 24 }}> |
| <img |
| src="/logo.svg" |
| alt="ThunderHub" |
| style={{ |
| width: 54, |
| height: 48, |
| marginBottom: 8, |
| filter: 'drop-shadow(0 0 8px #6cf8)', |
| }} |
| /> |
| <div |
| style={{ |
| color: '#fff', |
| fontWeight: 700, |
| fontSize: 26, |
| letterSpacing: 2, |
| marginBottom: 4, |
| fontFamily: 'Montserrat, sans-serif', |
| }} |
| > |
| ThunderHub |
| </div> |
| <div style={{ color: '#b3c7f9', fontSize: 14, letterSpacing: 1 }}> |
| 探索你的专属星球,畅享PT世界 |
| <span style={{ color: '#6cf', marginLeft: 8, fontWeight: 500, fontSize: 12 }}> |
| JRX MSY ZYT HXQ LJB |
| </span> |
| </div> |
| </div> |
| <LoginForm |
| contentStyle={{ |
| background: 'transparent', |
| boxShadow: 'none', |
| padding: 0, |
| color: '#fff', |
| width: '100%', |
| overflow: 'hidden', |
| }} |
| initialValues={{ |
| autoLogin: true, |
| }} |
| onFinish={async (values) => { |
| if (type === 'account') { |
| await handleSubmit(values as API.LoginParams); |
| } else { |
| try { |
| const response = await registerUser( |
| values.username ?? '', |
| values.password ?? '', |
| values.inviteCode ?? '' |
| ); |
| if (response.code === 0) { |
| message.success('注册成功!'); |
| // 注册成功后自动跳转到登录页或自动登录 |
| setType('account'); |
| } else { |
| message.error(response.msg || '注册失败,请重试!'); |
| clearSessionToken(); |
| setUserLoginState({ ...response, type }); |
| } |
| } catch (error) { |
| message.error('注册失败,请重试!'); |
| } |
| } |
| }} |
| submitter={false} |
| > |
| <Tabs |
| activeKey={type} |
| onChange={setType} |
| centered |
| items={[ |
| { |
| key: 'account', |
| label: ( |
| <span style={{ color: '#fff', fontWeight: 500, fontSize: 16 }}> |
| {intl.formatMessage({ |
| id: 'pages.login.accountLogin.tab', |
| defaultMessage: '账户密码登录', |
| })} |
| </span> |
| ), |
| }, |
| { |
| key: 'mobile', |
| label: ( |
| <span style={{ color: '#fff', fontWeight: 500, fontSize: 16 }}> |
| {intl.formatMessage({ |
| id: 'pages.login.phoneLogin.tab', |
| defaultMessage: '注册', |
| })} |
| </span> |
| ), |
| }, |
| ]} |
| style={{ marginBottom: 24 }} |
| indicatorSize={36} |
| /> |
| |
| {code !== 200 && loginType === 'account' && ( |
| <LoginMessage |
| content={intl.formatMessage({ |
| id: 'pages.login.accountLogin.errorMessage', |
| defaultMessage: '账户或密码错误', |
| })} |
| /> |
| )} |
| {type === 'account' && ( |
| <> |
| <ProFormText |
| name="username" |
| fieldProps={{ |
| size: 'large', |
| prefix: <UserOutlined style={{ color: '#6cf' }} />, |
| style: { |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| borderRadius: 8, |
| }, |
| }} |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.username.placeholder', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.username.required" |
| defaultMessage="请输入用户名!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| <ProFormText.Password |
| name="password" |
| fieldProps={{ |
| size: 'large', |
| prefix: <LockOutlined style={{ color: '#6cf' }} />, |
| style: { |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| borderRadius: 8, |
| }, |
| }} |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.password.placeholder', |
| })} |
| 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', |
| borderRadius: 8, |
| }} |
| 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: '90px', |
| 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: <LockOutlined style={{ color: '#6cf' }} />, // 换成钥匙图标 |
| style: { |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| borderRadius: 8, |
| }, |
| }} |
| name="inviteCode" |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.inviteCode.placeholder', |
| defaultMessage: '请输入邀请码', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.inviteCode.required" |
| defaultMessage="请输入邀请码!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| <ProFormText |
| fieldProps={{ |
| size: 'large', |
| prefix: <UserOutlined style={{ color: '#6cf' }} />, |
| style: { |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| borderRadius: 8, |
| }, |
| }} |
| name="username" |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.username.placeholder', |
| defaultMessage: '请输入用户名', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.username.required" |
| defaultMessage="请输入用户名!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| <ProFormText.Password |
| fieldProps={{ |
| size: 'large', |
| prefix: <LockOutlined style={{ color: '#6cf' }} />, |
| style: { |
| background: 'rgba(255,255,255,0.08)', |
| color: '#fff', |
| borderRadius: 8, |
| }, |
| }} |
| name="password" |
| placeholder={intl.formatMessage({ |
| id: 'pages.login.password.placeholder', |
| defaultMessage: '请输入密码', |
| })} |
| rules={[ |
| { |
| required: true, |
| message: ( |
| <FormattedMessage |
| id="pages.login.password.required" |
| defaultMessage="请输入密码!" |
| /> |
| ), |
| }, |
| ]} |
| /> |
| </> |
| )} |
| {type === 'account' && ( |
| <div |
| style={{ |
| marginBottom: 24, |
| color: '#b3c7f9', |
| display: 'flex', |
| alignItems: 'center', |
| justifyContent: 'space-between', |
| fontSize: 13, |
| }} |
| > |
| <ProFormCheckbox> |
| <span style={{ color: '#fff' }}> |
| <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" /> |
| </span> |
| </ProFormCheckbox> |
| <a |
| style={{ |
| color: '#6cf', |
| }} |
| > |
| <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" /> |
| </a> |
| </div> |
| )} |
| {/* 登录/注册按钮 */} |
| <div style={{ marginTop: 24, display: 'flex', gap: 16 }}> |
| {type === 'account' && ( |
| <button |
| type="submit" |
| style={{ |
| width: '100%', |
| background: 'linear-gradient(90deg, #6cf 0%, #3E71FF 100%)', |
| color: '#fff', |
| border: 'none', |
| borderRadius: 8, |
| padding: '12px 0', |
| fontSize: 16, |
| fontWeight: 600, |
| cursor: 'pointer', |
| boxShadow: '0 2px 8px #6cf4', |
| letterSpacing: 2, |
| }} |
| onClick={() => { |
| // 触发表单提交,登录 |
| document.querySelector('form')?.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true })); |
| }} |
| > |
| 登录 |
| </button> |
| )} |
| {type === 'mobile' && ( |
| <button |
| type="button" |
| style={{ |
| width: '100%', |
| background: 'linear-gradient(90deg, #6cf 0%, #3E71FF 100%)', |
| color: '#fff', |
| border: 'none', |
| borderRadius: 8, |
| padding: '12px 0', |
| fontSize: 16, |
| fontWeight: 600, |
| cursor: 'pointer', |
| boxShadow: '0 2px 8px #6cf4', |
| letterSpacing: 2, |
| }} |
| onClick={async () => { |
| // 触发表单校验并注册 |
| const form = document.querySelector('form'); |
| if (form) { |
| form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true })); |
| } |
| }} |
| > |
| 注册 |
| </button> |
| )} |
| </div> |
| </LoginForm> |
| </div> |
| </div> |
| <Footer /> |
| </div> |
| ); |
| }; |
| |
| export default Login; |