#ProForm 高级表单
ProForm 在原来的 Form 的基础上增加了一些语法糖和更多的布局设置,帮助我们快速地开发一个表单,同时添加了一些默认行为,让我们的表单默认好用。
分步表单、Modal 表单、Drawer 表单、查询表单、轻量筛选等多种 layout 可以覆盖大部分的使用场景,让我们脱离复杂而且繁琐的表单布局工作,用更少的代码完成更多的功能。
- 如果想要设置默认值,请使用
initialValues,任何直接使用组件value和onChange的方式都有可能导致值绑定失效 - 如果想要表单联动或者做一些依赖,可以使用 render props 模式,ProFormDependency 绝对是最好的选择
- ProForm 的 onFinish 与 antd 的 Form 不同,支持 Promise,如果你正常返回会自动为你设置按钮的加载效果
- 如果想要 监听某个值,建议使用
onValuesChange。保持单向的数据流无论对开发者还是维护者都大有裨益 - ProForm 没有「魔法」:
ProForm只是对 antdForm的薄封装;自定义控件用Form.Item包裹后与 Pro 表单项混用即可。
#Issue 回归场景
import type { ProFormInstance } from '@ant-design/pro-components';
import {
BetaSchemaForm,
ModalForm,
ProCard,
ProForm,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
import { Alert, Button, Space, Tabs, Typography, message } from 'antd';
import { useRef, useState } from 'react';
const toList = (value: Record<string, string>) =>
Object.entries(value).map(([key, itemValue]) => ({
key,
value: itemValue,
}));
const ListConvertValue = () => {
const formRef = useRef<ProFormInstance>();
return (
<ProForm
formRef={formRef}
submitter={false}
initialValues={{ configs: { first: 'one', second: 'two' } }}
>
<Space style={{ marginBlockEnd: 16 }}>
<Button
onClick={() =>
formRef.current?.setFieldsValue({ configs: { third: 'three' } })
}
>
setFieldsValue 写入对象
</Button>
<Typography.Text type="secondary">
对象会在初始值及外部写入时转换为列表。
</Typography.Text>
</Space>
<ProFormList name="configs" convertValue={toList}>
<ProFormText name="key" label="Key" />
<ProFormText name="value" label="Value" />
</ProFormList>
</ProForm>
);
};
const RequestReuse = () => {
const [id, setId] = useState(0);
return (
<Space direction="vertical" style={{ width: '100%' }}>
<Space>
{[0, 1, 0].map((value, index) => (
<Button key={`${value}-${index}`} onClick={() => setId(value)}>
参数 {value}
</Button>
))}
</Space>
<ProForm
key="request-reuse-form"
params={{ id }}
request={async ({ id: requestId }) => ({
name: `服务端返回:参数 ${requestId}(${new Date().toLocaleTimeString()})`,
})}
submitter={false}
>
<ProFormText name="name" label="请求结果" width="lg" />
</ProForm>
</Space>
);
};
const OverlayInitialization = () => {
const [open, setOpen] = useState(false);
const [record, setRecord] = useState({ name: 'Alice' });
return (
<Space>
{['Alice', 'Bob'].map((name) => (
<Button
key={name}
onClick={() => {
setRecord({ name });
setOpen(true);
}}
>
编辑 {name}
</Button>
))}
<ModalForm
open={open}
onOpenChange={setOpen}
initialValues={record}
request={async () => record}
title="受控弹窗初始化"
onFinish={async ({ name }) => {
message.success(`已保存 ${name}`);
return true;
}}
>
<ProFormText name="name" label="姓名" />
</ModalForm>
</Space>
);
};
export default () => (
<ProCard direction="column" ghost gutter={[0, 16]}>
<Alert
type="info"
showIcon
message="表单初始化与转换回归场景"
description="覆盖弹层初始化、ProFormList convertValue、SchemaForm 独立宽度及重复参数请求。"
/>
<ProCard>
<Tabs
items={[
{
key: 'overlay',
label: '弹层初始化',
children: <OverlayInitialization />,
},
{
key: 'list',
label: '列表转换 #8702',
children: <ListConvertValue />,
},
{
key: 'schema',
label: 'SchemaForm 宽度 #9061',
children: (
<BetaSchemaForm
submitter={false}
columns={[
{
title: '表格列宽 80,表单宽度 328',
dataIndex: 'title',
width: 80,
formWidth: 328,
},
]}
/>
),
},
{
key: 'request',
label: '重复参数请求 #8375',
children: <RequestReuse />,
},
]}
/>
</ProCard>
</ProCard>
);
// 设置整体默认值
<ProForm initialValues={obj} />
// 设置单个控件的
<ProForm
onValuesChange={(changeValues) => console.log(changeValues)}
>
<ProFormText initialValue="prop"/>
</ProForm>
// 相互依赖的组件联动
<ProForm>
<Form.Item noStyle shouldUpdate>
{(form) => {
return (
<ProFormSelect
options={[
{
value: "chapter",
label: "盖章后生效",
},
]}
width="md"
name="useMode"
label={`与${form.getFieldValue("name")}合同约定生效方式`}
/>
);
}}
</Form.Item>
</ProForm>;
// 使用自定义组件
<ProForm>
<Form.Item name="switch" label="Switch" valuePropName="checked">
<Switch />
</Form.Item>
</ProForm>#何时使用 ProForm?
当你想快速实现一个表单但不想花太多时间去布局时 ProForm 是最好的选择。
ProForm 是基于 antd Form 的可降级封装,与 antd 功能完全对齐,但是在其之上还增加一些预设行为和多种布局。这些布局之间可以无缝切换,并且拥有公共的 API。
| 布局 | 使用场景 |
|---|---|
| ProForm | 标准 Form,增加了 onFinish 中自动 loading 和根据 request 自动获取默认值的功能。 |
| ModalForm|DrawerForm | 在 ProForm 的基础上增加了 trigger ,无需维护 open 状态。 |
| QueryFilter | 一般用于作为筛选表单,需要配合其他数据展示组件使用。 |
| LightFilter | 一般用于作为行内内置的筛选,比如卡片操作栏和表格操作栏。 |
| StepsForm | 分步表单,需要配置 StepForm 使用。 |
import {
AlipayCircleOutlined,
LockOutlined,
PlusOutlined,
TaobaoCircleOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
import {
DrawerForm,
LightFilter,
LoginForm,
ModalForm,
ProForm,
ProFormDateRangePicker,
ProFormDigit,
ProFormRadio,
ProFormSelect,
ProFormText,
QueryFilter,
StepsForm,
} from '@ant-design/pro-components';
import { Button, Space, message } from 'antd';
import { useState } from 'react';
const iconStyles = {
marginInlineStart: '16px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
};
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const Components = {
ProForm,
ModalForm,
DrawerForm,
QueryFilter,
LightFilter,
StepsForm,
LoginForm,
};
const [type, setType] = useState<keyof typeof Components>('ProForm');
if (type === 'StepsForm') {
return (
<>
<ProFormRadio.Group
style={{
margin: 16,
}}
radioType="button"
fieldProps={{
value: type,
onChange: (e) => setType(e.target.value),
}}
options={[
'LightFilter',
'ProForm',
'ModalForm',
'DrawerForm',
'QueryFilter',
'StepsForm',
'LoginForm',
]}
/>
<StepsForm
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
}}
>
<StepsForm.StepForm title="Step One">
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
name={['contract', 'name']}
width="md"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
width="md"
name={['contract', 'createTime']}
label="Contract Effective Time"
/>
</ProForm.Group>
</StepsForm.StepForm>
<StepsForm.StepForm title="Step Two">
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
readonly
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
</StepsForm.StepForm>
<StepsForm.StepForm title="Step Three">
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
width="md"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
</StepsForm.StepForm>
</StepsForm>
</>
);
}
const FormComponents = Components[type as 'LoginForm'];
if (type === 'LoginForm') {
return (
<>
<ProFormRadio.Group
style={{
margin: 16,
}}
radioType="button"
fieldProps={{
value: type,
onChange: (e) => setType(e.target.value),
}}
options={[
'LightFilter',
'ProForm',
'ModalForm',
'DrawerForm',
'QueryFilter',
'StepsForm',
'LoginForm',
]}
/>
<FormComponents
title="Github"
subTitle="The world's largest code hosting platform"
actions={
<Space>
Other login methods
<AlipayCircleOutlined style={iconStyles} />
<TaobaoCircleOutlined style={iconStyles} />
<WeiboCircleOutlined style={iconStyles} />
</Space>
}
>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={'prefixIcon'} />,
}}
placeholder={'Username: admin or user'}
rules={[
{
required: true,
message: 'Please enter your username!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={'prefixIcon'} />,
}}
placeholder={'Password: ant.design'}
rules={[
{
required: true,
message: 'Please enter your password!',
},
]}
/>
</FormComponents>
</>
);
}
return (
<>
<ProFormRadio.Group
style={{
margin: 16,
}}
radioType="button"
fieldProps={{
value: type,
onChange: (e) => setType(e.target.value),
}}
options={[
'LightFilter',
'ProForm',
'ModalForm',
'DrawerForm',
'QueryFilter',
'StepsForm',
'LoginForm',
]}
/>
<div
style={{
margin: 24,
}}
>
<FormComponents
// @ts-ignore
labelWidth="auto"
trigger={
<Button type="primary">
<PlusOutlined />
New Form
</Button>
}
onFinish={async () => {
await waitTime(500);
message.success('Submission successful');
}}
initialValues={{
name: 'Ant Design Co., Ltd.',
useMode: 'chapter',
}}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
name={['contract', 'name']}
width="md"
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
width="md"
name={['contract', 'createTime']}
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
readonly
width="xs"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
width="md"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
<ProFormDigit
name="percent"
label="Percent"
width="xs"
min={1}
max={10}
fieldProps={{
precision: 2,
addonAfter: '%',
}}
/>
</FormComponents>
</div>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#数据转化
很多时候组件需要的数据和后端需要的数据之间不能完全匹配,ProForm 为了解决这个问题提供了 transform 和 convertValue 两个 API 来处理这种情况。
#convertValue 前置转化
convertValue 发生在组件获得数据之前,一般是后端直接给前端的数据,有时需要精加工一下。
export type SearchConvertKeyFn =
(value: any, field: NamePath)=>string | boolean | Record<string, any>;
/**
* @name 获取时转化值,一般用于将数据格式化为组件接收的格式
* @param value 字段的值
* @param namePath 字段的name
* @returns 字段新的值
*
* @example a,b => [a,b]
* convertValue:(value,namePath)=>value.split(",")
* @example string =>json
* convertValue:(value,namePath)=>JSON.parse(value)
* @example number =>date
* convertValue:(value,namePath)=>Moment(value)
* @example YYYY-MM-DD => date
* convertValue:(value,namePath)=>Moment(value,"YYYY-MM-DD")
* @example string => object
* convertValue:(value,namePath)=>({value,label:value})
*/
convertValue?: SearchConvertKeyFn;#transform 提交时转化
transform 发生在提交的时候,一般来说都是吐给后端的存在数据库里的数据。
为了方便大家使用,ProFormDependency 和 formRef 都支持了 transform,可以获取到被转化后的值。
<ProFormDependency>
{(value, form) => {
// value 被 transform转化之后的值
// form 当前的formRef,可以获取未转化的值
return ReactNode;
}}
</ProFormDependency>formRef 内置了几个方法来获取转化之后的值,这也是相比 antd 的 Form 新增的功能,详细可以看 ProFormInstance 的类型定义。
/** 获取被 ProForm 格式化后的所有数据 */
getFieldsFormatValue?: (nameList?: true) => T;
/** 获取格式化之后的单个数据 */
getFieldFormatValue?: (nameList?: NamePath) => T;
/** 获取格式化之后的单个数据 */
getFieldFormatValueObject?: (nameList?: NamePath) => T;
/** 验字段后返回格式化之后的所有数据*/
validateFieldsReturnFormatValue?: (nameList?: NamePath[]) => Promise<T>; export type SearchTransformKeyFn = (
value: any,
namePath: string[],
allValues: any,
) => string | Record<string, any>;
/**
* @name 提交时转化值,一般用于将值转化为提交的数据
* @param value 字段的值
* @param namePath 字段的name
* @param allValues 所有的字段
* @returns 字段新的值,如果返回对象,会和所有值深度 merge 一次
*
* @example {name:[a,b] => {name:a,b }
* transform: (value,namePath,allValues)=> value.join(",")
* @example {name: string => { newName:string }
* transform: (value,namePath,allValues)=> { newName:value }
* @example {name:moment} => {name:string
* transform: (value,namePath,allValues)=> value.format("YYYY-MM-DD")
* @example {name:moment}=> {name:时间戳}
* transform: (value,namePath,allValues)=> value.valueOf()
* @example {name:{value,label}} => { name:string}
* transform: (value,namePath,allValues)=> value.value
* @example {name:{value,label}} => { valueName,labelName }
* transform: (value)=>{valueName:value.value,labelName:value.name}
*/
transform?: SearchTransformKeyFn;#transform 的两种常见返回写法(建议直接照着用)
transform 的返回值常见有两种写法:
- 1)返回普通值(最直观,也最稳定):会替换当前字段的提交值。
<ProFormText name="name" transform={(value) => `${value}:suffix`} />
// 提交时:{ name: 'xxx:suffix' }- 2)返回对象(用于“改名/拆分字段/写回嵌套路径”):推荐按字段的
name/namePath构造对象,避免“看起来对但提交没变”的情况。
import { set } from '@rc-component/util';
// 写回同一路径(推荐:不依赖外层 merge 行为)
<ProFormText
name={['company', 'name']}
transform={(value) => set({}, ['company', 'name'], `${value}:suffix`)}
/>
// 提交时:{ company: { name: 'xxx:suffix' } }
// 变更 key(示例:把 name 写成 displayName)
<ProFormText
name="name"
transform={(value) => ({ displayName: value })}
/>
// 提交时:{ displayName: 'xxx' }(注意:这会改变最终输出结构)提醒:
SearchTransformKeyFn的类型签名里第二个参数是namePath: string[],但在部分场景(例如嵌套、ProFormList)里你可能会观察到传入值并不总是你期望的“完整路径数组”。这也是推荐你直接用组件的name构造返回对象的原因。
#代码示例
import type { ProFormInstance } from '@ant-design/pro-components';
import {
ProForm,
ProFormCascader,
ProFormDatePicker,
ProFormDateRangePicker,
ProFormDigit,
ProFormList,
ProFormMoney,
ProFormSelect,
ProFormText,
ProFormTextArea,
ProFormTreeSelect,
} from '@ant-design/pro-components';
import { TreeSelect, message } from 'antd';
import moment from 'dayjs';
import { useRef } from 'react';
import { DEMO_AREA_CASCADER, DEMO_DEPARTMENT_TREE } from '../mockData';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const formRef = useRef<
ProFormInstance<{
name: string;
company?: string;
useMode?: string;
}>
>();
return (
<ProForm<{
name: string;
company?: string;
useMode?: string;
}>
name="base-form-demo"
onFinish={async (values) => {
await waitTime(2000);
console.log(values);
const val1 = await formRef.current?.validateFields();
console.log('validateFields:', val1);
const val2 = await formRef.current?.validateFieldsReturnFormatValue?.();
console.log('validateFieldsReturnFormatValue:', val2);
message.success('提交成功');
}}
formRef={formRef}
params={{ id: '100' }}
formKey="base-demo"
dateFormatter={(value, _valueType) => {
return value.format('YYYY/MM/DD HH:mm:ss');
}}
request={async () => {
await waitTime(1500);
return {
name: '杭州星辰科技有限公司',
useMode: 'chapter',
};
}}
autoFocusFirstInput
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
required
dependencies={[['contract', 'name']]}
addonBefore={<a>如何获取客户名称?</a>}
addonAfter={<a>点击查看更多</a>}
label="签约客户名称"
tooltip="最长 24 个字符"
placeholder="请输入客户名称"
rules={[{ required: true, message: '此项为必填项' }]}
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入公司名称"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormDigit name="count" label="项目团队人数" width="lg" />
</ProForm.Group>
<ProForm.Group>
<ProFormText
name={['contract', 'name']}
width="md"
label="合同名称"
placeholder="请输入合同名称"
/>
<ProFormDateRangePicker
width="md"
name={['contract', 'createTime']}
label="合同有效期"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: '盖章后生效',
},
]}
readonly
width="xs"
cacheForSwr
name="useMode"
label="合同约定生效方式"
/>
<ProFormSelect.SearchSelect
width="xs"
options={[
{
value: 'time',
label: '履行完毕后终止',
type: 'time',
options: [
{
value: 'time1',
label: '履行完毕后终止(自动续期)',
},
{
value: 'time2',
label: '履行完毕后终止(不续期)',
},
],
},
]}
name="unusedMode"
label="合同约定失效方式"
/>
<ProFormMoney
width="md"
name="money"
label="合同签约金额"
fieldProps={{
numberPopoverRender: true,
}}
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProFormText
name="project"
width="md"
disabled
label="关联项目"
initialValue="智慧零售平台"
/>
<ProFormTextArea
colProps={{ span: 24 }}
name="address"
label="详细办公地址"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="商务经理"
initialValue="期贤"
/>
<ProFormCascader
width="md"
request={async () => DEMO_AREA_CASCADER as any}
name="areaList"
label="所在地区"
initialValue={['zhejiang', 'hangzhou', 'binjiang']}
addonAfter="区县"
/>
<ProFormTreeSelect
initialValue={['tech-fe']}
label="所属部门"
width={600}
fieldProps={{
fieldNames: {
label: 'title',
},
treeData: DEMO_DEPARTMENT_TREE as any,
treeCheckable: true,
showCheckedStrategy: TreeSelect.SHOW_PARENT,
placeholder: '请选择部门',
}}
/>
<ProFormDatePicker
name="date"
label="签约日期"
transform={(value) => {
return {
date: moment(value).unix(),
};
}}
/>
<ProFormList name="datas" label="补充条款">
{() => {
return (
<>
<ProFormDatePicker
name="date"
label="生效日期"
transform={(value) => {
return {
date: moment(value).unix(),
};
}}
/>
<ProFormList name="innerDatas" label="子条款">
{() => {
return (
<>
<ProFormDatePicker
name="date"
label="子条款生效日期"
transform={(value) => {
return {
date: moment(value).unix(),
};
}}
/>
</>
);
}}
</ProFormList>
</>
);
}}
</ProFormList>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#标签与表单项布局
除了 LightFilter 和 QueryFilter 这样固定布局的表单样式,其他表单布局支持配置与 antd 一致的三种布局方式。
import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
import { Col, Row, Space, message } from 'antd';
import { useState } from 'react';
type LayoutType = Parameters<typeof ProForm>[0]['layout'];
const LAYOUT_TYPE_HORIZONTAL = 'horizontal';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const [formLayoutType, setFormLayoutType] = useState<LayoutType>(
LAYOUT_TYPE_HORIZONTAL,
);
const formItemLayout =
formLayoutType === LAYOUT_TYPE_HORIZONTAL
? {
labelCol: { span: 4 },
wrapperCol: { span: 14 },
}
: null;
return (
<ProForm<{
name: string;
company?: string;
useMode?: string;
}>
name="form-layout-demo"
{...formItemLayout}
layout={formLayoutType}
submitter={{
render: (props, doms) => {
return formLayoutType === LAYOUT_TYPE_HORIZONTAL ? (
<Row>
<Col span={14} offset={4}>
<Space>{doms}</Space>
</Col>
</Row>
) : (
doms
);
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
}}
params={{}}
request={async () => {
await waitTime(100);
return {
name: 'Ant Design Co., Ltd.',
useMode: 'chapter',
};
}}
>
<ProFormRadio.Group
style={{
margin: 16,
}}
label="Label Layout"
radioType="button"
fieldProps={{
value: formLayoutType,
onChange: (e) => setFormLayoutType(e.target.value),
}}
options={['horizontal', 'vertical', 'inline']}
/>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
<ProFormText
name={['contract', 'name']}
width="md"
label="Contract Name"
placeholder="Please enter a name"
/>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#栅格化布局
同时支持在 ProForm, SchemaForm, ModalForm, DrawerForm, StepsForm 中使用
import {
ProForm,
ProFormDatePicker,
ProFormDateRangePicker,
ProFormDigit,
ProFormRadio,
ProFormSelect,
ProFormSwitch,
ProFormText,
ProFormTextArea,
} from '@ant-design/pro-components';
import { Col, Row, Space, message } from 'antd';
import { useState } from 'react';
import type { FormLayout } from '../../src/utils/antdTypes';
const LAYOUT_TYPE_HORIZONTAL = 'horizontal';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const [formLayoutType, setFormLayoutType] = useState<FormLayout>(
LAYOUT_TYPE_HORIZONTAL,
);
const [grid, setGrid] = useState(true);
return (
<ProForm<{
name: string;
company?: string;
useMode?: string;
}>
layout={formLayoutType}
grid={grid}
rowProps={{
gutter: [16, formLayoutType === 'inline' ? 16 : 0],
}}
submitter={{
render: (props, doms) => {
return formLayoutType === LAYOUT_TYPE_HORIZONTAL ? (
<Row>
<Col span={14} offset={4}>
<Space>{doms}</Space>
</Col>
</Row>
) : (
doms
);
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
}}
params={{}}
request={async () => {
await waitTime(100);
return {
name: 'Ant Design Co., Ltd.',
useMode: 'chapter',
};
}}
>
<ProFormRadio.Group
label="Label Layout"
radioType="button"
fieldProps={{
value: formLayoutType,
onChange: (e) => setFormLayoutType(e.target.value),
}}
colProps={{
span: 20,
}}
options={['horizontal', 'vertical', 'inline']}
/>
<ProFormSwitch
colProps={{
span: 4,
}}
fieldProps={{
onChange: setGrid,
}}
initialValue={true}
label="Grid Switch"
name="grid"
/>
<ProFormText
name="name"
label="Title"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText colProps={{ md: 12, xl: 8 }} name="company" label="Name" />
<ProFormDigit colProps={{ md: 12, xl: 8 }} name="phone" label="Phone" />
<ProFormText colProps={{ md: 12, xl: 8 }} name="email" label="Email" />
<ProFormTextArea
colProps={{ span: 24 }}
name="address"
label="Detailed Work Address or Home Address"
/>
<ProFormDatePicker
colProps={{ xl: 8, md: 12 }}
label="Entry Date"
name="date"
/>
<ProFormDateRangePicker
colProps={{ xl: 8, md: 12 }}
label="Work Period"
name="dateRange"
/>
<ProFormSelect
colProps={{ xl: 8, md: 12 }}
label="Position"
name="level"
valueEnum={{
1: 'Front End',
2: 'Back End',
3: 'Full Stack',
}}
/>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
ProForm,
ProFormDependency,
ProFormSelect,
ProFormText,
} from '@ant-design/pro-components';
import { message } from 'antd';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
return (
<ProForm
name="form-dependency-demo"
onFinish={async () => {
await waitTime(2000);
message.success('Submission successful');
}}
initialValues={{
name: 'Ant Design Co., Ltd.',
name2: 'Ant Design Group',
useMode: 'chapter',
}}
>
<ProFormText
width="md"
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name={['name2', 'text']}
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
{/* ProFormDependency will automatically inject and perform shouldUpdate comparison */}
<ProFormDependency name={['name', ['name2', 'text']]}>
{({ name, name2 }) => {
return (
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="md"
name="useMode"
label={`Effective method agreed in the contract with "${name || ''}" and "${name2?.text || ''}"`}
/>
);
}}
</ProFormDependency>
{/* noStyle shouldUpdate is required, writing name will invalidate it */}
<ProForm.Item noStyle shouldUpdate>
{(form) => {
return (
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="md"
name="useMode"
label={`Effective method agreed in the contract with "${form.getFieldValue('name')}"`}
/>
);
}}
</ProForm.Item>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import type { ProFormInstance } from '@ant-design/pro-components';
import {
ProForm,
ProFormDatePicker,
ProFormText,
} from '@ant-design/pro-components';
import { Button, message, Space } from 'antd';
import dayjs from 'dayjs';
import { useRef } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const formRef = useRef<ProFormInstance>();
const onFill = () => {
formRef?.current?.setFieldsValue({
name: '书琰',
company: '杭州星辰科技有限公司',
});
};
const getCompanyName = () => {
message.info(`公司名称为 "${formRef?.current?.getFieldValue('company')}"`);
};
const getFormatValues = () => {
message.info(
`格式化后的所有数据:${JSON.stringify(formRef.current?.getFieldsFormatValue?.() ?? {})}`,
);
};
const validateAndGetFormatValue = () => {
formRef.current?.validateFieldsReturnFormatValue?.().then((values) => {
message.success(`校验表单并返回格式化后的所有数据:${JSON.stringify(values)}`);
});
};
return (
<ProForm
name="formref-demo"
title="新建表单"
formRef={formRef}
submitter={{
render: (props, doms) => {
return [
...doms,
<Button htmlType="button" onClick={onFill} key="edit">
一键填写
</Button>,
<Button htmlType="button" onClick={getCompanyName} key="read">
读取公司
</Button>,
<Space.Compact key="refs" style={{ display: 'block' }}>
<Button htmlType="button" onClick={getFormatValues} key="format">
获取格式化后的所有数据
</Button>
<Button
htmlType="button"
onClick={validateAndGetFormatValue}
key="format2"
>
校验表单并返回格式化后的所有数据
</Button>
</Space.Compact>,
];
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
<ProFormDatePicker name="date" initialValue={dayjs('2021-08-09')} />
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#同步提交结果到 url
打开 时也会把 url 的参数设置为默认值,支持 transform, 但是要注意字段的映射。
import {
ProForm,
ProFormDatePicker,
ProFormDateRangePicker,
ProFormSelect,
} from '@ant-design/pro-components';
import { message } from 'antd';
const Demo = () => {
return (
<ProForm
name="sync-to-url-demo"
onFinish={async () => {
message.success('Submission successful');
}}
syncToUrl={(values, type) => {
if (type === 'get') {
// To cooperate with transform
// startTime and endTime are combined into createTimeRanger
return {
...values,
createTimeRanger:
values.startTime || values.endTime
? [values.startTime, values.endTime]
: undefined,
};
}
// expirationTime is not synchronized to the URL
return {
...values,
expirationTime: undefined,
};
}}
initialValues={{
name: 'Ant Design Co., Ltd.',
useMode: 'chapter',
}}
autoFocusFirstInput
>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="sm"
name="useMode"
label="Contract Agreed Effective Method"
/>
<ProFormDateRangePicker
transform={(values) => {
return {
startTime: values ? values[0] : undefined,
endTime: values ? values[1] : undefined,
};
}}
width="md"
name="createTimeRanger"
label="Contract Effective Time"
/>
<ProFormDatePicker
width="md"
name="expirationTime"
label="Contract Expiration Time"
/>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import type { ProFormInstance } from '@ant-design/pro-components';
import {
ProForm,
ProFormMoney,
ProFormSwitch,
} from '@ant-design/pro-components';
import { message } from 'antd';
import { useRef, useState } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const formRef = useRef<
ProFormInstance<{
name: string;
company?: string;
useMode?: string;
}>
>();
const [readonly, setReadonly] = useState(false);
return (
<>
<ProFormSwitch
checkedChildren="On"
unCheckedChildren="Off"
label="Read Only"
fieldProps={{
onChange: setReadonly,
}}
/>
<ProForm<{
name: string;
company?: string;
useMode?: string;
}>
name="money-demo"
onFinish={async () => {
await waitTime(2000);
await formRef.current?.validateFields();
await formRef.current?.validateFieldsReturnFormatValue?.();
message.success('Submission successful');
}}
formRef={formRef}
params={{ id: '100' }}
formKey="money-demo"
readonly={readonly}
request={async () => {
await waitTime(100);
return {
name: 'Ant Design Co., Ltd.',
useMode: 'chapter',
};
}}
autoFocusFirstInput
>
<ProFormMoney
label="No Symbol"
name="amount0"
fieldProps={{
moneySymbol: false,
}}
locale="en-US"
initialValue={22.22}
min={0}
width="lg"
/>
<ProFormMoney
label="Width"
name="amount1"
locale="en-US"
initialValue={22.22}
min={0}
width="lg"
/>
<ProFormMoney
label="Minimum Amount 0"
name="amount2"
locale="en-US"
initialValue={22.22}
min={0}
trigger="onBlur"
/>
<ProFormMoney
label="No Limit"
name="amount3"
locale="en-GB"
initialValue={22.22}
/>
<ProFormMoney
label="Follow Global Locale"
name="amount4"
initialValue={22.22}
/>
<ProFormMoney
label="Locale ms-MY"
name="amount-ms-My"
locale="ms-MY"
initialValue={-22.22}
/>
<ProFormMoney
label="Locale zh-TW"
name="amount-zh-TW"
locale="zh-TW"
initialValue={22.22}
/>
<ProFormMoney
label="Custom Symbol"
name="amount5"
initialValue={22.22}
customSymbol="💰"
/>
<ProFormMoney
label="Precision"
name="amount6"
initialValue={2222222222.222222}
fieldProps={{ precision: 2 }}
customSymbol="💰"
/>
<ProFormMoney
label="Precision 0"
name="amount7"
initialValue={2222222222.222222}
fieldProps={{ precision: 0 }}
customSymbol="💰"
/>
</ProForm>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { SmileOutlined } from '@ant-design/icons';
import {
FooterToolbar,
PageContainer,
ProForm,
ProFormDateRangePicker,
ProFormDigit,
ProFormRadio,
ProFormSelect,
ProFormText,
ProFormTextArea,
ProFormUploadButton,
ProLayout,
} from '@ant-design/pro-components';
import { Card, message } from 'antd';
const Demo = () => {
return (
<ProLayout
fixSiderbar
fixedHeader
breakpoint={false}
defaultCollapsed
pageTitleRender={false}
menuDataRender={() => [
{
path: '/one',
icon: <SmileOutlined />,
name: 'Level One Name',
children: [
{
path: 'two',
name: 'Level Two Name',
},
],
},
]}
layout="mix"
location={{
pathname: '/one/two',
}}
>
<PageContainer title="Input Form">
<Card>
<ProForm
name="layout-footer-demo"
submitter={{
render: (_, dom) => <FooterToolbar>{dom}</FooterToolbar>,
}}
onFinish={async () => {
message.success('Submitted successfully');
}}
>
<ProForm.Group>
<ProFormText
name="name"
label="Contract Customer Name"
tooltip="Up to 24 characters"
placeholder="Please enter a name"
/>
<ProFormText
width="md"
name="company"
label="Our Company Name"
placeholder="Please enter a name"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormText
name={['contract', 'name']}
label="Contract Name"
placeholder="Please enter a name"
/>
<ProFormDateRangePicker
name={['contract', 'createTime']}
label="Contract Effective Time"
/>
</ProForm.Group>
<ProForm.Group>
<ProFormSelect
options={[
{
value: 'chapter',
label: 'Effective after stamping',
},
]}
width="xs"
name="chapter"
label="Contract Agreed Effective Method"
/>
<ProFormSelect
width="xs"
options={[
{
value: 'time',
label: 'Terminate after performance',
},
]}
name="unusedMode"
label="Contract Agreed Invalid Method"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="Main Contract Number" />
<ProFormText
name="project"
disabled
label="Project Name"
initialValue="示例项目"
/>
<ProFormText
width="xs"
name="mangerName"
disabled
label="Business Manager"
initialValue="书琰"
/>
<ProForm.Group>
<ProFormSelect
initialValue="money"
options={[
{
value: 'money',
label: 'Confirm Amount',
},
]}
width="xs"
name="useMode"
label="Amount Type"
/>
<ProFormSelect
options={[
{
value: '6',
label: '6%',
},
{
value: '12',
label: '12%',
},
]}
initialValue="6"
width="xs"
name="taxRate"
label="Tax Rate"
/>
<ProFormRadio.Group
label="Invoice Type"
name="invoiceType"
initialValue="Invoice"
options={['Invoice', 'General Invoice', 'No Invoice']}
/>
</ProForm.Group>
<ProFormUploadButton
extra="Supported extensions: .jpg .zip .doc .wps"
label="Retroactive Report Attachment"
name="file"
title="Upload File"
/>
<ProFormDigit
width="xs"
name="num"
label="Number of Copies"
initialValue={5}
/>
<ProFormTextArea
width="xl"
label="Contract Remarks"
name="remark"
/>
</ProForm>
</Card>
</PageContainer>
</ProLayout>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import type { ProColumns } from '@ant-design/pro-components';
import {
EditableProTable,
ProForm,
ProFormText,
} from '@ant-design/pro-components';
import { Input, message, Space } from 'antd';
import React, { useState } from 'react';
import { FIXED_BASE_TIMESTAMP } from '../mockData';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
type DataSourceType = {
id: React.Key;
title?: string;
decs?: string;
state?: string;
created_at?: number;
children?: DataSourceType[];
};
const defaultData: DataSourceType[] = [
{
id: 624748504,
title: '优化首页加载速度',
decs: '首页白屏时间超过 3s,需优化资源加载和首屏渲染',
state: 'open',
created_at: 1705286400000,
},
{
id: 624691229,
title: '修复登录超时问题',
decs: '用户反馈高峰期登录请求超时,需排查连接池配置',
state: 'closed',
created_at: 1705200000000,
},
];
const columns: ProColumns<DataSourceType>[] = [
{
title: '任务名称',
dataIndex: 'title',
width: '30%',
},
{
title: '状态',
key: 'state',
dataIndex: 'state',
valueType: 'select',
valueEnum: {
all: { text: '全部', status: 'Default' },
open: {
text: '未解决',
status: 'Error',
},
closed: {
text: '已解决',
status: 'Success',
},
},
},
{
title: '描述',
dataIndex: 'decs',
formItemRender: (_, { record }) => {
console.log('----===>', record);
const addonBefore = (record as any)?.addonBefore;
return (
<Space.Compact>
<Space.Addon>{addonBefore}</Space.Addon>
<Input />
</Space.Compact>
);
},
},
{
title: '操作',
valueType: 'option',
},
];
const Demo = () => {
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
defaultData.map((item) => item.id),
);
return (
<ProForm<{
name: string;
company: string;
}>
grid
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
}}
initialValues={{
name: '蚂蚁设计有限公司',
useMode: 'chapter',
}}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProForm.Item
label="数组数据"
name="dataSource"
initialValue={defaultData}
trigger="onValuesChange"
>
<EditableProTable<DataSourceType>
rowKey="id"
toolBarRender={false}
columns={columns}
recordCreatorProps={{
newRecordType: 'dataSource',
position: 'top',
record: () => ({
id: FIXED_BASE_TIMESTAMP,
addonBefore: 'ccccccc',
decs: 'testdesc',
}),
}}
editable={{
type: 'multiple',
editableKeys,
onChange: setEditableRowKeys,
actionRender: (row, _, dom) => {
return [dom.delete];
},
}}
/>
</ProForm.Item>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#劫持渲染函数的组件
FormItemRender 用来专门处理,采用 render props 的方式来组织代码,更好的聚合带请求的业务代码,也更好的完成自定义表单项的功能
在中后台项目表单是必不可少的,通常还伴随着一 些非标准控件、复杂的表单项,此时需要借助自定义表单项,而完成一个自定义表单项至少需要完成 value 和 onChange 的实现。而如果该组件只被使用一次且需要的上下文参数很多,那么封装起来就是很不讨好,因此就有了该组件。
- 使用 useControlModel 来快速的创建一个自定义表单项,同时支持单实例或多实例(适用于封装自定义表单组件,在多个地方使用的场景)
- 使用 withFormItemRender 来生成一个 FormItemRender,可以以内联的方式去组织代码(适用于只被使用一次或需要的上下文参数很多的场景)
- 使用 FormControlRender 来把一个 form 组件转换成 render props 的形式,在特定情况下是很有用的(例如某些第三方组件的设计缺陷导致 render 里面的组件不能调用 onChange 方法,这个时候包裹一下就可以解决)
当然,也不一定非要用 withFormItemRender,Form.Item 是可以嵌套使用,也可以 Form.Item 嵌套外层设置 noStyle 的方式来组织你的代码,这样会多一些 div 的元素包裹,如果对你样式没有影响也可以使用
#使用 useControlModel
从一个官网例子开始自定义表单项
import { Button, Form, Input, message, Select } from 'antd';
import React, { useState } from 'react';
const { Option } = Select;
type Currency = 'rmb' | 'dollar';
interface PriceValue {
number?: number;
currency?: Currency;
}
interface PriceInputProps {
value?: PriceValue;
onChange?: (value: PriceValue) => void;
}
const PriceInput: React.FC<PriceInputProps> = ({ value = {}, onChange }) => {
const [number, setNumber] = useState(0);
const [currency, setCurrency] = useState<Currency>('rmb');
const triggerChange = (changedValue: {
number?: number;
currency?: Currency;
}) => {
onChange?.({ number, currency, ...value, ...changedValue });
};
const onNumberChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newNumber = parseInt(e.target.value || '0', 10);
if (Number.isNaN(number)) {
return;
}
if (!('number' in value)) {
setNumber(newNumber);
}
triggerChange({ number: newNumber });
};
const onCurrencyChange = (newCurrency: Currency) => {
if (!('currency' in value)) {
setCurrency(newCurrency);
}
triggerChange({ currency: newCurrency });
};
return (
<span>
<Input
type="text"
value={value.number || number}
onChange={onNumberChange}
style={{ width: 100 }}
/>
<Select
value={value.currency || currency}
style={{ width: 80, margin: '0 8px' }}
onChange={onCurrencyChange}
>
<Option value="rmb">RMB</Option>
<Option value="dollar">Dollar</Option>
</Select>
</span>
);
};
const App: React.FC = () => {
const onFinish = () => {
message.success('表单已提交');
};
const checkPrice = (_: any, value: { number: number }) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject(new Error('Price must be greater than zero!'));
};
return (
<Form
name="customized_form_controls_antd"
layout="inline"
onFinish={onFinish}
initialValues={{
price: {
number: 0,
currency: 'rmb',
},
}}
>
<Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}>
<PriceInput />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default () => (
<div style={{ padding: 24 }}>
<App />
</div>
);
import {
WithControlPropsType,
useControlModel,
} from '@ant-design/pro-components';
import { Button, Form, Input, message, Select } from 'antd';
import React from 'react';
export function PriceInput(
props: WithControlPropsType<{
// other props...
}>,
) {
const model = useControlModel(props, ['number', 'currency']);
return (
<span>
<Input type="text" {...model.number} style={{ width: 100 }} />
<Select {...model.currency} style={{ width: 80, margin: '0 8px' }}>
<Select.Option value="rmb">RMB</Select.Option>
<Select.Option value="dollar">Dollar</Select.Option>
</Select>
</span>
);
}
const App: React.FC = () => {
const onFinish = () => {
message.success('表单已提交');
};
const checkPrice = (_: any, value: { number: number }) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject(new Error('Price must be greater than zero!'));
};
return (
<Form
name="customized_form_controls_modify"
layout="inline"
onFinish={onFinish}
initialValues={{
price: {
number: 0,
currency: 'rmb',
},
}}
>
<Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}>
<PriceInput />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default () => (
<div style={{ padding: 24 }}>
<App />
</div>
);
import { Button, Form, Input, message, Select, Space } from 'antd';
import React from 'react';
export function PriceInput() {
return (
<Space size={0}>
<Form.Item noStyle name={['price', 'number']}>
<Input type="text" style={{ width: 100 }} />
</Form.Item>
<Form.Item noStyle name={['price', 'currency']}>
<Select style={{ width: 80, margin: '0 8px' }}>
<Select.Option value="rmb">RMB</Select.Option>
<Select.Option value="dollar">Dollar</Select.Option>
</Select>
</Form.Item>
</Space>
);
}
const App: React.FC = () => {
const onFinish = () => {
message.success('表单已提交');
};
const checkPrice = (_: any, value: { number: number }) => {
if (value.number > 0) {
return Promise.resolve();
}
return Promise.reject(new Error('Price must be greater than zero!'));
};
return (
<Form
name="customized_form_controls_nest"
layout="inline"
onFinish={onFinish}
initialValues={{
price: {
number: 0,
currency: 'rmb',
},
}}
>
<Form.Item name="price" label="Price" rules={[{ validator: checkPrice }]}>
<PriceInput />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default () => (
<div style={{ padding: 24 }}>
<App />
</div>
);
#FormControlRender
使用 FormControlRender 既可以内联的书写代码,又可以更灵活的编写逻辑,适用于一些组件外层包裹了 ProForm.Item 或者 Form.Item。
有的时候需要使用 Form.Item.useStatus,但必须满足 hooks 的使用规范,这使得开发就必须提取成单独的组件来使用,没办法内联使用,而 FormControlRender 很好的解决这种情况
import {
FormControlRender,
pickControlPropsWithId,
} from '@ant-design/pro-components';
import { Button, Checkbox, Form } from 'antd';
import React, { useEffect } from 'react';
const App: React.FC = () => {
const [form] = Form.useForm();
useEffect(() => {
void form.validateFields();
}, []);
return (
<Form name="form-control-render-demo" form={form} onFinish={console.log}>
<Form.Item
name={'text1'}
label="文本框(没错误边框)"
rules={[{ required: true }]}
>
<textarea />
</Form.Item>
<Form.Item
name="text2"
label="文本框(添加自定义的错误边框)"
rules={[{ required: true }]}
>
<FormControlRender>
{(itemProps) => {
return (
<textarea
style={{
borderColor: itemProps.status === 'error' ? 'red' : undefined,
}}
{...pickControlPropsWithId(itemProps)}
/>
);
}}
</FormControlRender>
</Form.Item>
<Form.Item
valuePropName="checked"
name="check"
label="复选框"
rules={[{ required: true }]}
>
<Checkbox>是否</Checkbox>
</Form.Item>
<Form.Item
valuePropName="checked"
name="check2"
label="复选框"
rules={[{ required: true }]}
>
<FormControlRender>
{(itemProps) => {
return (
<Checkbox
{...itemProps}
style={{
color: itemProps.status === 'error' ? 'red' : undefined,
}}
>
是否
</Checkbox>
);
}}
</FormControlRender>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default () => (
<div style={{ padding: 24 }}>
<App />
</div>
);
#FormItemRender & ProFormItemRender
使用 FormItemRender 或者 ProFormItemRender 可以更方便的在 Form 里书写表单项
import {
FormControlFC,
FormItemRender,
ProForm,
ProFormItemRender,
WithControlPropsType,
pickControlProps,
pickControlPropsWithId,
useControlModel,
} from '@ant-design/pro-components';
import { Checkbox, Input, message, Select } from 'antd';
const SingletonA = (
props: WithControlPropsType<{
title: string;
}>,
) => {
const model = useControlModel(props);
return (
<>
<span>{props.title}</span>
<Input {...model} placeholder="直接使用useControlModel" />
</>
);
};
const SingletonB = (
props: WithControlPropsType<{
title: string;
}>,
) => {
const model = useControlModel(props);
return (
<>
<span>{props.title}</span>
<Select
{...model}
options={[
{ label: 'A', value: 'a' },
{ label: 'B', value: 'b' },
]}
/>
</>
);
};
const CustomInput = (
props: WithControlPropsType<{
title: string;
description: string;
}>,
) => {
const model = useControlModel(props, [
{
name: 'a',
valuePropName: 'checked',
},
{
name: 'b',
},
]);
return (
<div>
<div>title: {props.title}</div>
<Checkbox {...model.a} />
<div>description: {props.description}</div>
<Input
{...model.b}
placeholder="可以通过第二个参数达到使用多个实例的情况"
/>
</div>
);
};
const CustomInput2: FormControlFC<{
title: string;
}> = (props) => {
const model = useControlModel(props, ['a', 'b']);
return (
<div>
<div>{props.title}</div>
<Input {...model.a} />
</div>
);
};
const Demo = () => {
const [form] = ProForm.useForm();
return (
<div>
<ProForm
name="form-item-render-demo"
form={form}
onFinish={async () => {
message.success('提交成功');
}}
>
<ProForm.Item name={'SingletonA'}>
<SingletonA title="单实例A" />
</ProForm.Item>
<ProForm.Item name={'SingletonB'}>
<SingletonB title="单实例B" />
</ProForm.Item>
<ProForm.Item name={'customInput'} label="多实例">
<CustomInput
title="customInput-title"
description="customInput-desc"
/>
</ProForm.Item>
<ProForm.Item name={'FormControlFC'} label="使用FormControlFC类型定义">
<CustomInput2 title="FormControlFC title" />
</ProForm.Item>
<ProFormItemRender name={'inputA'}>
{(itemProps) => {
return (
<div id={itemProps.id}>
<h3>使用pickControlProps提取表单项属性</h3>
<Input {...pickControlProps(itemProps)} />
</div>
);
}}
</ProFormItemRender>
<ProFormItemRender name={'inputB'}>
{(itemProps) => {
return (
<div>
<h3>使用pickControlPropsWithId提取表单项属性(包括id)</h3>
<Input {...pickControlPropsWithId(itemProps)} />
</div>
);
}}
</ProFormItemRender>
<ProFormItemRender name={'selectA'}>
{(itemProps) => {
return (
<div>
<h3>自定义标题:</h3>
<Select
{...pickControlProps(itemProps)}
options={[{ label: 'A', value: 'a' }]}
/>
</div>
);
}}
</ProFormItemRender>
<FormItemRender
label="FormItemRender"
name={'selectB'}
initialValue={'xxx'}
>
{(itemProps) => {
return (
<div>
<Input {...pickControlPropsWithId(itemProps)} />
</div>
);
}}
</FormItemRender>
</ProForm>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
ProCard,
ProForm,
ProFormCheckbox,
ProFormDatePicker,
ProFormDateRangePicker,
ProFormDependency,
ProFormSelect,
ProFormText,
ProFormTextArea,
StepsForm,
} from '@ant-design/pro-components';
import { Button, message } from 'antd';
import { useState } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const [loading, setLoading] = useState(false);
return (
<ProCard>
<StepsForm
onFinish={async () => {
setLoading(true);
await waitTime(1000);
message.success('Submission successful');
setLoading(false);
}}
submitter={{
render: ({ form, onSubmit, step, onPre }) => {
return [
<Button
key="rest"
onClick={() => {
form?.resetFields();
}}
>
Reset
</Button>,
step > 0 && (
<Button
key="pre"
onClick={() => {
onPre?.();
}}
>
Previous
</Button>
),
<Button
key="next"
loading={loading}
type="primary"
onClick={() => {
onSubmit?.();
}}
>
Next
</Button>,
];
},
}}
formProps={{
validateMessages: {
required: 'This field is required',
},
}}
>
<StepsForm.StepForm
name="base"
title="Create Experiment"
onFinish={async () => {
setLoading(true);
await waitTime(2000);
setLoading(false);
return true;
}}
>
<ProFormText
name="name"
label="Experiment Name"
width="md"
tooltip="Up to 24 characters, used as a unique id"
placeholder="Please enter a name"
rules={[{ required: true }]}
/>
<ProFormDatePicker name="date" label="Date" />
<ProFormDateRangePicker name="dateTime" label="Time Range" />
<ProFormTextArea
name="remark"
label="Remarks"
width="lg"
placeholder="Please enter remarks"
/>
</StepsForm.StepForm>
<StepsForm.StepForm name="checkbox" title="Set Parameters">
<ProFormCheckbox.Group
name="checkbox"
label="Migration Type"
width="lg"
options={[
'Structural Migration',
'Full Migration',
'Incremental Migration',
'Full Verification',
]}
/>
<ProForm.Group>
<ProFormText name="dbName" label="Business DB Username" />
<ProFormDatePicker
name="datetime"
label="Record Retention Time"
width="sm"
/>
</ProForm.Group>
<ProFormDependency name={['dbName']}>
{({ dbName }) => {
return (
<ProFormCheckbox.Group
name="checkbox"
label="Migration Type"
options={
dbName
? ['Complete LOB', 'Do Not Sync LOB', 'Restricted LOB']
: ['Complete LOB']
}
/>
);
}}
</ProFormDependency>
</StepsForm.StepForm>
<StepsForm.StepForm name="time" title="Publish Experiment">
<ProFormCheckbox.Group
name="checkbox"
label="Deployment Units"
rules={[
{
required: true,
},
]}
options={[
'Deployment Unit 1',
'Deployment Unit 2',
'Deployment Unit 3',
]}
/>
<ProFormSelect
label="Deployment Group Strategy"
name="remark"
rules={[
{
required: true,
},
]}
initialValue="1"
width="md"
options={[
{
value: '1',
label: 'Strategy One',
},
{ value: '2', label: 'Strategy Two' },
]}
/>
<ProFormSelect
label="Pod Scheduling Strategy"
name="remark2"
initialValue="2"
width="md"
options={[
{
value: '1',
label: 'Strategy One',
},
{ value: '2', label: 'Strategy Two' },
]}
/>
</StepsForm.StepForm>
</StepsForm>
</ProCard>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
ProCard,
ProForm,
ProFormDependency,
ProFormGroup,
ProFormList,
ProFormSwitch,
ProFormText,
} from '@ant-design/pro-components';
const Demo = () => {
return (
<ProForm
name="dependency-debug-demo"
onFinish={async (e) => console.log(e)}
>
<ProFormText name="name" label="姓名" />
<ProFormList
name="users"
label="用户信息"
initialValue={[
{
name: '1111',
},
]}
itemRender={({ listDom, action }, { record }) => {
return (
<ProCard
variant="outlined"
extra={action}
title={record?.name}
style={{
marginBlockEnd: 8,
}}
>
{listDom}
</ProCard>
);
}}
>
<ProFormGroup>
<ProFormText name="name" label="姓名" />
</ProFormGroup>
<ProFormList
name="labels"
label="用户信息"
initialValue={[
{
value: '333',
label: '333',
},
]}
copyIconProps={{
tooltipText: '复制此项到末尾',
}}
deleteIconProps={{
tooltipText: '不需要这行了',
}}
>
<ProFormGroup>
<ProFormSwitch name="is_show" label="显示名称" />
</ProFormGroup>
<ProFormDependency name={['is_show']}>
{({ is_show }) => {
if (!is_show) return null;
return (
<ProFormList
name="labels"
label="用户信息"
initialValue={[
{
value: '333',
label: '333',
},
]}
copyIconProps={{
tooltipText: '复制此项到末尾',
}}
deleteIconProps={{
tooltipText: '不需要这行了',
}}
>
<ProFormGroup>
<ProFormText name="value" label="值" />
<ProFormSwitch name="is_show" label="显示名称" />
</ProFormGroup>
</ProFormList>
);
}}
</ProFormDependency>
</ProFormList>
</ProFormList>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import {
ProForm,
ProFormCheckbox,
ProFormRadio,
ProFormText,
} from '@ant-design/pro-components';
import { Button } from 'antd';
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
};
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
};
const Demo = () => {
return (
<>
<ProForm
{...layout}
layout="horizontal"
submitter={false}
name="basic"
initialValues={{ remember: true }}
>
<ProFormText label="Name" name="name" />
<ProFormText.Password
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
/>
<ProFormCheckbox name="remember" {...tailLayout}>
Remember me
</ProFormCheckbox>
<ProFormRadio name="remember" {...tailLayout}>
Remember me
</ProFormRadio>
<ProForm.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
Submit
</Button>
</ProForm.Item>
</ProForm>
</>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#Select labelInValue
演示 ProFormSelect 在 labelInValue={true} 下与提交值的写法。
import { ProForm, ProFormSelect } from '@ant-design/pro-components';
import { message } from 'antd';
const Demo = () => {
return (
<ProForm
name="label-in-value-demo"
onFinish={async () => {
message.success('提交成功');
}}
>
<ProFormSelect
options={[
{
value: 'chapter',
label: '盖章后生效',
},
]}
width="sm"
name="useMode"
label="合同约定生效方式"
fieldProps={{
labelInValue: true,
}}
rules={[
{
required: true,
message: '请选择',
},
]}
/>
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#自定义 request 加载状态
request 加载期间默认渲染居中的 Spin,通过 loadingRender 替换为 Skeleton 等自定义内容。
import { ProForm, ProFormText } from '@ant-design/pro-components';
import { Skeleton } from 'antd';
export default () => (
<div style={{ padding: 24 }}>
<ProForm
request={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
return { name: 'pro components' };
}}
loadingRender={<Skeleton paragraph={{ rows: 4 }} />}
>
<ProFormText
name="name"
label="Name"
placeholder="Please enter your name"
/>
</ProForm>
</div>
);
#Select request 数据本地搜索
request 一次性拉取全量数据后,设置 fetchDataOnSearch={false} 可让搜索走本地过滤,不再重复发起请求。
import { ProForm, ProFormSelect } from '@ant-design/pro-components';
function App() {
return (
<div className="App">
<ProForm>
<ProFormSelect
showSearch
name="remote"
label="远程搜索(默认)"
request={async ({ keyWords }: any) => {
console.log('remote request called:', keyWords);
return [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Orange', value: 'orange' },
].filter((item) => item.label.includes(keyWords || ''));
}}
/>
<ProFormSelect
showSearch
name="local"
label="本地搜索(request 只拉取一次)"
fetchDataOnSearch={false}
request={async () => [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Orange', value: 'orange' },
]}
/>
</ProForm>
</div>
);
}
export default () => (
<div style={{ padding: 24 }}>
<App />
</div>
);
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| fetchDataOnSearch | 搜索时是否重新触发 request;设为 false 时仅初始化拉取一次,搜索走本地过滤 | boolean | true |
#ProProvider / ConfigProvider(调试)
import { SmileOutlined } from '@ant-design/icons';
import {
LightWrapper,
ProForm,
ProFormCheckbox,
ProFormField,
ProFormRadio,
ProFormSlider,
ProFormSwitch,
ProFormText,
ProFormUploadButton,
ProFormUploadDragger,
StepsForm,
} from '@ant-design/pro-components';
import { ConfigProvider, Input, message } from 'antd';
import enUS from 'antd/locale/en_US';
const Demo = () => (
<ConfigProvider locale={enUS}>
<StepsForm>
<StepsForm.StepForm title="新建">
<ProFormText name="name" />
</StepsForm.StepForm>
</StepsForm>
<ProForm
name="validate_other"
initialValues={{
'input-number': 3,
'checkbox-group': ['A', 'B'],
rate: 3.5,
range: 5,
name: 'demo_user',
}}
onFinish={async () => {
message.success('Submitted');
}}
>
<ProFormUploadButton
name="upload"
icon={<SmileOutlined />}
label="Upload"
title="点击上传"
action="/upload.do"
extra="longgggggggggggggggggggggggggggggggggg"
/>
<ProFormRadio name="test" />
<ProFormCheckbox name="test2" />
<ProFormSwitch width="lg" label="是否打开" />
<ProFormUploadDragger
title="拖动上传"
icon={<SmileOutlined />}
description="支持 text"
label="Dragger"
name="dragger"
fieldProps={{
showUploadList: true,
}}
/>
<LightWrapper valuePropName="value">test</LightWrapper>
<LightWrapper valuePropName="value">test</LightWrapper>
<ProFormSlider name="range" label="范围" />
<ProFormField>test</ProFormField>
<ProFormField>
<Input />
</ProFormField>
</ProForm>
</ConfigProvider>
);
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
#ProForm
ProForm 是对 antd Form 的再封装,如果你想要自定义表单元素,ProForm 与 antd 的方法是相同的,你仍然可以用 FormItem + 自定义组件的方式来自定义。当然这样不会影响到别的组件,QueryFilter 等组件同理。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| onFinish | 提交表单且数据验证成功后回调事件,支持 Promise,会自动设置按钮的加载效果 | (formData: T) => Promise<boolean | void> | void | - |
| onReset | 点击重置按钮的回调 | (e) => void | - |
| submitter | 提交按钮相关配置 | SubmitterProps<{form?: FormInstance<any>}> | false | true |
| loading | 表单按钮的 loading 状态 | boolean | - |
| loadingRender | request 加载期间的自定义渲染,默认为居中的 Spin,可传 Skeleton 对齐 antd 5.18+ 加载风格 | ReactNode | (() => ReactNode) | <Spin /> |
| onLoadingChange | loading 状态改变时的回调 | (loading: boolean) => void | - |
| formRef | 获取表单所使用的 form,ProFormInstance 相比 antd Form 增加了格式化数据的方法 | React.MutableRefObject<(ProFormInstance<T> & { nativeElement?: HTMLElement; focus?: () => void }) | undefined> | React.RefObject<(ProFormInstance<T> & { nativeElement?: HTMLElement; focus?: () => void }) | undefined> | - |
| syncToUrl | 同步参数到 url 上,url 只支持 string,在使用之前最好读一下url 中的参数类型 | boolean | ((values: T, type: 'get' | 'set') => T) | - |
| syncToUrlAsImportant | 当 syncToUrl 为 true,在页面回显示时,以url上的参数为主,默认为false | boolean | false |
| extraUrlParams | 额外的 url 参数 | Record<string, any> | - |
| syncToInitialValues | 同步结果到 initialValues,默认为 true,如果为 false,form.reset 的时候将会忽略从 url 上获取的数据 | boolean | true |
| omitNil | ProForm 会自动清空 null 和 undefined 的数据,如果你约定了 nil 代表某种数据,可以设置为 false 关闭此功能 | boolean | true |
| dateFormatter | 自动格式化数据,主要是 dayjs 的表单,支持 string 和 number 两种模式,此外还支持指定函数进行格式化。 | string | 'string' | 'number' | ((value: dayjs.Dayjs, valueType: string) => string | number) | false | 'string' |
| onInit | 表单初始化成功,比如布局,label等计算完成 | (values: T, form: ProFormInstance<any>) => void | - |
| params | 发起网络请求的参数,与 request 配合使用 | U | - |
| request | 发起网络请求的参数,返回值会覆盖给 initialValues | ProRequestData<T, U> | - |
| isKeyPressSubmit | 是否使用回车提交 | boolean | - |
| formKey | 用于控制form 是否相同的key,高阶用法 | string | - |
| autoFocusFirstInput | 自动 focus 表单第一个输入框,只对有 input 的类型有效 | boolean | true |
| readonly | 是否只读模式,对所有表单项生效,优先低于表单项的 readonly | boolean | - |
| grid | 开启栅格化模式,宽度默认百分比,请使用 colProps 控制宽度 查看示例 | boolean | false |
| rowProps | 开启 grid 模式时传递给 Row, 仅在ProFormGroup, ProFormList, ProFormFieldSet 中有效 | RowProps | { gutter: 8 } |
| colProps | 开启 grid 模式时传递给 Col, 仅在ProFormGroup, ProFormList, ProFormFieldSet 中有效 | ColProps | { xs: 24 } |
| (...) | 注意 LightFilter 和 QueryFilter 仅支持除 wrapperCol | labelCol | layout 外的其他 antd Form 组件参数 | - | - |
#ProFormInstance
ProFormInstance 与 antd 的 form 相比增加了一些能力。
/**
* 获取被 ProForm 格式化后的所有数据
* @param nameList boolean
* @param omitNil boolean 是否忽略 null 和 undefined
* @returns T
*
* @example getFieldsFormatValue() ->返回所有数据
* @example getFieldsFormatValue(true) ->返回所有数据,即使没有被 form 托管的
* @example getFieldsFormatValue(true, true) ->返回所有数据,忽略 null 和 undefined
*/
getFieldsFormatValue?: (nameList?: true, omitNil?: boolean) => T;
/**
* 获取被 ProForm 格式化后的单个数据
* @param nameList (string|number)[]
* @returns T
* @example {a:{b:value}} -> getFieldFormatValue(['a', 'b']) -> value
*/
/** 获取格式化之后的单个数据 */
getFieldFormatValue?: (nameList?: NamePath) => T;
/**
* 获取被 ProForm 格式化后的单个数据, 包含他的 name
* @param nameList (string|number)[]
* @returns T
* @example
* {a:{b:value}} -> getFieldFormatValueObject(['a', 'b']) -> {a:{b:value}}
*/
getFieldFormatValueObject?: (nameList?: NamePath) => T;
/**
*验字段后返回格式化之后的所有数据
* @param nameList (string|number)[]
* @returns T
*
* @example validateFieldsReturnFormatValue -> {a:{b:value}}
*/
validateFieldsReturnFormatValue?: (nameList?: NamePath[]) => Promise<T>;#ProForm.Group
ProForm.Group 支持 title、tooltip、extra 等分组配置,完整类型见 ProFormBaseGroupProps。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| title | 标题 | ReactNode | - |
| children | 表单控件或者其他元素 | React.ReactNode | - |
#submitter
虽然我们希望不要对 submitter 进行修改,但在使用中修改是很常见的需求,ProForm 的各个组件都使用了同样的 API 来支持需求。
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| onSubmit | 提交方法 | (value?: T)=>void | - |
| onReset | 重置方法 | (value?: T)=>void | - |
| searchConfig | 搜索的配置,一般用来配置文本 | {resetText,submitText} | - |
| submitButtonProps | 提交按钮的 props | false | ButtonProps | - |
| resetButtonProps | 重置按钮的 props | false | ButtonProps | - |
| render | 自定义操作的渲染 | false|((props,dom:JSX[])=>ReactNode[]|ReactNode|false) | - |
render 的第二个参数是默认的 dom 数组,第一个是提交按钮,第二个是重置按钮。
<ProForm
submitter={{
// 配置按钮文本
searchConfig: {
resetText: '重置',
submitText: '提交',
},
// 配置按钮的属性
resetButtonProps: {
style: {
// 隐藏重置按钮
display: 'none',
},
},
submitButtonProps: {},
// 完全自定义整个区域
render: (props, doms) => {
console.log(props);
return [
<button
type="button"
key="rest"
onClick={() => props.form?.resetFields()}
>
重置
</button>,
<button
type="button"
key="submit"
onClick={() => props.form?.submit?.()}
>
提交
</button>,
];
},
}}
/>#formRef
该属性是 ProForm 在原有的 Antd 的 FormInstance 的基础上做的一个上层分装,增加了一些更加便捷的方法。使用方式如下:
import type { ProFormInstance } from '@ant-design/pro-components';
import {
ProForm,
ProFormDatePicker,
ProFormText,
} from '@ant-design/pro-components';
import { Button, message, Space } from 'antd';
import dayjs from 'dayjs';
import { useRef } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
const Demo = () => {
const formRef = useRef<ProFormInstance>();
const onFill = () => {
formRef?.current?.setFieldsValue({
name: '书琰',
company: '杭州星辰科技有限公司',
});
};
const getCompanyName = () => {
message.info(`公司名称为 "${formRef?.current?.getFieldValue('company')}"`);
};
const getFormatValues = () => {
message.info(
`格式化后的所有数据:${JSON.stringify(formRef.current?.getFieldsFormatValue?.() ?? {})}`,
);
};
const validateAndGetFormatValue = () => {
formRef.current?.validateFieldsReturnFormatValue?.().then((values) => {
message.success(`校验表单并返回格式化后的所有数据:${JSON.stringify(values)}`);
});
};
return (
<ProForm
name="formref-demo"
title="新建表单"
formRef={formRef}
submitter={{
render: (props, doms) => {
return [
...doms,
<Button htmlType="button" onClick={onFill} key="edit">
一键填写
</Button>,
<Button htmlType="button" onClick={getCompanyName} key="read">
读取公司
</Button>,
<Space.Compact key="refs" style={{ display: 'block' }}>
<Button htmlType="button" onClick={getFormatValues} key="format">
获取格式化后的所有数据
</Button>
<Button
htmlType="button"
onClick={validateAndGetFormatValue}
key="format2"
>
校验表单并返回格式化后的所有数据
</Button>
</Space.Compact>,
];
},
}}
onFinish={async () => {
await waitTime(2000);
message.success('提交成功');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
<ProFormDatePicker name="date" initialValue={dayjs('2021-08-09')} />
</ProForm>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import type { ProFormInstance } from '@ant-design/pro-components';
import {
ProForm,
ProFormDatePicker,
ProFormText,
} from '@ant-design/pro-components';
import { Button, message, Space } from 'antd';
import moment from 'dayjs';
import { useRef } from 'react';
const waitTime = (time: number = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};
export default () => {
const formRef = useRef<ProFormInstance>();
const onFill = () => {
formRef?.current?.setFieldsValue({
name: '张三',
company: '蚂蚁金服',
});
};
const getCompanyName = () => {
message.info(`公司名称为 "${formRef?.current?.getFieldValue('company')}"`);
};
const getFormatValues = () => {
console.log(
'格式化后的所有数据:',
formRef.current?.getFieldsFormatValue?.(),
);
};
const validateAndGetFormatValue = async () => {
const values = await formRef.current?.validateFieldsReturnFormatValue?.();
console.log('校验表单并返回格式化后的所有数据:', values);
};
return (
<ProForm
title="新建表单"
formRef={formRef}
submitter={{
render: (props, doms) => {
return [
...doms,
<Button htmlType="button" onClick={onFill} key="edit">
一键填写
</Button>,
<Button htmlType="button" onClick={getCompanyName} key="read">
读取公司
</Button>,
<Space.Compact key="refs" style={{ display: 'block' }}>
<Button htmlType="button" onClick={getFormatValues} key="format">
获取格式化后的所有数据
</Button>
<Button
htmlType="button"
onClick={validateAndGetFormatValue}
key="format2"
>
校验表单并返回格式化后的所有数据
</Button>
</Space.Compact>,
];
},
}}
onFinish={async (values) => {
await waitTime(2000);
console.log(values);
message.success('提交成功');
return true;
}}
>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
<ProFormDatePicker name="date" initialValue={moment('2021-08-09')} />
</ProForm>
);
};ProFormInstance 在原先 FormInstance 的基础上增加了如下方法:
| 方法名 | 使用描述 | 备注 |
|---|---|---|
getFieldsFormatValue | 使用方法与 FormInstance 的 getFieldsValue 方法相同,将返回格式化后的所有数据 | |
getFieldFormatValue | 使用方法与 FormInstance 的 getFieldValue 方法相同,将返回格式化后的指定数据 | |
getFieldFormatValueObject | 使用方法与 FormInstance 的 getFieldValue 方法相同,将返回格式化后的指定数据(包含 name) | |
validateFieldsReturnFormatValue | 使用方法与 FormInstance 的 validateFields 方法相同,验证通过后将返回格式化后的所有数据 |
import { PlusOutlined } from '@ant-design/icons';
import {
ModalForm,
ProCard,
ProForm,
ProFormList,
ProFormText,
} from '@ant-design/pro-components';
import { Button } from 'antd';
// 弹窗表单
const FormModal = () => {
return (
<ModalForm<{
name: string;
company: string;
}>
title="新建表单"
trigger={
<Button type="primary">
<PlusOutlined />
新建表单
</Button>
}
onFinish={async (values) => {
alert(JSON.stringify(values));
return true;
}}
>
<ProFormText width="sm" name="id" label="主合同编号" />
</ModalForm>
);
};
const Demo = () => {
return (
<div>
<div>
在form外边可以
<FormModal />
</div>
<div>
在form里面可以
<ProForm name="modalform-test-demo" layout="horizontal">
<FormModal />
<ProFormList
name="attributes"
label="规格"
creatorButtonProps={{
creatorButtonText: '添加规格项',
}}
min={1}
copyIconProps={false}
itemRender={({ listDom, action }, { index }) => (
<ProCard
variant="outlined"
style={{ marginBlockEnd: 8 }}
title={`规格${index + 1}`}
extra={action}
styles={{ body: { paddingBlockEnd: 0 } }}
>
{listDom}
</ProCard>
)}
creatorRecord={{ name: '', items: [{ name: '' }] }}
initialValue={[{ name: '颜色', items: [{ name: '红' }] }]}
>
<ProFormText
style={{ padding: 0 }}
width="md"
name="name"
label="规格名"
/>
<ProForm.Item
isListField
style={{ marginBlockEnd: 0 }}
label="规格值"
>
<ProFormList
name="items"
creatorButtonProps={{
creatorButtonText: '新建',
icon: false,
type: 'link',
style: { width: 'unset' },
}}
min={1}
copyIconProps={false}
deleteIconProps={{ tooltipText: '删除' }}
itemRender={({ listDom, action }) => (
<div
style={{
display: 'inline-flex',
marginInlineEnd: 25,
}}
>
{listDom}
{action}
</div>
)}
>
<ProFormText allowClear={false} width="xs" name={['name']} />
<div>
在内层formlist拿不到
<FormModal />
</div>
</ProFormList>
</ProForm.Item>
<div>
在外层formlist拿不到
<FormModal />
</div>
</ProFormList>
</ProForm>
</div>
</div>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);
import { BetaSchemaForm } from '@ant-design/pro-components';
import { useEffect, useRef, useState } from 'react';
const Demo = () => {
const targetRef = useRef();
const [requestLibData, setRequestLibData] = useState(0);
useEffect(() => {
// 更新requestLibData,并引发reRender
setTimeout(() => {
setRequestLibData(1);
});
}, []);
return (
<BetaSchemaForm
name="params-formref-demo"
request={async () => ({})}
params={{ requestLibData }}
columns={[
{
title: 'money',
dataIndex: 'money',
valueType: 'money',
},
]}
formRef={targetRef}
/>
);
};
export default () => (
<div style={{ padding: 24 }}>
<Demo />
</div>
);