初始化 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,323 @@
import React, { PureComponent } from 'react';
import {
Card,
Button,
Form,
Icon,
Col,
Row,
DatePicker,
TimePicker,
Input,
Select,
Popover,
} from 'antd';
import { connect } from 'dva';
import FooterToolbar from '@/components/FooterToolbar';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import TableForm from './TableForm';
import styles from './style.less';
const { Option } = Select;
const { RangePicker } = DatePicker;
const fieldLabels = {
name: '仓库名',
url: '仓库域名',
owner: '仓库管理员',
approver: '审批人',
dateRange: '生效日期',
type: '仓库类型',
name2: '任务名',
url2: '任务描述',
owner2: '执行人',
approver2: '责任人',
dateRange2: '生效日期',
type2: '任务类型',
};
const tableData = [
{
key: '1',
workId: '00001',
name: 'John Brown',
department: 'New York No. 1 Lake Park',
},
{
key: '2',
workId: '00002',
name: 'Jim Green',
department: 'London No. 1 Lake Park',
},
{
key: '3',
workId: '00003',
name: 'Joe Black',
department: 'Sidney No. 1 Lake Park',
},
];
@connect(({ loading }) => ({
submitting: loading.effects['form/submitAdvancedForm'],
}))
@Form.create()
class AdvancedForm extends PureComponent {
state = {
width: '100%',
};
componentDidMount() {
window.addEventListener('resize', this.resizeFooterToolbar, { passive: true });
}
componentWillUnmount() {
window.removeEventListener('resize', this.resizeFooterToolbar);
}
getErrorInfo = () => {
const {
form: { getFieldsError },
} = this.props;
const errors = getFieldsError();
const errorCount = Object.keys(errors).filter(key => errors[key]).length;
if (!errors || errorCount === 0) {
return null;
}
const scrollToField = fieldKey => {
const labelNode = document.querySelector(`label[for="${fieldKey}"]`);
if (labelNode) {
labelNode.scrollIntoView(true);
}
};
const errorList = Object.keys(errors).map(key => {
if (!errors[key]) {
return null;
}
return (
<li key={key} className={styles.errorListItem} onClick={() => scrollToField(key)}>
<Icon type="cross-circle-o" className={styles.errorIcon} />
<div className={styles.errorMessage}>{errors[key][0]}</div>
<div className={styles.errorField}>{fieldLabels[key]}</div>
</li>
);
});
return (
<span className={styles.errorIcon}>
<Popover
title="表单校验信息"
content={errorList}
overlayClassName={styles.errorPopover}
trigger="click"
getPopupContainer={trigger => trigger.parentNode}
>
<Icon type="exclamation-circle" />
</Popover>
{errorCount}
</span>
);
};
resizeFooterToolbar = () => {
requestAnimationFrame(() => {
const sider = document.querySelectorAll('.ant-layout-sider')[0];
if (sider) {
const width = `calc(100% - ${sider.style.width})`;
const { width: stateWidth } = this.state;
if (stateWidth !== width) {
this.setState({ width });
}
}
});
};
validate = () => {
const {
form: { validateFieldsAndScroll },
dispatch,
} = this.props;
validateFieldsAndScroll((error, values) => {
if (!error) {
// submit the values
dispatch({
type: 'form/submitAdvancedForm',
payload: values,
});
}
});
};
render() {
const {
form: { getFieldDecorator },
submitting,
} = this.props;
const { width } = this.state;
return (
<PageHeaderWrapper
title="高级表单"
content="高级表单常见于一次性输入和提交大批量数据的场景。"
wrapperClassName={styles.advancedForm}
>
<Card title="仓库管理" className={styles.card} bordered={false}>
<Form layout="vertical" hideRequiredMark>
<Row gutter={16}>
<Col lg={6} md={12} sm={24}>
<Form.Item label={fieldLabels.name}>
{getFieldDecorator('name', {
rules: [{ required: true, message: '请输入仓库名称' }],
})(<Input placeholder="请输入仓库名称" />)}
</Form.Item>
</Col>
<Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
<Form.Item label={fieldLabels.url}>
{getFieldDecorator('url', {
rules: [{ required: true, message: '请选择' }],
})(
<Input
style={{ width: '100%' }}
addonBefore="http://"
addonAfter=".com"
placeholder="请输入"
/>
)}
</Form.Item>
</Col>
<Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
<Form.Item label={fieldLabels.owner}>
{getFieldDecorator('owner', {
rules: [{ required: true, message: '请选择管理员' }],
})(
<Select placeholder="请选择管理员">
<Option value="xiao">付晓晓</Option>
<Option value="mao">周毛毛</Option>
</Select>
)}
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col lg={6} md={12} sm={24}>
<Form.Item label={fieldLabels.approver}>
{getFieldDecorator('approver', {
rules: [{ required: true, message: '请选择审批员' }],
})(
<Select placeholder="请选择审批员">
<Option value="xiao">付晓晓</Option>
<Option value="mao">周毛毛</Option>
</Select>
)}
</Form.Item>
</Col>
<Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
<Form.Item label={fieldLabels.dateRange}>
{getFieldDecorator('dateRange', {
rules: [{ required: true, message: '请选择生效日期' }],
})(
<RangePicker placeholder={['开始日期', '结束日期']} style={{ width: '100%' }} />
)}
</Form.Item>
</Col>
<Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
<Form.Item label={fieldLabels.type}>
{getFieldDecorator('type', {
rules: [{ required: true, message: '请选择仓库类型' }],
})(
<Select placeholder="请选择仓库类型">
<Option value="private">私密</Option>
<Option value="public">公开</Option>
</Select>
)}
</Form.Item>
</Col>
</Row>
</Form>
</Card>
<Card title="任务管理" className={styles.card} bordered={false}>
<Form layout="vertical" hideRequiredMark>
<Row gutter={16}>
<Col lg={6} md={12} sm={24}>
<Form.Item label={fieldLabels.name2}>
{getFieldDecorator('name2', {
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
<Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
<Form.Item label={fieldLabels.url2}>
{getFieldDecorator('url2', {
rules: [{ required: true, message: '请选择' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
<Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
<Form.Item label={fieldLabels.owner2}>
{getFieldDecorator('owner2', {
rules: [{ required: true, message: '请选择管理员' }],
})(
<Select placeholder="请选择管理员">
<Option value="xiao">付晓晓</Option>
<Option value="mao">周毛毛</Option>
</Select>
)}
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col lg={6} md={12} sm={24}>
<Form.Item label={fieldLabels.approver2}>
{getFieldDecorator('approver2', {
rules: [{ required: true, message: '请选择审批员' }],
})(
<Select placeholder="请选择审批员">
<Option value="xiao">付晓晓</Option>
<Option value="mao">周毛毛</Option>
</Select>
)}
</Form.Item>
</Col>
<Col xl={{ span: 6, offset: 2 }} lg={{ span: 8 }} md={{ span: 12 }} sm={24}>
<Form.Item label={fieldLabels.dateRange2}>
{getFieldDecorator('dateRange2', {
rules: [{ required: true, message: '请输入' }],
})(
<TimePicker
placeholder="提醒时间"
style={{ width: '100%' }}
getPopupContainer={trigger => trigger.parentNode}
/>
)}
</Form.Item>
</Col>
<Col xl={{ span: 8, offset: 2 }} lg={{ span: 10 }} md={{ span: 24 }} sm={24}>
<Form.Item label={fieldLabels.type2}>
{getFieldDecorator('type2', {
rules: [{ required: true, message: '请选择仓库类型' }],
})(
<Select placeholder="请选择仓库类型">
<Option value="private">私密</Option>
<Option value="public">公开</Option>
</Select>
)}
</Form.Item>
</Col>
</Row>
</Form>
</Card>
<Card title="成员管理" bordered={false}>
{getFieldDecorator('members', {
initialValue: tableData,
})(<TableForm />)}
</Card>
<FooterToolbar style={{ width }}>
{this.getErrorInfo()}
<Button type="primary" onClick={this.validate} loading={submitting}>
提交
</Button>
</FooterToolbar>
</PageHeaderWrapper>
);
}
}
export default AdvancedForm;

View File

@@ -0,0 +1,247 @@
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import { formatMessage, FormattedMessage } from 'umi/locale';
import {
Form,
Input,
DatePicker,
Select,
Button,
Card,
InputNumber,
Radio,
Icon,
Tooltip,
} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './style.less';
const FormItem = Form.Item;
const { Option } = Select;
const { RangePicker } = DatePicker;
const { TextArea } = Input;
@connect(({ loading }) => ({
submitting: loading.effects['form/submitRegularForm'],
}))
@Form.create()
class BasicForms extends PureComponent {
handleSubmit = e => {
const { dispatch, form } = this.props;
e.preventDefault();
form.validateFieldsAndScroll((err, values) => {
if (!err) {
dispatch({
type: 'form/submitRegularForm',
payload: values,
});
}
});
};
render() {
const { submitting } = this.props;
const {
form: { getFieldDecorator, getFieldValue },
} = this.props;
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 7 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
md: { span: 10 },
},
};
const submitFormLayout = {
wrapperCol: {
xs: { span: 24, offset: 0 },
sm: { span: 10, offset: 7 },
},
};
return (
<PageHeaderWrapper
title={<FormattedMessage id="app.forms.basic.title" />}
content={<FormattedMessage id="app.forms.basic.description" />}
>
<Card bordered={false}>
<Form onSubmit={this.handleSubmit} hideRequiredMark style={{ marginTop: 8 }}>
<FormItem {...formItemLayout} label={<FormattedMessage id="form.title.label" />}>
{getFieldDecorator('title', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.title.required' }),
},
],
})(<Input placeholder={formatMessage({ id: 'form.title.placeholder' })} />)}
</FormItem>
<FormItem {...formItemLayout} label={<FormattedMessage id="form.date.label" />}>
{getFieldDecorator('date', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.date.required' }),
},
],
})(
<RangePicker
style={{ width: '100%' }}
placeholder={[
formatMessage({ id: 'form.date.placeholder.start' }),
formatMessage({ id: 'form.date.placeholder.end' }),
]}
/>
)}
</FormItem>
<FormItem {...formItemLayout} label={<FormattedMessage id="form.goal.label" />}>
{getFieldDecorator('goal', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.goal.required' }),
},
],
})(
<TextArea
style={{ minHeight: 32 }}
placeholder={formatMessage({ id: 'form.goal.placeholder' })}
rows={4}
/>
)}
</FormItem>
<FormItem {...formItemLayout} label={<FormattedMessage id="form.standard.label" />}>
{getFieldDecorator('standard', {
rules: [
{
required: true,
message: formatMessage({ id: 'validation.standard.required' }),
},
],
})(
<TextArea
style={{ minHeight: 32 }}
placeholder={formatMessage({ id: 'form.standard.placeholder' })}
rows={4}
/>
)}
</FormItem>
<FormItem
{...formItemLayout}
label={
<span>
<FormattedMessage id="form.client.label" />
<em className={styles.optional}>
<FormattedMessage id="form.optional" />
<Tooltip title={<FormattedMessage id="form.client.label.tooltip" />}>
<Icon type="info-circle-o" style={{ marginRight: 4 }} />
</Tooltip>
</em>
</span>
}
>
{getFieldDecorator('client')(
<Input placeholder={formatMessage({ id: 'form.client.placeholder' })} />
)}
</FormItem>
<FormItem
{...formItemLayout}
label={
<span>
<FormattedMessage id="form.invites.label" />
<em className={styles.optional}>
<FormattedMessage id="form.optional" />
</em>
</span>
}
>
{getFieldDecorator('invites')(
<Input placeholder={formatMessage({ id: 'form.invites.placeholder' })} />
)}
</FormItem>
<FormItem
{...formItemLayout}
label={
<span>
<FormattedMessage id="form.weight.label" />
<em className={styles.optional}>
<FormattedMessage id="form.optional" />
</em>
</span>
}
>
{getFieldDecorator('weight')(
<InputNumber
placeholder={formatMessage({ id: 'form.weight.placeholder' })}
min={0}
max={100}
/>
)}
<span className="ant-form-text">%</span>
</FormItem>
<FormItem
{...formItemLayout}
label={<FormattedMessage id="form.public.label" />}
help={<FormattedMessage id="form.public.label.help" />}
>
<div>
{getFieldDecorator('public', {
initialValue: '1',
})(
<Radio.Group>
<Radio value="1">
<FormattedMessage id="form.public.radio.public" />
</Radio>
<Radio value="2">
<FormattedMessage id="form.public.radio.partially-public" />
</Radio>
<Radio value="3">
<FormattedMessage id="form.public.radio.private" />
</Radio>
</Radio.Group>
)}
<FormItem style={{ marginBottom: 0 }}>
{getFieldDecorator('publicUsers')(
<Select
mode="multiple"
placeholder={formatMessage({ id: 'form.publicUsers.placeholder' })}
style={{
margin: '8px 0',
display: getFieldValue('public') === '2' ? 'block' : 'none',
}}
>
<Option value="1">
<FormattedMessage id="form.publicUsers.option.A" />
</Option>
<Option value="2">
<FormattedMessage id="form.publicUsers.option.B" />
</Option>
<Option value="3">
<FormattedMessage id="form.publicUsers.option.C" />
</Option>
</Select>
)}
</FormItem>
</div>
</FormItem>
<FormItem {...submitFormLayout} style={{ marginTop: 32 }}>
<Button type="primary" htmlType="submit" loading={submitting}>
<FormattedMessage id="form.submit" />
</Button>
<Button style={{ marginLeft: 8 }}>
<FormattedMessage id="form.save" />
</Button>
</FormItem>
</Form>
</Card>
</PageHeaderWrapper>
);
}
}
export default BasicForms;

