9.固定列
对于列数很多的数据,可以固定前后的列,横向滚动查看其它数据,需要和 scroll.x 配合使用。通过columns的fixed: ‘left’属性实现。
import { Table } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import React from 'react';
interface DataType {
key: React.Key;
name: string;
age: number;
address: string;
}
const columns: ColumnsType<DataType> = [
{
title: 'Full Name',
width: 100,
dataIndex: 'name',
key: 'name',
fixed: 'left',
},
{
title: 'Age',
width: 100,
dataIndex: 'age',
key: 'age',
fixed: 'left',
},
{ title: 'Column 1', dataIndex: 'address', key: '1' },
{ title: 'Column 2', dataIndex: 'address', key: '2' },
{ title: 'Column 3', dataIndex: 'address', key: '3' },
{ title: 'Column 4', dataIndex: 'address', key: '4' },
{ title: 'Column 5', dataIndex: 'address', key: '5' },
{ title: 'Column 6', dataIndex: 'address', key: '6' },
{ title: 'Column 7', dataIndex: 'address', key: '7' },
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
render: () => <a>action</a>,
},
];
const data: DataType[] = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York Park',
},
{
key: '2',
name: 'Jim Green',
age: 40,
address: 'London Park',
},
];
const App: React.FC = () => <Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />;
export default App;
10.固定表头和列
同时展示有大量数据和数据列,通过设置scroll={{ x: 1500, y: 300 }}和columns的fixed: ‘left’属性
11.表头分组
columns[n]内嵌children,可实现分组表头。
{
title: 'Address',
children: [
{
title: 'Street',
dataIndex: 'street',
key: 'street',
width: 150,
},
{
title: 'Block',
children: [
{
title: 'Building',
dataIndex: 'building',
key: 'building',
width: 100,
},
{
title: 'Door No.',
dataIndex: 'number',
key: 'number',
width: 100,
},
],
},
],
},
],
},
12.可编辑单元格
const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface Item {
key: string;
name: string;
age: string;
address: string;
}
interface EditableRowProps {
index: number;
}
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
type EditableTableProps = Parameters<typeof Table>[0];
interface DataType {
key: React.Key;
name: string;
age: string;
address: string;
}
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
const App: React.FC = () => {
const [dataSource, setDataSource] = useState<DataType[]>([
{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0',
},
{
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1',
},
]);
const [count, setCount] = useState(2);
const handleDelete = (key: React.Key) => {
const newData = dataSource.filter(item => item.key !== key);
setDataSource(newData);
};
const defaultColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [
{
title: 'name',
dataIndex: 'name',
width: '30%',
editable: true,
},
{
title: 'age',
dataIndex: 'age',
},
{
title: 'address',
dataIndex: 'address',
},
{
title: 'operation',
dataIndex: 'operation',
render: (_, record: { key: React.Key }) =>
dataSource.length >= 1 ? (
<Popconfirm title="Sure to delete?" onConfirm={() => handleDelete(record.key)}>
<a>Delete</a>
</Popconfirm>
) : null,
},
];
const handleAdd = () => {
const newData: DataType = {
key: count,
name: `Edward King ${count}`,
age: '32',
address: `London, Park Lane no. ${count}`,
};
setDataSource([...dataSource, newData]);
setCount(count + 1);
};
const handleSave = (row: DataType) => {
const newData = [...dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const columns = defaultColumns.map(col => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
return (
<div>
<Button onClick={handleAdd} type="primary" style={{ marginBottom: 16 }}>
Add a row
</Button>
<Table
components={components}
rowClassName={() => 'editable-row'}
bordered
dataSource={dataSource}
columns={columns as ColumnTypes}
/>
</div>
);
};
export default App;
13.可编辑行
import React, { useState } from 'react';
import { Table, Input, InputNumber, Form, Button, Space } from 'antd';
import { EditOutlined, SaveOutlined, CloseOutlined } from '@ant-design/icons';
interface DataType {
key: string;
name: string;
age: number;
address: string;
}
interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
editing: boolean;
dataIndex: string;
title: string;
inputType: 'text' | 'number';
record: DataType;
index: number;
children: React.ReactNode;
}
const EditableCell: React.FC<EditableCellProps> = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
return (
<td {...restProps}>
{editing ? (
<Form.Item
name={dataIndex}
style={{ margin: 0 }}
rules={[
{
required: true,
message: `请输入 ${title}!`,
},
]}
>
{inputNode}
</Form.Item>
) : (
children
)}
</td>
);
};
const App: React.FC = () => {
const [form] = Form.useForm();
const [data, setData] = useState<DataType[]>([
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号',
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
},
]);
const [editingKey, setEditingKey] = useState('');
const isEditing = (record: DataType) => record.key === editingKey;
const edit = (record: Partial<DataType> & { key: React.Key }) => {
form.setFieldsValue({ name: '', age: '', address: '', ...record });
setEditingKey(record.key);
};
const cancel = () => {
setEditingKey('');
};
const save = async (key: React.Key) => {
try {
const row = (await form.validateFields()) as DataType;
const newData = [...data];
const index = newData.findIndex((item) => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setData(newData);
setEditingKey('');
} else {
newData.push({ ...row, key });
setData(newData);
setEditingKey('');
}
} catch (errInfo) {
console.log('验证失败:', errInfo);
}
};
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
editable: true,
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
editable: true,
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
editable: true,
},
{
title: '操作',
dataIndex: 'operation',
key: 'operation',
render: (_: any, record: DataType) => {
const editable = isEditing(record);
return editable ? (
<Space>
<Button
type="link"
onClick={() => save(record.key)}
icon={<SaveOutlined />}
>
保存
</Button>
<Button
type="link"
onClick={cancel}
icon={<CloseOutlined />}
>
取消
</Button>
</Space>
) : (
<Button
type="link"
disabled={editingKey !== ''}
onClick={() => edit(record)}
icon={<EditOutlined />}
>
编辑
</Button>
);
},
},
];
const mergedColumns = columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
inputType: col.dataIndex === 'age' ? 'number' : 'text',
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
}),
};
});
return (
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
dataSource={data}
columns={mergedColumns}
rowClassName="editable-row"
pagination={false}
/>
</Form>
);
};
export default App;
14.嵌套子表格
也是通过expandable实现的
const expandedRowRender = () => {
const columns: TableColumnsType<ExpandedDataType> = [
{ title: 'Date', dataIndex: 'date', key: 'date' },
...//其他字段
];
const data = [];
for (let i = 0; i < 3; ++i) {
data.push({
key: i.toString(),
date: '2014-12-24 23:12:00',
name: 'This is production name',
upgradeNum: 'Upgraded: 56',
});
}
return <Table columns={columns} dataSource={data} pagination={false} />;
};
<Table
columns={columns}
expandable={{ expandedRowRender, defaultExpandedRowKeys: ['0'] }}
dataSource={data}
/>
15.拖拽排序
interface DraggableBodyRowProps extends React.HTMLAttributes<HTMLTableRowElement> {
index: number;
moveRow: (dragIndex: number, hoverIndex: number) => void;
}//定义接口
const type = 'DraggableBodyRow';
const DraggableBodyRow = ({
index,
moveRow,
className,
style,
...restProps
}: DraggableBodyRowProps) => {
const ref = useRef<HTMLTableRowElement>(null);
const [{ isOver, dropClassName }, drop] = useDrop({
accept: type,
collect: monitor => {
const { index: dragIndex } = monitor.getItem() || {};
if (dragIndex === index) {
return {};
}
return {
isOver: monitor.isOver(),
dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
};
},
drop: (item: { index: number }) => {
moveRow(item.index, index);
},
});
const [, drag] = useDrag({
type,
item: { index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
});
drop(drag(ref));
return (
<tr
ref={ref}
className={`${className}${isOver ? dropClassName : ''}`}
style={{ cursor: 'move', ...style }}
{...restProps}
/>
);
};
const App: React.FC = () => {
const [data, setData] = useState([
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
]);
const components = {
body: {
row: DraggableBodyRow,
},
};
const moveRow = useCallback(
(dragIndex: number, hoverIndex: number) => {
const dragRow = data[dragIndex];
setData(
update(data, {
$splice: [
[dragIndex, 1],
[hoverIndex, 0, dragRow],
],
}),
);
},
[data],
);
return (
<DndProvider backend={HTML5Backend}>
<Table
columns={columns}
dataSource={data}
components={components}
onRow={(_, index) => {
const attr = {
index,
moveRow,
};
return attr as React.HTMLAttributes<any>;
}}
/>
</DndProvider>
);
};
16.单元格字段省略
使用columns的ellipsis可以让单元格内容根据宽度自动省略。
{
title: 'Long Column Long Column Long Column',
dataIndex: 'address',
key: 'address 2',
ellipsis: true,
}
17.自定义单元格省略提示
使用columns的ellipsis的showTitle 关闭单元格内容自动省略后默认的 title 提示, 使用 Tooltip 替代
{
title: 'Address',
dataIndex: 'address',
key: 'address 1',
ellipsis: {
showTitle: false,
},
render: address => (
<Tooltip placement="topLeft" title={address}>
{address}
</Tooltip>
),
}