Преглед на файлове

✨ feat: use ProForm replace compents form (#7544)

* ✨ feat: use ProForm replace compents form

* up version

* fix lint error
陈帅 преди 5 години
родител
ревизия
704eb3fa3e

+ 13 - 2
mock/user.ts

@@ -1,8 +1,18 @@
 import { Request, Response } from 'express';
 
-function getFakeCaptcha(req: Request, res: Response) {
+const waitTime = (time: number = 100) => {
+  return new Promise((resolve) => {
+    setTimeout(() => {
+      resolve(true);
+    }, time);
+  });
+};
+
+async function getFakeCaptcha(req: Request, res: Response) {
+  await waitTime(2000);
   return res.json('captcha-xxx');
 }
+
 // 代码中会兼容本地 service mock 以及部署站点的静态数据
 export default {
   // 支持值为 Object 和 Array
@@ -77,8 +87,9 @@ export default {
       address: 'Sidney No. 1 Lake Park',
     },
   ],
-  'POST /api/login/account': (req: Request, res: Response) => {
+  'POST /api/login/account': async (req: Request, res: Response) => {
     const { password, userName, type } = req.body;
+    await waitTime(2000);
     if (password === 'ant.design' && userName === 'admin') {
       res.send({
         status: 'ok',

+ 2 - 2
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ant-design-pro",
-  "version": "4.2.2",
+  "version": "4.3.0",
   "private": true,
   "description": "An out-of-box UI solution for enterprise applications",
   "scripts": {
@@ -55,7 +55,7 @@
   "dependencies": {
     "@ant-design/icons": "^4.0.0",
     "@ant-design/pro-descriptions": "^1.0.19",
-    "@ant-design/pro-form": "^1.0.2",
+    "@ant-design/pro-form": "^1.2.0",
     "@ant-design/pro-layout": "^6.4.19",
     "@ant-design/pro-table": "^2.9.5",
     "@umijs/route-utils": "^1.0.33",

+ 2 - 0
src/locales/zh-CN.ts

@@ -4,6 +4,7 @@ import menu from './zh-CN/menu';
 import pwa from './zh-CN/pwa';
 import settingDrawer from './zh-CN/settingDrawer';
 import settings from './zh-CN/settings';
+import pages from './zh-CN/pages';
 
 export default {
   'navBar.lang': '语言',
@@ -13,6 +14,7 @@ export default {
   'app.preview.down.block': '下载此页面到本地项目',
   'app.welcome.link.fetch-blocks': '获取全部区块',
   'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+  ...pages,
   ...globalHeader,
   ...menu,
   ...settingDrawer,

+ 23 - 0
src/locales/zh-CN/pages.ts

@@ -0,0 +1,23 @@
+export default {
+  'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
+  'pages.login.accountLogin.tab': '账户密码登录',
+  'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
+  'pages.login.username.placeholder': '用户名: admin or user',
+  'pages.login.username.required': '用户名是必填项!',
+  'pages.login.password.placeholder': '密码: ant.design',
+  'pages.login.password.required': '密码是必填项!',
+  'pages.login.phoneLogin.tab': '手机号登录',
+  'pages.login.phoneLogin.errorMessage': '验证码错误',
+  'pages.login.phoneNumber.placeholder': '请输入手机号!',
+  'pages.login.phoneNumber.required': '手机号是必填项!',
+  'pages.login.phoneNumber.invalid': '不合法的手机号!',
+  'pages.login.captcha.placeholder': '请输入验证码!',
+  'pages.login.captcha.required': '验证码是必填项!',
+  'pages.login.phoneLogin.getVerificationCode': '获取验证码',
+  'pages.getCaptchaSecondText': '秒后重新获取',
+  'pages.login.rememberMe': '自动登录',
+  'pages.login.forgotPassword': '忘记密码 ?',
+  'pages.login.submit': '提交',
+  'pages.login.loginWith': '其他登录方式 :',
+  'pages.login.registerAccount': '注册账户',
+};

+ 2 - 0
src/models/login.ts

@@ -4,6 +4,7 @@ import { history, Reducer, Effect } from 'umi';
 import { fakeAccountLogin } from '@/services/login';
 import { setAuthority } from '@/utils/authority';
 import { getPageQuery } from '@/utils/utils';
+import { message } from 'antd';
 
 export interface StateType {
   status?: 'ok' | 'error';
@@ -41,6 +42,7 @@ const Model: LoginModelType = {
       if (response.status === 'ok') {
         const urlParams = new URL(window.location.href);
         const params = getPageQuery();
+        message.success('🎉 🎉 🎉  登录成功!');
         let { redirect } = params as { redirect: string };
         if (redirect) {
           const redirectUrlParams = new URL(redirect);

+ 0 - 13
src/pages/user/login/components/Login/LoginContext.tsx

@@ -1,13 +0,0 @@
-import { createContext } from 'react';
-
-export interface LoginContextProps {
-  tabUtil?: {
-    addTab: (id: string) => void;
-    removeTab: (id: string) => void;
-  };
-  updateActive?: (activeItem: { [key: string]: string } | string) => void;
-}
-
-const LoginContext: React.Context<LoginContextProps> = createContext({});
-
-export default LoginContext;

+ 0 - 177
src/pages/user/login/components/Login/LoginItem.tsx

@@ -1,177 +0,0 @@
-import { Button, Col, Input, Row, Form, message } from 'antd';
-import React, { useState, useCallback, useEffect } from 'react';
-import omit from 'omit.js';
-import { FormItemProps } from 'antd/es/form/FormItem';
-import { getFakeCaptcha } from '@/services/login';
-import { FormattedMessage } from 'umi';
-
-import ItemMap from './map';
-import LoginContext, { LoginContextProps } from './LoginContext';
-import styles from './index.less';
-
-export type WrappedLoginItemProps = LoginItemProps;
-export type LoginItemKeyType = keyof typeof ItemMap;
-export interface LoginItemType {
-  UserName: React.FC<WrappedLoginItemProps>;
-  Password: React.FC<WrappedLoginItemProps>;
-  Mobile: React.FC<WrappedLoginItemProps>;
-  Captcha: React.FC<WrappedLoginItemProps>;
-}
-
-export interface LoginItemProps extends Partial<FormItemProps> {
-  name?: string;
-  style?: React.CSSProperties;
-  placeholder?: string;
-  buttonText?: React.ReactNode;
-  countDown?: number;
-  getCaptchaButtonText?: string;
-  getCaptchaSecondText?: string;
-  updateActive?: LoginContextProps['updateActive'];
-  type?: string;
-  defaultValue?: string;
-  customProps?: { [key: string]: unknown };
-  onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
-  tabUtil?: LoginContextProps['tabUtil'];
-}
-
-const FormItem = Form.Item;
-
-const getFormItemOptions = ({
-  onChange,
-  defaultValue,
-  customProps = {},
-  rules,
-}: LoginItemProps) => {
-  const options: {
-    rules?: LoginItemProps['rules'];
-    onChange?: LoginItemProps['onChange'];
-    initialValue?: LoginItemProps['defaultValue'];
-  } = {
-    rules: rules || (customProps.rules as LoginItemProps['rules']),
-  };
-  if (onChange) {
-    options.onChange = onChange;
-  }
-  if (defaultValue) {
-    options.initialValue = defaultValue;
-  }
-  return options;
-};
-
-const LoginItem: React.FC<LoginItemProps> = (props) => {
-  const [count, setCount] = useState<number>(props.countDown || 0);
-  const [timing, setTiming] = useState(false);
-  // 这么写是为了防止restProps中 带入 onChange, defaultValue, rules props tabUtil
-  const {
-    onChange,
-    customProps,
-    defaultValue,
-    rules,
-    name,
-    getCaptchaButtonText,
-    getCaptchaSecondText,
-    updateActive,
-    type,
-    tabUtil,
-    ...restProps
-  } = props;
-
-  const onGetCaptcha = useCallback(async (mobile: string) => {
-    const result = await getFakeCaptcha(mobile);
-    if (result === false) {
-      return;
-    }
-    message.success('获取验证码成功!验证码为:1234');
-    setTiming(true);
-  }, []);
-
-  useEffect(() => {
-    let interval: number = 0;
-    const { countDown } = props;
-    if (timing) {
-      interval = window.setInterval(() => {
-        setCount((preSecond) => {
-          if (preSecond <= 1) {
-            setTiming(false);
-            clearInterval(interval);
-            // 重置秒数
-            return countDown || 60;
-          }
-          return preSecond - 1;
-        });
-      }, 1000);
-    }
-    return () => clearInterval(interval);
-  }, [timing]);
-  if (!name) {
-    return null;
-  }
-  // get getFieldDecorator props
-  const options = getFormItemOptions(props);
-  const otherProps = restProps || {};
-
-  if (type === 'Captcha') {
-    const inputProps = omit(otherProps, ['onGetCaptcha', 'countDown']);
-
-    return (
-      <FormItem shouldUpdate noStyle>
-        {({ getFieldValue }) => (
-          <Row gutter={8}>
-            <Col span={16}>
-              <FormItem name={name} {...options}>
-                <Input {...customProps} {...inputProps} />
-              </FormItem>
-            </Col>
-            <Col span={8}>
-              <Button
-                disabled={timing}
-                className={styles.getCaptcha}
-                size="large"
-                onClick={() => {
-                  const value = getFieldValue('mobile');
-                  onGetCaptcha(value);
-                }}
-              >
-                {timing ? (
-                  `${count} 秒`
-                ) : (
-                  <FormattedMessage
-                    id="pages.login.phoneLogin.getVerificationCode"
-                    defaultMessage="获取验证码"
-                  />
-                )}
-              </Button>
-            </Col>
-          </Row>
-        )}
-      </FormItem>
-    );
-  }
-  return (
-    <FormItem name={name} {...options}>
-      <Input {...customProps} {...otherProps} />
-    </FormItem>
-  );
-};
-
-const LoginItems: Partial<LoginItemType> = {};
-
-Object.keys(ItemMap).forEach((key) => {
-  const item = ItemMap[key];
-  LoginItems[key] = (props: LoginItemProps) => (
-    <LoginContext.Consumer>
-      {(context) => (
-        <LoginItem
-          customProps={item.props}
-          rules={item.rules}
-          {...props}
-          type={key}
-          {...context}
-          updateActive={context.updateActive}
-        />
-      )}
-    </LoginContext.Consumer>
-  );
-});
-
-export default LoginItems as LoginItemType;

+ 0 - 23
src/pages/user/login/components/Login/LoginSubmit.tsx

@@ -1,23 +0,0 @@
-import { Button, Form } from 'antd';
-
-import { ButtonProps } from 'antd/es/button';
-import React from 'react';
-import classNames from 'classnames';
-import styles from './index.less';
-
-const FormItem = Form.Item;
-
-interface LoginSubmitProps extends ButtonProps {
-  className?: string;
-}
-
-const LoginSubmit: React.FC<LoginSubmitProps> = ({ className, ...rest }) => {
-  const clsString = classNames(styles.submit, className);
-  return (
-    <FormItem>
-      <Button size="large" className={clsString} type="primary" htmlType="submit" {...rest} />
-    </FormItem>
-  );
-};
-
-export default LoginSubmit;

+ 0 - 45
src/pages/user/login/components/Login/LoginTab.tsx

@@ -1,45 +0,0 @@
-import React, { useEffect } from 'react';
-import { Tabs } from 'antd';
-import LoginContext, { LoginContextProps } from './LoginContext';
-
-const { TabPane } = Tabs;
-
-const generateId = (() => {
-  let i = 0;
-  return (prefix = '') => {
-    i += 1;
-    return `${prefix}${i}`;
-  };
-})();
-
-type TabPaneProps = Parameters<typeof Tabs.TabPane>[0];
-
-interface LoginTabProps extends TabPaneProps {
-  tabUtil: LoginContextProps['tabUtil'];
-  active?: boolean;
-}
-
-const LoginTab: React.FC<LoginTabProps> = (props) => {
-  useEffect(() => {
-    const uniqueId = generateId('login-tab-');
-    const { tabUtil } = props;
-    if (tabUtil) {
-      tabUtil.addTab(uniqueId);
-    }
-  }, []);
-  const { children } = props;
-  return <TabPane {...props}>{props.active && children}</TabPane>;
-};
-
-const WrapContext: React.FC<TabPaneProps> & {
-  typeName: string;
-} = (props) => (
-  <LoginContext.Consumer>
-    {(value) => <LoginTab tabUtil={value.tabUtil} {...props} />}
-  </LoginContext.Consumer>
-);
-
-// 标志位 用来判断是不是自定义组件
-WrapContext.typeName = 'LoginTab';
-
-export default WrapContext;

+ 0 - 49
src/pages/user/login/components/Login/index.less

@@ -1,49 +0,0 @@
-@import '~antd/es/style/themes/default.less';
-
-.login {
-  :global {
-    .ant-tabs .ant-tabs-bar {
-      margin-bottom: 24px;
-      text-align: center;
-      border-bottom: 0;
-    }
-  }
-
-  .getCaptcha {
-    display: block;
-    width: 100%;
-  }
-
-  .icon {
-    margin-left: 16px;
-    color: rgba(0, 0, 0, 0.2);
-    font-size: 24px;
-    vertical-align: middle;
-    cursor: pointer;
-    transition: color 0.3s;
-
-    &:hover {
-      color: @primary-color;
-    }
-  }
-
-  .other {
-    margin-top: 24px;
-    line-height: 22px;
-    text-align: left;
-
-    .register {
-      float: right;
-    }
-  }
-
-  .prefixIcon {
-    color: @disabled-color;
-    font-size: @font-size-base;
-  }
-
-  .submit {
-    width: 100%;
-    margin-top: 24px;
-  }
-}

+ 0 - 119
src/pages/user/login/components/Login/index.tsx

@@ -1,119 +0,0 @@
-import { Tabs, Form } from 'antd';
-import React, { useState } from 'react';
-import useMergeValue from 'use-merge-value';
-import classNames from 'classnames';
-import { FormInstance } from 'antd/es/form';
-import { LoginParamsType } from '@/services/login';
-
-import LoginContext from './LoginContext';
-import LoginItem, { LoginItemProps } from './LoginItem';
-import LoginSubmit from './LoginSubmit';
-import LoginTab from './LoginTab';
-import styles from './index.less';
-
-export interface LoginProps {
-  activeKey?: string;
-  onTabChange?: (key: string) => void;
-  style?: React.CSSProperties;
-  onSubmit?: (values: LoginParamsType) => void;
-  className?: string;
-  from?: FormInstance;
-  children: React.ReactElement<typeof LoginTab>[];
-}
-
-interface LoginType extends React.FC<LoginProps> {
-  Tab: typeof LoginTab;
-  Submit: typeof LoginSubmit;
-  UserName: React.FunctionComponent<LoginItemProps>;
-  Password: React.FunctionComponent<LoginItemProps>;
-  Mobile: React.FunctionComponent<LoginItemProps>;
-  Captcha: React.FunctionComponent<LoginItemProps>;
-}
-
-const Login: LoginType = (props) => {
-  const { className } = props;
-  const [tabs, setTabs] = useState<string[]>([]);
-  const [active, setActive] = useState({});
-  const [type, setType] = useMergeValue('', {
-    value: props.activeKey,
-    onChange: props.onTabChange,
-  });
-  const TabChildren: React.ReactComponentElement<typeof LoginTab>[] = [];
-  const otherChildren: React.ReactElement<unknown>[] = [];
-  React.Children.forEach(
-    props.children,
-    (child: React.ReactComponentElement<typeof LoginTab> | React.ReactElement<unknown>) => {
-      if (!child) {
-        return;
-      }
-      if ((child.type as { typeName: string }).typeName === 'LoginTab') {
-        TabChildren.push(child as React.ReactComponentElement<typeof LoginTab>);
-      } else {
-        otherChildren.push(child);
-      }
-    },
-  );
-  return (
-    <LoginContext.Provider
-      value={{
-        tabUtil: {
-          addTab: (id) => {
-            setTabs([...tabs, id]);
-          },
-          removeTab: (id) => {
-            setTabs(tabs.filter((currentId) => currentId !== id));
-          },
-        },
-        updateActive: (activeItem) => {
-          if (!active) return;
-          if (active[type]) {
-            active[type].push(activeItem);
-          } else {
-            active[type] = [activeItem];
-          }
-          setActive(active);
-        },
-      }}
-    >
-      <div className={classNames(className, styles.login)}>
-        <Form
-          form={props.from}
-          onFinish={(values) => {
-            if (props.onSubmit) {
-              props.onSubmit(values as LoginParamsType);
-            }
-          }}
-        >
-          {tabs.length ? (
-            <React.Fragment>
-              <Tabs
-                destroyInactiveTabPane
-                animated={false}
-                className={styles.tabs}
-                activeKey={type}
-                onChange={(activeKey) => {
-                  setType(activeKey);
-                }}
-              >
-                {TabChildren}
-              </Tabs>
-              {otherChildren}
-            </React.Fragment>
-          ) : (
-            props.children
-          )}
-        </Form>
-      </div>
-    </LoginContext.Provider>
-  );
-};
-
-Login.Tab = LoginTab;
-Login.Submit = LoginSubmit;
-
-Login.UserName = LoginItem.UserName;
-Login.Password = LoginItem.Password;
-Login.Mobile = LoginItem.Mobile;
-Login.Captcha = LoginItem.Captcha;
-
-export default Login;

+ 0 - 72
src/pages/user/login/components/Login/map.tsx

@@ -1,72 +0,0 @@
-import { LockTwoTone, MailTwoTone, MobileTwoTone, UserOutlined } from '@ant-design/icons';
-import React from 'react';
-import styles from './index.less';
-
-export default {
-  UserName: {
-    props: {
-      size: 'large',
-      id: 'userName',
-      prefix: (
-        <UserOutlined
-          style={{
-            color: '#1890ff',
-          }}
-          className={styles.prefixIcon}
-        />
-      ),
-      placeholder: 'admin',
-    },
-    rules: [
-      {
-        required: true,
-        message: 'Please enter username!',
-      },
-    ],
-  },
-  Password: {
-    props: {
-      size: 'large',
-      prefix: <LockTwoTone className={styles.prefixIcon} />,
-      type: 'password',
-      id: 'password',
-      placeholder: '888888',
-    },
-    rules: [
-      {
-        required: true,
-        message: 'Please enter password!',
-      },
-    ],
-  },
-  Mobile: {
-    props: {
-      size: 'large',
-      prefix: <MobileTwoTone className={styles.prefixIcon} />,
-      placeholder: 'mobile number',
-    },
-    rules: [
-      {
-        required: true,
-        message: 'Please enter mobile number!',
-      },
-      {
-        pattern: /^1\d{10}$/,
-        message: 'Wrong mobile number format!',
-      },
-    ],
-  },
-  Captcha: {
-    props: {
-      size: 'large',
-      prefix: <MailTwoTone className={styles.prefixIcon} />,
-      placeholder: 'captcha',
-    },
-    rules: [
-      {
-        required: true,
-        message: 'Please enter Captcha!',
-      },
-    ],
-  },
-};

+ 12 - 7
src/pages/user/login/style.less

@@ -1,10 +1,18 @@
 @import '~antd/es/style/themes/default.less';
 
 .main {
-  width: 368px;
+  width: 328px;
   margin: 0 auto;
   @media screen and (max-width: @screen-sm) {
     width: 95%;
+    max-width: 328px;
+  }
+
+  :global {
+    .@{ant-prefix}-tabs-nav-list {
+      margin: auto;
+      font-size: 16rpx;
+    }
   }
 
   .icon {
@@ -24,16 +32,13 @@
     margin-top: 24px;
     line-height: 22px;
     text-align: left;
-
     .register {
       float: right;
     }
   }
 
-  :global {
-    .antd-pro-login-submit {
-      width: 100%;
-      margin-top: 24px;
-    }
+  .prefixIcon {
+    color: @primary-color;
+    font-size: @font-size-base;
   }
 }

+ 195 - 139
src/pages/user/login/index.tsx

@@ -1,15 +1,22 @@
-import { AlipayCircleOutlined, TaobaoCircleOutlined, WeiboCircleOutlined } from '@ant-design/icons';
-import { Alert, Checkbox } from 'antd';
+import {
+  AlipayCircleOutlined,
+  LockTwoTone,
+  MailTwoTone,
+  MobileTwoTone,
+  TaobaoCircleOutlined,
+  UserOutlined,
+  WeiboCircleOutlined,
+} from '@ant-design/icons';
+import { Alert, Space, message, Tabs } from 'antd';
 import React, { useState } from 'react';
-import { Link, connect, Dispatch, useIntl, FormattedMessage } from 'umi';
+import ProForm, { ProFormCaptcha, ProFormCheckbox, ProFormText } from '@ant-design/pro-form';
+import { connect, Dispatch, useIntl, FormattedMessage } from 'umi';
 import { StateType } from '@/models/login';
-import { LoginParamsType } from '@/services/login';
+import { getFakeCaptcha, LoginParamsType } from '@/services/login';
 import { ConnectState } from '@/models/connect';
-import LoginForm from './components/Login';
 
-import styles from './style.less';
+import styles from './index.less';
 
-const { Tab, UserName, Password, Mobile, Captcha, Submit } = LoginForm;
 interface LoginProps {
   dispatch: Dispatch;
   userLogin: StateType;
@@ -32,7 +39,6 @@ const LoginMessage: React.FC<{
 const Login: React.FC<LoginProps> = (props) => {
   const { userLogin = {}, submitting } = props;
   const { status, type: loginType } = userLogin;
-  const [autoLogin, setAutoLogin] = useState(true);
   const [type, setType] = useState<string>('account');
   const intl = useIntl();
 
@@ -45,131 +51,187 @@ const Login: React.FC<LoginProps> = (props) => {
   };
   return (
     <div className={styles.main}>
-      <LoginForm activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
-        <Tab
-          key="account"
-          tab={intl.formatMessage({
-            id: 'pages.login.accountLogin.tab',
-            defaultMessage: '账户密码登录',
-          })}
-        >
-          {status === 'error' && loginType === 'account' && !submitting && (
-            <LoginMessage
-              content={intl.formatMessage({
-                id: 'pages.login.accountLogin.errorMessage',
-                defaultMessage: '账户或密码错误(admin/ant.design)',
-              })}
-            />
-          )}
-
-          <UserName
-            name="userName"
-            placeholder={intl.formatMessage({
-              id: 'pages.login.username.placeholder',
-              defaultMessage: '用户名: admin or user',
+      <ProForm
+        initialValues={{
+          autoLogin: true,
+        }}
+        submitter={{
+          render: (_, dom) => dom.pop(),
+          submitButtonProps: {
+            loading: submitting,
+            size: 'large',
+            style: {
+              width: '100%',
+            },
+          },
+        }}
+        onFinish={async (values) => {
+          handleSubmit(values);
+        }}
+      >
+        <Tabs activeKey={type} onChange={setType}>
+          <Tabs.TabPane
+            key="account"
+            tab={intl.formatMessage({
+              id: 'pages.login.accountLogin.tab',
+              defaultMessage: '账户密码登录',
             })}
-            rules={[
-              {
-                required: true,
-                message: (
-                  <FormattedMessage
-                    id="pages.login.username.required"
-                    defaultMessage="请输入用户名!"
-                  />
-                ),
-              },
-            ]}
           />
-          <Password
-            name="password"
-            placeholder={intl.formatMessage({
-              id: 'pages.login.password.placeholder',
-              defaultMessage: '密码: ant.design',
+          <Tabs.TabPane
+            key="mobile"
+            tab={intl.formatMessage({
+              id: 'pages.login.phoneLogin.tab',
+              defaultMessage: '手机号登录',
             })}
-            rules={[
-              {
-                required: true,
-                message: (
-                  <FormattedMessage
-                    id="pages.login.password.required"
-                    defaultMessage="请输入密码!"
-                  />
-                ),
-              },
-            ]}
           />
-        </Tab>
-        <Tab
-          key="mobile"
-          tab={intl.formatMessage({
-            id: 'pages.login.phoneLogin.tab',
-            defaultMessage: '手机号登录',
-          })}
-        >
-          {status === 'error' && loginType === 'mobile' && !submitting && (
-            <LoginMessage
-              content={intl.formatMessage({
-                id: 'pages.login.phoneLogin.errorMessage',
-                defaultMessage: '验证码错误',
-              })}
-            />
-          )}
-          <Mobile
-            name="mobile"
-            placeholder={intl.formatMessage({
-              id: 'pages.login.phoneNumber.placeholder',
-              defaultMessage: '手机号',
-            })}
-            rules={[
-              {
-                required: true,
-                message: (
-                  <FormattedMessage
-                    id="pages.login.phoneNumber.required"
-                    defaultMessage="请输入手机号!"
-                  />
-                ),
-              },
-              {
-                pattern: /^1\d{10}$/,
-                message: (
-                  <FormattedMessage
-                    id="pages.login.phoneNumber.invalid"
-                    defaultMessage="手机号格式错误!"
-                  />
-                ),
-              },
-            ]}
-          />
-          <Captcha
-            name="captcha"
-            placeholder={intl.formatMessage({
-              id: 'pages.login.captcha.placeholder',
-              defaultMessage: '验证码',
-            })}
-            countDown={120}
-            getCaptchaButtonText=""
-            getCaptchaSecondText={intl.formatMessage({
-              id: 'pages.getCaptchaSecondText',
-              defaultMessage: '秒',
+        </Tabs>
+
+        {status === 'error' && loginType === 'account' && !submitting && (
+          <LoginMessage
+            content={intl.formatMessage({
+              id: 'pages.login.accountLogin.errorMessage',
+              defaultMessage: '账户或密码错误(admin/ant.design)',
             })}
-            rules={[
-              {
-                required: true,
-                message: (
-                  <FormattedMessage
-                    id="pages.login.captcha.required"
-                    defaultMessage="请输入验证码!"
-                  />
-                ),
-              },
-            ]}
           />
-        </Tab>
-        <div>
-          <Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
+        )}
+        {type === 'account' && (
+          <>
+            <ProFormText
+              name="userName"
+              fieldProps={{
+                size: 'large',
+                prefix: <UserOutlined className={styles.prefixIcon} />,
+              }}
+              placeholder={intl.formatMessage({
+                id: 'pages.login.username.placeholder',
+                defaultMessage: '用户名: admin or user',
+              })}
+              rules={[
+                {
+                  required: true,
+                  message: (
+                    <FormattedMessage
+                      id="pages.login.username.required"
+                      defaultMessage="请输入用户名!"
+                    />
+                  ),
+                },
+              ]}
+            />
+            <ProFormText.Password
+              name="password"
+              fieldProps={{
+                size: 'large',
+                prefix: <LockTwoTone className={styles.prefixIcon} />,
+              }}
+              placeholder={intl.formatMessage({
+                id: 'pages.login.password.placeholder',
+                defaultMessage: '密码: ant.design',
+              })}
+              rules={[
+                {
+                  required: true,
+                  message: (
+                    <FormattedMessage
+                      id="pages.login.password.required"
+                      defaultMessage="请输入密码!"
+                    />
+                  ),
+                },
+              ]}
+            />
+          </>
+        )}
+
+        {status === 'error' && loginType === 'mobile' && !submitting && (
+          <LoginMessage content="验证码错误" />
+        )}
+        {type === 'mobile' && (
+          <>
+            <ProFormText
+              fieldProps={{
+                size: 'large',
+                prefix: <MobileTwoTone className={styles.prefixIcon} />,
+              }}
+              name="mobile"
+              placeholder={intl.formatMessage({
+                id: 'pages.login.phoneNumber.placeholder',
+                defaultMessage: '手机号',
+              })}
+              rules={[
+                {
+                  required: true,
+                  message: (
+                    <FormattedMessage
+                      id="pages.login.phoneNumber.required"
+                      defaultMessage="请输入手机号!"
+                    />
+                  ),
+                },
+                {
+                  pattern: /^1\d{10}$/,
+                  message: (
+                    <FormattedMessage
+                      id="pages.login.phoneNumber.invalid"
+                      defaultMessage="手机号格式错误!"
+                    />
+                  ),
+                },
+              ]}
+            />
+            <ProFormCaptcha
+              fieldProps={{
+                size: 'large',
+                prefix: <MailTwoTone className={styles.prefixIcon} />,
+              }}
+              captchaProps={{
+                size: 'large',
+              }}
+              placeholder={intl.formatMessage({
+                id: 'pages.login.captcha.placeholder',
+                defaultMessage: '请输入验证码',
+              })}
+              captchaTextRender={(timing, count) =>
+                timing
+                  ? `${count} ${intl.formatMessage({
+                      id: 'pages.getCaptchaSecondText',
+                      defaultMessage: '获取验证码',
+                    })}`
+                  : intl.formatMessage({
+                      id: 'pages.login.phoneLogin.getVerificationCode',
+                      defaultMessage: '获取验证码',
+                    })
+              }
+              name="captcha"
+              rules={[
+                {
+                  required: true,
+                  message: (
+                    <FormattedMessage
+                      id="pages.login.captcha.required"
+                      defaultMessage="请输入验证码!"
+                    />
+                  ),
+                },
+              ]}
+              onGetCaptcha={async (mobile) => {
+                const result = await getFakeCaptcha(mobile);
+                if (result === false) {
+                  return;
+                }
+                message.success('获取验证码成功!验证码为:1234');
+              }}
+            />
+          </>
+        )}
+        <div
+          style={{
+            marginBottom: 24,
+          }}
+        >
+          <ProFormCheckbox noStyle name="autoLogin">
             <FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
-          </Checkbox>
+          </ProFormCheckbox>
           <a
             style={{
               float: 'right',
@@ -178,19 +240,13 @@ const Login: React.FC<LoginProps> = (props) => {
             <FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
           </a>
         </div>
-        <Submit loading={submitting}>
-          <FormattedMessage id="pages.login.submit" defaultMessage="登录" />
-        </Submit>
-        <div className={styles.other}>
-          <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
-          <AlipayCircleOutlined className={styles.icon} />
-          <TaobaoCircleOutlined className={styles.icon} />
-          <WeiboCircleOutlined className={styles.icon} />
-          <Link className={styles.register} to="/user/register">
-            <FormattedMessage id="pages.login.registerAccount" defaultMessage="注册账户" />
-          </Link>
-        </div>
-      </LoginForm>
+      </ProForm>
+      <Space className={styles.other}>
+        <FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
+        <AlipayCircleOutlined className={styles.icon} />
+        <TaobaoCircleOutlined className={styles.icon} />
+        <WeiboCircleOutlined className={styles.icon} />
+      </Space>
     </div>
   );
 };

+ 1 - 0
tsconfig.json

@@ -23,6 +23,7 @@
     }
   },
   "include": [
+    "mock/**/*",
     "src/**/*",
     "tests/**/*",
     "test/**/*",