| @@ -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; | |||