初始化 antd-pro

This commit is contained in:
sin
2019-02-27 11:06:55 +08:00
parent 9f4fdb7f6e
commit b2068ae44b
458 changed files with 28090 additions and 2 deletions

View File

@@ -0,0 +1,171 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
import Link from 'umi/link';
import { Checkbox, Alert, Icon } from 'antd';
import Login from '@/components/Login';
import styles from './Login.less';
const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
@connect(({ login, loading }) => ({
login,
submitting: loading.effects['login/login'],
}))
class LoginPage extends Component {
state = {
type: 'account',
autoLogin: true,
};
onTabChange = type => {
this.setState({ type });
};
onGetCaptcha = () =>
new Promise((resolve, reject) => {
this.loginForm.validateFields(['mobile'], {}, (err, values) => {
if (err) {
reject(err);
} else {
const { dispatch } = this.props;
dispatch({
type: 'login/getCaptcha',
payload: values.mobile,
})
.then(resolve)
.catch(reject);
}
});
});
handleSubmit = (err, values) => {
const { type } = this.state;
if (!err) {
const { dispatch } = this.props;
dispatch({
type: 'login/login',
payload: {
...values,
type,
},
});
}
};
changeAutoLogin = e => {
this.setState({
autoLogin: e.target.checked,
});
};
renderMessage = content => (
<Alert style={{ marginBottom: 24 }} message={content} type="error" showIcon />
);
render() {
const { login, submitting } = this.props;
const { type, autoLogin } = this.state;
return (
<div className={styles.main}>
<Login
defaultActiveKey={type}
onTabChange={this.onTabChange}
onSubmit={this.handleSubmit}
ref={form => {
this.loginForm = form;
}}
>
<Tab key="account" tab={formatMessage({ id: 'app.login.tab-login-credentials' })}>
{login.status === 'error' &&
login.type === 'account' &&
!submitting &&
this.renderMessage(formatMessage({ id: 'app.login.message-invalid-credentials' }))}
<UserName
name="userName"
placeholder={`${formatMessage({ id: 'app.login.userName' })}: admin or user`}
rules={[
{
required: true,
message: formatMessage({ id: 'validation.userName.required' }),
},
]}
/>
<Password
name="password"
placeholder={`${formatMessage({ id: 'app.login.password' })}: ant.design`}
rules={[
{
required: true,
message: formatMessage({ id: 'validation.password.required' }),
},
]}
onPressEnter={e => {
e.preventDefault();
this.loginForm.validateFields(this.handleSubmit);
}}
/>
</Tab>
<Tab key="mobile" tab={formatMessage({ id: 'app.login.tab-login-mobile' })}>
{login.status === 'error' &&
login.type === 'mobile' &&
!submitting &&
this.renderMessage(
formatMessage({ id: 'app.login.message-invalid-verification-code' })
)}
<Mobile
name="mobile"
placeholder={formatMessage({ id: 'form.phone-number.placeholder' })}
rules={[
{
required: true,
message: formatMessage({ id: 'validation.phone-number.required' }),
},
{
pattern: /^1\d{10}$/,
message: formatMessage({ id: 'validation.phone-number.wrong-format' }),
},
]}
/>
<Captcha
name="captcha"
placeholder={formatMessage({ id: 'form.verification-code.placeholder' })}
countDown={120}
onGetCaptcha={this.onGetCaptcha}
getCaptchaButtonText={formatMessage({ id: 'form.get-captcha' })}
getCaptchaSecondText={formatMessage({ id: 'form.captcha.second' })}
rules={[
{
required: true,
message: formatMessage({ id: 'validation.verification-code.required' }),
},
]}
/>
</Tab>
<div>
<Checkbox checked={autoLogin} onChange={this.changeAutoLogin}>
<FormattedMessage id="app.login.remember-me" />
</Checkbox>
<a style={{ float: 'right' }} href="">
<FormattedMessage id="app.login.forgot-password" />
</a>
</div>
<Submit loading={submitting}>
<FormattedMessage id="app.login.login" />
</Submit>
<div className={styles.other}>
<FormattedMessage id="app.login.sign-in-with" />
<Icon type="alipay-circle" className={styles.icon} theme="outlined" />
<Icon type="taobao-circle" className={styles.icon} theme="outlined" />
<Icon type="weibo-circle" className={styles.icon} theme="outlined" />
<Link className={styles.register} to="/user/register">
<FormattedMessage id="app.login.signup" />
</Link>
</div>
</Login>
</div>
);
}
}
export default LoginPage;

View File

@@ -0,0 +1,32 @@
@import '~antd/lib/style/themes/default.less';
.main {
width: 388px;
margin: 0 auto;
@media screen and (max-width: @screen-sm) {
width: 95%;
}
.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;
}
}
}