View File

@@ -0,0 +1,115 @@
import React, { Fragment } from 'react';
import { connect } from 'dva';
import { Form, Input, Button, Select, Divider } from 'antd';
import router from 'umi/router';
import styles from './style.less';
const { Option } = Select;
const formItemLayout = {
labelCol: {
span: 5,
},
wrapperCol: {
span: 19,
},
};
@connect(({ form }) => ({
data: form.step,
}))
@Form.create()
class Step1 extends React.PureComponent {
render() {
const { form, dispatch, data } = this.props;
const { getFieldDecorator, validateFields } = form;
const onValidateForm = () => {
validateFields((err, values) => {
if (!err) {
dispatch({
type: 'form/saveStepFormData',
payload: values,
});
router.push('/form/step-form/confirm');
}
});
};
return (
<Fragment>
<Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
<Form.Item {...formItemLayout} label="付款账户">
{getFieldDecorator('payAccount', {
initialValue: data.payAccount,
rules: [{ required: true, message: '请选择付款账户' }],
})(
<Select placeholder="test@example.com">
<Option value="ant-design@alipay.com">ant-design@alipay.com</Option>
</Select>
)}
</Form.Item>
<Form.Item {...formItemLayout} label="收款账户">
<Input.Group compact>
<Select defaultValue="alipay" style={{ width: 100 }}>
<Option value="alipay">支付宝</Option>
<Option value="bank">银行账户</Option>
</Select>
{getFieldDecorator('receiverAccount', {
initialValue: data.receiverAccount,
rules: [
{ required: true, message: '请输入收款人账户' },
{ type: 'email', message: '账户名应为邮箱格式' },
],
})(<Input style={{ width: 'calc(100% - 100px)' }} placeholder="test@example.com" />)}
</Input.Group>
</Form.Item>
<Form.Item {...formItemLayout} label="收款人姓名">
{getFieldDecorator('receiverName', {
initialValue: data.receiverName,
rules: [{ required: true, message: '请输入收款人姓名' }],
})(<Input placeholder="请输入收款人姓名" />)}
</Form.Item>
<Form.Item {...formItemLayout} label="转账金额">
{getFieldDecorator('amount', {
initialValue: data.amount,
rules: [
{ required: true, message: '请输入转账金额' },
{
pattern: /^(\d+)((?:\.\d+)?)$/,
message: '请输入合法金额数字',
},
],
})(<Input prefix="¥" placeholder="请输入金额" />)}
</Form.Item>
<Form.Item
wrapperCol={{
xs: { span: 24, offset: 0 },
sm: {
span: formItemLayout.wrapperCol.span,
offset: formItemLayout.labelCol.span,
},
}}
label=""
>
<Button type="primary" onClick={onValidateForm}>
下一步
</Button>
</Form.Item>
</Form>
<Divider style={{ margin: '40px 0 24px' }} />
<div className={styles.desc}>
<h3>说明</h3>
<h4>转账到支付宝账户</h4>
<p>
如果需要这里可以放一些关于产品的常见问题说明如果需要这里可以放一些关于产品的常见问题说明如果需要这里可以放一些关于产品的常见问题说明
</p>
<h4>转账到银行卡</h4>
<p>
如果需要这里可以放一些关于产品的常见问题说明如果需要这里可以放一些关于产品的常见问题说明如果需要这里可以放一些关于产品的常见问题说明
</p>
</div>
</Fragment>
);
}
}
export default Step1;

