168 lines
5.3 KiB
TypeScript
168 lines
5.3 KiB
TypeScript
import React from "react";
|
|
import { Empty, Table } from "antd";
|
|
import type { TablePaginationConfig, TableProps } from "antd";
|
|
import "./ListTable.css";
|
|
import i18n from "../../../i18n";
|
|
|
|
export type ListTableProps<T extends Record<string, any>> = {
|
|
columns: TableProps<T>["columns"];
|
|
dataSource: T[];
|
|
rowKey?: string;
|
|
selectedRowKeys?: React.Key[];
|
|
onSelectionChange?: (keys: React.Key[]) => void;
|
|
isAllPagesSelected?: boolean;
|
|
totalCount?: number;
|
|
onSelectAllPages?: () => void;
|
|
onClearSelection?: () => void;
|
|
pagination?: TablePaginationConfig | false;
|
|
scroll?: { x?: number | true | string; y?: number | string };
|
|
onRowClick?: (record: T) => void;
|
|
selectedRow?: T | null;
|
|
loading?: boolean;
|
|
className?: string;
|
|
locale?: TableProps<T>["locale"];
|
|
onChange?: TableProps<T>["onChange"];
|
|
};
|
|
|
|
function ListTable<T extends Record<string, any>>({
|
|
columns,
|
|
dataSource,
|
|
rowKey = "id",
|
|
selectedRowKeys = [],
|
|
onSelectionChange,
|
|
isAllPagesSelected = false,
|
|
totalCount,
|
|
onSelectAllPages,
|
|
onClearSelection,
|
|
pagination = {
|
|
pageSize: 10,
|
|
showSizeChanger: { showSearch: false },
|
|
showQuickJumper: true,
|
|
},
|
|
scroll = { x: "max-content" },
|
|
onRowClick,
|
|
selectedRow,
|
|
loading = false,
|
|
className = "",
|
|
locale,
|
|
onChange,
|
|
}: ListTableProps<T>) {
|
|
const mergedScroll = React.useMemo(() => {
|
|
return {
|
|
x: "max-content",
|
|
...scroll,
|
|
};
|
|
}, [scroll]);
|
|
|
|
const hasVerticalScroll = mergedScroll.y !== undefined;
|
|
const isEmptyTable = !loading && dataSource.length === 0;
|
|
const tableClassName = [
|
|
hasVerticalScroll ? "list-table-table--y-scroll" : undefined,
|
|
isEmptyTable ? "list-table-table--empty" : undefined,
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
const rowSelection: TableProps<T>["rowSelection"] = onSelectionChange
|
|
? {
|
|
selectedRowKeys,
|
|
onChange: (newSelectedRowKeys: React.Key[]) => {
|
|
onSelectionChange?.(newSelectedRowKeys);
|
|
},
|
|
getCheckboxProps: () => ({
|
|
disabled: isAllPagesSelected,
|
|
}),
|
|
}
|
|
: undefined;
|
|
|
|
const mergedPagination =
|
|
pagination === false
|
|
? false
|
|
: (() => {
|
|
const mergedShowSizeChanger =
|
|
pagination.showSizeChanger === undefined || pagination.showSizeChanger === true
|
|
? { showSearch: false }
|
|
: pagination.showSizeChanger;
|
|
|
|
return {
|
|
...pagination,
|
|
className: ["app-global-pagination", pagination.className].filter(Boolean).join(" "),
|
|
showSizeChanger: mergedShowSizeChanger,
|
|
showTotal: (total: number) => (
|
|
<div className="table-selection-info">
|
|
{isAllPagesSelected ? (
|
|
<>
|
|
<span className="selection-count">
|
|
已选择 <span className="count-highlight">{totalCount || total}</span> 项
|
|
</span>
|
|
{onClearSelection && (
|
|
<button type="button" onClick={onClearSelection} className="selection-action">
|
|
清除选择
|
|
</button>
|
|
)}
|
|
</>
|
|
) : selectedRowKeys.length > 0 ? (
|
|
<>
|
|
<span className="selection-count">
|
|
已选择 <span className="count-highlight">{selectedRowKeys.length}</span> 项
|
|
</span>
|
|
{onSelectAllPages && selectedRowKeys.length < (totalCount || total) && (
|
|
<button type="button" onClick={onSelectAllPages} className="selection-action">
|
|
选择全部 {totalCount || total} 项
|
|
</button>
|
|
)}
|
|
{onClearSelection && (
|
|
<button type="button" onClick={onClearSelection} className="selection-action">
|
|
清除
|
|
</button>
|
|
)}
|
|
</>
|
|
) : (
|
|
<span className="selection-count">
|
|
{i18n.t("common.total", { total: totalCount || total })}
|
|
</span>
|
|
)}
|
|
</div>
|
|
),
|
|
};
|
|
})();
|
|
|
|
const wrapperStyle = hasVerticalScroll
|
|
? ({
|
|
["--list-table-scroll-y" as string]: typeof mergedScroll.y === "number" ? `${mergedScroll.y}px` : mergedScroll.y,
|
|
} as React.CSSProperties)
|
|
: undefined;
|
|
const emptyText = locale?.emptyText ?? (
|
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据" />
|
|
);
|
|
const emptyContent = typeof emptyText === "function" ? emptyText() : emptyText;
|
|
const tableLocale = {
|
|
...locale,
|
|
emptyText: null,
|
|
};
|
|
|
|
return (
|
|
<div className={`list-table-container ${className}`} style={wrapperStyle}>
|
|
<Table
|
|
className={tableClassName || undefined}
|
|
size="middle"
|
|
rowSelection={rowSelection}
|
|
columns={columns}
|
|
dataSource={dataSource}
|
|
rowKey={rowKey}
|
|
pagination={mergedPagination}
|
|
scroll={mergedScroll}
|
|
loading={loading}
|
|
locale={tableLocale}
|
|
onChange={onChange}
|
|
onRow={(record) => ({
|
|
onClick: () => onRowClick?.(record),
|
|
className: selectedRow?.[rowKey] === record[rowKey] ? "row-selected" : "",
|
|
})}
|
|
/>
|
|
{isEmptyTable ? <div className="list-table-empty-overlay">{emptyContent}</div> : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default ListTable;
|