You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 lines
7.4 KiB
TypeScript

import React, { useState, useRef, useEffect, useContext, useCallback } from 'react';
import { Button, Table, Input, Select, Form, FormInstance, Switch, DatePicker } from '@arco-design/web-react';
const FormItem = Form.Item;
const EditableContext = React.createContext<{ getForm?: () => FormInstance }>({});
interface EditableTableProps {
columns: ColumnProps[];
data: any[];
onDataChange?: (data: any[]) => void;
showAddButton?: boolean;
addButtonText?: string;
onAdd?: () => void;
showDeleteButton?: boolean;
deleteButtonText?: string;
onDelete?: (key: string) => void;
// Table组件的其他配置属性
tableProps?: any;
// 添加空数据的配置
emptyItem?: any;
}
interface ColumnProps {
title: string;
dataIndex: string;
editable?: boolean;
render?: (text: any, record: any, index: number) => React.ReactNode;
// 用于指定编辑时使用的组件类型
editComponent?: 'input' | 'select' | 'switch' | 'date' | 'custom';
// 用于select组件的选项
options?: { label: string; value: any }[];
// 用于自定义渲染编辑组件
renderEdit?: (props: EditComponentProps) => React.ReactNode;
// 其他列配置属性
[key: string]: any;
}
interface EditComponentProps {
value: any;
onChange: (value: any) => void;
rowData: any;
column: ColumnProps;
}
interface EditableRowProps {
children: React.ReactNode;
record: any;
className: string;
[key: string]: any;
}
interface EditableCellProps {
children: React.ReactNode;
className: string;
rowData: any;
column: ColumnProps;
onHandleSave: (row: any) => void;
}
function EditableRow(props: EditableRowProps) {
const { children, record, className, ...rest } = props;
const refForm = useRef<FormInstance | null>(null);
const getForm = () => refForm.current;
return (
<EditableContext.Provider
value={{
getForm
}}
>
<Form
style={{ display: 'table-row' }}
ref={refForm}
wrapper="tr"
wrapperProps={rest}
className={`${className} editable-row`}
>
{children}
</Form>
</EditableContext.Provider>
);
}
function EditableCell(props: EditableCellProps) {
const { children, className, rowData, column, onHandleSave } = props;
const { getForm } = useContext(EditableContext);
const cellValueChangeHandler = (value: any) => {
const values = {
[column.dataIndex]: value
};
onHandleSave && onHandleSave({ ...rowData, ...values });
};
// 渲染编辑组件
const renderEditComponent = () => {
// 如果有自定义渲染函数,使用它
if (column.renderEdit) {
return column.renderEdit({
value: rowData[column.dataIndex],
onChange: cellValueChangeHandler,
rowData,
column
});
}
// 根据editComponent类型渲染不同的组件
switch (column.editComponent) {
case 'select':
return (
<Select
defaultValue={rowData[column.dataIndex]}
options={column.options || []}
onChange={cellValueChangeHandler}
style={{ width: '100%' }}
/>
);
case 'switch':
return (
<Switch
checked={rowData[column.dataIndex]}
onChange={cellValueChangeHandler}
/>
);
case 'date':
return (
<DatePicker
defaultValue={rowData[column.dataIndex]}
onChange={cellValueChangeHandler}
style={{ width: '100%' }}
/>
);
case 'custom':
// 自定义组件需要通过renderEdit提供
return null;
case 'input':
default:
return (
<Input
defaultValue={rowData[column.dataIndex]}
onChange={(value) => cellValueChangeHandler(value)}
style={{ width: '100%' }}
/>
);
}
};
// 如果列是可编辑的,直接渲染编辑组件
if (column.editable) {
return (
<div>
<FormItem
style={{ marginBottom: 0 }}
labelCol={{ span: 0 }}
wrapperCol={{ span: 24 }}
initialValue={rowData[column.dataIndex]}
field={column.dataIndex}
rules={[{ required: true }]}
>
{renderEditComponent()}
</FormItem>
</div>
);
}
// 非编辑状态直接显示文本
return (
<div className={className}>
{children}
</div>
);
}
const EditableTable: React.FC<EditableTableProps> = ({
columns,
data,
onDataChange,
showAddButton = true,
addButtonText = 'Add',
onAdd,
showDeleteButton = true,
deleteButtonText = 'Delete',
onDelete,
tableProps = {},
emptyItem = {}
}) => {
const [tableData, setTableData] = useState<any[]>(data);
useEffect(() => {
setTableData(data);
}, [data]);
const handleSave = (row: any) => {
const newData = [...tableData];
const index = newData.findIndex((item) => row.key === item.key);
if (index !== -1) {
newData.splice(index, 1, { ...newData[index], ...row });
setTableData(newData);
onDataChange && onDataChange(newData);
}
};
const removeRow = (key: string) => {
const newData = tableData.filter((item) => item.key !== key);
setTableData(newData);
onDataChange && onDataChange(newData);
onDelete && onDelete(key);
};
const addRow = () => {
if (onAdd) {
onAdd();
}
else {
// 添加一条空数据
const newKey = `${Date.now()}`;
const newRow = {
key: newKey,
...emptyItem
};
const newData = [...tableData, newRow];
setTableData(newData);
onDataChange && onDataChange(newData);
}
};
// 处理列配置,添加删除按钮
const processedColumns = [...columns];
if (showDeleteButton && !columns.some(col => col.dataIndex === 'op')) {
processedColumns.push({
title: 'Operation',
dataIndex: 'op',
render: (_: any, record: any) => (
<Button onClick={() => removeRow(record.key)} type="primary" status="danger">
{deleteButtonText}
</Button>
)
});
}
return (
<>
{showAddButton && (
<Button
style={{ marginBottom: 10 }}
type="primary"
onClick={addRow}
>
{addButtonText}
</Button>
)}
<Table
data={tableData}
components={{
body: {
row: EditableRow,
cell: EditableCell
}
}}
columns={processedColumns.map((column) =>
column.editable
? {
...column,
onCell: () => ({
onHandleSave: handleSave
})
}
: column
)}
className="table-demo-editable-cell"
{...tableProps}
/>
</>
);
};
export default EditableTable;