546 lines
17 KiB
TypeScript
546 lines
17 KiB
TypeScript
import Footer from '@/components/Footer';
|
||
import { getCaptchaImg, login } from '@/services/system/auth';
|
||
import { getFakeCaptcha, sancGenerateQrcode, sancAuthCheck } 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, Helmet, history, SelectLang, useIntl, useModel } from '@umijs/max';
|
||
import { Alert, Col, Image, message, Row, Tabs, QRCode } from 'antd';
|
||
import Settings from '../../../../config/defaultSettings';
|
||
import React, { useEffect, useState } from 'react';
|
||
import { flushSync } from 'react-dom';
|
||
// flushSync 允许你强制 React 同步刷新提供的回调中的任何更新。这确保了 DOM 立即更新
|
||
import { clearSessionToken, setSessionToken } from '@/access';
|
||
import logoImg from '@/assets/logo.svg';
|
||
import login_imge2b033b1 from '@/assets/login_img.e2b033b1.png';
|
||
import { use } from 'echarts';
|
||
|
||
let timer: any = null;
|
||
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 [qrcodeVal, setQrcodeVal] = useState<string>('');
|
||
const [qrcodeTime, setQrcodeTime] = useState<string>('');
|
||
const [qrcodeStatus, setQrcodeStatus] = useState<string>('loading');
|
||
|
||
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);
|
||
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') || '/');
|
||
setTimeout(() => history.go(0), 0);
|
||
return;
|
||
} else {
|
||
message.error(response.msg);
|
||
clearSessionToken();
|
||
// 如果失败去设置用户错误信息
|
||
setUserLoginState({ ...response, type });
|
||
getCaptchaCode();
|
||
}
|
||
} catch (error) {
|
||
const defaultLoginFailureMessage = intl.formatMessage({
|
||
id: 'pages.login.failure',
|
||
defaultMessage: '登录失败,请重试!',
|
||
});
|
||
message.error(defaultLoginFailureMessage);
|
||
}
|
||
};
|
||
|
||
const generateQrcode = async () => {
|
||
setQrcodeStatus('loading');
|
||
const response = await sancGenerateQrcode();
|
||
if (response.code === 200) {
|
||
const { qrCode, validDate } = response.data;
|
||
setQrcodeStatus('active');
|
||
setQrcodeVal(qrCode);
|
||
setQrcodeTime(validDate);
|
||
if (timer) {
|
||
clearInterval(timer);
|
||
}
|
||
timer = setInterval(() => {
|
||
const timeDiff = getTimeDiff(validDate);
|
||
if (timeDiff > 0) {
|
||
clearInterval(timer);
|
||
setQrcodeStatus('expired');
|
||
return;
|
||
}
|
||
checkQrcode(qrCode as string);
|
||
}, 2000);
|
||
}
|
||
};
|
||
|
||
const checkQrcode = async (text: string) => {
|
||
const response = await sancAuthCheck(text);
|
||
if (response.code === 200) {
|
||
const { authStatus } = response.data;
|
||
switch (authStatus) {
|
||
case 'success':
|
||
setQrcodeStatus('scanned');
|
||
message.success('扫码成功');
|
||
const defaultLoginSuccessMessage = intl.formatMessage({
|
||
id: 'pages.login.success',
|
||
defaultMessage: '登录成功!',
|
||
});
|
||
const current = new Date();
|
||
const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
|
||
setSessionToken(response.data?.token, response.data?.token, expireTime);
|
||
message.success(defaultLoginSuccessMessage);
|
||
await fetchUserInfo();
|
||
const urlParams = new URL(window.location.href).searchParams;
|
||
history.push(urlParams.get('redirect') || '/');
|
||
setTimeout(() => history.go(0), 0);
|
||
break;
|
||
case 'failed':
|
||
setQrcodeStatus('expired');
|
||
message.error(response.data.errCode + ':' + response.data.errMsg);
|
||
break;
|
||
case 'pending':
|
||
break;
|
||
}
|
||
} else {
|
||
clearInterval(timer);
|
||
setQrcodeStatus('expired');
|
||
}
|
||
};
|
||
|
||
function getTimeDiff(validTimeStr: string): number {
|
||
const now = new Date();
|
||
const [h, m, s] = validTimeStr.split(':').map(Number);
|
||
|
||
const target = new Date();
|
||
target.setHours(h, m, s, 0);
|
||
|
||
return now.getTime() - target.getTime();
|
||
}
|
||
|
||
const { code } = userLoginState;
|
||
const loginType = type;
|
||
|
||
useEffect(() => {
|
||
getCaptchaCode();
|
||
}, []);
|
||
|
||
return (
|
||
<div className={containerClassName}>
|
||
<Helmet>
|
||
<title>
|
||
{intl.formatMessage({
|
||
id: 'menu.login',
|
||
defaultMessage: '登录页',
|
||
})}
|
||
- {Settings.title}
|
||
</title>
|
||
</Helmet>
|
||
<Lang />
|
||
<div
|
||
style={{
|
||
flex: '1',
|
||
padding: '32px 0',
|
||
}}
|
||
>
|
||
<LoginForm
|
||
contentStyle={{
|
||
minWidth: 280,
|
||
maxWidth: '75vw',
|
||
}}
|
||
submitter={type === 'scanQode' ? false : true}
|
||
// logo={<img alt="logo" src={logoImg} />}
|
||
title="石河子智慧就业服务系统"
|
||
// subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
|
||
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={(type) => {
|
||
setType(type);
|
||
if (timer) {
|
||
clearInterval(timer);
|
||
}
|
||
if (type === 'scanQode') {
|
||
generateQrcode();
|
||
}
|
||
}}
|
||
centered
|
||
items={[
|
||
{
|
||
key: 'account',
|
||
label: intl.formatMessage({
|
||
id: 'pages.login.accountLogin.tab',
|
||
defaultMessage: '账户密码登录',
|
||
}),
|
||
},
|
||
{
|
||
key: 'scanQode',
|
||
label: '社保卡扫码登录',
|
||
},
|
||
// {
|
||
// key: 'mobile',
|
||
// label: intl.formatMessage({
|
||
// id: 'pages.login.phoneLogin.tab',
|
||
// defaultMessage: '手机号登录',
|
||
// }),
|
||
// },
|
||
]}
|
||
/>
|
||
|
||
{/* {code !== 200 && loginType === 'account' && ( */}
|
||
{/* <LoginMessage*/}
|
||
{/* content={intl.formatMessage({*/}
|
||
{/* id: 'pages.login.accountLogin.errorMessage',*/}
|
||
{/* defaultMessage: '账户或密码错误(admin/admin123)',*/}
|
||
{/* })}*/}
|
||
{/* />*/}
|
||
{/*)}*/}
|
||
|
||
{type === 'scanQode' && (
|
||
<>
|
||
<Row>
|
||
<Col offset={1}>
|
||
<QRCode
|
||
errorLevel="H"
|
||
size={300}
|
||
iconSize={300 / 5}
|
||
status={qrcodeStatus}
|
||
onRefresh={() => generateQrcode()}
|
||
value={qrcodeVal}
|
||
icon={login_imge2b033b1}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
</>
|
||
)}
|
||
|
||
{type === 'account' && (
|
||
<>
|
||
<ProFormText
|
||
name="username"
|
||
initialValue="admin"
|
||
fieldProps={{
|
||
size: 'large',
|
||
prefix: <UserOutlined />,
|
||
}}
|
||
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 />,
|
||
}}
|
||
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',
|
||
}}
|
||
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',
|
||
}}
|
||
preview={false}
|
||
onClick={() => getCaptchaCode()}
|
||
/>
|
||
</Col>
|
||
</Row>
|
||
</>
|
||
)}
|
||
|
||
{code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
|
||
{type === 'mobile' && (
|
||
<>
|
||
<ProFormText
|
||
fieldProps={{
|
||
size: 'large',
|
||
prefix: <MobileOutlined />,
|
||
}}
|
||
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 />,
|
||
}}
|
||
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');
|
||
}}
|
||
/>
|
||
</>
|
||
)}
|
||
{type !== 'scanQode' && (
|
||
<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;
|