• 中文
  • ProForm 高级表单

    ProForm 在原来的 Form 的基础上增加了一些语法糖和更多的布局设置,帮助我们快速地开发一个表单,同时添加了一些默认行为,让我们的表单默认好用。

    分步表单、Modal 表单、Drawer 表单、查询表单、轻量筛选等多种 layout 可以覆盖大部分的使用场景,让我们脱离复杂而且繁琐的表单布局工作,用更少的代码完成更多的功能。

    • 如果想要设置默认值,请使用 initialValues,任何直接使用组件 value 和 onChange 的方式都有可能导致值绑定失效
    • 如果想要表单联动或者做一些依赖,可以使用 render props 模式,ProFormDependency 绝对是最好的选择
    • ProForm 的 onFinish 与 antd 的 Form 不同,支持 Promise,如果你正常返回会自动为你设置按钮的加载效果
    • 如果想要监听某个值,建议使用 onValuesChange。保持单向的数据流无论对开发者还是维护者都大有裨益
    • ProForm 没有「魔法」:ProForm 只是对 antd Form 的薄封装;自定义控件用 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 使用。
    Effective after stamping
    请选择
    %
    Form 的 layout 切换
    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 构造返回对象的原因。

    代码示例

    -
    zhejiang / hangzhou / binjiang
    区县
    请选择部门
    基本使用
    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>
    );
    
    Effective after stamping
    Effective after stamping
    表单联动
    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, 但是要注意字段的映射。

    Effective after stamping
    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>
    );
    
    任务名称状态描述操作
    未解决
    已解决
    ProForm 和 EditableTable 同时使用
    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

    从一个官网例子开始自定义表单项

    RMB
    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>
    );
    
    RMB
    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>
    );
    
    RMB
    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 里书写表单项

    单实例A
    单实例B
    title: customInput-title
    description: customInput-desc
    FormControlFC title

    使用pickControlProps提取表单项属性

    使用pickControlPropsWithId提取表单项属性(包括id)

    自定义标题:

    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>
    );
    
    Strategy One
    Strategy Two
    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>
    );
    
    1111
    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 时仅初始化拉取一次,搜索走本地过滤booleantrue

    ProProvider / ConfigProvider(调试)

    longgggggggggggggggggggggggggggggggggg

    拖动上传

    支持 text

    test
    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 等组件同理。

    antd 的 Form api 查看这里,initialValues 相关知识查看这里

    参数说明类型默认值
    onFinish提交表单且数据验证成功后回调事件,支持 Promise,会自动设置按钮的加载效果(formData: T) => Promise<boolean | void> | void-
    onReset点击重置按钮的回调(e) => void-
    submitter提交按钮相关配置SubmitterProps<{form?: FormInstance<any>}> | falsetrue
    loading表单按钮的 loading 状态boolean-
    loadingRenderrequest 加载期间的自定义渲染,默认为居中的 Spin,可传 Skeleton 对齐 antd 5.18+ 加载风格ReactNode | (() => ReactNode)<Spin />
    onLoadingChangeloading 状态改变时的回调(loading: boolean) => void-
    formRef获取表单所使用的 form,ProFormInstance 相比 antd Form 增加了格式化数据的方法React.MutableRefObject<(ProFormInstance<T> & { nativeElement?: HTMLElement; focus?: () => void }) | undefined> | React.RefObject<(ProFormInstance<T> & &#123; nativeElement?: HTMLElement; focus?: () => void &#125;) | undefined>-
    syncToUrl同步参数到 url 上,url 只支持 string,在使用之前最好读一下url 中的参数类型boolean | ((values: T, type: 'get' | 'set') => T)-
    syncToUrlAsImportant当 syncToUrl 为 true,在页面回显示时,以url上的参数为主,默认为falsebooleanfalse
    extraUrlParams额外的 url 参数Record<string, any>-
    syncToInitialValues同步结果到 initialValues,默认为 true,如果为 false,form.reset 的时候将会忽略从 url 上获取的数据booleantrue
    omitNilProForm 会自动清空 null 和 undefined 的数据,如果你约定了 nil 代表某种数据,可以设置为 false 关闭此功能booleantrue
    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发起网络请求的参数,返回值会覆盖给 initialValuesProRequestData<T, U>-
    isKeyPressSubmit是否使用回车提交boolean-
    formKey用于控制form 是否相同的key,高阶用法string-
    autoFocusFirstInput自动 focus 表单第一个输入框,只对有 input 的类型有效booleantrue
    readonly是否只读模式,对所有表单项生效,优先低于表单项的 readonlyboolean-
    grid开启栅格化模式,宽度默认百分比,请使用 colProps 控制宽度 查看示例booleanfalse
    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提交按钮的 propsfalse | ButtonProps-
    resetButtonProps重置按钮的 propsfalse | 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 的基础上做的一个上层分装,增加了一些更加便捷的方法。使用方式如下:

    formRef的使用
    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 方法相同,验证通过后将返回格式化后的所有数据
    在form外边可以
    在form里面可以
    规格1
    在内层formlist拿不到
    在外层formlist拿不到
    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>
    );