View File

@@ -0,0 +1,334 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
import Link from 'umi/link';
import router from 'umi/router';
import { Form, Input, Button, Select, Row, Col, Popover, Progress } from 'antd';
import styles from './Register.less';
const FormItem = Form.Item;
const { Option } = Select;
const InputGroup = Input.Group;
const passwordStatusMap = {
ok: (
<div className={styles.success}>
<FormattedMessage id="validation.password.strength.strong" />
</div>
),
pass: (
<div className={styles.warning}>
<FormattedMessage id="validation.password.strength.medium" />
</div>
),
poor: (
<div className={styles.error}>
<FormattedMessage id="validation.password.strength.short" />
</div>
),
};
const passwordProgressMap = {
ok: 'success',
pass: 'normal',
poor: 'exception',
};
@connect(({ register, loading }) => ({
register,
submitting: loading.effects['register/submit'],
}))
@Form.create()
class Register extends Component {
state = {
count: 0,
confirmDirty: false,
visible: false,
help: '',
prefix: '86',
};
componentDidUpdate() {
const { form, register } = this.props;
const account = form.getFieldValue('mail');
if (register.status === 'ok') {
router.push({
pathname: '/user/register-result',
state: {
account,
},
});
}
}
componentWillUnmount() {
clearInterval(this.interval);
}
onGetCaptcha = () => {
let count = 59;
this.setState({ count });
this.interval = setInterval(() => {
count -= 1;
this.setState({ count });
if (count === 0) {
clearInterval(this.interval);
}
}, 1000);
};
getPasswordStatus = () => {
const { form } = this.props;
const value = form.getFieldValue('password');
if (value && value.length > 9) {
return 'ok';
}
if (value && value.length > 5) {
return 'pass';
}
return 'poor';
};
handleSubmit = e => {
e.preventDefault();
const { form, dispatch } = this.props;
form.validateFields({ force: true }, (err, values) => {
if (!err) {
const { prefix } = this.state;
dispatch({
type: 'register/submit',
payload: {
...values,
prefix,
},
});
}
});
};
handleConfirmBlur = e => {
const { value } = e.target;
const { confirmDirty } = this.state;
this.setState({ confirmDirty: confirmDirty || !!value });
};
checkConfirm = (rule, value, callback) => {
const { form } = this.props;
if (value && value !== form.getFieldValue('password')) {
callback(formatMessage({ id: 'validation.password.twice' }));
} else {
callback();
}
};
checkPassword = (rule, value, callback) => {
const { visible, confirmDirty } = this.state;
if (!value) {
this.setState({
help: formatMessage({ id: 'validation.password.required' }),
visible: !!value,
});
callback('error');
} else {
this.setState({
help: '',
});
if (!visible) {
this.setState({
visible: !!value,
});
}
if (value.length < 6) {
callback('error');
} else {
const { form } = this.props;
if (value && confirmDirty) {
form.validateFields(['confirm'], { force: true });
}
callback();
}
}
};
changePrefix = value => {
this.setState({
prefix: value,
});
};
renderPasswordProgress = () => {
const { form } = this.props;
const value = form.getFieldValue('password');
const passwordStatus = this.getPasswordStatus();
return value && value.length ? (
<div className={styles[`progress-${passwordStatus}`]}>
<Progress
status={passwordProgressMap[passwordStatus]}
className={styles.progress}
strokeWidth={6}
percent={value.length * 10 > 100 ? 100 : value.length * 10}
showInfo={false}
/>
</div>
) : null;
};
render() {
const { form, submitting } = this.props;
const { getFieldDecorator } = form;
const { count, prefix, help, visible } = this.state;
return (
<div className={styles.main}>
<h3>
<FormattedMessage id="app.register.register" />
</h3>
<Form onSubmit={this.handleSubmit}>
<FormItem>
{getFieldDecorator('mail', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.email.required' }),
},
{
type: 'email',
message: formatMessage({ id: 'validation.email.wrong-format' }),
},
],
})(
<Input size="large" placeholder={formatMessage({ id: 'form.email.placeholder' })} />
)}
</FormItem>
<FormItem help={help}>
<Popover
getPopupContainer={node => node.parentNode}
content={
<div style={{ padding: '4px 0' }}>
{passwordStatusMap[this.getPasswordStatus()]}
{this.renderPasswordProgress()}
<div style={{ marginTop: 10 }}>
<FormattedMessage id="validation.password.strength.msg" />
</div>
</div>
}
overlayStyle={{ width: 240 }}
placement="right"
visible={visible}
>
{getFieldDecorator('password', {
rules: [
{
validator: this.checkPassword,
},
],
})(
<Input
size="large"
type="password"
placeholder={formatMessage({ id: 'form.password.placeholder' })}
/>
)}
</Popover>
</FormItem>
<FormItem>
{getFieldDecorator('confirm', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.confirm-password.required' }),
},
{
validator: this.checkConfirm,
},
],
})(
<Input
size="large"
type="password"
placeholder={formatMessage({ id: 'form.confirm-password.placeholder' })}
/>
)}
</FormItem>
<FormItem>
<InputGroup compact>
<Select
size="large"
value={prefix}
onChange={this.changePrefix}
style={{ width: '20%' }}
>
<Option value="86">+86</Option>
<Option value="87">+87</Option>
</Select>
{getFieldDecorator('mobile', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.phone-number.required' }),
},
{
pattern: /^\d{11}$/,
message: formatMessage({ id: 'validation.phone-number.wrong-format' }),
},
],
})(
<Input
size="large"
style={{ width: '80%' }}
placeholder={formatMessage({ id: 'form.phone-number.placeholder' })}
/>
)}
</InputGroup>
</FormItem>
<FormItem>
<Row gutter={8}>
<Col span={16}>
{getFieldDecorator('captcha', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.verification-code.required' }),
},
],
})(
<Input
size="large"
placeholder={formatMessage({ id: 'form.verification-code.placeholder' })}
/>
)}
</Col>
<Col span={8}>
<Button
size="large"
disabled={count}
className={styles.getCaptcha}
onClick={this.onGetCaptcha}
>
{count
? `${count} s`
: formatMessage({ id: 'app.register.get-verification-code' })}
</Button>
</Col>
</Row>
</FormItem>
<FormItem>
<Button
size="large"
loading={submitting}
className={styles.submit}
type="primary"
htmlType="submit"
>
<FormattedMessage id="app.register.register" />
</Button>
<Link className={styles.login} to="/User/Login">
<FormattedMessage id="app.register.sign-in" />
</Link>
</FormItem>
</Form>
</div>
);
}
}
export default Register;