View File

@@ -0,0 +1,99 @@
import React from 'react';
import { connect } from 'dva';
import { Form, Input, Button, Alert, Divider } from 'antd';
import router from 'umi/router';
import { digitUppercase } from '@/utils/utils';
import styles from './style.less';
const formItemLayout = {
labelCol: {
span: 5,
},
wrapperCol: {
span: 19,
},
};
@connect(({ form, loading }) => ({
submitting: loading.effects['form/submitStepForm'],
data: form.step,
}))
@Form.create()
class Step2 extends React.PureComponent {
render() {
const { form, data, dispatch, submitting } = this.props;
const { getFieldDecorator, validateFields } = form;
const onPrev = () => {
router.push('/form/step-form/info');
};
const onValidateForm = e => {
e.preventDefault();
validateFields((err, values) => {
if (!err) {
dispatch({
type: 'form/submitStepForm',
payload: {
...data,
...values,
},
});
}
});
};
return (
<Form layout="horizontal" className={styles.stepForm}>
<Alert
closable
showIcon
message="确认转账后,资金将直接打入对方账户,无法退回。"
style={{ marginBottom: 24 }}
/>
<Form.Item {...formItemLayout} className={styles.stepFormText} label="付款账户">
{data.payAccount}
</Form.Item>
<Form.Item {...formItemLayout} className={styles.stepFormText} label="收款账户">
{data.receiverAccount}
</Form.Item>
<Form.Item {...formItemLayout} className={styles.stepFormText} label="收款人姓名">
{data.receiverName}
</Form.Item>
<Form.Item {...formItemLayout} className={styles.stepFormText} label="转账金额">
<span className={styles.money}>{data.amount}</span>
<span className={styles.uppercase}>{digitUppercase(data.amount)}</span>
</Form.Item>
<Divider style={{ margin: '24px 0' }} />
<Form.Item {...formItemLayout} label="支付密码" required={false}>
{getFieldDecorator('password', {
initialValue: '123456',
rules: [
{
required: true,
message: '需要支付密码才能进行支付',
},
],
})(<Input type="password" autoComplete="off" style={{ width: '80%' }} />)}
</Form.Item>
<Form.Item
style={{ marginBottom: 8 }}
wrapperCol={{
xs: { span: 24, offset: 0 },
sm: {
span: formItemLayout.wrapperCol.span,
offset: formItemLayout.labelCol.span,
},
}}
label=""
>
<Button type="primary" onClick={onValidateForm} loading={submitting}>
提交
</Button>
<Button onClick={onPrev} style={{ marginLeft: 8 }}>
上一步
</Button>
</Form.Item>
</Form>
);
}
}
export default Step2;

