| @@ -0,0 +1,221 @@ | |||||
| import { clearSessionToken, setSessionToken } from '@/access'; | |||||
| import { getCaptchaImg, login } from '@/services/system/auth'; | |||||
| import { parseJsonText } from '@/utils'; | |||||
| import { safeInvoke } from '@/utils/functional'; | |||||
| import LocalStorage from '@/utils/localStorage'; | |||||
| import { to } from '@/utils/promise'; | |||||
| import { history, useModel } from '@umijs/max'; | |||||
| import { Button, Checkbox, Flex, Form, Image, Input, message, type InputRef } from 'antd'; | |||||
| import CryptoJS from 'crypto-js'; | |||||
| import { useEffect, useRef, useState } from 'react'; | |||||
| import { flushSync } from 'react-dom'; | |||||
| import styles from './login.less'; | |||||
| const VERSION = 1; | |||||
| const AESKEY = 'OPENSOURCETECHNOLOGYCENTER'; | |||||
| const LoginInputPrefix = ({ icon }: { icon: string }) => { | |||||
| return ( | |||||
| <div className={styles['login-input-prefix']}> | |||||
| <img className={styles['login-input-prefix__icon']} src={icon} alt="" draggable={false} /> | |||||
| <div className={styles['login-input-prefix__line']}></div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| const Login = () => { | |||||
| const { initialState, setInitialState } = useModel('@@initialState'); | |||||
| const [captchaCode, setCaptchaCode] = useState<string>(''); | |||||
| const [uuid, setUuid] = useState<string>(''); | |||||
| const [form] = Form.useForm(); | |||||
| const captchaInputRef = useRef<InputRef>(null); | |||||
| useEffect(() => { | |||||
| getCaptchaCode(); | |||||
| const autoLogin = LocalStorage.getItem(LocalStorage.rememberPasswordKey) ?? 'false'; | |||||
| if (autoLogin === 'true') { | |||||
| const userStorage = LocalStorage.getItem(LocalStorage.loginUserKey); | |||||
| const userJson = safeInvoke((text: string) => | |||||
| CryptoJS.AES.decrypt(text, AESKEY).toString(CryptoJS.enc.Utf8), | |||||
| )(userStorage); | |||||
| const user = safeInvoke(parseJsonText)(userJson); | |||||
| if (user && typeof user === 'object' && user.version === VERSION) { | |||||
| const { username, password } = user; | |||||
| form.setFieldsValue({ username: username, password: password, autoLogin: true }); | |||||
| } else { | |||||
| form.setFieldsValue({ username: '', password: '', autoLogin: true }); | |||||
| LocalStorage.removeItem(LocalStorage.loginUserKey); | |||||
| } | |||||
| } else { | |||||
| form.setFieldsValue({ username: '', password: '', autoLogin: false }); | |||||
| } | |||||
| }, []); | |||||
| const getCaptchaCode = async () => { | |||||
| const [res] = await to(getCaptchaImg()); | |||||
| if (res) { | |||||
| const imgdata = `data:image/png;base64,${res.img}`; | |||||
| setCaptchaCode(imgdata); | |||||
| setUuid(res.uuid); | |||||
| } | |||||
| }; | |||||
| const fetchUserInfo = async () => { | |||||
| const userInfo = await initialState?.fetchUserInfo?.(); | |||||
| if (userInfo) { | |||||
| flushSync(() => { | |||||
| setInitialState((s) => ({ | |||||
| ...s, | |||||
| currentUser: userInfo, | |||||
| })); | |||||
| }); | |||||
| } | |||||
| }; | |||||
| // 登录 | |||||
| const handleSubmit = async (values: API.LoginParams) => { | |||||
| const [res, error] = await to(login({ ...values, uuid })); | |||||
| if (res && res.data) { | |||||
| const current = new Date(); | |||||
| const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60); | |||||
| const { access_token } = res.data; | |||||
| setSessionToken(access_token, access_token, expireTime); | |||||
| message.success('登录成功!'); | |||||
| LocalStorage.setItem(LocalStorage.rememberPasswordKey, values.autoLogin ? 'true' : 'false'); | |||||
| if (values.autoLogin) { | |||||
| const user = { | |||||
| username: values.username, | |||||
| password: values.password, | |||||
| version: VERSION, | |||||
| }; | |||||
| const encrypted = CryptoJS.AES.encrypt(JSON.stringify(user), AESKEY).toString(); | |||||
| LocalStorage.setItem(LocalStorage.loginUserKey, encrypted); | |||||
| } else { | |||||
| LocalStorage.removeItem(LocalStorage.loginUserKey); | |||||
| } | |||||
| await fetchUserInfo(); | |||||
| const urlParams = new URL(window.location.href).searchParams; | |||||
| history.push(urlParams.get('redirect') || '/'); | |||||
| } else { | |||||
| if (error?.data?.code === 500 && error?.data?.msg === '验证码错误') { | |||||
| captchaInputRef.current?.focus({ | |||||
| cursor: 'all', | |||||
| }); | |||||
| } | |||||
| clearSessionToken(); | |||||
| getCaptchaCode(); | |||||
| } | |||||
| }; | |||||
| return ( | |||||
| <div className={styles['user-login']}> | |||||
| <div className={styles['user-login__left']}> | |||||
| <div className={styles['user-login__left__top']}> | |||||
| <img | |||||
| src={require('@/assets/img/logo.png')} | |||||
| style={{ width: '32px', marginRight: '12px' }} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| 智能材料科研平台 | |||||
| </div> | |||||
| <div className={styles['user-login__left__title']}> | |||||
| <span>智能材料科研平台</span> | |||||
| <img | |||||
| src={require('@/assets/img/login-ai-logo.png')} | |||||
| className={styles['user-login__left__title__img']} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['user-login__left__message']}> | |||||
| <span>大语言模型运维 统一管理平台</span> | |||||
| </div> | |||||
| <img | |||||
| className={styles['user-login__left__bottom-img']} | |||||
| src={require('@/assets/img/login-left-image.png')} | |||||
| draggable={false} | |||||
| alt="" | |||||
| /> | |||||
| </div> | |||||
| <div className={styles['user-login__right']}> | |||||
| <div> | |||||
| <div className={styles['user-login__right__title']}> | |||||
| <span style={{ color: '#111111' }}>欢迎登录</span> | |||||
| <span>智能材料科研平台</span> | |||||
| </div> | |||||
| <div className={styles['user-login__right__content']}> | |||||
| <div className={styles['user-login__right__content__title']}>账号登录</div> | |||||
| <div className={styles['user-login__right__content__form']}> | |||||
| <Form | |||||
| labelCol={{ span: 0 }} | |||||
| wrapperCol={{ span: 24 }} | |||||
| initialValues={{ autoLogin: true }} | |||||
| onFinish={handleSubmit} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| > | |||||
| <Form.Item name="username" rules={[{ required: true, message: '请输入用户名' }]}> | |||||
| <Input | |||||
| placeholder="请输入用户名" | |||||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-user.png')} />} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]}> | |||||
| <Input.Password | |||||
| placeholder="请输入密码" | |||||
| prefix={<LoginInputPrefix icon={require('@/assets/img/login-password.png')} />} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| <Flex align="start" style={{ height: '98px' }}> | |||||
| <div style={{ flex: 1 }}> | |||||
| <Form.Item name="code" rules={[{ required: true, message: '请输入验证码' }]}> | |||||
| <Input | |||||
| placeholder="请输入验证码" | |||||
| prefix={ | |||||
| <LoginInputPrefix icon={require('@/assets/img/login-captcha.png')} /> | |||||
| } | |||||
| ref={captchaInputRef} | |||||
| allowClear | |||||
| /> | |||||
| </Form.Item> | |||||
| </div> | |||||
| <Image | |||||
| className={styles['user-login__right__content__form__captcha']} | |||||
| src={captchaCode} | |||||
| alt="验证码" | |||||
| preview={false} | |||||
| onClick={() => getCaptchaCode()} | |||||
| /> | |||||
| </Flex> | |||||
| <Form.Item | |||||
| name="autoLogin" | |||||
| valuePropName="checked" | |||||
| labelCol={{ span: 0 }} | |||||
| wrapperCol={{ span: 16 }} | |||||
| > | |||||
| <Checkbox>记住密码</Checkbox> | |||||
| </Form.Item> | |||||
| <Form.Item labelCol={{ span: 0 }} wrapperCol={{ span: 24 }}> | |||||
| <Button type="primary" htmlType="submit"> | |||||
| 登录 | |||||
| </Button> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| ); | |||||
| }; | |||||
| export default Login; | |||||