index.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import { Button, Checkbox, Divider, message, Spin } from 'antd';
  2. import React, { useEffect, useRef, useState } from 'react';
  3. import { 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 [bindings, setBindings] = useState<any>([]);
  20. const { initialState, setInitialState } = useModel('@@initialState');
  21. const intl = useIntl();
  22. const iconMap = new Map();
  23. iconMap.set('dingtalk', require('/public/images/bind/dingtalk.png'));
  24. iconMap.set('wechat-webapp', require('/public/images/bind/wechat-webapp.png'));
  25. const fetchUserInfo = async () => {
  26. const userInfo = (await initialState?.fetchUserInfo?.()) as UserInfo;
  27. if (userInfo) {
  28. await setInitialState({
  29. ...initialState,
  30. currentUser: userInfo,
  31. });
  32. }
  33. };
  34. const loginRef = useRef<Partial<LoginParam>>({});
  35. const loginForm = createForm({
  36. validateFirst: true,
  37. initialValues: loginRef.current,
  38. });
  39. const [loading, setLoading] = useState<boolean>(false);
  40. /** 此方法会跳转到 redirect 参数所在的位置 */
  41. // const goto = () => {
  42. // setTimeout(() => {
  43. // // history.push(redirect || '/');
  44. // // 用于触发app中的render,生成路由
  45. // window.location.href = '/';
  46. // setLoading(false);
  47. // }, 10);
  48. // };
  49. const getCode = () => {
  50. delete loginForm.values?.verifyCode;
  51. loginRef.current = loginForm.values;
  52. Service.captchaConfig()
  53. .pipe(
  54. filter((r) => r.enabled),
  55. mergeMap(Service.getCaptcha),
  56. catchError(() => message.error('系统开小差,请稍后重试')),
  57. )
  58. .subscribe((res) => {
  59. setCaptcha(res);
  60. setLoading(false);
  61. });
  62. };
  63. useEffect(getCode, []);
  64. useEffect(() => {
  65. localStorage.clear();
  66. Service.bindInfo().then((res) => {
  67. if (res.status === 200) {
  68. setBindings(res.result);
  69. }
  70. });
  71. }, []);
  72. const SchemaField = createSchemaField({
  73. components: {
  74. FormItem,
  75. Input,
  76. Password,
  77. // Checkbox,
  78. },
  79. scope: {
  80. icon(name: any) {
  81. return React.createElement(ICONS[name]);
  82. },
  83. },
  84. });
  85. const schema = {
  86. type: 'object',
  87. properties: {
  88. username: {
  89. type: 'string',
  90. 'x-decorator': 'FormItem',
  91. 'x-validator': { required: true, message: '请输入用户名!' },
  92. 'x-component': 'Input',
  93. 'x-component-props': {
  94. placeholder: intl.formatMessage({
  95. id: 'pages.login.username.placeholder',
  96. defaultMessage: '用户名',
  97. }),
  98. prefix: "{{icon('UserOutlined')}}",
  99. },
  100. },
  101. password: {
  102. type: 'string',
  103. 'x-validator': { required: true, message: '请输入密码!' },
  104. 'x-decorator': 'FormItem',
  105. 'x-component': 'Password',
  106. 'x-component-props': {
  107. prefix: "{{icon('LockOutlined')}}",
  108. placeholder: intl.formatMessage({
  109. id: 'pages.login.password.placeholder',
  110. defaultMessage: '密码',
  111. }),
  112. },
  113. },
  114. verifyCode: {
  115. type: 'string',
  116. 'x-visible': !!captcha.key,
  117. 'x-decorator': 'FormItem',
  118. 'x-component': 'Input',
  119. 'x-component-props': {
  120. addonAfter: <img src={captcha.base64} alt="验证码" onClick={getCode} />,
  121. placeholder: intl.formatMessage({
  122. id: 'pages.login.captcha.placeholder',
  123. defaultMessage: '请输入验证码',
  124. }),
  125. },
  126. },
  127. },
  128. };
  129. const doLogin = async (data: LoginParam) => {
  130. setLoading(true);
  131. Service.login({ expires: loginRef.current.expires, verifyKey: captcha.key, ...data }).subscribe(
  132. {
  133. next: async (userInfo) => {
  134. Token.set(userInfo.token);
  135. await fetchUserInfo();
  136. // goto();
  137. window.location.href = '/';
  138. setLoading(false);
  139. },
  140. error: () => {
  141. message.error(
  142. intl.formatMessage({
  143. id: 'pages.login.failure',
  144. defaultMessage: '登录失败,请重试!',
  145. }),
  146. );
  147. getCode();
  148. // setLoading(false);
  149. },
  150. complete: () => {
  151. getCode();
  152. // setLoading(false);
  153. },
  154. },
  155. );
  156. };
  157. return (
  158. <Spin spinning={loading} delay={500}>
  159. <div className={styles.container}>
  160. <div className={styles.left}>
  161. <div className={styles.lang} data-lang="">
  162. {SelectLang && <SelectLang />}
  163. </div>
  164. <div className={styles.content}>
  165. <div className={styles.top}>
  166. <div className={styles.header}>
  167. <Link to="/">
  168. <img alt="logo" className={styles.logo} src="/logo.svg" />
  169. {/*<span className={styles.title}>{SystemConst.SYSTEM_NAME}</span>*/}
  170. </Link>
  171. </div>
  172. <div className={styles.desc}>
  173. {intl.formatMessage({
  174. id: 'pages.layouts.userLayout.title',
  175. defaultMessage: 'Jetlinks',
  176. })}
  177. </div>
  178. <div className={styles.main}>
  179. <Form form={loginForm} layout="horizontal" size="large" onAutoSubmit={doLogin}>
  180. <SchemaField schema={schema} />
  181. <div className={styles.remember}>
  182. <Checkbox
  183. onChange={(e) => {
  184. loginRef.current.expires = e.target.checked ? -1 : 3600000;
  185. }}
  186. >
  187. 记住我
  188. </Checkbox>
  189. </div>
  190. <Submit block size="large">
  191. {intl.formatMessage({
  192. id: 'pages.login.submit',
  193. defaultMessage: '登录',
  194. })}
  195. </Submit>
  196. <div style={{ marginTop: 20 }}>
  197. <Divider plain style={{ height: 12 }}>
  198. <div style={{ color: '#807676d9', fontSize: 12 }}>其他方式登录</div>
  199. </Divider>
  200. <div style={{ position: 'relative', bottom: '10px' }}>
  201. {bindings.map((item: any) => (
  202. <Button
  203. type="link"
  204. onClick={() => {
  205. localStorage.setItem('onLogin', 'no');
  206. // window.open(`/#/account/center/bind`);
  207. window.open(`/${SystemConst.API_BASE}/sso/${item.provider}/login`);
  208. window.onstorage = (e) => {
  209. if (e.newValue) {
  210. window.location.href = '/';
  211. }
  212. };
  213. }}
  214. >
  215. <img src={iconMap.get(item.type)} />
  216. </Button>
  217. ))}
  218. </div>
  219. </div>
  220. </Form>
  221. </div>
  222. </div>
  223. </div>
  224. <div>
  225. <Footer />
  226. </div>
  227. </div>
  228. <div className={styles.right}>
  229. {/* <img src={require('/public/images/login.png')}/> */}
  230. {/*<div className={styles.systemName}>{SystemConst.SYSTEM_NAME}</div>*/}
  231. {/*<div className={styles.systemDesc}>OPEN SOURCE INTERNET OF THINGS BASIC PLATFORM</div>*/}
  232. </div>
  233. </div>
  234. </Spin>
  235. );
  236. };
  237. export default Login;