View File

@@ -0,0 +1,74 @@
import React, { Fragment } from 'react';
import { connect } from 'dva';
import { Button, Row, Col } from 'antd';
import router from 'umi/router';
import Result from '@/components/Result';
import styles from './style.less';
@connect(({ form }) => ({
data: form.step,
}))
class Step3 extends React.PureComponent {
render() {
const { data } = this.props;
const onFinish = () => {
router.push('/form/step-form/info');
};
const information = (
<div className={styles.information}>
<Row>
<Col xs={24} sm={8} className={styles.label}>
付款账户
</Col>
<Col xs={24} sm={16}>
{data.payAccount}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
收款账户
</Col>
<Col xs={24} sm={16}>
{data.receiverAccount}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
收款人姓名
</Col>
<Col xs={24} sm={16}>
{data.receiverName}
</Col>
</Row>
<Row>
<Col xs={24} sm={8} className={styles.label}>
转账金额
</Col>
<Col xs={24} sm={16}>
<span className={styles.money}>{data.amount}</span>
</Col>
</Row>
</div>
);
const actions = (
<Fragment>
<Button type="primary" onClick={onFinish}>
再转一笔
</Button>
<Button>查看账单</Button>
</Fragment>
);
return (
<Result
type="success"
title="操作成功"
description="预计两小时内到账"
extra={information}
actions={actions}
className={styles.result}
/>
);
}
}
export default Step3;

