ProTable
pro-table在数据列表外配齐查询条件、工具栏、行选择和分页,拼出完整列表页
用法
托管模式
传 request 即由 ProTable 自管 page/sort/filters/loading/data,按需请求服务端。
暂无数据 | ||||||
const request = async (p) => {
const rows = await fetchEmployees(p.filters, p.sort);
const start = (p.page - 1) * p.pageSize;
return { data: rows.slice(start, start + p.pageSize), total: rows.length };
};
<ProTable
title="员工列表"
columns={columns}
request={request}
defaultPageSize={8}
enableRowSelection
search={{ fields: searchFields }}
/>默认排序 + 固定查询参数
defaultSorting 让首次 request 就带排序;params 是页面上下文钉死的条件,浅比较变化即回第 1 页重查。request/params 都可内联,不会重复请求。
暂无数据 | |||||
<ProTable
title="默认按月薪倒序"
columns={columns}
defaultSorting={[{ id: "salary", desc: true }]}
params={{ dept }}
request={async (p) => {
const { rows, total } = await api.list({
page: p.page,
pageSize: p.pageSize,
sort: p.sort,
...p.filters,
...p.params,
});
return { data: rows, total };
}}
/>cursor 分页
paginationMode="cursor",request 返回 { data, nextCursor, hasMore },底部为上一页/下一页。
暂无数据 | |||||
<ProTable
title="日志"
columns={columns}
request={request}
paginationMode="cursor"
defaultPageSize={8}
pageSizeOptions={[8, 16, 32]}
/>展示模式(受控分页)
自管 data/分页时,传 data + pagination + search 回调即可。
| 员工 01 | 1 | 研发部 | 在职 | 2024-01-15 | ¥8,000 | |
| 员工 02 | 2 | 市场部 | 离职 | 2024-02-15 | ¥9,500 | |
| 员工 03 | 3 | 财务部 | 待入职 | 2024-03-15 | ¥11,000 | |
| 员工 04 | 4 | 人事部 | 在职 | 2024-04-15 | ¥12,500 | |
| 员工 05 | 5 | 研发部 | 离职 | 2024-05-15 | ¥14,000 | |
| 员工 06 | 6 | 市场部 | 待入职 | 2024-06-15 | ¥15,500 | |
| 员工 07 | 7 | 财务部 | 在职 | 2024-07-15 | ¥17,000 | |
| 员工 08 | 8 | 人事部 | 离职 | 2024-08-15 | ¥18,500 |
<ProTable
title="员工列表"
columns={columns}
data={pageData}
enableRowSelection
onReload={reload}
toolbarActions={<Button size="sm">+ 新增员工</Button>}
search={{ fields: searchFields, onSearch, onReset }}
pagination={{ page, pageSize, total, onPageChange: setPage }}
/>精简表
紧凑密度 + 关掉全屏按钮 + 无查询区。
| 员工 01 | 1 | 研发部 | 在职 |
| 员工 02 | 2 | 市场部 | 离职 |
| 员工 03 | 3 | 财务部 | 待入职 |
| 员工 04 | 4 | 人事部 | 在职 |
| 员工 05 | 5 | 研发部 | 离职 |
<ProTable
title="紧凑表"
columns={columns.slice(0, 4)}
data={rows}
density="compact"
toolbar={{ fullscreen: false }}
/>何时用
企业中后台「一整个列表页」的旗舰组件:顶部查询区(SearchForm)+ 工具栏(密度/列设置/刷新/全屏)+ 主表(Table)+ 底部分页一套打包。只要列表 + 服务端分页/排序/筛选,优先用它。区别于 Table:Table 是裸表皮,要自己拼查询区、工具栏、分页、请求生命周期;ProTable 的「托管模式」(传 request)连这些都自管。
导入
import { ProTable } from "@hulianui/ui"Props
继承 Omit<TableProps<TData>, "data">(即 Table 的 columns/enableSorting/enableRowSelection/density/getRowId/rowClassName… 全可用),并新增:
大数据列表记得开 `virtual`。 它继承自 Table,透传下去即生效,但因为没有出现在下面这张表里, 很容易被当成 ProTable 不支持而把上万行直接铺进 DOM:tsx <ProTable columns={columns} request={fetchRows} virtual={{ enabled: true, height: 480 }} />参数与禁忌见 Table 的 `virtual`(需装@tanstack/react-virtual;不建议与树形/明细面板、行拖拽同开)。
| 名称 | 类型 | 默认 | 说明 |
|---|---|---|---|
| data | TData[] | - | 展示模式必传;托管模式由 request 提供,忽略此项 |
| request | (params: ProTableRequestParams) => Promise<ProTableRequestResult<TData>> | - | 传则进「托管模式」:自管 page/pageSize/sort/filters/loading/data/选择,忽略 data/pagination/loading。内部以 ref 持有,不进请求依赖(内联箭头函数也不会死循环) |
| params | Record<string, unknown> | - | 托管模式固定查询参数;浅比较,内容变化才回第 1 页重查。以 params 字段单独传给 request,不混入 filters |
| paginationMode | "page" | "cursor" | "page" | 托管分页协议:page=返回 {data,total} 数字分页;cursor=入参带 cursor、返回 {data,nextCursor,hasMore} 上下页 |
| defaultPageSize | number | 10 | 托管模式初始每页条数 |
| defaultSorting | SortingState | [] | 托管模式初始排序(非受控默认值,仅首次挂载生效);首次 request 即带上,用来表达「默认按某列倒序」 |
| pageSizeOptions | number[] | - | 提供则渲染「每页条数」切换器(如 [10,20,50,100]) |
| pagination | ProTablePagination | - | 展示模式集成分页(底部);{page,pageSize,total,onPageChange,showFirstLast?,onPageSizeChange?} |
| search | Omit<SearchFormProps,"onSearch"> & { onSearch? } | - | 集成查询区(复用 SearchForm);托管模式下 onSearch 可省 |
| toolbar | boolean | ProTableToolbarFeatures | true | true=全开 / false=不渲染 / 对象逐项开关(reload/density/columnSetting/fullscreen) |
| loading | boolean | - | 加载态:刷新图标旋转 |
| actionRef | Ref<ProTableActions> | - | 命令式句柄:reload() 重新请求 / clearSelection() 清选 |
| columnVisibility | Record<string, boolean> | - | 受控列显隐(列 id → 是否可见,缺省的键视为可见)。与 rowSelection / sorting 同口径:传了就受控、必须配 onColumnVisibilityChange,不传才内部自持。列 id 取 ColumnDef.id,没有则取 accessorKey。带 meta.lockVisible 的列恒可见,对它写 false 不生效 |
| rootClassName | string | - | 外层容器类名(区别透传 Table 的 className) |
Events
继承的 Table 事件(onSortingChange / onRowSelectionChange / onExpandedChange / onColumnFiltersChange)随 Omit<TableProps,"data"> 一并可用。ProTable 自有:
| 事件 | 类型 | 说明 |
|---|---|---|
| onReload | () => void | 点工具栏刷新图标触发 |
| onRequestError | (error: unknown) => void | 托管 request 失败回调(默认 console.error);失败时 loading 复位、保留上次数据 |
| onColumnVisibilityChange | (next: Record<string, boolean>) => void | 列显隐变化。回传的是完整的下一份映射(不是 patch),直接落 localStorage / PATCH 回服务端即可 |
Slots
| 插槽 | 类型 | 说明 |
|---|---|---|
| title | ReactNode | 卡片标题(工具栏左侧) |
| toolbarActions | ReactNode | 工具栏右侧自定义操作(新增按钮等),位于内置图标按钮左侧 |
| batchActions | (ctx: ProTableBatchCtx) => ReactNode | 渲染函数;选中行时渲染批量操作区(需 enableRowSelection) |
禁忌 / 坑
- `columns` 必须 memo(与 Table 同源):cell 函数经 TanStack 的
flexRender被当作组件类型渲染,identity 一变整格卸载重挂。格子里有输入框时直接坏功能 —— 受控输入框每敲一个字失焦 + 光标跳末尾,挂了onBlur提交的还会被重挂时的 blur 触发误提交。useMemo的依赖里不要放逐键变化的输入值(那等于没 memo),行内编辑优先让输入框非受控。
- 托管模式(传
request)下data/pagination/loading三个 prop 被忽略——别两种模式混用。cursor 分页无 total/不能随机跳页,且 filters/sort/pageSize 任一变化会自动重置回第 1 页。 - 托管模式必须给
getRowId,否则行选择/批量在翻页后 key 不稳。 - 行选择的判据是「你有没有接管」,不是「是不是托管模式」:传了
rowSelection就走受控(须同时给onRowSelectionChange,否则勾不动且只有 dev 告警提示),不传才由组件内部自持。0.29.0 及之前托管模式一律自持、把这两个 prop 静默丢弃——页面上勾得动、表头全选框也会变半选,看起来完全正常,但消费方的 state 恒为{},直到提交时拿到空数组才暴露(#202)。 - 列显隐同一套受控判据(#236):传
columnVisibility就是你接管(须同时给onColumnVisibilityChange,否则列设置点不动且只有 dev 告警)。不接管时列偏好只活在内部 useState 里 —— 工具栏点得动、刷新就没了,「同一个运营换台机器、列偏好跟人走」这件事在组件外一行都写不了。映射口径是缺省即可见,所以落库只需要记被关掉的那几列。 - 身份列与操作列要挂 `meta.lockVisible`:全量开关表达不了「这两列不给关」,而关掉了它们的那一行既没有身份也没有出口。锁定列在工具栏里置灰且恒选中,受控值对它写
false也不生效 —— 否则一份旧的落库偏好就能把出口关掉,而界面上根本打不开。 - 组件仍留着「不允许关掉最后一列可见列」的保底(避免空表头),受控模式下这一步表现为回调不触发。
requestreject 默认走console.error兜底(保证不 unhandled),生产里接onRequestError弹 toast / 上报。batchActions需配合enableRowSelection且有选中行才显示警示条。- `request` 走 ref 持有,不进请求依赖:内联写
request={async (p) => …}不会因函数身份每次 render 变化而无限请求(组件层防呆,不需要消费者useCallback)。代价是换一个 request 函数本身不会触发重查——要换数据源请改params,或调actionRef.reload()。 - `defaultSorting` 是非受控默认值:只在首次挂载读一次,之后由用户点表头接管,后续改这个 prop 不会回灌(同
defaultValue家族)。想在运行中强制改排序请用key重挂或改用受控sorting。展示模式下它不生效(展示模式的默认排序直接传sorting)。首点方向由 TanStack 按列类型决定(数值列默认 desc 优先),要精确控制请写defaultSorting而非依赖点击。 - `params` 是浅比较(只比第一层):
params={{ scopeId }}这种内联对象字面量安全,不用useMemo;但params={{ filter: { a: 1 } }}这种嵌套对象每次 render 都是新引用 → 每次都重查,嵌套值请自己保持引用稳定或拍平成一层。 params不会并入 `filters`:filters只装查询区提交的值,params单独一个字段。这样固定条件不会被同名 filter 覆盖、也不受查询区「重置」影响;request 里自己合并{ ...p.filters, ...p.params }。params内容变化会强制回到第 1 页(cursor 模式同时重置游标栈)——旧页码/旧游标在新固定条件下已无意义。
相关
Table · Book3D · PricingTable · JsonViewer · EditableTable · List