View File

@@ -0,0 +1,57 @@
@import '~antd/lib/style/themes/default.less';
.main {
width: 388px;
margin: 0 auto;
:global {
.ant-form-item {
margin-bottom: 24px;
}
}
h3 {
margin-bottom: 20px;
font-size: 16px;
}
.getCaptcha {
display: block;
width: 100%;
}
.submit {
width: 50%;
}
.login {
float: right;
line-height: @btn-height-lg;
}
}
.success,
.warning,
.error {
transition: color 0.3s;
}
.success {
color: @success-color;
}
.warning {
color: @warning-color;
}
.error {
color: @error-color;
}
.progress-pass > .progress {
:global {
.ant-progress-bg {
background-color: @warning-color;
}
}
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { formatMessage, FormattedMessage } from 'umi/locale';
import { Button } from 'antd';
import Link from 'umi/link';
import Result from '@/components/Result';
import styles from './RegisterResult.less';
const actions = (
<div className={styles.actions}>
<a href="">
<Button size="large" type="primary">
<FormattedMessage id="app.register-result.view-mailbox" />
</Button>
</a>
<Link to="/">
<Button size="large">
<FormattedMessage id="app.register-result.back-home" />
</Button>
</Link>
</div>
);
const RegisterResult = ({ location }) => (
<Result
className={styles.registerResult}
type="success"
title={
<div className={styles.title}>
<FormattedMessage
id="app.register-result.msg"
values={{ email: location.state ? location.state.account : 'AntDesign@example.com' }}
/>
</div>
}
description={formatMessage({ id: 'app.register-result.activation-email' })}
actions={actions}
style={{ marginTop: 56 }}
/>
);
export default RegisterResult;

View File

@@ -0,0 +1,18 @@
.registerResult {
:global {
.anticon {
font-size: 64px;
}
}
.title {
margin-top: 32px;
font-size: 20px;
line-height: 28px;
}
.actions {
margin-top: 40px;
a + a {
margin-left: 8px;
}
}
}

View File

@@ -0,0 +1,32 @@
import { fakeRegister } from '@/services/api';
import { setAuthority } from '@/utils/authority';
import { reloadAuthorized } from '@/utils/Authorized';
export default {
namespace: 'register',
state: {
status: undefined,
},
effects: {
*submit({ payload }, { call, put }) {
const response = yield call(fakeRegister, payload);
yield put({
type: 'registerHandle',
payload: response,
});
},
},
reducers: {
registerHandle(state, { payload }) {
setAuthority('user');
reloadAuthorized();
return {
...state,
status: payload.status,
};
},
},
};