blob: a18f7851a4fb7d61ed0c87c8a14778532765e1a5 [file] [log] [blame]
Jiarenxiang38dcb052025-03-13 16:40:09 +08001import Footer from '@/components/Footer';
2import { getCaptchaImg, login } from '@/services/system/auth';
3import { getFakeCaptcha } from '@/services/ant-design-pro/login';
4import {
5 AlipayCircleOutlined,
6 LockOutlined,
7 MobileOutlined,
8 TaobaoCircleOutlined,
9 UserOutlined,
10 WeiboCircleOutlined,
11} from '@ant-design/icons';
12import {
13 LoginForm,
14 ProFormCaptcha,
15 ProFormCheckbox,
16 ProFormText,
17} from '@ant-design/pro-components';
18import { useEmotionCss } from '@ant-design/use-emotion-css';
19import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
20import { Alert, Col, message, Row, Tabs, Image } from 'antd';
21import Settings from '../../../../config/defaultSettings';
22import React, { useEffect, useState } from 'react';
23import { flushSync } from 'react-dom';
24import { clearSessionToken, setSessionToken } from '@/access';
25
26const ActionIcons = () => {
27 const langClassName = useEmotionCss(({ token }) => {
28 return {
29 marginLeft: '8px',
30 color: 'rgba(0, 0, 0, 0.2)',
31 fontSize: '24px',
32 verticalAlign: 'middle',
33 cursor: 'pointer',
34 transition: 'color 0.3s',
35 '&:hover': {
36 color: token.colorPrimaryActive,
37 },
38 };
39 });
40
41 return (
42 <>
43 <AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
44 <TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
45 <WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
46 </>
47 );
48};
49
50const Lang = () => {
51 const langClassName = useEmotionCss(({ token }) => {
52 return {
53 width: 42,
54 height: 42,
55 lineHeight: '42px',
56 position: 'fixed',
57 right: 16,
58 borderRadius: token.borderRadius,
59 ':hover': {
60 backgroundColor: token.colorBgTextHover,
61 },
62 };
63 });
64
65 return (
66 <div className={langClassName} data-lang>
67 {SelectLang && <SelectLang />}
68 </div>
69 );
70};
71
72const LoginMessage: React.FC<{
73 content: string;
74}> = ({ content }) => {
75 return (
76 <Alert
77 style={{
78 marginBottom: 24,
79 }}
80 message={content}
81 type="error"
82 showIcon
83 />
84 );
85};
86
87const Login: React.FC = () => {
88 const [userLoginState, setUserLoginState] = useState<API.LoginResult>({code: 200});
89 const [type, setType] = useState<string>('account');
90 const { initialState, setInitialState } = useModel('@@initialState');
91 const [captchaCode, setCaptchaCode] = useState<string>('');
92 const [uuid, setUuid] = useState<string>('');
93
94 const containerClassName = useEmotionCss(() => {
95 return {
96 display: 'flex',
97 flexDirection: 'column',
98 height: '100vh',
99 overflow: 'auto',
100 backgroundImage:
101 "url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
102 backgroundSize: '100% 100%',
103 };
104 });
105
106 const intl = useIntl();
107
108 const getCaptchaCode = async () => {
109 const response = await getCaptchaImg();
110 const imgdata = `data:image/png;base64,${response.img}`;
111 setCaptchaCode(imgdata);
112 setUuid(response.uuid);
113 };
114
115 const fetchUserInfo = async () => {
116 const userInfo = await initialState?.fetchUserInfo?.();
117 if (userInfo) {
118 flushSync(() => {
119 setInitialState((s) => ({
120 ...s,
121 currentUser: userInfo,
122 }));
123 });
124 }
125 };
126
127 const handleSubmit = async (values: API.LoginParams) => {
128 try {
129 // 登录
130 const response = await login({ ...values, uuid });
131 if (response.code === 200) {
132 const defaultLoginSuccessMessage = intl.formatMessage({
133 id: 'pages.login.success',
134 defaultMessage: '登录成功!',
135 });
136 const current = new Date();
137 const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
138 console.log('login response: ', response);
139 setSessionToken(response?.token, response?.token, expireTime);
140 message.success(defaultLoginSuccessMessage);
141 await fetchUserInfo();
142 console.log('login ok');
143 const urlParams = new URL(window.location.href).searchParams;
144 history.push(urlParams.get('redirect') || '/');
145 return;
146 } else {
147 console.log(response.msg);
148 clearSessionToken();
149 // 如果失败去设置用户错误信息
150 setUserLoginState({ ...response, type });
151 getCaptchaCode();
152 }
153 } catch (error) {
154 const defaultLoginFailureMessage = intl.formatMessage({
155 id: 'pages.login.failure',
156 defaultMessage: '登录失败,请重试!',
157 });
158 console.log(error);
159 message.error(defaultLoginFailureMessage);
160 }
161 };
162 const { code } = userLoginState;
163 const loginType = type;
164
165 useEffect(() => {
166 getCaptchaCode();
167 }, []);
168
169 return (
170 <div className={containerClassName}>
171 <Helmet>
172 <title>
173 {intl.formatMessage({
174 id: 'menu.login',
175 defaultMessage: '登录页',
176 })}
177 - {Settings.title}
178 </title>
179 </Helmet>
180 <Lang />
181 <div
182 style={{
183 flex: '1',
184 padding: '32px 0',
185 }}
186 >
187 <LoginForm
188 contentStyle={{
189 minWidth: 280,
190 maxWidth: '75vw',
191 }}
192 logo={<img alt="logo" src="/logo.svg" />}
193 title="Ant Design"
194 subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
195 initialValues={{
196 autoLogin: true,
197 }}
198 actions={[
199 <FormattedMessage
200 key="loginWith"
201 id="pages.login.loginWith"
202 defaultMessage="其他登录方式"
203 />,
204 <ActionIcons key="icons" />,
205 ]}
206 onFinish={async (values) => {
207 await handleSubmit(values as API.LoginParams);
208 }}
209 >
210 <Tabs
211 activeKey={type}
212 onChange={setType}
213 centered
214 items={[
215 {
216 key: 'account',
217 label: intl.formatMessage({
218 id: 'pages.login.accountLogin.tab',
219 defaultMessage: '账户密码登录',
220 }),
221 },
222 {
223 key: 'mobile',
224 label: intl.formatMessage({
225 id: 'pages.login.phoneLogin.tab',
226 defaultMessage: '手机号登录',
227 }),
228 },
229 ]}
230 />
231
232 {code !== 200 && loginType === 'account' && (
233 <LoginMessage
234 content={intl.formatMessage({
235 id: 'pages.login.accountLogin.errorMessage',
236 defaultMessage: '账户或密码错误(admin/admin123)',
237 })}
238 />
239 )}
240 {type === 'account' && (
241 <>
242 <ProFormText
243 name="username"
244 initialValue="admin"
245 fieldProps={{
246 size: 'large',
247 prefix: <UserOutlined />,
248 }}
249 placeholder={intl.formatMessage({
250 id: 'pages.login.username.placeholder',
251 defaultMessage: '用户名: admin',
252 })}
253 rules={[
254 {
255 required: true,
256 message: (
257 <FormattedMessage
258 id="pages.login.username.required"
259 defaultMessage="请输入用户名!"
260 />
261 ),
262 },
263 ]}
264 />
265 <ProFormText.Password
266 name="password"
267 initialValue="admin123"
268 fieldProps={{
269 size: 'large',
270 prefix: <LockOutlined />,
271 }}
272 placeholder={intl.formatMessage({
273 id: 'pages.login.password.placeholder',
274 defaultMessage: '密码: admin123',
275 })}
276 rules={[
277 {
278 required: true,
279 message: (
280 <FormattedMessage
281 id="pages.login.password.required"
282 defaultMessage="请输入密码!"
283 />
284 ),
285 },
286 ]}
287 />
288 <Row>
289 <Col flex={3}>
290 <ProFormText
291 style={{
292 float: 'right',
293 }}
294 name="code"
295 placeholder={intl.formatMessage({
296 id: 'pages.login.captcha.placeholder',
297 defaultMessage: '请输入验证',
298 })}
299 rules={[
300 {
301 required: true,
302 message: (
303 <FormattedMessage
304 id="pages.searchTable.updateForm.ruleName.nameRules"
305 defaultMessage="请输入验证啊"
306 />
307 ),
308 },
309 ]}
310 />
311 </Col>
312 <Col flex={2}>
313 <Image
314 src={captchaCode}
315 alt="验证码"
316 style={{
317 display: 'inline-block',
318 verticalAlign: 'top',
319 cursor: 'pointer',
320 paddingLeft: '10px',
321 width: '100px',
322 }}
323 preview={false}
324 onClick={() => getCaptchaCode()}
325 />
326 </Col>
327 </Row>
328 </>
329 )}
330
331 {code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
332 {type === 'mobile' && (
333 <>
334 <ProFormText
335 fieldProps={{
336 size: 'large',
337 prefix: <MobileOutlined />,
338 }}
339 name="mobile"
340 placeholder={intl.formatMessage({
341 id: 'pages.login.phoneNumber.placeholder',
342 defaultMessage: '手机号',
343 })}
344 rules={[
345 {
346 required: true,
347 message: (
348 <FormattedMessage
349 id="pages.login.phoneNumber.required"
350 defaultMessage="请输入手机号!"
351 />
352 ),
353 },
354 {
355 pattern: /^1\d{10}$/,
356 message: (
357 <FormattedMessage
358 id="pages.login.phoneNumber.invalid"
359 defaultMessage="手机号格式错误!"
360 />
361 ),
362 },
363 ]}
364 />
365 <ProFormCaptcha
366 fieldProps={{
367 size: 'large',
368 prefix: <LockOutlined />,
369 }}
370 captchaProps={{
371 size: 'large',
372 }}
373 placeholder={intl.formatMessage({
374 id: 'pages.login.captcha.placeholder',
375 defaultMessage: '请输入验证码',
376 })}
377 captchaTextRender={(timing, count) => {
378 if (timing) {
379 return `${count} ${intl.formatMessage({
380 id: 'pages.getCaptchaSecondText',
381 defaultMessage: '获取验证码',
382 })}`;
383 }
384 return intl.formatMessage({
385 id: 'pages.login.phoneLogin.getVerificationCode',
386 defaultMessage: '获取验证码',
387 });
388 }}
389 name="captcha"
390 rules={[
391 {
392 required: true,
393 message: (
394 <FormattedMessage
395 id="pages.login.captcha.required"
396 defaultMessage="请输入验证码!"
397 />
398 ),
399 },
400 ]}
401 onGetCaptcha={async (phone) => {
402 const result = await getFakeCaptcha({
403 phone,
404 });
405 if (!result) {
406 return;
407 }
408 message.success('获取验证码成功!验证码为:1234');
409 }}
410 />
411 </>
412 )}
413 <div
414 style={{
415 marginBottom: 24,
416 }}
417 >
418 <ProFormCheckbox noStyle name="autoLogin">
419 <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
420 </ProFormCheckbox>
421 <a
422 style={{
423 float: 'right',
424 }}
425 >
426 <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
427 </a>
428 </div>
429 </LoginForm>
430 </div>
431 <Footer />
432 </div>
433 );
434};
435
436export default Login;