diff --git a/package.json b/package.json index fc6f3c9e63..515bae9442 100755 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "lodash.clonedeep": "^4.5.0", "moment": "^2.19.1", "numeral": "^2.0.6", + "omit.js": "^1.0.0", "prop-types": "^15.5.10", "qs": "^6.5.0", "react": "^16.0.0", diff --git a/src/components/Login/LoginItem.js b/src/components/Login/LoginItem.js new file mode 100644 index 0000000000..0371b5613a --- /dev/null +++ b/src/components/Login/LoginItem.js @@ -0,0 +1,104 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Form, Button, Row, Col } from 'antd'; +import omit from 'omit.js'; +import styles from './index.less'; +import map from './map'; + +const FormItem = Form.Item; + +function generator({ defaultProps, defaultRules, type }) { + return (WrappedComponent) => { + return class BasicComponent extends Component { + static contextTypes = { + form: PropTypes.object, + updateActive: PropTypes.func, + }; + constructor(props) { + super(props); + this.state = { + count: 0, + }; + } + componentDidMount() { + if (this.context.updateActive) { + this.context.updateActive(this.props.itemKey); + } + } + componentWillUnmount() { + clearInterval(this.interval); + } + onGetCaptcha = () => { + let count = 59; + this.setState({ count }); + if (this.props.onGetCaptcha) { + this.props.onGetCaptcha(); + } + this.interval = setInterval(() => { + count -= 1; + this.setState({ count }); + if (count === 0) { + clearInterval(this.interval); + } + }, 1000); + } + render() { + const { getFieldDecorator } = this.context.form; + const options = {}; + let otherProps = {}; + const { onChange, defaultValue, rules, itemKey, ...restProps } = this.props; + const { count } = this.state; + options.rules = rules || defaultRules; + if (onChange) { + options.onChange = onChange; + } + if (defaultValue) { + options.initialValue = defaultValue; + } + otherProps = restProps || otherProps; + if (type === 'Captcha') { + const inputProps = omit(otherProps, ['onGetCaptcha']); + return ( + + + + {getFieldDecorator(itemKey, options)( + + )} + + + + + + + ); + } + return ( + + {getFieldDecorator(itemKey, options)( + + )} + + ); + } + }; + }; +} + +const LoginItem = {}; +Object.keys(map).forEach((item) => { + LoginItem[item] = generator({ + defaultProps: map[item].props, + defaultRules: map[item].rules, + type: item, + })(map[item].component); +}); + +export default LoginItem; diff --git a/src/components/Login/LoginSubmit.js b/src/components/Login/LoginSubmit.js new file mode 100644 index 0000000000..8770a97386 --- /dev/null +++ b/src/components/Login/LoginSubmit.js @@ -0,0 +1,15 @@ +import React from 'react'; +import classNames from 'classnames'; +import { Button, Form } from 'antd'; +import styles from './index.less'; + +const FormItem = Form.Item; + +export default ({ className, ...rest }) => { + const clsString = classNames(styles.submit, className); + return ( + + - - - - ); - } - return ( - - {getFieldDecorator(control.key, options)( - - )} - - ); - } else { - return null; - } + ); } render() { - const { className, data, notice, extra, moreLoginTypes, register } = this.props; - const { type } = this.state; + const { className, children } = this.props; + const { type, tabs } = this.state; + const TabChildren = []; + const otherChildren = []; + React.Children.forEach(children, (item) => { + if (item.type.name === 'LoginTab') { + TabChildren.push(item); + } else { + otherChildren.push(item); + } + }); return (
- - { - data.map(item => ( - - { - notice && this.renderNotice(notice) - } - { - item.inputControls.map(control => this.renderControl(control)) - } - - )) - } - - { extra &&
{extra}
} - - - + { + tabs.length ? ( +
+ + { TabChildren } + + { otherChildren } +
+ ) : children + }
- { - (moreLoginTypes || register) && -
- {moreLoginTypes && (moreLoginTypes.title || '其他登录方式')} - {moreLoginTypes && moreLoginTypes.types} - {register && - (register.href ? - {register.title || '注册账户'} : - {register.title || '注册账户'} - )} -
- }
); } } + +Login.Tab = LoginTab; +Login.Submit = LoginSubmit; +Object.keys(LoginItem).forEach((item) => { + Login[item] = LoginItem[item]; +}); + +export default Login; diff --git a/src/components/Login/index.less b/src/components/Login/index.less index 51648a8dd7..4f1f226897 100644 --- a/src/components/Login/index.less +++ b/src/components/Login/index.less @@ -40,28 +40,8 @@ width: 100%; } - .additional { - text-align: left; - - :global { - .ant-form-item-control { - line-height: 22px; - } - } - } - .submit { width: 100%; margin-top: 24px; } - - .other { - text-align: left; - margin-top: 24px; - line-height: 22px; - - .register { - float: right; - } - } } diff --git a/src/components/Login/index.md b/src/components/Login/index.md index 25eb0ecc07..20937f437b 100644 --- a/src/components/Login/index.md +++ b/src/components/Login/index.md @@ -4,21 +4,48 @@ title: zh-CN: Login subtitle: 登录 cols: 1 -order: 80 +order: 15 --- -登录控件,支持自定义输入控件。 +支持多种登录方式切换,内置了几种常见的登录控件,可以灵活组合,也支持和自定义控件配合使用。 ## API +### Login + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +defaultActiveKey | 默认激活 tab 面板的 key | String | - +onTabChange | 切换页签时的回调 | (key) => void | - +onSubmit | 点击提交时的回调 | (err, values) => void | - + +### Login.Tab + 参数 | 说明 | 类型 | 默认值 ----|------|-----|------ -data | 类型及输入控件信息 | Array<{ loginType: String, key: String, inputControls: Array<{ key: String, type: Enum{'AutoComplete', 'Input', 'Select'} 或 Enum{'userName', 'password', 'mobile', 'captcha'} `后面几种为内置控件,若配置为这些选项,会自动包含默认的样式,校验规则及相关属性`, props: 各类控件支持的属性, rules: 同 [antd-form-rules](https://ant.design/components/form-cn/#校验规则) }> }> | - -activeKey | 当前激活 tab 面板的 key | String | - -notice | 提示信息,可用来展现服务端返回的报错,警告之类,会展示为 Alert 形式,位于输入控件上方 | 支持属性与 Alert 相同 | - -extra | 其他内容,位于提交按钮上方 | ReactNode | - -moreLoginTypes | 其他登录方式 | {title: ReactNode, types: ReactNode} | title 默认为 '其他登录方式' -register | 注册操作 | {title: ReactNode, href: String} | title 默认为 '注册账户' -onTabChange | 切换登录方式的回调 | (key) => void | - -onSubmit | 提交信息的回调 | (err, values) => void | - -onGetCaptcha | 点击获取校验码按钮的回调,仅当 inputControls 中包含 'captcha' 类型的控件时生效 | () => void | - +key | 对应选项卡的 key | String | - +tab | 选项卡头显示文字 | ReactNode | - + +### Login.UserName + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +itemKey | 控件标记,提交数据中同样以此为 key | String | - +rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | - + +除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。 + +### Login.Password、Login.Mobile 同 Login.UserName + +### Login.Captcha + +参数 | 说明 | 类型 | 默认值 +----|------|-----|------ +onGetCaptcha | 点击获取校验码的回调 | () => void | - + +除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。 + +### Login.Submit + +支持 antd.Button 的所有属性。 + diff --git a/src/components/Login/map.js b/src/components/Login/map.js index a4f5e38b51..81fd7a20be 100644 --- a/src/components/Login/map.js +++ b/src/components/Login/map.js @@ -1,30 +1,9 @@ import React from 'react'; -import { Input, AutoComplete, Select, Icon } from 'antd'; +import { Input, Icon } from 'antd'; import styles from './index.less'; const map = { - Input: { - component: Input, - props: { - size: 'large', - }, - rules: [], - }, - AutoComplete: { - component: AutoComplete, - props: { - size: 'large', - }, - rules: [], - }, - Select: { - component: Select, - props: { - size: 'large', - }, - rules: [], - }, - userName: { + UserName: { component: Input, props: { size: 'large', @@ -35,7 +14,7 @@ const map = { required: true, message: '请输入账户名!', }], }, - password: { + Password: { component: Input, props: { size: 'large', @@ -47,7 +26,7 @@ const map = { required: true, message: '请输入密码!', }], }, - mobile: { + Mobile: { component: Input, props: { size: 'large', @@ -60,7 +39,7 @@ const map = { pattern: /^1\d{10}$/, message: '手机号格式错误!', }], }, - captcha: { + Captcha: { component: Input, props: { size: 'large',