Commit f0d97f9b authored by 陈帅's avatar 陈帅

add LoginContext

parent 68809608
......@@ -3,7 +3,7 @@ export default {
[
'umi-plugin-block-dev',
{
layout: 'ant-design-pro',
layout: 'ant-design-pro-user',
menu: {
name: '主页',
icon: 'home',
......
import { createContext } from 'react';
export interface ILoginContext {
tabUtil?: {
addTab: (id: string) => void;
removeTab: (id: string) => void;
};
updateActive?: (activeItem: { [key: string]: string } | string) => void;
}
const LoginContext: React.Context<ILoginContext> = createContext({});
export default LoginContext;
import React, { Component } from 'react';
import { Form, Input, Button, Row, Col } from 'antd';
import omit from 'omit.js';
import styles from './index.less';
import ItemMap from './map';
import LoginContext, { ILoginContext } from './loginContext';
import { FormComponentProps } from 'antd/lib/form';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type WrappedLoginItemProps = Omit<LoginItemProps, 'form' | 'type' | 'updateActive'>;
export type LoginItemKeyType = keyof typeof ItemMap;
export type LoginItemType = { [K in keyof typeof ItemMap]: React.FC<WrappedLoginItemProps> };
export interface LoginItemProps extends FormComponentProps {
name?: string;
rules?: any[];
style?: React.CSSProperties;
onGetCaptcha?: (event?: MouseEvent) => void | Promise<any> | false;
placeholder?: string;
buttonText?: React.ReactNode;
onPressEnter?: (e: any) => void;
countDown?: number;
getCaptchaButtonText?: string;
getCaptchaSecondText?: string;
updateActive: ILoginContext['updateActive'];
type: string;
defaultValue?: string;
customProps?: { [key: string]: any };
onChange?: (e: any) => void;
}
interface LoginItemState {
count: number;
}
const FormItem = Form.Item;
class WrapFormItem extends Component<LoginItemProps, LoginItemState> {
static defaultProps = {
getCaptchaButtonText: 'captcha',
getCaptchaSecondText: 'second',
};
constructor(props: LoginItemProps) {
super(props);
this.state = {
count: 0,
};
}
interval: number | undefined;
componentDidMount() {
const { updateActive, name = '' } = this.props;
if (updateActive) {
updateActive(name);
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
onGetCaptcha = () => {
const { onGetCaptcha } = this.props;
const result = onGetCaptcha ? onGetCaptcha() : null;
if (result === false) {
return;
}
if (result instanceof Promise) {
result.then(this.runGetCaptchaCountDown);
} else {
this.runGetCaptchaCountDown();
}
};
getFormItemOptions = ({ onChange, defaultValue, customProps = {}, rules }: LoginItemProps) => {
const options: {
rules?: Array<any>;
onChange?: LoginItemProps['onChange'];
initialValue?: LoginItemProps['defaultValue'];
} = {
rules: rules || customProps.rules,
};
if (onChange) {
options.onChange = onChange;
}
if (defaultValue) {
options.initialValue = defaultValue;
}
return options;
};
runGetCaptchaCountDown = () => {
const { countDown } = this.props;
let count = countDown || 59;
this.setState({ count });
this.interval = window.setInterval(() => {
count -= 1;
this.setState({ count });
if (count === 0) {
clearInterval(this.interval);
}
}, 1000);
};
render() {
const { count } = this.state;
// 这么写是为了防止restProps中 带入 onChange, defaultValue, rules props
const {
onChange,
customProps,
defaultValue,
rules,
name,
getCaptchaButtonText,
getCaptchaSecondText,
updateActive,
type,
form,
...restProps
} = this.props;
if (!name) {
console.warn('name is required!');
return null;
}
console.log(form);
if (!form) {
return null;
}
const { getFieldDecorator } = form;
// get getFieldDecorator props
const options = this.getFormItemOptions(this.props);
const otherProps = restProps || {};
if (type === 'Captcha') {
const inputProps = omit(otherProps, ['onGetCaptcha', 'countDown']);
return (
<FormItem>
<Row gutter={8}>
<Col span={16}>
{getFieldDecorator(name, options)(<Input {...customProps} {...inputProps} />)}
</Col>
<Col span={8}>
<Button
disabled={!!count}
className={styles.getCaptcha}
size="large"
onClick={this.onGetCaptcha}
>
{count ? `${count} ${getCaptchaSecondText}` : getCaptchaButtonText}
</Button>
</Col>
</Row>
</FormItem>
);
}
return (
<FormItem>
{getFieldDecorator(name, options)(<Input {...customProps} {...otherProps} />)}
</FormItem>
);
}
}
const LoginItem: Partial<LoginItemType> = {};
Object.keys(ItemMap).forEach(key => {
const item = ItemMap[key];
LoginItem[key] = (props: LoginItemProps) => (
<LoginContext.Consumer>
{context => {
console.log(context);
return (
<WrapFormItem
customProps={item.props}
rules={item.rules}
{...props}
type={key}
{...context}
updateActive={context.updateActive}
/>
);
}}
</LoginContext.Consumer>
);
});
export default LoginItem as LoginItemType;
import React from 'react';
import classNames from 'classnames';
import { Button, Form } from 'antd';
import styles from './index.less';
import { ButtonProps } from 'antd/lib/button';
const FormItem = Form.Item;
interface LoginSubmitProps extends ButtonProps {
className: string;
}
const LoginSubmit: React.SFC<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;
import React, { Component } from 'react';
import { Tabs } from 'antd';
import { TabPaneProps } from 'antd/lib/tabs';
import LoginContext, { ILoginContext } from './LoginContext';
const { TabPane } = Tabs;
const generateId = (() => {
let i = 0;
return (prefix = '') => {
i += 1;
return `${prefix}${i}`;
};
})();
interface LoginTabProps extends TabPaneProps {
tabUtil: ILoginContext['tabUtil'];
}
class LoginTab extends Component<LoginTabProps> {
constructor(props: LoginTabProps) {
super(props);
this.uniqueId = generateId('login-tab-');
}
uniqueId: string;
componentDidMount() {
const { tabUtil } = this.props;
tabUtil && tabUtil.addTab(this.uniqueId);
}
render() {
const { children } = this.props;
return <TabPane {...this.props}>{children}</TabPane>;
}
}
const wrapContext = (props: TabPaneProps) => (
<LoginContext.Consumer>
{value => <LoginTab tabUtil={value.tabUtil} {...props} />}
</LoginContext.Consumer>
);
// 标志位 用来判断是不是自定义组件
wrapContext.typeName = 'LoginTab';
export default wrapContext;
@import '~antd/lib/style/themes/default.less';
.login {
:global {
.ant-tabs .ant-tabs-bar {
margin-bottom: 24px;
text-align: center;
border-bottom: 0;
}
.ant-form-item {
margin: 0 2px 24px;
}
}
.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;
}
}
import React, { Component } from 'react';
import { Form, Tabs } from 'antd';
import classNames from 'classnames';
import LoginItem, { LoginItemType, LoginItemProps } from './LoginItem';
import LoginTab from './LoginTab';
import styles from './index.less';
import LoginContext, { ILoginContext } from './LoginContext';
import { FormComponentProps } from 'antd/lib/form';
import LoginSubmit from './LoginSubmit';
export interface LoginProps extends FormComponentProps {
defaultActiveKey?: string;
onTabChange?: (key: string) => void;
style?: React.CSSProperties;
onSubmit?: (error: any, values: any) => void;
className?: string;
children: React.ReactElement<LoginTab>[];
}
interface LoginState {
tabs?: string[];
type?: string;
active?: { [key: string]: Array<any> };
}
class Login extends Component<LoginProps, LoginState> {
public static Tab = LoginTab;
public static Submit = LoginSubmit;
public static UserName: React.FunctionComponent<LoginItemProps>;
public static Password: React.FunctionComponent<LoginItemProps>;
public static Mobile: React.FunctionComponent<LoginItemProps>;
public static Captcha: React.FunctionComponent<LoginItemProps>;
static defaultProps = {
className: '',
defaultActiveKey: '',
onTabChange: () => {},
onSubmit: () => {},
};
constructor(props: LoginProps) {
super(props);
this.state = {
type: props.defaultActiveKey,
tabs: [],
active: {},
};
}
onSwitch = (type: string) => {
this.setState(
{
type,
},
() => {
const { onTabChange } = this.props;
if (onTabChange) {
onTabChange(type);
}
}
);
};
getContext: () => ILoginContext = () => {
const { form } = this.props;
const { tabs = [] } = this.state;
return {
tabUtil: {
addTab: id => {
this.setState({
tabs: [...tabs, id],
});
},
removeTab: id => {
this.setState({
tabs: tabs.filter(currentId => currentId !== id),
});
},
},
form: { ...form },
updateActive: activeItem => {
const { type = '', active = {} } = this.state;
if (active[type]) {
active[type].push(activeItem);
} else {
active[type] = [activeItem];
}
this.setState({
active,
});
},
};
};
handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const { active = {}, type = '' } = this.state;
const { form, onSubmit } = this.props;
const activeFields = active[type] || [];
form.validateFields(activeFields, { force: true }, (err, values) => {
onSubmit && onSubmit(err, values);
});
};
render() {
const { className, children } = this.props;
const { type, tabs = [] } = this.state;
const TabChildren: React.ReactComponentElement<LoginTab>[] = [];
const otherChildren: React.ReactElement<any>[] = [];
React.Children.forEach(
children,
(child: React.ReactComponentElement<LoginTab> | React.ReactElement<any>) => {
if (!child) {
return;
}
if (child.type.typeName === 'LoginTab') {
TabChildren.push(child);
} else {
otherChildren.push(child);
}
}
);
console.log(this.getContext());
return (
<LoginContext.Provider value={this.getContext()}>
<div className={classNames(className, styles.login)}>
{tabs.length ? (
<React.Fragment>
<Form onSubmit={this.handleSubmit}>
<Tabs
animated={false}
className={styles.tabs}
activeKey={type}
onChange={this.onSwitch}
>
{TabChildren}
</Tabs>
{otherChildren}
</Form>
</React.Fragment>
) : (
children
)}
</div>
</LoginContext.Provider>
);
}
}
(Object.keys(LoginItem) as Array<keyof LoginItemType>).forEach(item => {
Login[item] = LoginItem[item];
});
export default Form.create()(Login);
import React from 'react';
import { Icon } from 'antd';
import styles from './index.less';
export default {
UserName: {
props: {
size: 'large',
id: 'userName',
prefix: <Icon type="user" className={styles.prefixIcon} />,
placeholder: 'admin',
},
rules: [
{
required: true,
message: 'Please enter username!',
},
],
},
Password: {
props: {
size: 'large',
prefix: <Icon type="lock" className={styles.prefixIcon} />,
type: 'password',
id: 'password',
placeholder: '888888',
},
rules: [
{
required: true,
message: 'Please enter password!',
},
],
},
Mobile: {
props: {
size: 'large',
prefix: <Icon type="mobile" 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: <Icon type="mail" className={styles.prefixIcon} />,
placeholder: 'captcha',
},
rules: [
{
required: true,
message: 'Please enter Captcha!',
},
],
},
};
......@@ -3,9 +3,8 @@ import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi-plugin-react/locale';
import Link from 'umi/link';
import { Checkbox, Alert, Icon } from 'antd';
import { Login } from 'ant-design-pro';
import Login from './components/Login';
import styles from './style.less';
const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
@connect(({ BLOCK_NAME_CAMEL_CASE, loading }) => ({
......@@ -21,7 +20,7 @@ class LoginPage extends Component {
onTabChange = type => {
this.setState({ type });
};
loginForm: loginForm;
onGetCaptcha = () =>
new Promise((resolve, reject) => {
this.loginForm.validateFields(['mobile'], {}, (err, values) => {
......@@ -93,7 +92,7 @@ class LoginPage extends Component {
message: formatMessage({ id: 'BLOCK_NAME.userName.required' }),
},
]}
/>
/>{' '}
<Password
name="password"
placeholder={`${formatMessage({ id: 'BLOCK_NAME.login.password' })}: ant.design`}
......@@ -113,7 +112,7 @@ class LoginPage extends Component {
this.renderMessage(
formatMessage({ id: 'BLOCK_NAME.login.message-invalid-verification-code' })
)}
<Mobile
{/* <Mobile
name="mobile"
placeholder={formatMessage({ id: 'BLOCK_NAME.phone-number.placeholder' })}
rules={[
......@@ -140,7 +139,7 @@ class LoginPage extends Component {
message: formatMessage({ id: 'BLOCK_NAME.verification-code.required' }),
},
]}
/>
/> */}
</Tab>
<div>
<Checkbox checked={autoLogin} onChange={this.changeAutoLogin}>
......
import { routerRedux } from 'dva/router';
import { getPageQuery } from './utils/utils';
import { setAuthority } from './utils/authority';
import { reloadAuthorized } from './utils/Authorized';
import { fakeAccountLogin, getFakeCaptcha } from './service';
export default {
......@@ -20,7 +18,6 @@ export default {
});
// Login successfully
if (response.status === 'ok') {
reloadAuthorized();
const urlParams = new URL(window.location.href);
const params = getPageQuery();
let { redirect } = params;
......
import RenderAuthorized from 'ant-design-pro/lib/Authorized';
import { getAuthority } from './authority';
let Authorized = RenderAuthorized(getAuthority()); // eslint-disable-line
// Reload the rights component
const reloadAuthorized = () => {
Authorized = RenderAuthorized(getAuthority());
};
export { reloadAuthorized };
export default Authorized;
// use localStorage to store the authority info, which might be sent from server in actual project.
export function getAuthority(str) {
// return localStorage.getItem('antd-pro-authority') || ['admin', 'user'];
const authorityString =
typeof str === 'undefined' ? localStorage.getItem('antd-pro-authority') : str;
// authorityString could be admin, "admin", ["admin"]
let authority;
try {
authority = JSON.parse(authorityString);
} catch (e) {
authority = authorityString;
}
if (typeof authority === 'string') {
return [authority];
}
return authority || ['admin'];
}
export function setAuthority(authority) {
const proAuthority = typeof authority === 'string' ? [authority] : authority;
return localStorage.setItem('antd-pro-authority', JSON.stringify(proAuthority));
}
{
"private": true,
"scripts": {
"dev": "cross-env PAGES_PATH='TableList/src' umi dev",
"dev": "cross-env PAGES_PATH='UserLogin/src' umi dev",
"lint:style": "stylelint \"src/**/*.less\" --syntax less",
"lint": "eslint --ext .js src mock tests && npm run lint:style",
"lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment