MUI 虚拟数据表
一个高性能的虚拟数据表组件,用于与Material-UI集成进行React,以虚拟化渲染,无限卷轴,键盘导航,自定义型号为主.
(原始内容存档于2018-09-29). React QQ Material-UI QQ. 가상화,무한也.
特征
- 🚀 高性能: 虚拟渲染用反应-虚拟来平滑地用大数据集滚动
- ♾️ 无限滚动: 内置有自动加载指示器的无限滚动
- ⌨️ 键盘导航: 完整的键盘支持(箭头键,PageUp/Down,Home/End)
- 🎨 物质-UI 整合: 与MUI组件无缝集成
- 🦓 斑马脱衣: 带有自定义颜色支持的可选交替行颜色
- 🔧 类型脚本支持: 包含全面类型定义的完整类型脚本支持
- 🔄 排序: 内置列排序功能
- 🎯 可自定义: 高度自定义的列、样式和滚动条选项
- 📊 空状态: 内建空状态处理自定义消息
- 🔍 装入状态: 有淡出动画的平滑加载过渡
- 📏 分组信头: 支持带有动态高度计算的分组列标题
- 🎨 自定义滚动栏: 带有完全定制选项的美丽的自定义滚动条
주요
- 🚀 고성능{\fn黑体\fs22\bord1\shad0\3ahbe\4ah00\fscx67\fscy66\2chffffff\3ch808080}你觉得呢
- ♾️ 무한자동:인디케이터가也
- ⌨️ 키보드:
- 🎨 物质UI翻译:
- 🦓 얼룩말{\fn黑体\fs20\shad2\2aH82\3aH20\4aH33\fscx95\3cH592001\be1}你觉得怎样
- 🔧 类型脚本QQ:====================================================================================================== (======================================================================
- 🔄 정렬내장된:
- 🎯 커스터마이징컬럼:컬럼也
- 📊 빈커스텀:지원하는也
- 🔍 로딩{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你觉得呢
- 📏 그룹동적:계산을也
- 🎨 커스텀완전한:옵션을也
安装
npm install @ehfuse/mui-virtual-data-table
同伴依赖关系
此软件包需要以下对等依赖性:
npm install react react-dom @mui/material react-virtuoso
页:1
详细的API参考和使用示例:
- 한국어 - API 설명 및 가이드
- {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}你觉得呢 - 다양한 사용
- 开始(英文) - API 参考和指南 (快来了)
快速启动
import { VirtualDataTable } from "@ehfuse/mui-virtual-data-table";
import type { DataColumn } from "@ehfuse/mui-virtual-data-table";
interface User {
id: number;
name: string;
email: string;
}
const columns: DataColumn<User>[] = [
{ id: "id", text: "ID", width: 80 },
{ id: "name", text: "Name", width: 200, sortable: true },
{ id: "email", text: "Email", width: 250 },
];
const data: User[] = [
{ id: 1, name: "John Doe", email: "[email protected]" },
{ id: 2, name: "Jane Smith", email: "[email protected]" },
// ... more data
];
function App() {
return (
<VirtualDataTable
data={data}
columns={columns}
totalCount={data.length}
loading={false}
/>
);
}
基本支持签名
<VirtualDataTable
// Required
data={T[]} // Data array
columns={DataColumn<T>[]} // Column definitions
totalCount={number} // Total data count
// Optional - Styling
striped={boolean | string} // Zebra striping (true: default gray (#f5f5f5), string: custom color, default: false)
rowDivider={boolean} // Show row borders (default: true)
rowHeight={number} // Fixed row height in px (default: 50) — rows keep this height even when showFooter fills a taller container
columnHeight={number} // Header height in px (default: 56, auto 2x for grouped headers)
showPaper={boolean} // Wrap in Paper component (default: true)
paddingX={string | number} // Horizontal padding (default: "1rem")
paddingTop={string | number} // Top padding (default: 0)
paddingBottom={string | number} // Bottom padding (default: 0)
variant={"default" | "notion"} // Table look. "notion": Notion-like (40px header, 13px grey header text, cell dividers, flat hover) — matches @ehfuse/taskbox tables
rowHoverColor={string} // Row hover background color (default: "#000000", auto-inverted brightness in dark mode)
rowHoverOpacity={number} // Row hover opacity 0-1 (default: 0.06)
viewportBuffer={number | { top, bottom }} // Viewport outside pre-render buffer in px (default: rowHeight-based auto buffer)
// Optional - Infinite Scroll
loading={boolean} // Loading state (default: false)
onLoadMore={(offset, limit) => void} // Load more callback (enables infinite scroll when provided)
// ⚠️ onLoadMore 계약: 호출되면 반드시 이행해서 data 가 늘어나거나 loading 이 토글돼야 합니다.
// (정렬/검색/필터로 목록을 1페이지로 되돌리면 컴포넌트가 자동으로 더보기 잠금을 풀어 재개합니다.)
// Optional - Sorting
sortBy={string} // Current sort field
sortDirection={"asc" | "desc"} // Current sort direction
onSort={(id, direction) => void} // Sort callback
// Optional - Interactions
onRowClick={(item, index) => void} // Row click handler
// Optional - Customization
emptyMessage={string | React.ReactNode} // Empty state message (default: "NO DATA")
scrollbars={VDTOverlayScrollbarProps} // Custom scrollbar options
LoadingComponent={React.ComponentType} // Custom loading component
// Optional - Layout (1.2.0)
framed={boolean} // notion: outer border + paper padding (default: true). false = fill an already-bordered box
headerTopBorder={boolean} // notion + framed=false: draw a top line on the header row (table shares a box with a toolbar above)
tableLayout={"fixed" | "auto"} // "auto": column widths follow content, wide tables scroll horizontally
rowHeightMode={"fixed" | "auto"} // "auto": rowHeight becomes a minimum, rows grow with content
autoHeight={boolean} // No virtualization / no own scroll — the table is as tall as its rows (page scrolls)
renderFullWidthRow={(item, index) => ReactNode | null} // Group band row: one cell spanning all columns (null = normal row)
fullWidthRowSx={SxProps} // Style of the band row cell (background, padding)
onRowContextMenu={(item, index, event) => void}
// Optional - 1.3.0
pinnedRows={T[]} // Rows pinned under the header (always visible, same columns/cells/band rows)
pinnedRowsMaxHeight={number} // Max height of the pinned area in px — it scrolls on its own beyond that
pinnedRowsSx={SxProps} // Pinned area style (default: 1px strong bottom border)
resetScrollOnDataChange={boolean} // false: keep the scroll position when rows are removed (collapse groups); remount with key to reset
rowKeyFromId={boolean} // Use getRowId as the React key of rows (rows with inline editors)
onEndReached={() => void} // Fired every time the end is reached (no minimum / no same-length latch)
colorMode={"light" | "dark"} // notion colors independent of the MUI palette mode
borderColor={string} // notion: color for every line (outer frame, header line, cell dividers). Default NOTION_LINE_COLOR (#d9d8d5, exported) — every notion line uses one color
mobile={boolean | { query?, renderRow? }} // 1.6.0 card list on narrow screens (see "Mobile list")
scrollParent={HTMLElement | null} // 1.7.0 virtualize against an outer (page) scroller
columnResize={boolean | { minWidth? }} // 1.8.0 drag the header's right edge to resize a column (see "Column resize")
columnWidths={Record<string, number>} // 1.8.0 resized widths by column id (you store them)
onColumnWidthsChange={(widths) => void} // 1.8.0 fired once on release / on double-click reset
rowReorder={{ onReorder, canDrag?, disabledReason?, handleWidth?, handleLabel? }} // 1.9.0 drag rows by a handle (see "Row reorder")
// Optional - 1.4.0 header context menu (right-click on th never shows the browser menu)
onSortClear={() => void} // adds "정렬 풀기" to the header menu (and re-picking the active direction clears)
onHeaderContextMenu={(column, event) => boolean | void} // custom header menu — return true or preventDefault() to skip the built-in one
/>
列选项 (1.2.0)
{ id: "name", text: "이름", width: 160, pinned: "left" } // sticky left (numeric width needed for the next pinned column)
{ id: "name", text: "이름", primary: true } // 15px body text for the row's main cell
{ id: "d1", text: "월", onCellClick: (item, i, e) => {} } // per-cell click + cell hover (row click is not fired)
{ id: "who", text: "담당", rowSpan: (item, i) => 2 } // merge down; return 0 on covered rows (use with autoHeight / short tables)
{ id: "m1", text: "1월", cellPadding: "4px 0" } // body padding override
{ id: "t", text: "업무", cellStyle: (item) => ({ color: "#999" }) } // per-row cell style
{ id: "c", text: "열", headerProps: { draggable: true, onDrop } } // extra th attributes (drag to reorder, title…)
{ id: "who", text: "책임자", width: 110, colSpan: (item) => (item.isEvent ? 2 : 1) } // 1.3.0: merge right (the covered column is not drawn)
{ id: "title", text: "제목", minWidth: 220 } // 1.3.0: flexible column (no width) with a minimum — table min width = sum, scrolls sideways
页眉上下文菜单(1.4.0)-右键单击页眉单元格:
// sort items appear for sortable columns when onSort is given
{ id: "name", text: "이름", sortable: true }
// filter checklist (multi by default; filterMultiple: false = pick one)
{
id: "status", text: "상태",
filterOptions: [{ value: "open", label: "진행" }, { value: "done", label: "완료" }],
filterValues: status, // string[] — non-empty → header turns primary + filter icon
onFilterChange: (values) => setStatus(values),
}
{ id: "memo", text: "메모", headerMenu: false } // no menu on this column (browser menu still blocked)
{ id: "due", text: <MyHeader />, sortMenu: true } // 1.4.1: sort items in the menu without VDT's sort label/click (custom-drawn header)
// open your own header menu instead
<VirtualDataTable onHeaderContextMenu={(col, e) => (col.id === "owner" ? (openOwnerMenu(e), true) : false)} … />
共享页眉菜单( 1. 5.0) – 从自定义页眉按钮/ 工具栏按钮中自己打开 :
import { HeaderMenu, headerMenuAnchor } from "@ehfuse/mui-virtual-data-table";
const [pos, setPos] = useState<{ left: number; top: number } | null>(null);
<button onClick={(e) => setPos(headerMenuAnchor(e.currentTarget))}>상태</button>
<HeaderMenu
open={Boolean(pos)}
anchorPosition={pos}
onClose={() => setPos(null)}
sort={{ active: sortDir, onChange: (d) => setSortDir(d) }} // null = 풀기
filter={{
options: [{ value: "open", label: "진행", icon: <Dot color="green" /> }],
value: picked, onChange: setPicked,
multiple: true, // false = radio (closes on pick), allLabel: "전체"
emptyLabel: "비어 있음", // adds value ""
searchable: undefined, // default: > 10 options
}}
>
{/* extra items */}
</HeaderMenu>
平接行实例 (1.3. 0) :
<VirtualDataTable
variant="notion"
data={rows} // without the pinned ones
pinnedRows={[{ kind: "band", label: "미확인" }, ...unconfirmed]}
pinnedRowsMaxHeight={49 * 6} // band + 5 rows, then it scrolls
renderFullWidthRow={(r) => (r.kind === "band" ? r.label : null)}
getRowId={(r) => r.id}
rowKeyFromId
resetScrollOnDataChange={false}
...
/>
概念默认 (1. 2. 0): 第48行, 标题文本 13px, 所选行(selectedRowId= (单位:千美元) #dbeafe + 3px 键 #3b82f6 左栏(包含暗值),
字符串 emptyMessage =头下为一浅灰色线条,页脚细胞使用体垫.
页面滚动 (1.7. 0)
scrollParent={element} 虚拟化对照外向滚动器(反应-虚拟) customScrollParent)——该表本身没有卷轴
并随页面而增长,所以捆绑到页面卷轴上的东西(collapsing app bar,滚动到上,拉到再刷)继续工作.
过 null ,而该元素尚不为人所知(在此之前没有绘图)。 与 mobile 财务报告和审定财务报表 onEndReached。 。 。 .
列大小调整(1.8.0)
columnResize 抓住每个信头的右边缘(col-resize 光标,悬浮上的细线). 拖动它来调整列的大小,
双击它,然后回到专栏 width。表格不存储任何东西——将宽度保留在您喜欢的地方
(账户设置, 本地Storage...) 并用 columnWidths没有 columnWidths 桌子只记得他们 直到它重新挂起.
const [widths, setWidths] = useAccountSetting("customer.list"); // Record<columnId, px>
<VirtualDataTable
columnResize // or { minWidth: 60 } — default minimum 48px (a column's own minWidth wins)
columnWidths={isPhone ? undefined : widths}
onColumnWidthsChange={setWidths} // once on release; double-click → the key is removed
...
/>
- 虽然只将 CSS 变量拖入到表格框变化中——行不重递,但虚拟化和水平卷轴保持不变.
- 拖曳之后的点击被吞噬, 因此释放到一个标题上 永远不要排序 / 打开标题菜单 .
- 灵活列( 无)
width(b) 保持最低限度的minWidth(或调整最小大小) 一旦任何东西被调整大小; 当每列结束 表格具有像素宽度,宽度与总和一样(没有拉伸使所拉宽与所拉宽不同). 如果没有一列是灵活的,第一个拖放会捕捉到每个一列所绘制的宽度(1.8.1),所以其他一列不会跳出. - 与
pinned: "left"(粘贴相冲后),autoHeight, (中文(简体) ),framed, (中文(简体) ),tableLayout, (中文(简体) ),variant="notion", (中文(简体) ),pinnedRows脚印. 在那个mobile没有卡片列表columnWidths被忽略.
行重排( 1. 9. 0)
rowReorder 添加拖动手柄( QQ) 为第一个单元格 。 拖放以移动行; 显示行将降落在哪里 onReorder(from, to, item)
释放时起火(to 是行尾的索引——与 arrayMove(data, from, to)) (中文(简体) ). 桌子永远不变 data· ;
自己重新订购,传回.
<VirtualDataTable
rowReorder={{
onReorder: (from, to) => setRows((r) => arrayMove(r, from, to)),
disabledReason: sortBy ? "정렬을 풀면 옮길 수 있어요" : undefined, // handle is dimmed, drag is off
canDrag: (row) => !row.locked, // no handle on locked rows
}}
...
/>
- 拖曳时没有再退让——被拖走的行得到一个
data-vdt-dragging属性( dimmed) 和放行是一个位置元素, 因此它保持轻与虚拟化和长列表。 拖入边缘自动滚动(表卷轴,scrollParent,或文档). - 键盘:将手柄聚焦于 → 空间(升起) → / (移动一步,
onReorder空间 / 输入 / Esc( 投放) . - 在那个
mobile卡片列表 手柄坐落在卡片左边. 与pinned: "left"手柄柱也被固定. 输入行pinnedRows无法移动 。 与autoHeight, (中文(简体) ),scrollParent, (中文(简体) ),variant="notion", (中文(简体) ),columnResize。 。 。 . - 除手柄外,行点击仍然在任何地方工作;拖动后点击不作为行点击处理.
移动列表 (1.6.0)
mobile 将表格转换为窄屏幕上的卡片列表(默认查询) (max-width:599.95px):没有标题行,每行一张牌,
虚拟化/ onLoadMore 编号 : onRowClick 编号 : selectedRowId 一样的工作。 上面的菜单、页脚和被绑的行不画在那里.
<VirtualDataTable
mobile // or { query: "(max-width:899.95px)", renderRow: (item, index) => <MyCard/> }
columns={[
{ id: "thumb", text: "", mobile: "thumb", render: (r) => <img src={r.image} width={56} /> },
{ id: "title", text: "작품", mobile: "title" },
{ id: "status", text: "상태", mobile: "badge", render: (r) => <Chip label={r.status} /> },
{ id: "artist", text: "작가", mobile: "sub" },
{ id: "price", text: "판매가" }, // no hint → meta line "판매가 1,000,000원"
{ id: "memo", text: "메모", mobile: false }, // hidden on mobile
]}
...
/>
槽数 : title (粗体一行-默认:第一栏), thumb (左边), badge (标题权), sub (标题下行),
meta ("头值"对相接-其他列的默认), false (隐也. mobileLabel 覆盖元标签( n)false = 仅值).
许可证
麦特 金·杨金(原始内容存档于2018-10-21). [email protected](中文(简体) )
捐款
欢迎捐款! 请随意提交拉力请求.
支助
支持, 电子邮件 (原始内容存档于2018-10-21). [email protected] 或者在 GitHub 上创建一个问题.