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
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; |