更新记录
1.0.10(2026-05-09)
Bug 修复
UniLegacyRenderer 不支持 #RRGGBBAA 颜色格式导致 App 端背景色渲染为黑色
- renderer/UniLegacyRenderer.ts:新增私有方法
_normalizeColor(color)——检测到 9 位#RRGGBBAA格式时自动转换为rgba(r,g,b,a)字符串,其余格式透传;旧版 canvas API(uni.createCanvasContext)不支持 CSS4 的#RRGGBBAA写法,遇到时会退为黑色;setFillStyle/setStrokeStyle调用前均经过此归一化,上层代码无需感知平台差异
1.0.9(2026-05-07)
性能优化
LayerDrawHelper 消除每帧 O(N) 分配
- types.ts:
TableLayout新增layerCols: IColumnGroup[]、layerColOffsets: number[]、layerColKeyMap: Map<string, number>三个字段——按 fixed 分组后的合并叶列数组、列宽前缀和及 key→索引 Map,列变化时重建一次,帧内直接复用 - hooks/useTableLayout.ts:
computeTableLayout在列分组计算完成后一次性构建layerCols/layerColOffsets/layerColKeyMap并嵌入返回对象 - layers/LayerDrawHelper.ts:构造函数改为直接引用
layout.layerCols/layout.layerColOffsets/layout.layerColKeyMap,移除buildColOffsetsimport;消除每帧 2× O(列数) 的数组分配与 Map 构建开销(Overlay body + fixedTop 两路各触发一次)
drawText / drawSmoothLine / drawPolyline / drawArea 行列双向自动裁剪
- layers/LayerDrawHelper.ts:
drawText改为复用_isInVisibleRange做行列双向可见性检测——以text.length × fontSize作为文字宽度的严格保守上界(CJK ≈ 1× fontSize/char,ASCII ≈ 0.55×,取 1× 保证无漏判),消除旧版仅有 Y 轴检测的遗漏;canvas clip 保障像素级正确性,_isInVisibleRange仅对完全位于主区可见列窗口外的文字跳过,固定列区域恒允许;至此LayerDrawHelper全部draw*方法均实现行列双向裁剪 - layers/LayerDrawHelper.ts:新增私有方法
_trimPoints(points, buffer)——检测点集主方向:Y 单调递增时按可见行像素窗口(_visY1/_visY2)裁剪;X 单调递增且整体在主区内时按可见主区列像素窗口(_visMainX1/_visMainX2)裁剪;两端各保留buffer个边界点供 Catmull-Rom 切线计算;跨固定列或双向非单调时不裁剪,保证正确性 - layers/LayerDrawHelper.ts:
drawSmoothLine、drawPolyline、drawArea在发送 Canvas 命令前调用_trimPoints(points, 1),将 JSBridge 命令量从 O(总行/列数) 降至 O(可见行/列数);500 行趋势线场景约减少 94% 的bezierCurveTo/lineTo调用,App 平台滑动帧率显著提升
1.0.8(2026-05-06)
性能优化
OverlayLayer 自定义绘制全局可见性裁剪
- layers/LayerDrawHelper.ts:新增完整的行列双向可见性裁剪系统——构造时从
ILayerDrawContext.virtual预算可见行范围(_visRowStart/_visRowEnd)、主区可见列范围(_visColStart/_visColEnd)、可见区像素边界(_visY1/_visY2、_visMainX1/_visMainX2)以及主区全范围像素边界(_mainAreaX1/_mainAreaX2),帧内常量,零额外计算 - layers/LayerDrawHelper.ts:
drawCell*系列(drawCellBg/drawCellCircle/drawCellText/drawCellBadge)和drawColBg/drawRowBg均加入行列索引级早退检测,不在可见窗口内的单元格直接跳过,避免无效 Canvas API 调用 - layers/LayerDrawHelper.ts:
drawCircle/drawRect/drawRoundRect/drawHLine/drawVLine/drawLine加入像素级包围盒检测(_isInVisibleRange),包围盒完全在可见区域外则跳过 - layers/LayerDrawHelper.ts:
_isInVisibleRange使用预算的_mainAreaX1/_mainAreaX2常量,热路径消除每次调用时的fixedLeftPx/fixedRightPx数组查找和算术计算
StickyHeaderLayer 表头列虚拟化
- layers/StickyHeaderLayer.ts:min 区单元格绘制和边框循环均增加列虚拟化过滤——基于
virtual.colStart/colEnd转换为全局叶列索引范围,跳过完全在可见区域外的单元格,含colSpan>1的分组表头仅在任意部分可见时才绘制;100 列只有 10 列可见时每帧 Canvas API 调用量降低约 90% - layers/StickyHeaderLayer.ts:查询
layout.leafColOffsets直接复用全量叶列前缀和,替代每帧重新分配和填充colOffsets数组
TableLayout 增加共享字段
- types.ts:
TableLayout新增leafColOffsets: number[]——全量叶列宽度前缀和(fixedLeft → main → fixedRight),长度 =leafColumns.length + 1,供 StickyHeaderLayer 等直接复用 - types.ts:
TableLayout新增hasSpan: boolean——布局计算时一次性检测主区是否存在span函数,避免BodyLayer每帧调用Array.some - hooks/useTableLayout.ts:
computeTableLayout在已算好mainColOffsets后延伸计算leafColOffsets和hasSpan,一并嵌入返回对象
BodyLayer 消除每帧高频遍历
- layers/BodyLayer.ts:
mainContentW由每帧Array.reduce改为直接取layout.mainColOffsets[mainCols.length](O(N) → O(1)) - layers/BodyLayer.ts:hover 列索引查找由每帧
Array.findIndex改为Map.get()(O(N) → O(1)),Map 仅在mainCols引用变化时重建 - layers/BodyLayer.ts:
hasSpans由每帧Array.some改为读取layout.hasSpan布尔缓存
hitTest 列命中改二分查找
- hooks/useHitTest.ts:
hitTestLeafColIndex和hitTestColKey的主区列命中由 O(N) 线性扫描改为利用mainColOffsets的二分查找(O(N) → O(log N)),优化高频 mousemove 场景下的列定位性能
BodySpanMask 跨帧缓存
- layers/BodyLayer.ts:引入
_spanMaskCache字段缓存上一帧的BodySpanMask;当data与mainCols引用不变且当前rowStart/rowEnd完全在上次扫描范围内时直接复用,已避免每帧 O(可见行 × 所有列)的span()函数重复调用
Bug 修复
OverlayLayer payload 遗漏新增属性导致崩溃
- layers/OverlayLayer.ts:手动构建的 payload 对象补充转发
visibleRowRange/visibleColRange(getter)、isRowVisible/isColVisible,修复因属性未转发导致payload.visibleRowRange.start抛出TypeError: Cannot read property 'start' of undefined的崩溃
drawCellBadge 测量宽度前未设置字号
- layers/LayerDrawHelper.ts:
drawCellBadge在调用measureText前加入this._r.setFontSize(fontSize),修复沿用上次渲染调用遗留字号状态导致 badge 宽度错误的问题
API 新增
ILayerDrawHelper 新增可见范围查询字段
- types.ts:
ILayerDrawHelper新增visibleRowRange: Readonly<{ start: number; end: number }>——当前可见行范围,可用于跳过不可见行的 JS 侧自定义计算 - types.ts:
ILayerDrawHelper新增visibleColRange: Readonly<{ start: number; end: number }>——当前可见主区列范围(绝对索引),fixedLeft/Right 列不受此约束 - types.ts:
ILayerDrawHelper新增isRowVisible(rowIndex: number): boolean——判断行是否在可见窗口内,draw*系列已内置此判断,通常无需手动调用 - types.ts:
ILayerDrawHelper新增isColVisible(colIndex: number): boolean——判断列是否在可见窗口内,fixedLeft/Right 列恒返回true
文档
- readme.md:
ILayerDrawHelper接口代码块同步新增 4 个字段;坐标查询 API 说明表补充对应条目;单元格便捷系列说明表上方新增内置可见性保护说明
平台兼容性
uni-app(3.7.7)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| - | √ | √ | √ | √ | - | √ | - | - |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | - | - | - | - | - | - | - | - | - | - | - |
其他
| 多语言 | 暗黑模式 | 宽屏模式 |
|---|---|---|
| × | × | √ |
> [!CAUTION]
⚠️ 购买前请务必阅读
- 试用版 和 普通授权版 均 不建议购买,试用版及导入示例项目后无法直接运行,普通授权版也不保证后续持续同步更新。
- 强烈建议购买 源码授权版,可获得完整源码并保障后续版本同步更新。
- 源码授权版定价合理,
pro-table基于单 canvas 分层渲染架构,功能完整、性能出色,物有所值。
pro-table 是一个基于单 canvas 分层渲染的高性能表格组件,面向 uni-app + Vue 3 场景,兼容 H5 / APP / 小程序端。
它不是 DOM 表格,而是把表头、表体、固定列、固定行、阴影、选区、tooltip、拖拽指示线等内容拆成多个 layer 绘制到同一张 canvas 上,因此更适合大数据量、固定列、虚拟滚动、复杂自定义绘制等场景。
特性
- 单 canvas 分层渲染
- H5 / 小程序双端适配
- 固定表头、固定列、固定行
- 行虚拟滚动、主区列虚拟滚动
- 单元格选区
- 行选择 checkbox / radio
- 列宽拖拽、行高拖拽
- 树形数据展开 / 折叠 / 懒加载
- 客户端排序
- 分页数据切片
- 悬浮提示 tooltip
- 自定义单元格绘制、覆盖层绘制、操作按钮
- 单元格横向 / 纵向合并
在线体验
扫描下方二维码,即可体验 pro-table 示例:

赞赏路径
扫描下方二维码,即可赞赏此项目:

快速开始
<template>
<view class="table-wrap">
<pro-table
ref="tableRef"
:columns="columns"
:data="data"
:options="options"
@cell-click="onCellClick"
/>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import ProTable from '@/uni_modules/pro-table/components/pro-table/pro-table.vue'
import type {
ColumnNode,
ProTableOptions,
RowData,
} from '@/uni_modules/pro-table'
interface UserRow extends RowData {
id: number
name: string
dept: string
salary: number
}
const tableRef = ref<any>(null)
const columns: ColumnNode[] = [
{ key: 'id', title: 'ID', width: 80, cellAlign: 'center', headerAlign: 'center' },
{ key: 'name', title: '姓名', width: 120 },
{ key: 'dept', title: '部门', width: 140 },
{ key: 'salary', title: '薪资', width: 120, cellAlign: 'right', sortable: true },
]
const data = ref<UserRow[]>([
{ id: 1, name: '张三', dept: '研发', salary: 12000 },
{ id: 2, name: '李四', dept: '产品', salary: 15000 },
])
const options: ProTableOptions = {
fontSize: 13,
padding: [8, 10],
stripedBgColor: '#fafafa',
}
function onCellClick(payload: any) {
console.log('cell-click', payload)
}
</script>
<style scoped>
.table-wrap {
height: 600px;
}
</style>
组件参数
Props
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| columns | ColumnNode[] |
必填 | 列定义,支持多级表头、固定列、排序、tooltip、自定义 render、单元格合并 |
| data | T[] |
[] |
表体数据 |
| options | ProTableOptions |
undefined |
视觉和交互选项 |
| fixedRows | FixedRowDef[] |
undefined |
固定行定义,同时支持顶部和底部固定行 |
| enableStriped | boolean |
false |
是否启用斑马纹 |
| enableHover | boolean |
true |
是否启用行 hover |
| enableColHover | boolean |
false |
是否启用列 hover |
| enableSelection | boolean |
false |
是否启用单元格框选 |
| rowSelection | RowSelectionConfig |
undefined |
行选择配置,启用后会自动注入选择列 |
| selectedRowKeys | number[] |
undefined |
受控行选择值。注意这里实际是“当前页行索引数组”,不是业务 rowKey |
| bodyOverlayDraw | (payload: OverlayDrawPayload) => void |
undefined |
表体覆盖层自定义绘制 |
| fixedTopOverlayDraw | (payload: OverlayDrawPayload) => void |
undefined |
顶部固定行覆盖层自定义绘制 |
| fixedBottomOverlayDraw | (payload: OverlayDrawPayload) => void |
undefined |
底部固定行覆盖层自定义绘制 |
| loading | boolean |
false |
加载态,开启后显示 loading layer 并阻止滚动交互 |
| treeProps | TreeConfig |
undefined |
树形表格配置 |
| pagination | PaginationConfig |
undefined |
分页配置。组件只负责分页逻辑和事件,不内置分页器 UI |
Props 详细说明
columns
- 作用:描述整张表的列结构,是最核心的输入参数
- 影响范围:表头结构、列宽、固定列、排序、tooltip、自定义单元格绘制、单元格合并都由它决定
- 使用建议:
key必须稳定且唯一,避免在运行时频繁修改列对象结构
data
- 作用:表格主体行数据
- 数据要求:每一项都是一个对象,字段名通常与列
key对应 - 额外能力:除业务字段外,还可以内联
__resizable__、__minHeight__、__maxHeight__等控制字段 - 更新建议:优先替换数组引用,而不是原地深改对象
options
- 作用:统一控制视觉样式和部分交互细节
- 典型用途:边框颜色、背景色、hover 色、字体大小、内边距、选区颜色、拖拽手柄显示
- 适用场景:想整体换风格时优先改这里,而不是在每列里单独覆盖
fixedRows
- 作用:定义固定在表头下方或底部的特殊行
- 典型用途:汇总行、合计行、说明行、固定提示行
- 数据结构:每一项都要显式声明
position: 'top' | 'bottom' - 能力范围:支持按列覆写渲染,也支持固定行内部单元格合并
enableStriped
- 作用:开启后按奇偶行绘制斑马纹背景
- 注意:只是视觉层面的辅助识别,不影响数据和事件
enableHover
- 作用:是否高亮当前悬浮行
- 平台差异:H5 主要响应鼠标悬浮,小程序依赖触摸交互流程
enableColHover
- 作用:是否高亮当前悬浮列
- 适用场景:强调列阅读对齐,例如财务表、对比表
enableSelection
- 作用:开启后可拖拽选择单元格区域
- 注意:这是“单元格框选”,和
rowSelection的“行勾选”是两套独立能力
rowSelection
- 作用:开启行级单选或多选能力
- 具体行为:组件会自动在最左侧插入一列选择列
- 注意:当前实现返回的是“当前页索引”,不是业务主键
selectedRowKeys
- 作用:行选择的受控值
- 实际含义:这里的数组成员不是 rowKey,而是当前分页结果中的行下标
- 适用场景:父组件完全接管行选择状态时使用
bodyOverlayDraw / fixedTopOverlayDraw / fixedBottomOverlayDraw
- 作用:在既有表格绘制完成后,再追加一层自定义图形
- 适用场景:趋势线、热点标记、角标、辅助线、状态点、业务注释
- 区别:三个回调分别作用于表体、顶部固定行、底部固定行区域
loading
- 作用:显示加载层,并阻止常规交互
- 适用场景:接口请求中、局部数据切换中、懒加载等待中
treeProps
- 作用:开启树形表格能力
- 适用场景:目录树、组织架构、层级账目、主从明细
- 能力范围:支持默认展开、指定展开、懒加载、缩进列配置
pagination
- 作用:开启组件内部的数据切片逻辑
- 注意:它不渲染分页器,只保存分页状态并对当前数据做 slice
- 适用场景:你自己在页面外部渲染分页栏,再通过 ref 方法或响应式配置联动
Events
| 事件名 | 回调参数 | 说明 |
|---|---|---|
| cell-click | { row, rowIndex, colKey, value, isHeader, fixedRow?, localX?, localY? } |
点击单元格或表头时触发。点击表头排序列时,会先切换排序再触发该事件 |
| selection-change | { range: SelectionRange \| null } |
选区拖拽过程中持续触发 |
| selection-end | { range: SelectionRange \| null } |
选区拖拽结束时触发 |
| row-select-change | { indices: number[]; rows: T[] } |
行选择变化。indices 为当前分页切片内的索引 |
| update:selectedRowKeys | number[] |
配合 v-model:selectedRowKeys 使用 |
| col-resize | { key: string; width: number } |
列宽拖拽结束时触发 |
| row-resize | { rowIndex: number; height: number } |
行高拖拽结束时触发 |
| sort-change | SortState |
排序变化 |
| load-more | 无 | 滚动到底部附近或内容不足填满容器时触发 |
| page-change | { page: number; pageSize: number } |
调用分页方法或切换页大小时触发 |
事件参数详细说明
cell-click
{
row: T | null
rowIndex: number
colKey: string
value: any
isHeader: boolean
fixedRow?: 'top' | 'bottom'
localX?: number
localY?: number
}
| 字段 | 含义 |
|---|---|
| row | 被点击的行数据。点击表头时为 null |
| rowIndex | 行索引。表头点击时通常为 -1,表体点击时为当前数据索引 |
| colKey | 被点击列的 key |
| value | 当前单元格的值,等价于 row?.[colKey] |
| isHeader | 是否点击的是表头 |
| fixedRow | 如果点击的是固定行单元格,这里会标记 'top' 或 'bottom' |
| localX | 指针在当前单元格内部的相对 X 坐标,可用于判断点到了单元格内部哪个子区域 |
| localY | 指针在当前单元格内部的相对 Y 坐标 |
适用场景:
- 统一处理普通单元格点击
- 区分表头点击和表体点击
- 在自定义 render 中根据
localX/localY做更细粒度交互
selection-change / selection-end
{
range: SelectionRange | null
}
| 字段 | 含义 |
|---|---|
| range | 当前选区。为 null 代表没有有效选区 |
区别:
selection-change在拖拽过程中持续触发,适合实时展示状态selection-end只在拖拽完成后触发,适合做最终提交或复制逻辑
row-select-change
{
indices: number[]
rows: T[]
}
| 字段 | 含义 |
|---|---|
| indices | 当前页中被选中的行索引数组 |
| rows | 与 indices 对应的行数据数组 |
注意:这里不是全量数据索引,也不是业务 id。
col-resize
{
key: string
width: number
}
| 字段 | 含义 |
|---|---|
| key | 被调整宽度的列 key |
| width | 调整结束后的列宽 |
row-resize
{
rowIndex: number
height: number
}
| 字段 | 含义 |
|---|---|
| rowIndex | 被调整高度的 body 行索引 |
| height | 调整结束后的最终行高 |
sort-change
排序回调返回 SortState:
key:当前参与排序的列 keyorder:当前排序方向,可能是'asc'、'desc'或null
page-change
{
page: number
pageSize: number
}
| 字段 | 含义 |
|---|---|
| page | 切换后的页码,从 1 开始 |
| pageSize | 切换后的每页条数 |
Ref 方法
组件通过 defineExpose 暴露了一组实例方法,推荐配合模板 ref 调用。
interface ProTableInstance {
scheduleRender(): void
recalcLayout(): void
scrollToRow(rowIndex: number): void
scrollToColumn(colKey: string): void
scrollTo(x?: number, y?: number): void
selectRows(indices: number[]): void
deselectRows(indices: number[]): void
selectAll(): void
deselectAll(): void
getSelectedRowIndices(): number[]
getSelection(): SelectionRange | null
clearSelection(): void
getSortState(): SortState
setSortState(state: SortState): void
getScrollLeft(): number
getScrollTop(): number
toggleExpand(rowKey: string | number): void
expandAll(): void
collapseAll(): void
getExpandedKeys(): Array<string | number>
goTo(page: number): void
prevPage(): void
nextPage(): void
setPageSize(size: number): void
getCurrentPage(): number
getPageCount(): number
getTotal(): number
}
方法说明
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| scheduleRender | 无 | void |
手动请求下一帧重绘 |
| recalcLayout | 无 | void |
重新计算布局,并自动夹紧滚动位置 |
| scrollToRow | rowIndex: number |
void |
滚动到指定 body 行,使该行进入可视区 |
| scrollToColumn | colKey: string |
void |
滚动到指定主区列。固定列不会参与横向滚动 |
| scrollTo | x?: number, y?: number |
void |
直接设置横纵滚动位置 |
| selectRows | indices: number[] |
void |
追加选中指定索引 |
| deselectRows | indices: number[] |
void |
取消指定索引 |
| selectAll | 无 | void |
全选当前页数据 |
| deselectAll | 无 | void |
清空选择 |
| getSelectedRowIndices | 无 | number[] |
获取当前已选中的当前页索引 |
| getSelection | 无 | SelectionRange \| null |
获取当前单元格选区 |
| clearSelection | 无 | void |
清空选区 |
| getSortState | 无 | SortState |
获取当前排序状态 |
| setSortState | state: SortState |
void |
直接设置排序状态并重绘 |
| getScrollLeft | 无 | number |
获取当前横向滚动位置 |
| getScrollTop | 无 | number |
获取当前纵向滚动位置 |
| toggleExpand | rowKey: string \| number |
void |
切换树节点展开状态 |
| expandAll | 无 | void |
展开全部树节点 |
| collapseAll | 无 | void |
折叠全部树节点 |
| getExpandedKeys | 无 | Array<string \| number> |
获取当前已展开 rowKey 集合 |
| goTo | page: number |
void |
翻到指定页 |
| prevPage | 无 | void |
上一页 |
| nextPage | 无 | void |
下一页 |
| setPageSize | size: number |
void |
设置每页条数,并重置到第 1 页 |
| getCurrentPage | 无 | number |
获取当前页码 |
| getPageCount | 无 | number |
获取总页数 |
| getTotal | 无 | number |
获取总条数 |
方法参数与行为说明
scheduleRender()
- 含义:请求组件在下一帧重新绘制
- 什么时候用:外部数据没有变,但你知道自定义覆盖层或内部显示状态需要刷新时
- 注意:它只负责重绘,不负责重新计算布局
recalcLayout()
- 含义:重新计算列宽、行高、总宽高、固定区宽度等布局信息
- 什么时候用:容器尺寸变化、列定义变化、你手动修改了需要影响布局的数据时
- 补充:内部会顺带把滚动位置限制到合法范围,避免空白区域
scrollToRow(rowIndex)
- 参数含义:
rowIndex是 body 区数据索引,从0开始 - 行为:若目标行已在可视区内,不会多余滚动;若越界则直接忽略
- 注意:这里不支持传固定行索引,也不是业务主键
scrollToColumn(colKey)
- 参数含义:
colKey是你在columns中定义的列 key - 行为:只会滚动主区列;如果目标列本身是固定列,不会产生横向滚动效果
- 适用场景:表单校验后快速跳到某一列、联动定位某列
scrollTo(x, y)
- 参数含义:
x是横向滚动值,y是纵向滚动值,单位都是像素 - 行为:任一参数传
undefined表示该方向保持不变 - 补充:内部会自动夹紧到合法滚动范围
selectRows(indices)
- 参数含义:
indices是当前页内需要追加选中的行索引数组 - 行为:是“追加选中”,不会先清空已有选择
deselectRows(indices)
- 参数含义:
indices是当前页内需要取消选择的行索引数组 - 行为:只移除指定项,不影响其它已选行
selectAll()
- 含义:选中当前分页切片中的全部行
- 注意:不是全量数据全选
deselectAll()
- 含义:清空当前组件维护的行选中状态
getSelectedRowIndices()
- 返回值含义:返回当前页中已选行索引,结果会按从小到大排序
getSelection()
- 返回值含义:返回当前单元格框选范围
- 结果解释:若返回
null,表示当前没有有效选区
clearSelection()
- 含义:清空当前单元格选区,不影响行选择状态
getSortState() / setSortState(state)
getSortState():获取当前排序列和排序方向setSortState(state):直接改排序状态并重绘state.key:排序列 keystate.order:'asc'、'desc'或null- 注意:这里是直接设置内部排序状态,适合做外部排序同步
getScrollLeft() / getScrollTop()
- 返回值含义:当前实际滚动位置,单位是像素
toggleExpand(rowKey)
- 参数含义:
rowKey是树节点业务主键,对应treeProps.rowKey指定的字段值 - 行为:已展开则折叠,未展开则展开
- 懒加载场景:首次展开时可能触发
loadChildren
expandAll() / collapseAll()
- 含义:一次性展开或折叠全部树节点
- 注意:只对树形模式有效
getExpandedKeys()
- 返回值含义:返回当前所有已展开节点的 rowKey 数组
goTo(page)
- 参数含义:目标页码,从
1开始 - 行为:内部会自动限制在合法页码范围内
prevPage() / nextPage()
- 含义:翻到上一页或下一页
- 行为:到达边界时不会继续超出
setPageSize(size)
- 参数含义:新的每页条数
- 行为:更新后会把当前页重置到第
1页
getCurrentPage() / getPageCount() / getTotal()
getCurrentPage():当前页码getPageCount():按total / pageSize计算出的总页数getTotal():当前总条数,优先取pagination.total
核心类型说明
类型定义位于 components/pro-table/types.ts。
RowMeta / RowData
interface RowMeta {
__checkable__?: boolean
__resizable__?: boolean
__minHeight__?: number
__maxHeight__?: number
__level__?: number
__expanded__?: boolean
__hasChildren__?: boolean
__isLeaf__?: boolean
__loading__?: boolean
}
type RowData = RowMeta & Record<string, any>
| 字段 | 类型 | 说明 |
|---|---|---|
| checkable | boolean |
类型中定义为“是否允许勾选”。但当前源码未在行选择逻辑中消费该字段,如需禁选需自行扩展 |
| resizable | boolean |
设为 true 后,该行底边会出现行高拖拽手柄 |
| minHeight | number |
行高拖拽最小值,默认 20 |
| maxHeight | number |
行高拖拽最大值,默认 4000 |
| level | number |
树形模式下自动注入的层级 |
| expanded | boolean |
树形模式下自动注入的展开状态 |
| hasChildren | boolean |
树形模式下自动注入,表示是否有子节点 |
| isLeaf | boolean |
树形模式下自动注入,表示是否叶子节点 |
| loading | boolean |
懒加载树形节点时的加载中状态 |
CellSpan
interface CellSpan {
colspan?: number
rowspan?: number
}
用于描述单元格合并:
colspan表示向右合并几列rowspan表示向下合并几行- 列合并不能跨固定区,
fixedLeft、主区、fixedRight各自独立
ColumnNode
interface ColumnNode {
key: string
title: string
width?: number
minWidth?: number
maxWidth?: number
resizable?: boolean
headerAlign?: 'left' | 'center' | 'right'
cellAlign?: 'left' | 'center' | 'right'
fixed?: 'left' | 'right'
cellBgColor?: string
render?: (params: CellRenderParams) => void
renderHeader?: (params: HeaderCellRenderParams) => void
children?: ColumnNode[]
sortable?: boolean
sorter?: (a: RowData, b: RowData) => number
defaultSortOrder?: 'asc' | 'desc' | null
cellOverflow?: 'clip' | 'ellipsis' | 'wrap'
maxLines?: number
tooltip?: boolean
span?: (row: RowData, rowIndex: number) => CellSpan | null | undefined
}
| 字段 | 类型 | 说明 |
|---|---|---|
| key | string |
列唯一标识,必须唯一 |
| title | string |
表头标题 |
| width | number |
列宽 |
| minWidth | number |
最小列宽,拖拽时生效,默认 30 |
| maxWidth | number |
最大列宽,拖拽时生效,默认 4000 |
| resizable | boolean |
是否允许拖拽调整列宽 |
| headerAlign | TextAlign |
表头文字对齐 |
| cellAlign | TextAlign |
单元格文字对齐 |
| fixed | 'left' \| 'right' |
固定列方向。父列设置后子列会继承 |
| cellBgColor | string |
当前列的单元格背景色 |
| render | (params: CellRenderParams) => void |
自定义单元格绘制函数。有该函数时默认文本不再绘制 |
| renderHeader | (params: HeaderCellRenderParams) => void |
自定义表头单元格绘制函数。有该函数时默认标题文字和排序箭头均不绘制,完全由回调接管;对父级分组列和叶子列均有效 |
| children | ColumnNode[] |
多级表头子列 |
| sortable | boolean |
是否支持排序 |
| sorter | (a, b) => number |
自定义排序函数 |
| defaultSortOrder | 'asc' \| 'desc' \| null |
初始排序状态 |
| cellOverflow | 'clip' \| 'ellipsis' \| 'wrap' |
文本溢出处理方式 |
| maxLines | number |
折行时最大行数,0 表示不限 |
| tooltip | boolean |
H5 悬浮或小程序点击时显示完整内容 |
| span | (row, rowIndex) => CellSpan |
body 单元格动态合并配置 |
CellRenderBase
CellRenderParams 和 HeaderCellRenderParams 共同继承的公共基础接口,包含坐标、样式和绘制相关的公共字段。
interface CellRenderBase {
ctx: IRenderer
x: number
y: number
width: number
height: number
align: TextAlign
fontSize: number
paddingX: number
textColor: string
drawText(text: string | number, color?: string): void
drawCircle(cx: number, cy: number, r: number, color: string, opts?: CircleOptions): void
}
| 字段 | 含义 |
|---|---|
| ctx | 平台无关绘图接口实例 |
| x / y | 单元格左上角画布绝对坐标 |
| width / height | 单元格宽高(跨列/跨行时为合计值) |
| align | 单元格对齐方式 |
| fontSize | 当前字号 |
| paddingX | 水平内边距 |
| textColor | 主题文字颜色 |
| drawText | 按 align / paddingX 规则绘制文字。body 单元格遵循 cellOverflow(wrap/ellipsis/clip)分支;表头单元格单行绘制 |
| drawCircle | 在指定坐标绘制填充圆,与 ILayerDrawHelper.drawCircle 签名一致 |
CellRenderParams
自定义列 render 和固定行 cellRender 会收到以下参数(继承 CellRenderBase):
interface CellRenderParams extends CellRenderBase {
row: RowData
value: any
rowIndex: number
isHovered: boolean
hoveredLocalX: number
hoveredLocalY: number
pressedAreaKey: string | null
drawButtons(buttons: ActionButtonConfig[]): void
registerClickArea(
key: string,
x: number,
y: number,
w: number,
h: number,
onClick: (row: RowData, rowIndex: number) => void,
): void
}
几个常用能力:
drawText:按列对齐和内边距绘制文本,遵循cellOverflow配置drawCircle(cx, cy, r, color, opts?):在指定坐标绘制圆形;cx/cy 为画布绝对坐标,居中绘制时可用x + width/2、y + height/2drawButtons:绘制操作按钮并自动接管点击态registerClickArea:注册当前单元格内部的局部点击区域
字段含义补充:
| 字段 | 含义 |
|---|---|
| ctx | 当前渲染器实例,可直接调用绘图 API |
| x / y | 单元格左上角坐标 |
| width / height | 单元格宽高 |
| row | 当前行数据对象 |
| value | 当前列在该行上的值 |
| rowIndex | 当前行索引 |
| align | 当前列最终文本对齐方式 |
| fontSize | 当前实际字号 |
| paddingX | 当前单元格水平内边距 |
| textColor | 当前默认文字颜色 |
| isHovered | 当前单元格所在行是否处于 hover 状态 |
| hoveredLocalX / hoveredLocalY | 指针位于单元格内部的相对坐标,未 hover 时通常为 -1 |
| pressedAreaKey | 当前被按下的内部点击区域 key,没有则为 null |
HeaderCellRenderParams
自定义表头 renderHeader 会收到以下参数(继承 CellRenderBase):
interface HeaderCellRenderParams extends CellRenderBase {
title: string
column: ColumnNode
isLeaf: boolean
drawSortArrow(): void
fillBackground(color: string, alpha?: number): void
drawRect(x: number, y: number, w: number, h: number, color: string, alpha?: number): void
drawRoundRect(x: number, y: number, w: number, h: number, radius: number, fillColor: string, strokeColor?: string, lineWidth?: number): void
}
| 字段 / 方法 | 含义 |
|---|---|
| title | 列的原始 title 值 |
| column | 当前列的 ColumnNode 配置对象 |
| isLeaf | 是否为叶子列(无 children) |
| drawText | 按 align / paddingX 规则绘制文字(继承自 CellRenderBase) |
| drawCircle | 在指定坐标绘制填充圆(继承自 CellRenderBase) |
| drawSortArrow | 绘制排序方向指示箭头。有此调用时才显示,否则不自动绘制 |
| fillBackground | 用指定颜色填充整个表头单元格背景(无需手动传坐标) |
| drawRect | 在任意坐标绘制填充矩形(支持透明度) |
| drawRoundRect | 绘制圆角矩形(填充 + 可选描边) |
所有绘制方法底层复用组件内部绘制原语,自动抹平 H5 / 小程序差异,无需直接操作
ctx。
ActionButtonConfig
interface ActionButtonConfig {
key: string
label: string
type?: 'button' | 'link'
color?: string
textColor?: string
hoverColor?: string
onClick?: (row: RowData, rowIndex: number) => void
}
| 字段 | 类型 | 说明 |
|---|---|---|
| key | string |
按钮唯一标识 |
| label | string |
按钮文案 |
| type | 'button' \| 'link' |
展现样式,默认 link |
| color | string |
主色,默认 #1890ff |
| textColor | string |
按钮文字色,仅 button 模式下有效 |
| hoverColor | string |
hover 时颜色 |
| onClick | (row, rowIndex) => void |
点击回调 |
FixedRowDef
interface FixedRowDef {
position: 'top' | 'bottom'
data: RowData
cellRender?: Record<string, (params: CellRenderParams) => void>
spans?: Record<string, CellSpan>
}
| 字段 | 类型 | 说明 |
|---|---|---|
| position | 'top' \| 'bottom' |
固定行位置 |
| data | RowData |
固定行数据 |
| cellRender | Record<string, (params) => void> |
指定列 key 的固定行自定义绘制,优先级高于列级 render |
| spans | Record<string, CellSpan> |
固定行单元格合并配置 |
ProTableOptions
interface ProTableOptions {
borderColor?: string
headerBgColor?: string
bodyBgColor?: string
hoverRowBgColor?: string
hoverColBgColor?: string
stripedBgColor?: string
textColor?: string
fontSize?: number
padding?: number | [number, number]
rowBgColor?: (row: RowData, rowIndex: number) => string | undefined
cellBgColor?: (row: RowData, rowIndex: number, colKey: string) => string | undefined
selectionFillColor?: string
selectionBorderColor?: string
resizeHandleVisible?: boolean
}
实际默认值如下:
{
borderColor: '#e8e8e8',
headerBgColor: '#fafafa',
bodyBgColor: '#ffffff',
hoverRowBgColor: '#f5f5f5',
hoverColBgColor: '#f0f7ff',
stripedBgColor: '#fafafa',
textColor: '#333333',
fontSize: 13,
padding: [8, 8],
selectionFillColor: 'rgba(24,144,255,0.12)',
selectionBorderColor: '#1890ff',
resizeHandleVisible: false,
}
说明:
padding支持number或[paddingY, paddingX]- 行高会按
ceil(fontSize * 1.2) + paddingY * 2自动推导 rowBgColor是行级背景色回调cellBgColor优先级高于rowBgColor和列级cellBgColorresizeHandleVisible当前实际默认值是false
字段含义补充:
| 字段 | 含义 |
|---|---|
| borderColor | 单元格网格线颜色 |
| headerBgColor | 表头背景色 |
| bodyBgColor | 表体默认背景色 |
| hoverRowBgColor | 行 hover 时覆盖的背景色 |
| hoverColBgColor | 列 hover 时覆盖的背景色 |
| stripedBgColor | 斑马纹行背景色 |
| textColor | 默认文字颜色 |
| fontSize | 默认字号,会参与行高计算 |
| padding | 单元格内边距,控制内容与边框的距离 |
| rowBgColor | 按行动态返回背景色的回调 |
| cellBgColor | 按单元格动态返回背景色的回调 |
| selectionFillColor | 选区内部填充色 |
| selectionBorderColor | 选区边框颜色 |
| resizeHandleVisible | 是否绘制列宽/行高拖拽手柄指示条 |
RowSelectionConfig
interface RowSelectionConfig {
type?: 'checkbox' | 'radio'
columnTitle?: string
columnWidth?: number | string
fixed?: boolean
hideSelectAll?: boolean
}
| 字段 | 类型 | 说明 |
|---|---|---|
| type | 'checkbox' \| 'radio' |
选择模式,默认 checkbox |
| columnTitle | string |
选择列表头标题。设置后表头不再绘制默认全选框 |
| columnWidth | number \| string |
选择列宽,默认 40。传字符串时会用 parseInt 解析 |
| fixed | boolean |
是否固定到左侧,默认 true |
| hideSelectAll | boolean |
隐藏表头全选框,仅 checkbox 模式且未设置 columnTitle 时有效 |
注意事项:
selectedRowKeys与row-select-change.indices使用的是当前分页切片内的索引,不是业务主键selectAll()也只作用于当前页数据
SelectionRange
interface SelectionRange {
startRow: number
endRow: number
startColIndex: number
endColIndex: number
}
SelectionRange 中的行索引是统一虚拟行索引:
- 负值:固定顶部行
0 ~ data.length - 1:body 行- 大于等于
data.length:固定底部行
列索引对应 layout.leafColumns 顺序,也就是“固定左列 -> 主区列 -> 固定右列”。
字段含义补充:
| 字段 | 含义 |
|---|---|
| startRow | 选区起始行 |
| endRow | 选区结束行 |
| startColIndex | 选区起始列索引 |
| endColIndex | 选区结束列索引 |
说明:起止值不要求一定是左上到右下,消费时建议自己做一次 min/max 归一化。
SortState
type SortOrder = 'asc' | 'desc' | null
interface SortState {
key: string
order: SortOrder
}
| 字段 | 类型 | 说明 |
|---|---|---|
| key | string |
当前排序列 key |
| order | 'asc' \| 'desc' \| null |
排序方向 |
TreeConfig
interface TreeConfig {
childrenKey?: string
indent?: number
defaultExpandAll?: boolean
defaultExpandedKeys?: Array<string | number>
rowKey?: string
treeColumnKey?: string
lazy?: boolean
loadChildren?: (row: RowData) => Promise<RowData[]>
hasChildrenField?: string
onLoadError?: (error: unknown, row: RowData) => void
}
| 字段 | 类型 | 说明 |
|---|---|---|
| childrenKey | string |
子节点字段名,默认 children |
| indent | number |
每级缩进像素,默认 8 |
| defaultExpandAll | boolean |
初始是否展开全部 |
| defaultExpandedKeys | Array<string \| number> |
初始展开的 rowKey 列表 |
| rowKey | string |
行唯一键字段,默认 id |
| treeColumnKey | string |
显示树形缩进和展开图标的列 key,默认第一列叶子列 |
| lazy | boolean |
是否启用懒加载树 |
| loadChildren | (row) => Promise<RowData[]> |
懒加载时首次展开节点所调用的异步方法 |
| hasChildrenField | string |
标识可展开节点的字段名,默认 hasChildren |
| onLoadError | (error: unknown, row: RowData) => void |
懒加载 loadChildren 抛出异常时的回调;展开状态自动恢复为收起,可在此提示用户或上报错误 |
行为说明:
- 懒加载模式下,首次展开某个节点时会调用
loadChildren - 加载过程中会给该行注入
__loading__ = true toggleExpand(rowKey)传的是 rowKey 值,不是行索引
PaginationConfig
interface PaginationConfig {
current: number
pageSize: number
total: number
pageSizeOptions?: number[]
}
| 字段 | 类型 | 说明 |
|---|---|---|
| current | number |
当前页码,从 1 开始 |
| pageSize | number |
每页条数 |
| total | number |
总条数。服务端分页时可传后端总数 |
| pageSizeOptions | number[] |
每页条数备选值,仅作为配置数据保存,组件本身不渲染分页器 |
行为说明:
- 不传
pagination时,组件直接使用全量数据 - 传入
pagination时,组件对当前数据执行切片 - 组件提供分页方法和
page-change事件,但不提供内置分页栏 UI
OverlayDrawPayload
bodyOverlayDraw、fixedTopOverlayDraw、fixedBottomOverlayDraw 会收到以下 payload,继承 ILayerDrawHelper 全部方法并额外提供底层渲染器:
interface OverlayDrawPayload extends ILayerDrawHelper {
renderer: IRenderer
}
interface ILayerDrawHelper {
// ─── 坐标查询 ────────────────────────────────────────────
getColIndexByKey(key: string): number
getCellRect(colIndex: number, rowIndex: number): { x: number; y: number; w: number; h: number }
getCellCenter(colIndex: number, rowIndex: number): { x: number; y: number }
getCellRadius(colIndex: number): number
getRowHeight(rowIndex: number): number
getRowCount(): number
getColCount(): number
getColBounds(colIndex: number): { x: number; w: number }
getRowBounds(rowIndex: number): { y: number; h: number }
visibleRowRange: Readonly<{ start: number; end: number }>
visibleColRange: Readonly<{ start: number; end: number }>
isRowVisible(rowIndex: number): boolean
isColVisible(colIndex: number): boolean
// ─── 基础几何 ────────────────────────────────────────────
drawCircle(x: number, y: number, r: number, color: string, opts?: CircleOptions): void
drawRoundRect(x: number, y: number, w: number, h: number, radius: number, fillColor: string, strokeColor?: string, lineWidth?: number): void
drawPolyline(points: Array<{ x: number; y: number }>, color: string, lineWidth?: number): void
drawSmoothLine(points: Array<{ x: number; y: number }>, color: string, lineWidth?: number): void
drawArea(points: Array<{ x: number; y: number }>, baseY: number, fillColor: string, alpha?: number): void
drawRect(x: number, y: number, w: number, h: number, color: string, alpha?: number): void
// ─── 辅助线 ──────────────────────────────────────────────
drawHLine(x1: number, x2: number, y: number, color: string, lineWidth?: number, dash?: number[]): void
drawVLine(x: number, y1: number, y2: number, color: string, lineWidth?: number, dash?: number[]): void
drawLine(x1: number, y1: number, x2: number, y2: number, color: string, lineWidth?: number): void
// ─── 文字 ────────────────────────────────────────────────
drawText(text: string, x: number, y: number, opts?: DrawTextOptions): void
measureText(text: string, fontSize?: number): number
// ─── 单元格便捷系列 ───────────────────────────────────────
drawCellBg(colIndex: number, rowIndex: number, color: string): void
drawCellCircle(colIndex: number, rowIndex: number, color: string, opts?: CircleOptions): void
drawCellText(colIndex: number, rowIndex: number, text: string, opts?: DrawTextOptions): void
drawCellBadge(colIndex: number, rowIndex: number, text: string, bgColor: string, textColor?: string): void
drawColBg(colIndex: number, color: string, alpha?: number): void
drawRowBg(rowIndex: number, color: string, alpha?: number): void
}
这个 payload 适合做趋势线、角标、额外装饰、热力区块等高阶绘制。
坐标查询:
| 方法 | 含义 |
|---|---|
| getColIndexByKey | 根据列 key 找到叶子列索引,O(1),未找到返回 -1 |
| getCellRect | 获取单元格逻辑矩形(不含滚动偏移) |
| getCellCenter | 获取单元格中心点坐标 |
| getCellRadius | 获取单元格内切圆半径(适合绘制紧贴边缘的圆形徽标) |
| getRowHeight | 获取指定行行高,body 区支持可变行高 |
| getRowCount | 当前区域行数 |
| getColCount | 当前区域列数(fixedLeft + main + fixedRight 合计) |
| getColBounds | 指定列的 { x, w },适合绘制跨行列分隔线 |
| getRowBounds | 指定行的 { y, h },适合绘制跨列行背景 |
| visibleRowRange | 当前可见行范围 { start, end }(body 含 overscan)。draw* 系列已内置跳过逻辑,仅在需要跳过 JS 侧自定义计算时手动读取 |
| visibleColRange | 当前可见主区列范围 { start, end }(绝对索引)。fixedLeft/Right 列不受此约束 |
| isRowVisible | 判断行索引是否在可见范围内,draw* 系列已内置,通常不需手动调用 |
| isColVisible | 判断列索引是否在可见范围内,fixedLeft/Right 恒返回 true |
绘制原语:
| 方法 | 含义 |
|---|---|
| drawCircle | 在指定坐标绘制填充圆;opts.text 居中文字,opts.strokeColor 描边环 |
| drawRoundRect | 绘制圆角矩形(填充 + 可选描边),兼容全平台 |
| drawPolyline | 按点集绘制折线(直线段) |
| drawSmoothLine | Catmull-Rom 平滑曲线,适合趋势折线图 |
| drawArea | 折线下方填充面积图,配合 drawSmoothLine 使用 |
| drawRect | 绘制任意填充矩形(支持 alpha 透明度) |
| drawHLine / drawVLine | 水平 / 垂直辅助线,可设虚线 |
| drawLine | 任意方向直线(含斜线) |
| drawText | 在任意坐标绘制文字,支持颜色、字号、对齐、基线 |
| measureText | 估算文字像素宽度,用于动态布局计算 |
单元格便捷系列:
以下方法内置行列可见性判断,不在虚拟化可见窗口内的单元格会自动跳过 Canvas API 调用,无需外部过滤。
| 方法 | 含义 |
|---|---|
| drawCellBg | 填充指定单元格背景色 |
| drawCellCircle | 在单元格中心绘制填充圆(自动取内切圆半径) |
| drawCellText | 在单元格中心绘制文字(自动居中对齐) |
| drawCellBadge | 在单元格中绘制 pill 样式徽章(自动计算文字宽度) |
| drawColBg | 填充指定列在当前区域内的所有行背景(单次 fillRect) |
| drawRowBg | 填充指定行跨所有列的背景(单次 fillRect) |
常见用法
1. 行选择
<pro-table
v-model:selectedRowKeys="selectedRowKeys"
:columns="columns"
:data="data"
:row-selection="{ type: 'checkbox', columnWidth: 48 }"
@row-select-change="onRowSelectChange"
/>
const selectedRowKeys = ref<number[]>([])
function onRowSelectChange(payload: { indices: number[]; rows: RowData[] }) {
console.log(payload.indices, payload.rows)
}
2. 树形表格
<pro-table
:columns="columns"
:data="treeData"
:tree-props="{
rowKey: 'id',
childrenKey: 'children',
treeColumnKey: 'name',
indent: 12,
defaultExpandAll: true,
}"
/>
3. 固定行
const fixedRows: FixedRowDef[] = [
{
position: 'top',
data: { name: '顶部汇总', salary: 300000 },
},
{
position: 'bottom',
data: { name: '底部合计', salary: 280000 },
spans: {
name: { colspan: 2 },
},
},
]
4. 自定义单元格绘制
const columns: ColumnNode[] = [
{
key: 'progress',
title: '进度',
width: 120,
render: ({ ctx, x, y, width, height, value, drawText }) => {
const percent = Number(value) || 0
ctx.setFillStyle('#f0f0f0')
ctx.fillRect(x + 8, y + height / 2 - 4, width - 16, 8)
ctx.setFillStyle('#1890ff')
ctx.fillRect(x + 8, y + height / 2 - 4, ((width - 16) * percent) / 100, 8)
drawText(`${percent}%`)
},
},
]
使用注意事项
1. data 变更建议替换引用
组件对 data 使用的是引用级 watch,而不是深度 watch。这样做是为了避免大数组深遍历的性能开销。
推荐:
data.value = [...data.value, newRow]
不推荐依赖深层属性原地修改后自动触发布局重算。
2. 行选择索引是“当前页索引”
无论是 selectedRowKeys、row-select-change.indices、getSelectedRowIndices(),当前实现返回的都是分页切片内索引,而不是业务主键。
如果你的业务需要稳定主键选择,建议在外层把索引映射成真实 id 存储。
3. 分页是逻辑分页,不是完整分页组件
pagination 只负责:
- 当前页切片
- 页码状态
- 对外暴露翻页方法
- 触发
page-change
它不负责渲染页码器、页大小切换器、总数展示。
4. resizeHandleVisible 当前默认是 false
源码合并默认值时,resizeHandleVisible 的实际默认行为是 false。如果你希望显示列宽 / 行高拖拽手柄,需要显式设置:
const options: ProTableOptions = {
resizeHandleVisible: true,
}
建议的类型导入方式
import type {
RowData,
ColumnNode,
ProTableOptions,
FixedRowDef,
RowSelectionConfig,
SelectionRange,
SortState,
TreeConfig,
PaginationConfig,
CellRenderBase,
CellRenderParams,
HeaderCellRenderParams,
ActionButtonConfig,
OverlayDrawPayload,
} from '@/uni_modules/pro-table'

收藏人数:
购买源码授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 96
赞赏 0
下载 11886118
赞赏 1912
赞赏
京公网安备:11010802035340号