imeeting/frontend/src/components/shared/ListTable/ListTable.tsx

168 lines
5.3 KiB
TypeScript
Raw Normal View History

import React from "react";
2026-07-01 14:00:19 +00:00
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;
2026-07-01 14:00:19 +00:00
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,
},
2026-07-06 03:08:36 +00:00
scroll = { x: "max-content" },
onRowClick,
selectedRow,
loading = false,
className = "",
2026-07-01 14:00:19 +00:00
locale,
onChange,
}: ListTableProps<T>) {
const mergedScroll = React.useMemo(() => {
return {
2026-07-06 03:08:36 +00:00
x: "max-content",
...scroll,
};
}, [scroll]);
const hasVerticalScroll = mergedScroll.y !== undefined;
2026-07-01 14:00:19 +00:00
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 && (
2026-07-06 03:08:36 +00:00
<button type="button" onClick={onClearSelection} className="selection-action">
2026-07-06 03:08:36 +00:00
</button>
)}
</>
) : selectedRowKeys.length > 0 ? (
<>
<span className="selection-count">
<span className="count-highlight">{selectedRowKeys.length}</span>
</span>
{onSelectAllPages && selectedRowKeys.length < (totalCount || total) && (
2026-07-06 03:08:36 +00:00
<button type="button" onClick={onSelectAllPages} className="selection-action">
{totalCount || total}
2026-07-06 03:08:36 +00:00
</button>
)}
{onClearSelection && (
2026-07-06 03:08:36 +00:00
<button type="button" onClick={onClearSelection} className="selection-action">
2026-07-06 03:08:36 +00:00
</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;
2026-07-01 14:00:19 +00:00
const emptyText = locale?.emptyText ?? (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据" />
);
2026-07-02 00:36:49 +00:00
const emptyContent = typeof emptyText === "function" ? emptyText() : emptyText;
2026-07-01 14:00:19 +00:00
const tableLocale = {
...locale,
emptyText: null,
};
return (
<div className={`list-table-container ${className}`} style={wrapperStyle}>
<Table
2026-07-01 14:00:19 +00:00
className={tableClassName || undefined}
size="middle"
rowSelection={rowSelection}
columns={columns}
dataSource={dataSource}
rowKey={rowKey}
pagination={mergedPagination}
scroll={mergedScroll}
loading={loading}
2026-07-01 14:00:19 +00:00
locale={tableLocale}
onChange={onChange}
onRow={(record) => ({
onClick: () => onRowClick?.(record),
className: selectedRow?.[rowKey] === record[rowKey] ? "row-selected" : "",
})}
/>
2026-07-02 00:36:49 +00:00
{isEmptyTable ? <div className="list-table-empty-overlay">{emptyContent}</div> : null}
</div>
);
}
export default ListTable;