View File

@@ -0,0 +1,46 @@
import React, { PureComponent, Fragment } from 'react';
import { Card, Steps } from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from '../style.less';
const { Step } = Steps;
export default class StepForm extends PureComponent {
getCurrentStep() {
const { location } = this.props;
const { pathname } = location;
const pathList = pathname.split('/');
switch (pathList[pathList.length - 1]) {
case 'info':
return 0;
case 'confirm':
return 1;
case 'result':
return 2;
default:
return 0;
}
}
render() {
const { location, children } = this.props;
return (
<PageHeaderWrapper
title="分步表单"
tabActiveKey={location.pathname}
content="将一个冗长或用户不熟悉的表单任务分成多个步骤,指导用户完成。"
>
<Card bordered={false}>
<Fragment>
<Steps current={this.getCurrentStep()} className={styles.steps}>
<Step title="填写转账信息" />
<Step title="确认转账信息" />
<Step title="完成" />
</Steps>
{children}
</Fragment>
</Card>
</PageHeaderWrapper>
);
}
}

View File

@@ -0,0 +1,78 @@
@import '~antd/lib/style/themes/default.less';
.stepForm {
max-width: 500px;
margin: 40px auto 0;
}
.stepFormText {
margin-bottom: 24px;
:global {
.ant-form-item-label,
.ant-form-item-control {
line-height: 22px;
}
}
}
.result {
max-width: 560px;
margin: 0 auto;
padding: 24px 0 8px;
}
.desc {
padding: 0 56px;
color: @text-color-secondary;
h3 {
margin: 0 0 12px 0;
color: @text-color-secondary;
font-size: 16px;
line-height: 32px;
}
h4 {
margin: 0 0 4px 0;
color: @text-color-secondary;
font-size: 14px;
line-height: 22px;
}
p {
margin-top: 0;
margin-bottom: 12px;
line-height: 22px;
}
}
@media screen and (max-width: @screen-md) {
.desc {
padding: 0;
}
}
.information {
line-height: 22px;
:global {
.ant-row:not(:last-child) {
margin-bottom: 24px;
}
}
.label {
padding-right: 8px;
color: @heading-color;
text-align: right;
@media screen and (max-width: @screen-sm) {
text-align: left;
}
}
}
.money {
font-weight: 500;
font-size: 20px;
font-family: 'Helvetica Neue', sans-serif;
line-height: 14px;
}
.uppercase {
font-size: 12px;
}

