初始化 antd-pro
This commit is contained in:
171
admin-web/src/pages/User/Login.js
Normal file
171
admin-web/src/pages/User/Login.js
Normal 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;
|
||||
32
admin-web/src/pages/User/Login.less
Normal file
32
admin-web/src/pages/User/Login.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
334
admin-web/src/pages/User/Register.js
Normal file
334
admin-web/src/pages/User/Register.js
Normal 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;
|
||||
57
admin-web/src/pages/User/Register.less
Normal file
57
admin-web/src/pages/User/Register.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
admin-web/src/pages/User/RegisterResult.js
Normal file
41
admin-web/src/pages/User/RegisterResult.js
Normal 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;
|
||||
18
admin-web/src/pages/User/RegisterResult.less
Normal file
18
admin-web/src/pages/User/RegisterResult.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
admin-web/src/pages/User/models/register.js
Normal file
32
admin-web/src/pages/User/models/register.js
Normal 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,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user