#EditableProTable - 可编辑表格
可编辑表格 EditableProTable 与 ProTable 的功能基本相同,为了方便使用 EditableProTable 增加了一些预设,关掉了查询表单和操作栏,同时修改了 value 和 onChange 使其可以方便的继承到 antd 的 Form 中。
#代码演示
#编辑模式切换
通过 Segmented 切换 editable.type,在单行编辑(single)和多行编辑(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>
);
#可编辑表格
任务管理
任务名称 | 任务名称(只读) | 状态 | 描述 | 创建时间 | 操作 |
|---|---|---|---|---|---|
任务名称 | 任务名称(只读) | 状态 | 描述 | 创建时间 | 操作 |
暂无数据 | |||||
表格数据
[]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>
);
#单元格编辑
任务管理(单元格编辑)
| 任务名称 | 状态 | 描述 |
|---|---|---|
任务名称 | 状态 | 描述 |
| 优化首页加载速度 | 待处理 | 首页白屏时间超过 3s,需优化资源加载 |
| 修复登录超时问题 | 已完成 | 用户反馈高峰期登录请求超时 |
| 新增数据导出功能 | 待处理 | 支持将查询结果导出为 Excel 和 CSV |
| 重构权限管理模块 | 已完成 | 现有权限模型不支持细粒度控制 |
| 接入第三方支付 SDK | 待处理 | 对接微信支付和支付宝当面付 |
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>
);
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'] }}
/>
);
};
#行编辑
任务管理(整行编辑)
| 任务名称 | 状态 | 描述 |
|---|---|---|
任务名称 | 状态 | 描述 |
| 优化首页加载速度 | 待处理 | 首页白屏时间超过 3s,需优化资源加载 |
| 修复登录超时问题 | 已完成 | 用户反馈高峰期登录请求超时 |
| 新增数据导出功能 | 待处理 | 支持将查询结果导出为 Excel 和 CSV |
| 重构权限管理模块 | 已完成 | 现有权限模型不支持细粒度控制 |
| 接入第三方支付 SDK | 待处理 | 对接微信支付和支付宝当面付 |
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>
);
#与 FormItem 配合
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>
);