View File

@@ -0,0 +1,261 @@
import React, { PureComponent, Fragment } from 'react';
import { Table, Button, Input, message, Popconfirm, Divider } from 'antd';
import isEqual from 'lodash/isEqual';
import styles from './style.less';
class TableForm extends PureComponent {
index = 0;
cacheOriginData = {};
constructor(props) {
super(props);
this.state = {
data: props.value,
loading: false,
/* eslint-disable-next-line react/no-unused-state */
value: props.value,
};
}
static getDerivedStateFromProps(nextProps, preState) {
if (isEqual(nextProps.value, preState.value)) {
return null;
}
return {
data: nextProps.value,
value: nextProps.value,
};
}
getRowByKey(key, newData) {
const { data } = this.state;
return (newData || data).filter(item => item.key === key)[0];
}
toggleEditable = (e, key) => {
e.preventDefault();
const { data } = this.state;
const newData = data.map(item => ({ ...item }));
const target = this.getRowByKey(key, newData);
if (target) {
// 进入编辑状态时保存原始数据
if (!target.editable) {
this.cacheOriginData[key] = { ...target };
}
target.editable = !target.editable;
this.setState({ data: newData });
}
};
newMember = () => {
const { data } = this.state;
const newData = data.map(item => ({ ...item }));
newData.push({
key: `NEW_TEMP_ID_${this.index}`,
workId: '',
name: '',
department: '',
editable: true,
isNew: true,
});
this.index += 1;
this.setState({ data: newData });
};
remove(key) {
const { data } = this.state;
const { onChange } = this.props;
const newData = data.filter(item => item.key !== key);
this.setState({ data: newData });
onChange(newData);
}
handleKeyPress(e, key) {
if (e.key === 'Enter') {
this.saveRow(e, key);
}
}
handleFieldChange(e, fieldName, key) {
const { data } = this.state;
const newData = data.map(item => ({ ...item }));
const target = this.getRowByKey(key, newData);
if (target) {
target[fieldName] = e.target.value;
this.setState({ data: newData });
}
}
saveRow(e, key) {
e.persist();
this.setState({
loading: true,
});
setTimeout(() => {
if (this.clickedCancel) {
this.clickedCancel = false;
return;
}
const target = this.getRowByKey(key) || {};
if (!target.workId || !target.name || !target.department) {
message.error('请填写完整成员信息。');
e.target.focus();
this.setState({
loading: false,
});
return;
}
delete target.isNew;
this.toggleEditable(e, key);
const { data } = this.state;
const { onChange } = this.props;
onChange(data);
this.setState({
loading: false,
});
}, 500);
}
cancel(e, key) {
this.clickedCancel = true;
e.preventDefault();
const { data } = this.state;
const newData = data.map(item => ({ ...item }));
const target = this.getRowByKey(key, newData);
if (this.cacheOriginData[key]) {
Object.assign(target, this.cacheOriginData[key]);
delete this.cacheOriginData[key];
}
target.editable = false;
this.setState({ data: newData });
this.clickedCancel = false;
}
render() {
const columns = [
{
title: '成员姓名',
dataIndex: 'name',
key: 'name',
width: '20%',
render: (text, record) => {
if (record.editable) {
return (
<Input
value={text}
autoFocus
onChange={e => this.handleFieldChange(e, 'name', record.key)}
onKeyPress={e => this.handleKeyPress(e, record.key)}
placeholder="成员姓名"
/>
);
}
return text;
},
},
{
title: '工号',
dataIndex: 'workId',
key: 'workId',
width: '20%',
render: (text, record) => {
if (record.editable) {
return (
<Input
value={text}
onChange={e => this.handleFieldChange(e, 'workId', record.key)}
onKeyPress={e => this.handleKeyPress(e, record.key)}
placeholder="工号"
/>
);
}
return text;
},
},
{
title: '所属部门',
dataIndex: 'department',
key: 'department',
width: '40%',
render: (text, record) => {
if (record.editable) {
return (
<Input
value={text}
onChange={e => this.handleFieldChange(e, 'department', record.key)}
onKeyPress={e => this.handleKeyPress(e, record.key)}
placeholder="所属部门"
/>
);
}
return text;
},
},
{
title: '操作',
key: 'action',
render: (text, record) => {
const { loading } = this.state;
if (!!record.editable && loading) {
return null;
}
if (record.editable) {
if (record.isNew) {
return (
<span>
<a onClick={e => this.saveRow(e, record.key)}>添加</a>
<Divider type="vertical" />
<Popconfirm title="是否要删除此行?" onConfirm={() => this.remove(record.key)}>
<a>删除</a>
</Popconfirm>
</span>
);
}
return (
<span>
<a onClick={e => this.saveRow(e, record.key)}>保存</a>
<Divider type="vertical" />
<a onClick={e => this.cancel(e, record.key)}>取消</a>
</span>
);
}
return (
<span>
<a onClick={e => this.toggleEditable(e, record.key)}>编辑</a>
<Divider type="vertical" />
<Popconfirm title="是否要删除此行?" onConfirm={() => this.remove(record.key)}>
<a>删除</a>
</Popconfirm>
</span>
);
},
},
];
const { loading, data } = this.state;
return (
<Fragment>
<Table
loading={loading}
columns={columns}
dataSource={data}
pagination={false}
rowClassName={record => (record.editable ? styles.editable : '')}
/>
<Button
style={{ width: '100%', marginTop: 16, marginBottom: 8 }}
type="dashed"
onClick={this.newMember}
icon="plus"
>
新增成员
</Button>
</Fragment>
);
}
}
export default TableForm;

