index.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { Checkbox, message, Spin } from 'antd';
  2. import React, { useEffect, useRef, useState } from 'react';
  3. import { history, Link } from 'umi';
  4. import styles from './index.less';
  5. import Token from '@/utils/token';
  6. import Service from '@/pages/user/Login/service';
  7. import { createForm } from '@formily/core';
  8. import { createSchemaField } from '@formily/react';
  9. import { Form, FormItem, Input, Password, Submit } from '@formily/antd';
  10. import { catchError, filter, mergeMap } from 'rxjs/operators';
  11. import * as ICONS from '@ant-design/icons';
  12. import { useModel } from '@@/plugin-model/useModel';
  13. // import SystemConst from '@/utils/const';
  14. import { useIntl } from '@@/plugin-locale/localeExports';
  15. import { SelectLang } from '@@/plugin-locale/SelectLang';
  16. import Footer from '@/components/Footer';
  17. const Login: React.FC = () => {
  18. const [captcha, setCaptcha] = useState<{ key?: string; base64?: string }>({});
  19. const { initialState, setInitialState } = useModel('@@initialState');
  20. const intl = useIntl();
  21. const fetchUserInfo = async () => {
  22. const userInfo = (await initialState?.fetchUserInfo?.()) as UserInfo;
  23. if (userInfo) {
  24. await setInitialState({
  25. ...initialState,
  26. currentUser: userInfo,
  27. });
  28. }
  29. };
  30. const loginRef = useRef<Partial<LoginParam>>({});
  31. const loginForm = createForm({
  32. validateFirst: true,
  33. initialValues: loginRef.current,
  34. });
  35. const [loading, setLoading] = useState<boolean>(false);
  36. /** 此方法会跳转到 redirect 参数所在的位置 */
  37. const goto = () => {
  38. if (!history) return;
  39. setTimeout(() => {
  40. const { query } = history.location;
  41. const { redirect } = query as {
  42. redirect: string;
  43. };
  44. // history.push(redirect || '/');
  45. // 用于触发app中的render,生成路由
  46. window.location.href = redirect || '/';
  47. setLoading(false);
  48. }, 10);
  49. };
  50. const getCode = () => {
  51. delete loginForm.values?.verifyCode;
  52. loginRef.current = loginForm.values;
  53. Service.captchaConfig()
  54. .pipe(
  55. filter((r) => r.enabled),
  56. mergeMap(Service.getCaptcha),
  57. catchError(() => message.error('服务端挂了!')),
  58. )
  59. .subscribe(setCaptcha);
  60. };
  61. useEffect(getCode, []);
  62. const SchemaField = createSchemaField({
  63. components: {
  64. FormItem,
  65. Input,
  66. Password,
  67. // Checkbox,
  68. },
  69. scope: {
  70. icon(name: any) {
  71. return React.createElement(ICONS[name]);
  72. },
  73. },
  74. });
  75. const schema = {
  76. type: 'object',
  77. properties: {
  78. username: {
  79. type: 'string',
  80. 'x-decorator': 'FormItem',
  81. 'x-validator': { required: true, message: '请输入用户名!' },
  82. 'x-component': 'Input',
  83. 'x-component-props': {
  84. placeholder: intl.formatMessage({
  85. id: 'pages.login.username.placeholder',
  86. defaultMessage: '用户名',
  87. }),
  88. prefix: "{{icon('UserOutlined')}}",
  89. },
  90. },
  91. password: {
  92. type: 'string',
  93. 'x-validator': { required: true, message: '请输入用户名!' },
  94. 'x-decorator': 'FormItem',
  95. 'x-component': 'Password',
  96. 'x-component-props': {
  97. prefix: "{{icon('LockOutlined')}}",
  98. placeholder: intl.formatMessage({
  99. id: 'pages.login.password.placeholder',
  100. defaultMessage: '密码',
  101. }),
  102. },
  103. },
  104. verifyCode: {
  105. type: 'string',
  106. 'x-visible': !!captcha.key,
  107. 'x-decorator': 'FormItem',
  108. 'x-component': 'Input',
  109. 'x-component-props': {
  110. addonAfter: <img src={captcha.base64} alt="验证码" onClick={getCode} />,
  111. placeholder: intl.formatMessage({
  112. id: 'pages.login.captcha.placeholder',
  113. defaultMessage: '请输入验证码',
  114. }),
  115. },
  116. },
  117. },
  118. };
  119. const doLogin = async (data: LoginParam) => {
  120. setLoading(true);
  121. Service.login({ expires: loginRef.current.expires, verifyKey: captcha.key, ...data }).subscribe(
  122. {
  123. next: async (userInfo: UserInfo) => {
  124. Token.set(userInfo.token);
  125. await fetchUserInfo();
  126. goto();
  127. },
  128. error: () => {
  129. message.error(
  130. intl.formatMessage({
  131. id: 'pages.login.failure',
  132. defaultMessage: '登录失败,请重试!',
  133. }),
  134. );
  135. getCode();
  136. setLoading(false);
  137. },
  138. complete: () => {
  139. getCode();
  140. setLoading(false);
  141. },
  142. },
  143. );
  144. };
  145. return (
  146. <Spin spinning={loading}>
  147. <div className={styles.container}>
  148. <div className={styles.left}>
  149. <div className={styles.lang} data-lang="">
  150. {SelectLang && <SelectLang />}
  151. </div>
  152. <div className={styles.content}>
  153. <div className={styles.top}>
  154. <div className={styles.header}>
  155. <Link to="/">
  156. <img alt="logo" className={styles.logo} src="/logo.svg" />
  157. {/*<span className={styles.title}>{SystemConst.SYSTEM_NAME}</span>*/}
  158. </Link>
  159. </div>
  160. <div className={styles.desc}>
  161. {intl.formatMessage({
  162. id: 'pages.layouts.userLayout.title',
  163. defaultMessage: 'Jetlinks',
  164. })}
  165. </div>
  166. <div className={styles.main}>
  167. <Form form={loginForm} layout="horizontal" size="large" onAutoSubmit={doLogin}>
  168. <SchemaField schema={schema} />
  169. <div className={styles.remember}>
  170. <Checkbox
  171. onChange={(e) => {
  172. loginRef.current.expires = e.target.checked ? -1 : 3600000;
  173. }}
  174. >
  175. 记住密码
  176. </Checkbox>
  177. </div>
  178. <Submit block size="large">
  179. {intl.formatMessage({
  180. id: 'pages.login.submit',
  181. defaultMessage: '登录',
  182. })}
  183. </Submit>
  184. </Form>
  185. </div>
  186. </div>
  187. </div>
  188. <div>
  189. <Footer />
  190. </div>
  191. </div>
  192. <div className={styles.right}>
  193. {/*<div className={styles.systemName}>{SystemConst.SYSTEM_NAME}</div>*/}
  194. {/*<div className={styles.systemDesc}>OPEN SOURCE INTERNET OF THINGS BASIC PLATFORM</div>*/}
  195. </div>
  196. </div>
  197. </Spin>
  198. );
  199. };
  200. export default Login;