• English
  • EditableProTable - Editable Tables

    EditableProTable is essentially the same as ProTable, with a few presets added to make it easier to use EditableProTable, turning off the query form and action bar, and modifying value and onChange to make it easy to inherit from antd's Form.

    Code Demo

    Edit mode switch

    Use a segmented control to toggle editable.type between inline single-row editing (single) and multi-row editing (multiple).

    editable.type 编辑模式:
    编辑模式切换
    任务名称状态描述创建时间操作
    暂无数据
    暂无数据
    表格数据
    []
    import type { ProColumns } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProFormField,
    } from '@ant-design/pro-components';
    import { Segmented } from 'antd';
    import React, { useState } from 'react';
    
    import { createEditableRowId, DEMO_TASK_STATUS_ENUM } from '../mockData';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      description?: string;
      status?: string;
      created_at?: number;
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: 624748504,
        title: '优化首页加载速度',
        description: '首页白屏时间超过 3s,需优化资源加载',
        status: 'open',
        created_at: 1705286400000,
      },
      {
        id: 624691229,
        title: '修复登录超时问题',
        description: '高峰期登录请求超时,需排查连接池',
        status: 'closed',
        created_at: 1705200000000,
      },
      {
        id: 624674560,
        title: '新增数据导出功能',
        description: '支持导出 Excel 和 CSV 格式',
        status: 'processing',
        created_at: 1705113600000,
      },
    ];
    
    const Demo = () => {
      const [editableType, setEditableType] = useState<'single' | 'multiple'>(
        'multiple',
      );
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>([]);
    
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          width: '30%',
        },
        {
          title: '状态',
          dataIndex: 'status',
          valueType: 'select',
          valueEnum: DEMO_TASK_STATUS_ENUM,
        },
        {
          title: '描述',
          dataIndex: 'description',
        },
        {
          title: '创建时间',
          dataIndex: 'created_at',
          valueType: 'date',
        },
        {
          title: '操作',
          valueType: 'option',
          width: 150,
          render: (text, record, _, action) => [
            <a key="editable" onClick={() => action?.startEditable?.(record.id)}>
              编辑
            </a>,
            <a
              key="delete"
              onClick={() =>
                setDataSource(dataSource.filter((item) => item.id !== record.id))
              }
            >
              删除
            </a>,
          ],
        },
      ];
    
      return (
        <div>
          <div style={{ marginBlockEnd: 16 }}>
            <span>editable.type 编辑模式:</span>
            <Segmented
              value={editableType}
              onChange={(v) => {
                setEditableType(v as any);
                setEditableRowKeys([]);
              }}
              options={[
                { label: '单行编辑 single', value: 'single' },
                { label: '多行编辑 multiple', value: 'multiple' },
              ]}
            />
          </div>
          <EditableProTable<DataSourceType>
            rowKey="id"
            headerTitle="编辑模式切换"
            columns={columns}
            request={async () => ({
              data: defaultData,
              total: defaultData.length,
              success: true,
            })}
            value={dataSource}
            onChange={setDataSource}
            recordCreatorProps={{
              position: 'bottom',
              record: () => ({ id: createEditableRowId() }),
              newRecordType: 'dataSource',
            }}
            editable={{
              type: editableType,
              editableKeys,
              onChange: setEditableRowKeys,
            }}
          />
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormField
              ignoreFormItem
              fieldProps={{ style: { width: '100%' } }}
              mode="read"
              valueType="jsonCode"
              text={JSON.stringify(dataSource)}
            />
          </ProCard>
        </div>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Editable Table

    任务管理
    任务名称
    任务名称(只读)
    状态描述创建时间操作
    暂无数据
    暂无数据
    表格数据
    []
    Basic editable table
    import type { ProColumns } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProFormField,
      ProFormRadio,
    } from '@ant-design/pro-components';
    import React, { useState } from 'react';
    
    import { createEditableRowId, DEMO_TASK_STATUS_ENUM } from '../../mockData';
    
    const waitTime = (time: number = 100) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(true);
        }, time);
      });
    };
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      readonly?: string;
      description?: string;
      status?: string;
      created_at?: number;
      update_at?: number;
      children?: DataSourceType[];
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: 624748504,
        title: '优化首页加载速度',
        readonly: '优化首页加载速度',
        description: '首页白屏时间超过 3s,需优化资源加载和首屏渲染',
        status: 'open',
        created_at: 1705286400000,
        update_at: 1705372800000,
      },
      {
        id: 624691229,
        title: '修复登录超时问题',
        readonly: '修复登录超时问题',
        description: '用户反馈高峰期登录请求超时,需排查连接池配置',
        status: 'closed',
        created_at: 1705200000000,
        update_at: 1705286400000,
      },
    ];
    
    const Demo = () => {
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>([]);
      const [position, setPosition] = useState<'top' | 'bottom' | 'hidden'>(
        'bottom',
      );
    
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          tooltip: '只读,使用form.getFieldValue获取不到值',
          formItemProps: (form, { rowIndex }) => {
            return {
              rules:
                rowIndex !== undefined && rowIndex > 1
                  ? [{ required: true, message: '此项为必填项' }]
                  : [],
            };
          },
          editable: (text, record, index) => {
            return index !== 0;
          },
          width: '15%',
        },
        {
          title: '任务名称(只读)',
          dataIndex: 'readonly',
          tooltip: '只读,使用form.getFieldValue可以获取到值',
          readonly: true,
          width: '15%',
        },
        {
          title: '状态',
          key: 'status',
          dataIndex: 'status',
          valueType: 'select',
          valueEnum: DEMO_TASK_STATUS_ENUM,
        },
        {
          title: '描述',
          dataIndex: 'description',
          fieldProps: (form, { rowKey, rowIndex }) => {
            if (form.getFieldValue([rowKey || '', 'title']) === '暂不处理') {
              return {
                disabled: true,
              };
            }
            if (rowIndex !== undefined && rowIndex > 9) {
              return {
                disabled: true,
              };
            }
            return {};
          },
        },
        {
          title: '创建时间',
          dataIndex: 'created_at',
          valueType: 'date',
        },
        {
          title: '操作',
          valueType: 'option',
          width: 200,
          render: (text, record, _, action) => [
            <a
              key="editable"
              onClick={() => {
                action?.startEditable?.(record.id);
              }}
            >
              编辑
            </a>,
            <a
              key="delete"
              onClick={() => {
                setDataSource(dataSource.filter((item) => item.id !== record.id));
              }}
            >
              删除
            </a>,
          ],
        },
      ];
    
      return (
        <>
          <EditableProTable<DataSourceType>
            rowKey="id"
            headerTitle="任务管理"
            maxLength={5}
            scroll={{
              x: 960,
            }}
            recordCreatorProps={
              position !== 'hidden'
                ? {
                    position: position as 'top',
                    record: () => ({ id: createEditableRowId() }),
                    newRecordType: 'dataSource',
                  }
                : false
            }
            loading={false}
            toolBarRender={() => [
              <ProFormRadio.Group
                key="render"
                fieldProps={{
                  value: position,
                  onChange: (e) => setPosition(e.target.value),
                }}
                options={[
                  {
                    label: '添加到顶部',
                    value: 'top',
                  },
                  {
                    label: '添加到底部',
                    value: 'bottom',
                  },
                  {
                    label: '隐藏',
                    value: 'hidden',
                  },
                ]}
              />,
            ]}
            columns={columns}
            request={async () => ({
              data: defaultData,
              total: 3,
              success: true,
            })}
            value={dataSource}
            onChange={(value) => {
              setDataSource((data) => {
                const newMap = new Map(value.map((item) => [item.id, item]));
                const merged = data.map((item) => {
                  return newMap.get(item.id) || item;
                });
                value.forEach((item) => {
                  if (!data.find((old) => old.id === item.id)) {
                    merged.push(item);
                  }
                });
                return merged;
              });
            }}
            editable={{
              type: 'multiple',
              editableKeys,
              onSave: async (_rowKey, _data, _row) => {
                await waitTime(2000);
              },
              onChange: setEditableRowKeys,
            }}
          />
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormField
              ignoreFormItem
              fieldProps={{
                style: {
                  width: '100%',
                },
              }}
              mode="read"
              valueType="jsonCode"
              text={JSON.stringify(dataSource)}
            />
          </ProCard>
        </>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Cell editing

    任务管理(单元格编辑)
    任务名称状态描述
    优化首页加载速度待处理首页白屏时间超过 3s,需优化资源加载
    修复登录超时问题已完成用户反馈高峰期登录请求超时
    新增数据导出功能待处理支持将查询结果导出为 Excel 和 CSV
    重构权限管理模块已完成现有权限模型不支持细粒度控制
    接入第三方支付 SDK待处理对接微信支付和支付宝当面付
    Cell editor
    import type { ProColumns } from '@ant-design/pro-components';
    import { CellEditorTable } from '@ant-design/pro-components';
    import React, { useState } from 'react';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      description?: string;
      status?: string;
      created_at?: number;
      children?: DataSourceType[];
    };
    
    const taskNames = [
      '优化首页加载速度',
      '修复登录超时问题',
      '新增数据导出功能',
      '重构权限管理模块',
      '接入第三方支付 SDK',
    ];
    
    const taskDescs = [
      '首页白屏时间超过 3s,需优化资源加载',
      '用户反馈高峰期登录请求超时',
      '支持将查询结果导出为 Excel 和 CSV',
      '现有权限模型不支持细粒度控制',
      '对接微信支付和支付宝当面付',
    ];
    
    const defaultData: DataSourceType[] = taskNames.map((name, index) => ({
      id: (1705286400000 + index).toString(),
      title: name,
      description: taskDescs[index],
      status: index % 2 === 0 ? 'open' : 'closed',
      created_at: 1705286400000 - index * 86400000,
    }));
    
    const Demo = () => {
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>(
        () => defaultData,
      );
    
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          width: '30%',
          formItemProps: {
            rules: [
              {
                required: true,
                whitespace: true,
                message: '此项是必填项',
              },
              {
                max: 30,
                whitespace: true,
                message: '最长为 30 位',
              },
              {
                min: 4,
                whitespace: true,
                message: '最小为 4 位',
              },
            ],
          },
        },
        {
          title: '状态',
          key: 'status',
          dataIndex: 'status',
          valueType: 'select',
          valueEnum: {
            all: { text: '全部', status: 'Default' },
            open: {
              text: '待处理',
              status: 'Error',
            },
            closed: {
              text: '已完成',
              status: 'Success',
            },
          },
        },
        {
          title: '描述',
          dataIndex: 'description',
        },
      ];
    
      return (
        <CellEditorTable<DataSourceType>
          headerTitle="任务管理(单元格编辑)"
          columns={columns}
          rowKey="id"
          scroll={{
            x: 960,
          }}
          value={dataSource}
          onChange={setDataSource}
          recordCreatorProps={{
            newRecordType: 'dataSource',
            record: () => ({
              id: Date.now(),
            }),
          }}
        />
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    
    姓名年龄地址操作
    18杭州西湖区
    李四22北京海淀区
    Cell-level editableKeys
    import type { ProColumns } from '@ant-design/pro-components';
    import { EditableProTable } from '@ant-design/pro-components';
    import React from 'react';
    
    type DataSourceType = {
      id: React.Key;
      name?: string;
      age?: number;
      address?: string;
    };
    
    const defaultData: DataSourceType[] = [
      { id: 1, name: '张三', age: 18, address: '杭州西湖区' },
      { id: 2, name: '李四', age: 22, address: '北京海淀区' },
    ];
    
    export default () => {
      const columns: ProColumns<DataSourceType>[] = [
        { title: '姓名', dataIndex: 'name', key: 'name' },
        { title: '年龄', dataIndex: 'age', key: 'age' },
        { title: '地址', dataIndex: 'address', key: 'address' },
        {
          title: '操作',
          valueType: 'option',
          key: 'option',
          render: () => [<a key="edit">编辑</a>],
        },
      ];
    
      return (
        <EditableProTable<DataSourceType>
          rowKey="id"
          columns={columns}
          value={defaultData}
          search={false}
          // cell 粒度复合键 `${rowKey}:${dataIndex}`:只激活第 1 行的 name 单元格
          editable={{ editableKeys: ['1:name'] }}
        />
      );
    };
    

    Row editing

    任务管理(整行编辑)
    任务名称状态描述
    优化首页加载速度待处理首页白屏时间超过 3s,需优化资源加载
    修复登录超时问题已完成用户反馈高峰期登录请求超时
    新增数据导出功能待处理支持将查询结果导出为 Excel 和 CSV
    重构权限管理模块已完成现有权限模型不支持细粒度控制
    接入第三方支付 SDK待处理对接微信支付和支付宝当面付
    Row editor
    import type { ProColumns } from '@ant-design/pro-components';
    import { RowEditorTable } from '@ant-design/pro-components';
    import React, { useState } from 'react';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      description?: string;
      status?: string;
      created_at?: number;
      children?: DataSourceType[];
    };
    
    const taskNames = [
      '优化首页加载速度',
      '修复登录超时问题',
      '新增数据导出功能',
      '重构权限管理模块',
      '接入第三方支付 SDK',
    ];
    
    const taskDescs = [
      '首页白屏时间超过 3s,需优化资源加载',
      '用户反馈高峰期登录请求超时',
      '支持将查询结果导出为 Excel 和 CSV',
      '现有权限模型不支持细粒度控制',
      '对接微信支付和支付宝当面付',
    ];
    
    const defaultData: DataSourceType[] = taskNames.map((name, index) => ({
      id: (1705286400000 + index).toString(),
      title: name,
      description: taskDescs[index],
      status: index % 2 === 0 ? 'open' : 'closed',
      created_at: 1705286400000 - index * 86400000,
    }));
    
    const Demo = () => {
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>(
        () => defaultData,
      );
    
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          width: '30%',
          formItemProps: {
            rules: [
              {
                required: true,
                whitespace: true,
                message: '此项是必填项',
              },
              {
                max: 30,
                whitespace: true,
                message: '最长为 30 位',
              },
              {
                min: 4,
                whitespace: true,
                message: '最小为 4 位',
              },
            ],
          },
        },
        {
          title: '状态',
          key: 'status',
          dataIndex: 'status',
          valueType: 'select',
          valueEnum: {
            all: { text: '全部', status: 'Default' },
            open: {
              text: '待处理',
              status: 'Error',
            },
            closed: {
              text: '已完成',
              status: 'Success',
            },
          },
        },
        {
          title: '描述',
          dataIndex: 'description',
        },
      ];
    
      return (
        <RowEditorTable<DataSourceType>
          headerTitle="任务管理(整行编辑)"
          columns={columns}
          rowKey="id"
          scroll={{
            x: 960,
          }}
          value={dataSource}
          onChange={setDataSource}
          recordCreatorProps={{
            newRecordType: 'dataSource',
            record: () => ({
              id: Date.now(),
            }),
          }}
        />
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    With Form.Item

    可编辑表格
    任务名称状态描述活动时间操作
    优化首页加载速度未解决首页白屏时间超过 3s,需优化资源加载和首屏渲染2024-01-15
    修复登录超时问题已解决用户反馈高峰期登录请求超时,需排查连接池配置2024-01-14
    表格数据
    [
      {
        "id": "624748504",
        "title": "优化首页加载速度",
        "decs": "首页白屏时间超过 3s,需优化资源加载和首屏渲染",
        "state": "open",
        "created_at": 1705286400000,
        "update_at": 1705372800000
      },
      {
        "id": "624691229",
        "title": "修复登录超时问题",
        "decs": "用户反馈高峰期登录请求超时,需排查连接池配置",
        "state": "closed",
        "created_at": 1705200000000,
        "update_at": 1705286400000
      }
    ]
    Editable table inside Form.Item
    import type {
      EditableFormInstance,
      ProColumns,
      ProFormInstance,
    } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProForm,
      ProFormDependency,
      ProFormField,
      ProFormSegmented,
      ProFormSwitch,
    } from '@ant-design/pro-components';
    import { Button, message } from 'antd';
    import isEmpty from 'es-toolkit/compat/isEmpty';
    import React, { useRef, useState } from 'react';
    
    import { createEditableRowId } from '../../mockData';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      decs?: string;
      state?: string;
      created_at?: number;
      update_at?: number;
      children?: DataSourceType[];
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: '624748504',
        title: '优化首页加载速度',
        decs: '首页白屏时间超过 3s,需优化资源加载和首屏渲染',
        state: 'open',
        created_at: 1705286400000,
        update_at: 1705372800000,
      },
      {
        id: '624691229',
        title: '修复登录超时问题',
        decs: '用户反馈高峰期登录请求超时,需排查连接池配置',
        state: 'closed',
        created_at: 1705200000000,
        update_at: 1705286400000,
      },
    ];
    
    let i = 0;
    
    const Demo = () => {
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => []);
      const [position, setPosition] = useState<'top' | 'bottom' | 'hidden'>(
        'bottom',
      );
      const [controlled, setControlled] = useState<boolean>(false);
      const formRef = useRef<ProFormInstance<any>>();
      const editorFormRef = useRef<EditableFormInstance<DataSourceType>>();
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          formItemProps: (_form, _config) =>{
            return ({
              rules: [
                // 自定义 validator 放前面,空值时显式通过,避免干扰 required
                {
                  warningOnly: true,
                  validator: (_rule: any, value: any) => {
                    if (isEmpty(value)) {
                      return Promise.resolve();
                    }
                    if ('warningOnly' === value) {
                      throw new Error('Warning Only');
                    }
                    return Promise.resolve();
                  },
                },
                { required: true, message: '此项为必填项' },
              ],
            });
          },
          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',
        },
        {
          title: '活动时间',
          dataIndex: 'created_at',
          valueType: 'date',
        },
        {
          title: '操作',
          valueType: 'option',
          width: 200,
          render: (text, record, _, action) => [
            <a
              key="editable"
              onClick={() => {
                action?.startEditable?.(record.id, record);
              }}
            >
              编辑
            </a>,
            <a
              key="delete"
              onClick={() => {
                const tableDataSource = formRef.current?.getFieldValue(
                  'table',
                ) as DataSourceType[];
                formRef.current?.setFieldsValue({
                  table: tableDataSource.filter((item) => item.id !== record.id),
                });
              }}
            >
              删除
            </a>,
          ],
        },
      ];
    
      return (
        <ProForm<{
          table: DataSourceType[];
        }>
          formRef={formRef}
          initialValues={{
            table: defaultData,
          }}
          validateTrigger="onBlur"
        >
          <EditableProTable<DataSourceType>
            rowKey="id"
            scroll={{
              x: 960,
            }}
            editableFormRef={editorFormRef}
            headerTitle="可编辑表格"
            maxLength={5}
            name="table"
            controlled={controlled}
            recordCreatorProps={
              position !== 'hidden'
                ? {
                    position: position as 'top',
                    record: () => ({ id: createEditableRowId() }),
                  }
                : false
            }
            toolBarRender={() => [
              <ProFormSwitch
                key="render"
                fieldProps={{
                  style: {
                    marginBlockEnd: 0,
                  },
                  checked: controlled,
                  onChange: (value) => {
                    setControlled(value);
                  },
                }}
                checkedChildren="数据更新通知 Form"
                unCheckedChildren="保存后通知 Form"
                noStyle
              />,
              <ProFormSegmented
                key="render"
                fieldProps={{
                  style: {
                    marginBlockEnd: 0,
                  },
                  value: position,
                  onChange: (value) => {
                    setPosition(value as 'top');
                  },
                }}
                noStyle
                request={async () => [
                  {
                    label: '添加到顶部',
                    value: 'top',
                  },
                  {
                    label: '添加到底部',
                    value: 'bottom',
                  },
                  {
                    label: '隐藏',
                    value: 'hidden',
                  },
                ]}
              />,
              <Button
                key="rows"
                onClick={() => {
                  const rows = editorFormRef.current?.getRowsData?.();
                  message.info(JSON.stringify(rows ?? []));
                }}
              >
                获取 table 的数据
              </Button>,
            ]}
            columns={columns}
            editable={{
              type: 'multiple',
              editableKeys,
              onChange: setEditableRowKeys,
              actionRender: (row, config, defaultDom) => {
                return [
                  defaultDom.save,
                  defaultDom.delete,
                  defaultDom.cancel,
                  <a
                    key="set"
                    onClick={() => {
                      i++;
                      editorFormRef.current?.setRowData?.(config.index!, {
                        title: '动态设置的title' + i,
                      });
                    }}
                  >
                    动态设置此项
                  </a>,
                ];
              },
            }}
          />
          <ProForm.Item>
            <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
              <ProFormDependency name={['table']}>
                {({ table }) => {
                  return (
                    <ProFormField
                      ignoreFormItem
                      fieldProps={{
                        style: {
                          width: '100%',
                        },
                      }}
                      mode="read"
                      valueType="jsonCode"
                      text={JSON.stringify(table)}
                    />
                  );
                }}
              </ProFormDependency>
            </ProCard>
          </ProForm.Item>
        </ProForm>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Linked with outer form fields

    总分:80
    题数:40
    关联题库题型题数计分方式分值操作
    前端基础知识多选题10continuous20
    React 框架进阶单选题10continuous20
    TypeScript 类型系统判断题10continuous20
    算法与数据结构填空题10continuous20
    Linkage outside the table
    import type {
      ActionType,
      EditableFormInstance,
      ProColumns,
      ProFormInstance,
    } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProForm,
      ProFormDependency,
      ProFormDigit,
    } from '@ant-design/pro-components';
    import React, { useRef, useState } from 'react';
    
    type DataSourceType = {
      id: React.Key;
      associate?: string;
      questionsNum?: number;
      type?: string;
      fraction?: number;
      scoringMethod?: string;
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: 624748504,
        associate: '前端基础知识',
        questionsNum: 10,
        type: 'multiple',
        scoringMethod: 'continuous',
        fraction: 20,
      },
      {
        id: 624691229,
        associate: 'React 框架进阶',
        questionsNum: 10,
        scoringMethod: 'continuous',
        type: 'radio',
        fraction: 20,
      },
      {
        id: 624748503,
        associate: 'TypeScript 类型系统',
        questionsNum: 10,
        type: 'judge',
        scoringMethod: 'continuous',
        fraction: 20,
      },
      {
        id: 624691220,
        associate: '算法与数据结构',
        questionsNum: 10,
        scoringMethod: 'continuous',
        type: 'vacant',
        fraction: 20,
      },
    ];
    
    const Demo = () => {
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() => []);
      const formRef = useRef<ProFormInstance<any>>();
      const actionRef = useRef<ActionType>();
      const editableFormRef = useRef<EditableFormInstance>();
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '关联题库',
          dataIndex: 'associate',
          valueType: 'text',
          ellipsis: true,
        },
        {
          title: '题型',
          key: 'type',
          dataIndex: 'type',
          valueType: 'select',
          valueEnum: {
            multiple: { text: '多选题', status: 'Default' },
            radio: { text: '单选题', status: 'Warning' },
            vacant: {
              text: '填空题',
              status: 'Error',
            },
            judge: {
              text: '判断题',
              status: 'Success',
            },
          },
        },
        {
          title: '题数',
          dataIndex: 'questionsNum',
          valueType: 'digit',
        },
        {
          title: '计分方式',
          dataIndex: 'scoringMethod',
          valueType: 'select',
          request: async () => [
            {
              value: 'discrete',
              label: '离散型',
            },
            {
              value: 'continuous',
              label: '连续型',
            },
          ],
          fieldProps: (_, { rowIndex }) => {
            return {
              onSelect: () => {
                // 每次选中重置参数
                if (rowIndex !== undefined) {
                  editableFormRef.current?.setRowData?.(rowIndex, { fraction: [] });
                }
              },
            };
          },
        },
        {
          title: '分值',
          width: 150,
          dataIndex: 'fraction',
          valueType: (record) => {
            const scoringMethod = record?.scoringMethod;
            if (scoringMethod === 'discrete') return 'select';
            return 'digit';
          },
          fieldProps: {
            mode: 'multiple',
          },
          request: async () =>
            ['A', 'B', 'D', 'E', 'F'].map((item, index) => ({
              label: item,
              value: index,
            })),
        },
        {
          title: '操作',
          valueType: 'option',
          render: (_, row) => [
            <a
              key="delete"
              onClick={() => {
                const tableDataSource = formRef.current?.getFieldValue(
                  'table',
                ) as DataSourceType[];
                formRef.current?.setFieldsValue({
                  table: tableDataSource.filter((item) => item.id !== row?.id),
                });
              }}
            >
              移除
            </a>,
            <a
              key="edit"
              onClick={() => {
                actionRef.current?.startEditable(row.id);
              }}
            >
              编辑
            </a>,
          ],
        },
      ];
    
      return (
        <ProCard>
          <div
            style={{
              maxWidth: 800,
              margin: 'auto',
            }}
          >
            <ProForm<{
              table: DataSourceType[];
            }>
              formRef={formRef}
              initialValues={{
                table: defaultData,
              }}
            >
              <ProFormDependency name={['table']}>
                {({ table }) => {
                  const info = (table as DataSourceType[]).reduce(
                    (pre, item) => {
                      return {
                        totalScore:
                          pre.totalScore +
                          parseInt((item?.fraction || 0).toString(), 10),
                        questions:
                          pre.questions +
                          parseInt((item?.questionsNum || 0).toString(), 10),
                      };
                    },
                    { totalScore: 0, questions: 0 },
                  );
                  return (
                    <div
                      style={{
                        display: 'flex',
                        alignItems: 'center',
                        gap: 16,
                        paddingBlockEnd: 16,
                      }}
                    >
                      <div style={{ flex: 1 }}>总分:{info.totalScore}</div>
                      <div style={{ flex: 1 }}>题数:{info.questions}</div>
                      <div style={{ flex: 2 }}>
                        <ProFormDigit label="及格分" />
                      </div>
                      <div style={{ flex: 2 }}>
                        <ProFormDigit label="考试时间(分钟)" />
                      </div>
                    </div>
                  );
                }}
              </ProFormDependency>
              <EditableProTable<DataSourceType>
                rowKey="id"
                scroll={{
                  x: true,
                }}
                editableFormRef={editableFormRef}
                controlled
                actionRef={actionRef}
                formItemProps={{
                  label: '题库编辑',
                  rules: [
                    {
                      validator: async (_, value) => {
                        if (value.length < 1) {
                          throw new Error('请至少添加一个题库');
                        }
    
                        if (value.length > 5) {
                          throw new Error('最多可以设置五个题库');
                        }
                      },
                    },
                  ],
                }}
                maxLength={10}
                name="table"
                columns={columns}
                recordCreatorProps={{
                  record: (index) => {
                    return { id: index + 1 };
                  },
                }}
                editable={{
                  type: 'multiple',
                  editableKeys,
                  onChange: setEditableRowKeys,
                }}
              />
            </ProForm>
          </div>
        </ProCard>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Table with grouped child columns

    可编辑表格
    任务名称状态描述活动时间操作
    优化首页加载速度未解决首页白屏时间超过 3s,需优化资源加载和首屏渲染2024-01-15
    拆分首屏关键资源已解决将非关键 JS/CSS 延迟加载2024-01-14
    修复登录超时问题已解决用户反馈高峰期登录请求超时,需排查连接池配置2024-01-14
    表格数据
    [
      {
        "id": 624748504,
        "title": "优化首页加载速度",
        "decs": "首页白屏时间超过 3s,需优化资源加载和首屏渲染",
        "state": "open",
        "created_at": 1705286400000,
        "update_at": 1705372800000,
        "children": [
          {
            "id": 6246912293,
            "title": "拆分首屏关键资源",
            "decs": "将非关键 JS/CSS 延迟加载",
            "state": "closed",
            "created_at": 1705200000000,
            "update_at": 1705286400000
          }
        ]
      },
      {
        "id": 624691229,
        "title": "修复登录超时问题",
        "decs": "用户反馈高峰期登录请求超时,需排查连接池配置",
        "state": "closed",
        "created_at": 1705200000000,
        "update_at": 1705286400000
      }
    ]
    Nested column headers
    import type { ProColumns } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProFormField,
      useRefFunction,
    } from '@ant-design/pro-components';
    import React, { useState } from 'react';
    
    import { createEditableRowId } 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;
      update_at?: number;
      children?: DataSourceType[];
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: 624748504,
        title: '优化首页加载速度',
        decs: '首页白屏时间超过 3s,需优化资源加载和首屏渲染',
        state: 'open',
        created_at: 1705286400000,
        update_at: 1705372800000,
        children: [
          {
            id: 6246912293,
            title: '拆分首屏关键资源',
            decs: '将非关键 JS/CSS 延迟加载',
            state: 'closed',
            created_at: 1705200000000,
            update_at: 1705286400000,
          },
        ],
      },
      {
        id: 624691229,
        title: '修复登录超时问题',
        decs: '用户反馈高峰期登录请求超时,需排查连接池配置',
        state: 'closed',
        created_at: 1705200000000,
        update_at: 1705286400000,
      },
    ];
    
    const loopDataSourceFilter = (
      data: readonly DataSourceType[],
      id: React.Key | undefined,
    ): DataSourceType[] => {
      return data
        .map((item) => {
          if (item.id !== id) {
            if (item.children) {
              const newChildren = loopDataSourceFilter(item.children, id);
              return {
                ...item,
                children: newChildren.length > 0 ? newChildren : undefined,
              };
            }
            return item;
          }
          return null;
        })
        .filter(Boolean) as DataSourceType[];
    };
    
    const Demo = () => {
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>(
        () => defaultData,
      );
    
      const removeRow = useRefFunction((record: DataSourceType) => {
        setDataSource(loopDataSourceFilter(dataSource, record.id));
      });
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          formItemProps: (form, { rowIndex }) => {
            return {
              rules:
                rowIndex !== undefined && rowIndex > 2
                  ? [{ required: true, message: '此项为必填项' }]
                  : [],
            };
          },
          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',
          fieldProps: (form, { rowKey, rowIndex }) => {
            if (form.getFieldValue([rowKey || '', 'title']) === '不好玩') {
              return {
                disabled: true,
              };
            }
            if (rowIndex !== undefined && rowIndex > 9) {
              return {
                disabled: true,
              };
            }
            return {};
          },
        },
        {
          title: '活动时间',
          dataIndex: 'created_at',
          valueType: 'date',
        },
        {
          title: '操作',
          valueType: 'option',
          width: 200,
          render: (text, record) => [
            <a
              key="delete"
              onClick={() => {
                removeRow(record);
              }}
            >
              删除
            </a>,
          ],
        },
      ];
    
      return (
        <>
          <EditableProTable<DataSourceType>
            expandable={{
              // 使用 request 请求数据时无效
              defaultExpandAllRows: true,
            }}
            scroll={{
              x: 960,
            }}
            rowKey="id"
            headerTitle="可编辑表格"
            maxLength={5}
            recordCreatorProps={{
              position: 'bottom',
              newRecordType: 'dataSource',
              parentKey: () => 624748504,
              record: () => ({ id: createEditableRowId() }),
            }}
            columns={columns}
            value={dataSource}
            onChange={setDataSource}
            editable={{
              type: 'multiple',
              editableKeys,
              onSave: async (_rowKey, _data, _row) => {
                await waitTime(2000);
              },
              onChange: setEditableRowKeys,
            }}
          />
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormField
              ignoreFormItem
              fieldProps={{
                style: {
                  width: '100%',
                },
              }}
              mode="read"
              valueType="jsonCode"
              text={JSON.stringify(dataSource)}
            />
          </ProCard>
        </>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Custom editable table

    自定义可编辑表格
    任务名称状态标签操作
    暂无数据
    暂无数据
    表格数据
    []
    Custom editable table
    import { PlusOutlined } from '@ant-design/icons';
    import type { ActionType, ProColumns } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProFormField,
    } from '@ant-design/pro-components';
    import type { InputRef } from 'antd';
    import { Button, Form, Input, Space, Tag } from 'antd';
    import React, { useRef, useState } from 'react';
    
    import { createEditableRowId } from '../../mockData';
    
    const waitTime = (time: number = 100) => {
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(true);
        }, time);
      });
    };
    
    
    const TagList = React.forwardRef<
      HTMLDivElement,
      {
        value?: {
          key: string;
          label: string;
        }[];
        onChange?: (
          value: {
            key: string;
            label: string;
          }[],
        ) => void;
      }
    >(({ value, onChange }, ref) => {
      const inputRef = useRef<InputRef | null>(null);
      const [newTags, setNewTags] = useState<
        {
          key: string;
          label: string;
        }[]
      >([]);
      const [inputValue, setInputValue] = useState<string>('');
    
      const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setInputValue(e.target.value);
      };
    
      const handleInputConfirm = () => {
        let tempsTags = [...(value || [])];
        if (
          inputValue &&
          tempsTags.filter((tag) => tag.label === inputValue).length === 0
        ) {
          tempsTags = [
            ...tempsTags,
            { key: `new-${tempsTags.length}`, label: inputValue },
          ];
        }
        onChange?.(tempsTags);
        setNewTags([]);
        setInputValue('');
      };
    
      return (
        <Space ref={ref}>
          {(value || []).concat(newTags).map((item) => (
            <Tag key={item.key}>{item.label}</Tag>
          ))}
          <Input
            ref={inputRef}
            type="text"
            size="small"
            style={{ width: 78 }}
            value={inputValue}
            onChange={handleInputChange}
            onBlur={handleInputConfirm}
            onPressEnter={handleInputConfirm}
          />
        </Space>
      );
    });
    TagList.displayName = 'TagList';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      labels?: {
        key: string;
        label: string;
      }[];
      status?: string;
      created_at?: number;
      children?: DataSourceType[];
    };
    
    const defaultData: DataSourceType[] = [
      {
        id: 624748504,
        title: '优化首页加载速度',
        labels: [{ key: 'perf', label: '性能优化' }],
        status: 'open',
        created_at: 1705286400000,
      },
      {
        id: 624691229,
        title: '修复登录超时问题',
        labels: [{ key: 'bug', label: '缺陷修复' }],
        status: 'closed',
        created_at: 1705200000000,
      },
    ];
    
    const columns: ProColumns<DataSourceType>[] = [
      {
        title: '任务名称',
        dataIndex: 'title',
        formItemProps: {
          rules: [
            {
              required: true,
              message: '此项为必填项',
            },
          ],
        },
        width: '30%',
      },
      {
        title: '状态',
        key: 'status',
        dataIndex: 'status',
        valueType: 'select',
        valueEnum: {
          all: { text: '全部', status: 'Default' },
          open: {
            text: '待处理',
            status: 'Error',
          },
          closed: {
            text: '已完成',
            status: 'Success',
          },
        },
      },
      {
        title: '标签',
        dataIndex: 'labels',
        width: '20%',
        formItemProps: {
          rules: [
            {
              required: true,
              message: '此项为必填项',
            },
          ],
        },
        formItemRender: (_, { isEditable }) => {
          return isEditable ? <TagList /> : <Input />;
        },
        render: (_, row) =>
          row?.labels?.map((item) => <Tag key={item.key}>{item.label}</Tag>),
      },
      {
        title: '操作',
        valueType: 'option',
        width: 250,
        render: (text, record, _, action) => [
          <a
            key="editable"
            onClick={() => {
              action?.startEditable?.(record.id);
            }}
          >
            编辑
          </a>,
          <EditableProTable.RecordCreator
            key="copy"
            record={{
              ...record,
              id: createEditableRowId(),
            }}
          >
            <a>复制此项到末尾</a>
          </EditableProTable.RecordCreator>,
        ],
      },
    ];
    
    const Demo = () => {
      const actionRef = useRef<ActionType>();
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>([]);
      const [form] = Form.useForm();
      return (
        <>
          <Space>
            <Button
              type="primary"
              onClick={() => {
                actionRef.current?.addEditRecord?.({
                  id: createEditableRowId(),
                  title: '新建任务',
                });
              }}
              icon={<PlusOutlined />}
            >
              新建一行
            </Button>
            <Button
              key="rest"
              onClick={() => {
                form.resetFields();
              }}
            >
              重置表单
            </Button>
          </Space>
    
          <EditableProTable<DataSourceType>
            rowKey="id"
            scroll={{
              x: 960,
            }}
            actionRef={actionRef}
            headerTitle="自定义可编辑表格"
            maxLength={5}
            recordCreatorProps={false}
            columns={columns}
            request={async () => ({
              data: defaultData,
              total: 3,
              success: true,
            })}
            value={dataSource}
            onChange={setDataSource}
            editable={{
              form,
              editableKeys,
              onSave: async () => {
                await waitTime(2000);
              },
              onChange: setEditableRowKeys,
              actionRender: (row, config, dom) => [dom.save, dom.cancel],
            }}
          />
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormField
              ignoreFormItem
              fieldProps={{
                style: {
                  width: '100%',
                },
              }}
              mode="read"
              valueType="jsonCode"
              text={JSON.stringify(dataSource)}
            />
          </ProCard>
        </>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Real-time persisted editing

    实时编辑任务列表
    任务名称状态描述操作
    待处理
    进行中
    已完成
    待处理
    进行中
    已完成
    待处理
    进行中
    已完成
    待处理
    进行中
    已完成
    待处理
    进行中
    已完成
    待处理
    进行中
    已完成
    待处理
    进行中
    表格数据
    [
      {
        "id": "1705286400000",
        "title": "优化首页加载速度",
        "description": "首页白屏时间超过 3s",
        "status": "open",
        "created_at": 1705286400000
      },
      {
        "id": "1705286400001",
        "title": "修复登录超时问题",
        "description": "高峰期登录请求超时",
        "status": "processing",
        "created_at": 1705200000000
      },
      {
        "id": "1705286400002",
        "title": "新增数据导出功能",
        "description": "导出 Excel 和 CSV",
        "status": "closed",
        "created_at": 1705113600000
      },
      {
        "id": "1705286400003",
        "title": "重构权限管理模块",
        "description": "细粒度权限控制",
        "status": "open",
        "created_at": 1705027200000
      },
      {
        "id": "1705286400004",
        "title": "接入第三方支付 SDK",
        "description": "对接微信和支付宝",
        "status": "processing",
        "created_at": 1704940800000
      },
      {
        "id": "1705286400005",
        "title": "升级 React 18",
        "description": "升级到最新 LTS 版本",
        "status": "closed",
        "created_at": 1704854400000
      },
      {
        "id": "1705286400006",
        "title": "优化搜索算法",
        "description": "ES 查询响应慢",
        "status": "open",
        "created_at": 1704768000000
      },
      {
        "id": "1705286400007",
        "title": "增加单元测试覆盖",
        "description": "核心模块覆盖率 > 90%",
        "status": "processing",
        "created_at": 1704681600000
      },
      {
        "id": "1705286400008",
        "title": "实现深色模式",
        "description": "支持系统主题切换",
        "status": "closed",
        "created_at": 1704595200000
      },
      {
        "id": "1705286400009",
        "title": "接入埋点 SDK",
        "description": "用户行为数据采集",
        "status": "open",
        "created_at": 1704508800000
      },
      {
        "id": "1705286400010",
        "title": "优化图片懒加载",
        "description": "减少首屏图片请求",
        "status": "processing",
        "created_at": 1704422400000
      },
      {
        "id": "1705286400011",
        "title": "重构表格组件",
        "description": "提升大数据量渲染性能",
        "status": "closed",
        "created_at": 1704336000000
      },
      {
        "id": "1705286400012",
        "title": "添加国际化支持",
        "description": "支持中英日三语",
        "status": "open",
        "created_at": 1704249600000
      },
      {
        "id": "1705286400013",
        "title": "修复移动端适配",
        "description": "iOS Safari 布局异常",
        "status": "processing",
        "created_at": 1704163200000
      },
      {
        "id": "1705286400014",
        "title": "集成 CI/CD 流程",
        "description": "自动化构建和部署",
        "status": "closed",
        "created_at": 1704076800000
      },
      {
        "id": "1705286400015",
        "title": "优化打包体积",
        "description": "减少 vendor chunk 体积",
        "status": "open",
        "created_at": 1703990400000
      },
      {
        "id": "1705286400016",
        "title": "实现 WebSocket 通信",
        "description": "实时消息推送",
        "status": "processing",
        "created_at": 1703904000000
      },
      {
        "id": "1705286400017",
        "title": "添加操作审计日志",
        "description": "记录关键操作日志",
        "status": "closed",
        "created_at": 1703817600000
      },
      {
        "id": "1705286400018",
        "title": "优化缓存策略",
        "description": "减少 API 冗余请求",
        "status": "open",
        "created_at": 1703731200000
      },
      {
        "id": "1705286400019",
        "title": "重构路由模块",
        "description": "支持嵌套路由和权限路由",
        "status": "processing",
        "created_at": 1703644800000
      }
    ]
    Save while editing
    import type { ProColumns } from '@ant-design/pro-components';
    import {
      EditableProTable,
      ProCard,
      ProFormField,
    } from '@ant-design/pro-components';
    import { Button } from 'antd';
    import React, { useState } from 'react';
    
    type DataSourceType = {
      id: React.Key;
      title?: string;
      description?: string;
      status?: string;
      created_at?: number;
      children?: DataSourceType[];
    };
    
    const taskNames = [
      '优化首页加载速度',
      '修复登录超时问题',
      '新增数据导出功能',
      '重构权限管理模块',
      '接入第三方支付 SDK',
      '升级 React 18',
      '优化搜索算法',
      '增加单元测试覆盖',
      '实现深色模式',
      '接入埋点 SDK',
      '优化图片懒加载',
      '重构表格组件',
      '添加国际化支持',
      '修复移动端适配',
      '集成 CI/CD 流程',
      '优化打包体积',
      '实现 WebSocket 通信',
      '添加操作审计日志',
      '优化缓存策略',
      '重构路由模块',
    ];
    
    const taskDescs = [
      '首页白屏时间超过 3s',
      '高峰期登录请求超时',
      '导出 Excel 和 CSV',
      '细粒度权限控制',
      '对接微信和支付宝',
      '升级到最新 LTS 版本',
      'ES 查询响应慢',
      '核心模块覆盖率 > 90%',
      '支持系统主题切换',
      '用户行为数据采集',
      '减少首屏图片请求',
      '提升大数据量渲染性能',
      '支持中英日三语',
      'iOS Safari 布局异常',
      '自动化构建和部署',
      '减少 vendor chunk 体积',
      '实时消息推送',
      '记录关键操作日志',
      '减少 API 冗余请求',
      '支持嵌套路由和权限路由',
    ];
    
    const defaultData: DataSourceType[] = taskNames.map((name, index) => ({
      id: (1705286400000 + index).toString(),
      title: name,
      description: taskDescs[index],
      status: index % 3 === 0 ? 'open' : index % 3 === 1 ? 'processing' : 'closed',
      created_at: 1705286400000 - index * 86400000,
    }));
    
    const Demo = () => {
      const [editableKeys, setEditableRowKeys] = useState<React.Key[]>(() =>
        defaultData.map((item) => item.id),
      );
      const [dataSource, setDataSource] = useState<readonly DataSourceType[]>(
        () => defaultData,
      );
    
      const columns: ProColumns<DataSourceType>[] = [
        {
          title: '任务名称',
          dataIndex: 'title',
          width: '30%',
          formItemProps: {
            rules: [
              {
                required: true,
                whitespace: true,
                message: '此项是必填项',
              },
              {
                max: 30,
                whitespace: true,
                message: '最长为 30 位',
              },
              {
                min: 4,
                whitespace: true,
                message: '最小为 4 位',
              },
            ],
          },
        },
        {
          title: '状态',
          key: 'status',
          dataIndex: 'status',
          valueType: 'select',
          valueEnum: {
            all: { text: '全部', status: 'Default' },
            open: {
              text: '待处理',
              status: 'Error',
            },
            processing: {
              text: '进行中',
              status: 'Processing',
            },
            closed: {
              text: '已完成',
              status: 'Success',
            },
          },
        },
        {
          title: '描述',
          dataIndex: 'description',
        },
        {
          title: '操作',
          valueType: 'option',
          width: 250,
          render: () => {
            return null;
          },
        },
      ];
    
      return (
        <>
          <EditableProTable<DataSourceType>
            headerTitle="实时编辑任务列表"
            columns={columns}
            rowKey="id"
            scroll={{
              x: 960,
            }}
            value={dataSource}
            onChange={setDataSource}
            recordCreatorProps={{
              newRecordType: 'dataSource',
              record: () => ({
                id: Date.now(),
              }),
            }}
            toolBarRender={() => {
              return [
                <Button
                  type="primary"
                  key="save"
                  onClick={() => {
                    console.log(dataSource);
                  }}
                >
                  保存数据
                </Button>,
              ];
            }}
            editable={{
              type: 'multiple',
              editableKeys,
              actionRender: (row, config, defaultDoms) => {
                return [defaultDoms.delete];
              },
              onValuesChange: (record, recordList) => {
                setDataSource(recordList);
              },
              onChange: setEditableRowKeys,
            }}
          />
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormField
              ignoreFormItem
              fieldProps={{
                style: {
                  width: '100%',
                },
              }}
              mode="read"
              valueType="jsonCode"
              text={JSON.stringify(dataSource)}
            />
          </ProCard>
        </>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    Advanced sample

    字段名称字段类型字段别名补充说明
    暂无数据
    暂无数据
    表格数据
    [
      {
        "name": "company_total",
        "value": {
          "schemaName": "namespace_e0d8926c7cd51a4ce3ca3675ee370478",
          "tableName": "company_total"
        },
        "columnsInfo": [
          {
            "name": "auto_id_by_system",
            "dataType": "bigint",
            "description": "系统自动生成-自增主键",
            "supplementComment": "系统自动生成-自增主键"
          },
          {
            "name": "project_name",
            "dataType": "text",
            "description": "project; 保险项目名称,包括赔款支出、保险金额、总资产、责任险、农业保险、原保险保费收入、健康险、意外险等。",
            "supplementComment": "project; 保险项目名称,包括赔款支出、保险金额、总资产、责任险、农业保险、原保险保费收入、健康险、意外险等。"
          },
          {
            "name": "amount",
            "dataType": "numeric(24,5)",
            "description": "amount; 数值金额,代表各种保险项目的金额。",
            "supplementComment": "amount; 数值金额,代表各种保险项目的金额。"
          }
        ],
        "description": "7月财产险公司经营情况表"
      },
      {
        "name": "part_compay_info",
        "value": {
          "schemaName": "namespace_e0d8926c7cd51a4ce3ca3675ee370478",
          "tableName": "part_compay_info"
        },
        "columnsInfo": [
          {
            "name": "auto_id_by_system",
            "dataType": "bigint",
            "description": "系统自动生成-自增主键",
            "supplementComment": "系统自动生成-自增主键"
          },
          {
            "name": "mct_one_id",
            "dataType": "text",
            "description": "mct_one_id; 唯一标识符",
            "supplementComment": "mct_one_id; 唯一标识符"
          },
          {
            "name": "list_type",
            "dataType": "text",
            "description": "list_type; 上市板块类型",
            "supplementComment": "list_type; 上市板块类型"
          },
          {
            "name": "report_year",
            "dataType": "numeric(24,5)",
            "description": "report_year; 报告年份",
            "supplementComment": "report_year; 报告年份"
          },
          {
            "name": "org_name",
            "dataType": "text",
            "description": "org_name; 公司名称",
            "supplementComment": "org_name; 公司名称"
          },
          {
            "name": "total_assets",
            "dataType": "numeric(24,5)",
            "description": "total_assets; 总资产",
            "supplementComment": "total_assets; 总资产"
          },
          {
            "name": "total_liability",
            "dataType": "numeric(24,5)",
            "description": "total_liability; 总负债",
            "supplementComment": "total_liability; 总负债"
          },
          {
            "name": "equity",
            "dataType": "numeric(24,5)",
            "description": "equity; 股东权益",
            "supplementComment": "equity; 股东权益"
          },
          {
            "name": "total_profit",
            "dataType": "numeric(24,5)",
            "description": "total_profit; 总利润",
            "supplementComment": "total_profit; 总利润"
          },
          {
            "name": "net_profit",
            "dataType": "numeric(24,5)",
            "description": "net_profit; 净利润",
            "supplementComment": "net_profit; 净利润"
          },
          {
            "name": "tax",
            "dataType": "numeric(24,5)",
            "description": "tax; 税额",
            "supplementComment": "tax; 税额"
          }
        ],
        "description": "企业年报"
      }
    ]
    Complex editable scenarios
    import {
      ProCard,
      ProForm,
      ProFormDependency,
      ProFormField,
      ProTable,
    } from '@ant-design/pro-components';
    import { FormInstance, Input, Tabs } from 'antd';
    import { useRef } from 'react';
    
    const defaultData = [
      {
        name: 'company_total',
        value: {
          schemaName: 'namespace_e0d8926c7cd51a4ce3ca3675ee370478',
          tableName: 'company_total',
        },
        columnsInfo: [
          {
            name: 'auto_id_by_system',
            dataType: 'bigint',
            description: '系统自动生成-自增主键',
            supplementComment: '系统自动生成-自增主键',
          },
          {
            name: 'project_name',
            dataType: 'text',
            description:
              'project; 保险项目名称,包括赔款支出、保险金额、总资产、责任险、农业保险、原保险保费收入、健康险、意外险等。',
            supplementComment:
              'project; 保险项目名称,包括赔款支出、保险金额、总资产、责任险、农业保险、原保险保费收入、健康险、意外险等。',
          },
          {
            name: 'amount',
            dataType: 'numeric(24,5)',
            description: 'amount; 数值金额,代表各种保险项目的金额。',
            supplementComment: 'amount; 数值金额,代表各种保险项目的金额。',
          },
        ],
        description: '7月财产险公司经营情况表',
      },
      {
        name: 'part_compay_info',
        value: {
          schemaName: 'namespace_e0d8926c7cd51a4ce3ca3675ee370478',
          tableName: 'part_compay_info',
        },
        columnsInfo: [
          {
            name: 'auto_id_by_system',
            dataType: 'bigint',
            description: '系统自动生成-自增主键',
            supplementComment: '系统自动生成-自增主键',
          },
          {
            name: 'mct_one_id',
            dataType: 'text',
            description: 'mct_one_id; 唯一标识符',
            supplementComment: 'mct_one_id; 唯一标识符',
          },
          {
            name: 'list_type',
            dataType: 'text',
            description: 'list_type; 上市板块类型',
            supplementComment: 'list_type; 上市板块类型',
          },
          {
            name: 'report_year',
            dataType: 'numeric(24,5)',
            description: 'report_year; 报告年份',
            supplementComment: 'report_year; 报告年份',
          },
          {
            name: 'org_name',
            dataType: 'text',
            description: 'org_name; 公司名称',
            supplementComment: 'org_name; 公司名称',
          },
          {
            name: 'total_assets',
            dataType: 'numeric(24,5)',
            description: 'total_assets; 总资产',
            supplementComment: 'total_assets; 总资产',
          },
          {
            name: 'total_liability',
            dataType: 'numeric(24,5)',
            description: 'total_liability; 总负债',
            supplementComment: 'total_liability; 总负债',
          },
          {
            name: 'equity',
            dataType: 'numeric(24,5)',
            description: 'equity; 股东权益',
            supplementComment: 'equity; 股东权益',
          },
          {
            name: 'total_profit',
            dataType: 'numeric(24,5)',
            description: 'total_profit; 总利润',
            supplementComment: 'total_profit; 总利润',
          },
          {
            name: 'net_profit',
            dataType: 'numeric(24,5)',
            description: 'net_profit; 净利润',
            supplementComment: 'net_profit; 净利润',
          },
          {
            name: 'tax',
            dataType: 'numeric(24,5)',
            description: 'tax; 税额',
            supplementComment: 'tax; 税额',
          },
        ],
        description: '企业年报',
      },
    ];
    
    const Demo = () => {
      const formRef = useRef<FormInstance>();
      return (
        <ProForm
          formRef={formRef}
          onValuesChange={(_, _e) => {
    
          }}
          initialValues={{ columnsInfo: defaultData, table: defaultData }}
        >
          <ProFormDependency name={['columnsInfo']}>
            {({ columnsInfo }) => {
              const list = (columnsInfo || []) as any[];
              return (
                <Tabs
                  style={{
                    padding: '0 8px',
                    maxWidth: '100%',
                  }}
                  items={list.map((item: any, index) => {
                    return {
                      key: item.name || '',
                      tab: (
                        <div
                          style={{
                            padding: 4,
                          }}
                        >
                          {item.name}
                        </div>
                      ),
                      label: (
                        <div
                          style={{
                            padding: 4,
                          }}
                        >
                          {item.name}
                        </div>
                      ),
                      children: (
                        <div
                          style={{
                            display: 'flex',
                            maxWidth: '1024px',
                          }}
                        >
                          <div
                            style={{
                              flex: 1,
                              overflow: 'auto',
                            }}
                          >
                            <ProTable
                              scroll={{ x: 'max-content' }}
                              size="small"
                              pagination={false}
                              search={false}
                              editable={{
                                editableKeys: item?.columnsInfo?.map(
                                  (row: any) => row.name || '',
                                ),
                              }}
                              rowKey="name"
                              name={['table', index, 'columnsInfo']}
                              columns={[
                                {
                                  title: '字段名称',
                                  dataIndex: 'name',
                                  editable: false,
                                },
                                {
                                  title: '字段类型',
                                  dataIndex: 'dataType',
                                  editable: false,
                                },
                                {
                                  title: '字段别名',
                                  dataIndex: 'description',
                                  editable: false,
                                  width: 140,
                                  render: (text, record) => {
                                    return (
                                      <div
                                        style={{
                                          WebkitLineClamp: 2,
                                          maxHeight: 40,
                                          lineHeight: '20px',
                                          textOverflow: 'ellipsis',
                                          overflow: 'hidden',
                                          display: '-webkit-box',
                                          WebkitBoxOrient: 'vertical',
                                          wordBreak: 'break-all',
                                        }}
                                      >
                                        {record.description
                                          ? record.description
                                          : '-'}
                                      </div>
                                    );
                                  },
                                },
                                {
                                  title: '补充说明',
                                  dataIndex: 'supplementComment',
                                  formItemRender: () => {
                                    return (
                                      <Input.TextArea
                                        placeholder="请输入补充说明"
                                        maxLength={200}
                                      />
                                    );
                                  },
                                },
                              ]}
                              toolBarRender={false}
                              request={async () => {
                                return {
                                  data: item.columnsInfo,
                                };
                              }}
                            />
                          </div>
                        </div>
                      ),
                    };
                  })}
                />
              );
            }}
          </ProFormDependency>
    
          <ProCard title="表格数据" headerBordered collapsible defaultCollapsed>
            <ProFormDependency name={['table']}>
              {({ table }) => {
                return (
                  <ProFormField
                    ignoreFormItem
                    fieldProps={{
                      style: {
                        width: '100%',
                      },
                    }}
                    mode="read"
                    valueType="jsonCode"
                    text={JSON.stringify(table, null, 2)}
                  />
                );
              }}
            </ProFormDependency>
          </ProCard>
        </ProForm>
      );
    };
    
    export default () => (
      <div style={{ padding: 24 }}>
        <Demo />
      </div>
    );
    

    EditableProTable

    PropertiesDescriptionTypeDefault
    valueSame as dataSource, pass in an array of metadata for table renderingT[]undefined
    onChangeThe dataSource is triggered when the table is modified, both deletion and modification.(value:T[])=>voidundefined
    recordCreatorPropsConfiguration related to creating a new row of dataRecordCreatorProps & ButtonProps-
    maxLengthThe maximum number of rows, the New button will disappear when the maximum number of rows is reachednumber-
    editableRelated configuration of editable tableTableRowEditable-
    controlledWhether controlled, if controlled every edit modifies the dataSourcebooleanfalse
    editableFormReftable All forms, with some table-specific operationsReact.Ref<EditableFormInstance<T>>undefined

    Other APIs are the same as ProTable.

    Editable New row

    When adding a new line, make sure that the recordCreatorProps.record key is unique, otherwise it will cause editing errors.

    <EditableTable
      rowKey="id"
      recordCreatorProps={{
        position: position as 'top',
        // Each time you add a new key, you need it
        record: () => ({ id: getNewId() }),
      }}
    />

    EditableFormInstance Form list form operation

    Compared with the ProForm form, the editable form adds the following three methods.

      /**
       * Get a row of data
       * @param rowIndex
       * @returns T | undefined
       *
       * @example getRowData(1) can pass in the data of the first row
       * @example getRowData("id") can also pass in rowKey to get it according to the unique key of your column.
       */
      getRowData?: (rowIndex: string | number) => T | undefined;
      /**
       * Get the data of the entire table
       * @returns T[] | undefined
       */
      getRowsData?: () => T[] | undefined;
      /**
       * Setting a row of data will simply merge the data
       *
       * {title:"old", decs:"old",id:"old"} -> set {title:"new"} -> {title:"new", decs:"old",id:"old" }
       *
       * @description will only do the merge of the first level object.
       * {title:"old", decs:{name:"old",key:"old"},id:"old"} -> set {decs:{name:"new"}} -> {title:" old", decs:{name:"new"},id:"old"} -> set {decs:{name:"old"}}
       *
       * @param rowIndex
       * @param data
       * @returns void
       *
       * Set according to line number
       * @example setRowData(1, { title:"new" }) You can pass in which row to modify
       *
       * set according to row id
       * @example setRowData("id", { title:"new" }) can also pass in rowKey, set it according to the unique key of your column.
       *
       * Clear the original data
       * @example setRowData(1, { title:undefined })
       *
       */
      setRowData?: (rowIndex: string | number, data: Partial<T>) => void;

    editable edit line configuration

    PropertyDescriptionTypeDefault Value
    typeType of editable table, single or multiplesingle | multiple-
    formForm instance of editable form, use Form.useForm to generate and useFormInstance-
    formPropsform properties can be configured, but onFinish is not supported`FormProps'-
    editableKeysRow being edited, controlled attributes. The defaultkey will use the configuration of rowKey,if there is no configuration, it will use theindex, it is recommended to use rowKey. Supports cell-level composite keys `${rowKey}:${dataIndex}` (e.g. ['1:name'] activates only that cell); row keys and cell keys can be mixed. In cell mode the option column does not render save/cancel buttonsKey[]-
    onChangeTriggered when row data is modified(editableKeys: Key[], editableRows: T[]) => void-
    onValuesChangeTriggered when row values change(record: T, dataSource: T[]) => void-
    onSaveTriggered when a row is saved(key: Key, row: T,originRow:T,newLine?:newLineConfig) => Promise<any>-
    saveTextText for saving a rowReact.ReactNodeSave
    onDeleteTriggered when a row is deleted(key: Key, row: T) => Promise<any>-
    deleteTextText for deleting a rowReact.ReactNodeDelete
    onCancelTriggered when cancel editing a line(key: Key, row: T,originRow:T,newLine?:newLineConfig) => Promise<any>-
    cancelTextText for canceling the editing of a rowReact.ReactNodeCancel
    actionRenderCustom edit mode action bar(row: T, config: ActionRenderConfig<T>) => ReactNode[]-
    deletePopconfirmMessageThe pop-up confirmation box prompt message when deletingReactNodeDelete this line?
    onlyOneLineEditorAlertMessageOnly one line can be editedReactNodeOnly one line can be edited at the same time
    onlyAddOneLineAlertMessageOnly one line can be added at the same timeReactNodeOnly add one line
    tableNameThe name set on the Table, used to splice names to get dataNamePath-
    getRealIndexFix FormItem namePath using the wrong index as path caused by pagination (issue #7790)(record: T) => number-
    dateFormatterSame as ProTable dateFormatter; converts in-row dayjs to string or number when merging into dataSource'string' | 'number' | false | ((value, valueType) => string | number)-

    recordCreatorProps New button configuration

    In order to use it, we preset a New function, which in most cases already meets most new creation needs, but many times the needs are always strange. We have also prepared recordCreatorProps to control the generation of buttons. Same API as the Pro series components, recordCreatorProps={false} turns off the button and uses actionRef.current?.addEditRecord(row) to control the new row.

    recordCreatorProps also supports some custom styles, position='top'|'bottom' can be configured to add at the head or at the end of the table. record can be configured to add a new row with default data. Here is an example

    recordCreatorProps = {
      // Add under which parent node, generally used for nested tables
      parentKey: React.Key,
      // Add at the top or bottom
      position: 'bottom',
      // Method to add a new row, default is caching, it will disappear after cancellation
      // If set to dataSource, it will trigger onchange, and it won't disappear after cancellation, only delete is allowed
      newRecordType: 'dataSource',
      // If not specified, it will use the index as the row ID
      record: {},
      // Button style settings, you can control whether the button is displayed
      // This can be used to implement features like maximum and minimum row limits
      style: {
        display: 'none',
      },
      // Button properties, see https://ant.design/components/button/#API
      ...antButtonProps,
    };
    recordCreatorProps = {
      // Add at the top or at the end
      position: 'bottom',
      // the way to add a new line, default is cached, will disappear when cancelled
      // if set to dataSource it will trigger onchange, it won't disappear if cancelled, only deleted
      newRecordType: 'dataSource',
      // If you don't write key, index will be used as row id
      record: {},
      // the style of the button, you can set whether the button is displayed or not
      // so that you can do things like max row limit and min row limit
      style: {
        display: 'none',
      },
      // https://ant.design/components/button/#API
      ...antButtonProps,
    };

    formItemRender Custom Edit Component

    As much as we would like the default valueType to meet all our needs, the reality is often not as good as it could be. So we also provide formItemRender to customize the edit input component.

    formItemRender can be understood as adding an element below Form.

    const dom = formItemRender();
    
    <Form.Item>{dom}</Form.Item>;

    So as with Form.Item, we assume that the components returned by formItemRender have value and onChange, and we'll see next that putting a simple TagList component into an editable form with formItemRender.

    Without value you won't be able to inject values and without onChange you won't be able to modify the row data!

    First we define a TagList component.

    const TagList: React.FC<{
      value?: {
        key: string;
        label: string;
      }[];
      onChange?: (
        value: {
          key: string;
          label: string;
        }[],
      ) => void;
    }> = ({ value, onChange }) => {
      const ref = useRef<Input | null>(null);
      const [newTags, setNewTags] = useState<
        {
          key: string;
          label: string;
        }[]
      >([]);
      const [inputValue, setInputValue] = useState<string>('');
    
      const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setInputValue(e.target.value);
      };
    
      const handleInputConfirm = () => {
        let tempsTags = [...(value || [])];
        if (
          inputValue &&
          tempsTags.filter((tag) => tag.label === inputValue).length === 0
        ) {
          tempsTags = [
            ...tempsTags,
            { key: `new-${tempsTags.length}`, label: inputValue },
          ];
        }
        onChange?.(tempsTags);
        setNewTags([]);
        setInputValue('');
      };
    
      return (
        <Space>
          {(value || []).concat(newTags).map((item) => (
            <Tag key={item.key}>{item.label}</Tag>
          ))}
          <Input
            ref={ref}
            type="text"
            size="small"
            style={{ width: 78 }}
            value={inputValue}
            onChange={handleInputChange}
            onBlur={handleInputConfirm}
            onPressEnter={handleInputConfirm}
          />
        </Space>
      );
    };

    In the column we can configure it like this.

     {
        title: 'labels',
        dataIndex: 'labels',
        width: '40%',
        formItemRender: () => <TagList />,
        render: (_, row) => row?.labels?.map((item) => <Tag key={item.key}>{item.label}</Tag>),
      },

    The effect of the transformed edit form is as follows.

    ! image.png

    value and onChange are injected automatically, we don't need to inject them explicitly. Data binding is also injected by the edit form itself, and we can get the finished data in onSave. Although we can write complex logic and even web requests inline, we recommend using the split component, which not only provides better performance, but also allows the logic to be split very simply.

    formItemRender is also used to generate query forms, if we need to distinguish between the two cases, we can use formItemRender: (_, { isEditable }) => (isEditable ? <TagList /> : <Input /> ) to render them separately.

    actionRender Custom Action Bar

    If we want to copy a line, or if we only need the Save and Cancel, but not the Delete button, we need to customize it. The editable form provides an API to customize it, and the following will show the code directly:

    Copy a line to the bottom

    render: (text, record, _, action) => [
      <a
        key="editable"
        onClick={() => {
          action?.startEditable? (record.id);
        }}
      >
        Edit
      </a>,
      <EditableProTable.RecordCreator
        record={()=>{
          ...record,
          id: (Math.random() * 1000000).toFixed(0),
        }}
      >
        <a>Copy this row to the end</a>
      </EditableProTable.RecordCreator>,
    ];

    Custom Action Bar

    const editable = {
      actionRender: (row, config, defaultDom) => [
        defaultDom.save,
        defaultDom.cancel,
      ],
    };