index.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. import {
  2. AlipayCircleOutlined,
  3. LockTwoTone,
  4. MailTwoTone,
  5. MobileTwoTone,
  6. TaobaoCircleOutlined,
  7. UserOutlined,
  8. WeiboCircleOutlined,
  9. } from '@ant-design/icons';
  10. import { Alert, Space, message, Tabs } from 'antd';
  11. import React, { useState } from 'react';
  12. import ProForm, { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
  13. import { useIntl, Link, history, FormattedMessage, SelectLang, useModel } from 'umi';
  14. import Footer from '@/components/Footer';
  15. import { fakeAccountLogin, getFakeCaptcha, LoginParamsType } from '@/services/login';
  16. import styles from './index.less';
  17. import { selectNavList } from '@/services/user';
  18. const LoginMessage: React.FC<{
  19. content: string;
  20. }> = ({ content }) => (
  21. <Alert
  22. style={{
  23. marginBottom: 24,
  24. }}
  25. message={content}
  26. type="error"
  27. showIcon
  28. />
  29. );
  30. /**
  31. * 此方法会跳转到 redirect 参数所在的位置
  32. */
  33. const goto = () => {
  34. if (!history) return;
  35. setTimeout(() => {
  36. const { query } = history.location;
  37. const { redirect } = query as {
  38. redirect: string;
  39. };
  40. history.push(redirect || '/');
  41. }, 10);
  42. };
  43. const Login: React.FC<{}> = () => {
  44. const [submitting, setSubmitting] = useState(false);
  45. const [userLoginState, setUserLoginState] = useState<API.LoginStateType>({});
  46. const [type, setType] = useState<string>('account');
  47. const { initialState, setInitialState } = useModel('@@initialState');
  48. const intl = useIntl();
  49. const fetchUserInfo = async () => {
  50. const userInfo = await initialState?.fetchUserInfo?.();
  51. if (userInfo) {
  52. setInitialState({
  53. ...initialState,
  54. settings: {
  55. menu: {
  56. loading: true,
  57. },
  58. },
  59. currentUser: userInfo,
  60. });
  61. const menuData = await selectNavList();
  62. setInitialState({
  63. ...initialState,
  64. currentUser: userInfo,
  65. settings: {
  66. menu: {
  67. loading: false,
  68. },
  69. },
  70. menuData: menuData.data,
  71. });
  72. }
  73. };
  74. const handleSubmit = async (values: LoginParamsType) => {
  75. setSubmitting(true);
  76. try {
  77. // 登录
  78. await fakeAccountLogin({ ...values, type });
  79. await fetchUserInfo();
  80. message.success('登录成功!');
  81. goto();
  82. } catch (error) {
  83. console.log(error, 'error'); // 如果失败去设置用户错误信息
  84. setUserLoginState({
  85. status: 'error',
  86. type: 'account',
  87. });
  88. }
  89. setSubmitting(false);
  90. };
  91. const { status, type: loginType } = userLoginState;
  92. return (
  93. <div className={styles.container}>
  94. <div className={styles.lang}>{SelectLang && <SelectLang />}</div>
  95. <div className={styles.content}>
  96. <div className={styles.top}>
  97. <div className={styles.header}>
  98. <Link to="/">
  99. <img alt="logo" className={styles.logo} src="/logo.svg" />
  100. <span className={styles.title}>Ant Design</span>
  101. </Link>
  102. </div>
  103. <div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
  104. </div>
  105. <div className={styles.main}>
  106. <ProForm
  107. initialValues={{
  108. remeber: true,
  109. }}
  110. submitter={{
  111. searchConfig: {
  112. submitText: intl.formatMessage({
  113. id: 'pages.login.submit',
  114. defaultMessage: '登录',
  115. }),
  116. },
  117. render: (_, dom) => dom.pop(),
  118. submitButtonProps: {
  119. loading: submitting,
  120. size: 'large',
  121. style: {
  122. width: '100%',
  123. },
  124. },
  125. }}
  126. onFinish={async (values) => {
  127. handleSubmit(values);
  128. }}
  129. >
  130. <Tabs activeKey={type} onChange={setType}>
  131. <Tabs.TabPane
  132. key="account"
  133. tab={intl.formatMessage({
  134. id: 'pages.login.accountLogin.tab',
  135. defaultMessage: '账户密码登录',
  136. })}
  137. />
  138. <Tabs.TabPane
  139. key="mobile"
  140. tab={intl.formatMessage({
  141. id: 'pages.login.phoneLogin.tab',
  142. defaultMessage: '手机号登录',
  143. })}
  144. />
  145. </Tabs>
  146. {status === 'error' && loginType === 'account' && (
  147. <LoginMessage
  148. content={intl.formatMessage({
  149. id: 'pages.login.accountLogin.errorMessage',
  150. defaultMessage: '账户或密码错误(admin/ant.design)',
  151. })}
  152. />
  153. )}
  154. {type === 'account' && (
  155. <>
  156. <ProFormText
  157. name="mobile"
  158. fieldProps={{
  159. size: 'large',
  160. prefix: <UserOutlined className={styles.prefixIcon} />,
  161. }}
  162. placeholder={intl.formatMessage({
  163. id: 'pages.login.username.placeholder',
  164. defaultMessage: '用户名: admin or user',
  165. })}
  166. rules={[
  167. {
  168. required: true,
  169. message: '用户名是必填项!',
  170. },
  171. ]}
  172. />
  173. <ProFormText.Password
  174. name="password"
  175. fieldProps={{
  176. size: 'large',
  177. prefix: <LockTwoTone className={styles.prefixIcon} />,
  178. }}
  179. placeholder={intl.formatMessage({
  180. id: 'pages.login.password.placeholder',
  181. defaultMessage: '密码: ant.design',
  182. })}
  183. rules={[
  184. {
  185. required: true,
  186. message: '密码是必填项!',
  187. },
  188. ]}
  189. />
  190. </>
  191. )}
  192. {status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
  193. {type === 'mobile' && (
  194. <>
  195. <ProFormText
  196. fieldProps={{
  197. size: 'large',
  198. prefix: <MobileTwoTone className={styles.prefixIcon} />,
  199. }}
  200. name="mobile"
  201. placeholder={intl.formatMessage({
  202. id: 'pages.login.phoneNumber.placeholder',
  203. defaultMessage: '手机号',
  204. })}
  205. rules={[
  206. {
  207. required: true,
  208. message: '手机号是必填项!',
  209. },
  210. {
  211. pattern: /^1\d{10}$/,
  212. message: '不合法的手机号!',
  213. },
  214. ]}
  215. />
  216. <ProFormCaptcha
  217. fieldProps={{
  218. size: 'large',
  219. prefix: <MailTwoTone className={styles.prefixIcon} />,
  220. }}
  221. captchaProps={{
  222. size: 'large',
  223. }}
  224. placeholder={intl.formatMessage({
  225. id: 'pages.login.captcha.placeholder',
  226. defaultMessage: '请输入验证码',
  227. })}
  228. captchaTextRender={(timing, count) =>
  229. timing
  230. ? `${count} ${intl.formatMessage({
  231. id: 'pages.getCaptchaSecondText',
  232. defaultMessage: '获取验证码',
  233. })}`
  234. : intl.formatMessage({
  235. id: 'pages.login.phoneLogin.getVerificationCode',
  236. defaultMessage: '获取验证码',
  237. })
  238. }
  239. name="captcha"
  240. rules={[
  241. {
  242. required: true,
  243. message: '验证码是必填项!',
  244. },
  245. ]}
  246. onGetCaptcha={async (mobile) => {
  247. const result = await getFakeCaptcha(mobile);
  248. if (result === false) {
  249. return;
  250. }
  251. message.success('获取验证码成功!验证码为:1234');
  252. }}
  253. />
  254. </>
  255. )}
  256. <div
  257. style={{
  258. marginBottom: 24,
  259. }}
  260. >
  261. <ProFormCheckbox noStyle name="remeber">
  262. 自动登录
  263. </ProFormCheckbox>
  264. <a
  265. style={{
  266. float: 'right',
  267. }}
  268. >
  269. 忘记密码 ?
  270. </a>
  271. </div>
  272. </ProForm>
  273. <Space className={styles.other}>
  274. 其他登录方式 :
  275. <AlipayCircleOutlined className={styles.icon} />
  276. <TaobaoCircleOutlined className={styles.icon} />
  277. <WeiboCircleOutlined className={styles.icon} />
  278. </Space>
  279. </div>
  280. </div>
  281. <Footer />
  282. </div>
  283. );
  284. };
  285. export default Login;