View File

@@ -0,0 +1,47 @@
import { routerRedux } from 'dva/router';
import { message } from 'antd';
import { fakeSubmitForm } from '@/services/api';
export default {
namespace: 'form',
state: {
step: {
payAccount: 'ant-design@alipay.com',
receiverAccount: 'test@example.com',
receiverName: 'Alex',
amount: '500',
},
},
effects: {
*submitRegularForm({ payload }, { call }) {
yield call(fakeSubmitForm, payload);
message.success('提交成功');
},
*submitStepForm({ payload }, { call, put }) {
yield call(fakeSubmitForm, payload);
yield put({
type: 'saveStepFormData',
payload,
});
yield put(routerRedux.push('/form/step-form/result'));
},
*submitAdvancedForm({ payload }, { call }) {
yield call(fakeSubmitForm, payload);
message.success('提交成功');
},
},
reducers: {
saveStepFormData(state, { payload }) {
return {
...state,
step: {
...state.step,
...payload,
},
};
},
},
};

View File

@@ -0,0 +1,90 @@
@import '~antd/lib/style/themes/default.less';
.card {
margin-bottom: 24px;
}
.heading {
margin: 0 0 16px 0;
font-size: 14px;
line-height: 22px;
}
.steps:global(.ant-steps) {
max-width: 750px;
margin: 16px auto;
}
.errorIcon {
margin-right: 24px;
color: @error-color;
cursor: pointer;
i {
margin-right: 4px;
}
}
.errorPopover {
:global {
.ant-popover-inner-content {
min-width: 256px;
max-height: 290px;
padding: 0;
overflow: auto;
}
}
}
.errorListItem {
padding: 8px 16px;
list-style: none;
border-bottom: 1px solid @border-color-split;
cursor: pointer;
transition: all 0.3s;
&:hover {
background: @primary-1;
}
&:last-child {
border: 0;
}
.errorIcon {
float: left;
margin-top: 4px;
margin-right: 12px;
padding-bottom: 22px;
color: @error-color;
}
.errorField {
margin-top: 2px;
color: @text-color-secondary;
font-size: 12px;
}
}
.editable {
td {
padding-top: 13px !important;
padding-bottom: 12.5px !important;
}
}
// custom footer for fixed footer toolbar
.advancedForm + div {
padding-bottom: 64px;
}
.advancedForm {
:global {
.ant-form .ant-row:last-child .ant-form-item {
margin-bottom: 24px;
}
.ant-table td {
transition: none !important;
}
}
}
.optional {
color: @text-color-secondary;
font-style: normal;
}