@canvas-components/list-table
@canvas-components/list-table 是整个项目的纯原生 Canvas 表格核心包,不依赖 React,可直接在 DOM 容器中创建高性能表格实例,也可作为适配层的底层引擎使用。
表头合并
使用 children 自动生成多级表头,或通过 onHeaderCell(column) 返回 rowSpan、colSpan 自定义合并;跨度为 0 时隐藏该表头,表体列保持独立。原生表格和 React 适配层共用此配置,详见 表头合并配置。
双击表头边界自动列宽
双击表头可拖拽的右边界,按内容自动计算当前列宽;双击左边界则调整左侧相邻列,与拖拽边界归属一致。支持左右固定列及最后一列,沿用右键菜单的内容测量、最小/最大宽度限制与列宽缓存设置。禁用列宽调整的边界和空白选择列等特殊列不响应。
内置图标
{ type: "icon", icon: "EditOutlined", size: 20 } 可绘制原生图标,完整提供官方 848 个图标(线框、实心、双色),另有 58 个内置短名称图标,包含表头同款排序、过滤、固定图标。
图标路径统一维护于 src/rendering/icons/,供单元格、评分、时间轴和附件功能复用。
详见 图标目录与配置。
时间轴单元格
render 返回 { type: "timeline", items: [{ title: "09:00", content: "订单创建", color: "green" }] }
即可绘制原生纵向时间轴;支持自动换行、自动行高、交替布局和倒序。
详见 时间轴配置。
上传单元格
设置 formItemProps: { valueType: "upload", upload: { action: "/api/upload", multiple: true } }
即可使用原生上传编辑器。图片回显缩略图,其他文件显示类型图标和文件名,不依赖 React 组件。
详见 上传配置与接口说明。
多列排序
使用列配置,设置 sorter: { multiple: 数值 } 开启该列的多列排序,数值越大越优先;同优先级按加入排序的先后顺序执行。
const columns = [
{ title: "部门", dataIndex: "dept", sorter: { multiple: 3 } },
{ title: "绩效", dataIndex: "score", sorter: { multiple: 2 } },
{ title: "姓名", dataIndex: "name", sorter: { multiple: 1 } },
];
- 点击排序图标依次切换升序、降序、取消,仅修改当前列;右键“取消排序”同样只清除当前列,“重置排序”清除全部条件。
multiple相同时,先点击加入排序的列优先;切换升降序不改变次序,取消后重新加入排在同权重列的最后。缓存恢复保留此顺序,未点击的默认排序按列配置顺序。sortValueGetter(record)用于提取数字或字符串排序值,支持状态权重映射、数值计算等;sorter.compare(a, b)则直接比较两条原始记录,优先于sortValueGetter。按升序返回负数、零或正数,降序由表格自动反转;返回0或NaN时继续比较下一列,全部相等保留原始顺序。比较函数需保持纯函数并满足排序一致性。- 自定义
compare不会序列化到 Worker,支持闭包,采用可取消的主线程分片排序;同一组排序中的普通列仍可使用 Worker。比较函数抛出异常时进入查询错误处理,不会静默改用默认规则。单次比较函数本身仍需快速返回。 - 未配置
multiple的列保持单列交互:开始排序时替换其他排序条件。sorter: true开启单列排序,也可继续使用allowSort控制排序能力。 defaultSortOrder可与sorter.multiple配合设置多列默认排序,父表和嵌套子表独立维护各自的排序条件。- 排序状态统一使用数组:
[{ key, order, priority }],未排序为[];query.onChange第三个参数也是数组:[{ columnKey, field, order, priority }]。extra.sorter、extra.sorters均返回排序状态数组。不转换旧单列缓存数据。 - 本地同步排序、Worker 排序及无 Worker 分片回退遵循相同优先级;服务端模式由业务端按回调数组执行排序。
- 基础示例中已配置部门、绩效、姓名三列,可连续点击它们验证组合排序。
const scoreColumn = {
title: "绩效",
dataIndex: "score",
sorter: {
multiple: 2,
compare: (a, b) => a.score - b.score,
},
};
多列拖拽排序
开启 draggable: true 后,可先使用 Shift 选中连续列,或使用 Ctrl / Command 选中不连续列,再从任意已选列的表头空白处拖动。同层级中允许拖拽的选中列会按原显示顺序整体移动;不连续选中的列在落点合并为连续的一组。高亮选区跟随列移动,浮层同步展示各列表头。
拖动未选中的列时仍只移动该列。固定列、draggable: false 的列不参与移动;多级表头沿用同级重排边界,不把子列移出所属分组。落回原位时不提交重排。
图片复制与导出
右键菜单「复制」支持「复制为图片」,「导出」支持「导出图片」,均提供三个范围:
- 所有区域:重新绘制当前已加载、经过排序过滤的全部数据及可见列,包含表头和已开启的汇总行。服务端分页不会额外请求尚未加载的数据。
- 选中区域:绘制当前单元格、拖拽矩形选区、整行或整列选区,不包含表头和汇总行;没有选区时不可用。
- 视口区域:截取当前表格画布,保留当前滚动位置及可见效果,不包含分页器、工具栏或右键菜单。
图片格式为 PNG。复制需要浏览器支持图片剪贴板,且通过 HTTPS 或 localhost 访问;下载不依赖剪贴板权限。
图片包含尚未加载的图片内容时,最多等待 10 秒。跨域资源禁止读取、编码失败或尺寸过大会明确提示,不会静默生成不完整文件。
为防止内存溢出,输出图片单边不超过 16384 像素,总像素不超过 1600 万(计入缩放及设备像素比);超过时请缩小选区或使用视口范围。
「复制」和「导出」仍分别遵循 contextMenu.features 的 copy、export 开关。
功能作用
- 原生 Canvas 表格渲染
- 大数据量虚拟滚动
- 多级表头、固定列、汇总行
- 排序、过滤、自动合并单元格
- 行选择、展开树表、右键菜单
- 单元格编辑、校验、tooltip、图片预览
- 列宽调整、列拖拽、行拖拽、轮播滚动
- 内建二维码、条形码、进度、图表、评分、按钮等单元格内容
展开详情自动高度
expandable.expandedRowRender 返回标签、文本等富内容时,不传 expandedRowHeight 即按实际内容宽度自动换行、测高。容器宽度、列宽、字体或密度变化后会重新计算,并同步更新行位置和滚动范围。测量与绘制共用布局规则,业务侧无需自行测量标签或监听容器宽度。
显式设置 expandedRowHeight 数字或回调时仍优先使用该配置;嵌套子表继续使用其独立的内容高度计算。
展开详情中的 button、link 等交互片段与普通单元格共用点击、禁用及二次确认逻辑。onClick 收到所属数据行的 record、扁平行索引 rowIndex、片段索引 contentIndex 和原生事件;column.key 为 __expanded_row__,value 为 undefined。文档站「可展开行」的入库条码明细示例演示物料标签与按钮组合、权限切换及明细弹窗。
内嵌子表头
children 树形数据共用父表列和表头,叶子行会预留展开按钮位置,使文字按 indentSize 逐级缩进。展开详情返回 type: "table" 时,子表默认独立显示表头,即使父子列配置相同也不会自动隐藏。
内嵌表格内容支持 showHeader: false:
expandable: {
expandedRowRender: (record) => ({
type: "table",
showHeader: false,
columns: detailColumns,
dataSource: record.details,
}),
}
隐藏时不绘制子表头、排序/筛选等表头入口,也不预留表头高度;首行数据从子表顶部开始,编辑和选区位置随之调整。showHeader 默认 true,设为 false 时优先于 headerHeight。父子可见业务列的字段和显示顺序一致,且子表未显式设置 width 时,子表会跟随父表实际列宽及固定列位置,缩进只占首个业务列;显示或隐藏子表头均保持后续列边界对齐。字段或顺序不同、显式指定子表宽度时仍独立计算列宽。文档站「可展开行」示例可切换子表头显示。
子表列宽与列顺序拖拽
内嵌 type: "table" 子表默认允许拖动相邻列在表头或表体中的边界调整两列宽度,内部边界保持子表总宽不变;拖动最右列的右边界可改变子表总宽,其他列宽保持不变。展开子表超出视口时,共用父表底部横向滚动条,折叠或收窄后自动更新滚动范围;拖动表头超过 5px 后可调整同一固定区域内的列顺序,拖动时显示与父表一致的带蓝色高亮边框的半透明整列浮层、蓝色落点线和圆点,并显示与父表一致的列插入位置 tooltip,松手后以相同的 320ms 缓动完成列交换与浮层归位。父子边界重合时,子表可见区域内优先调整子表列宽。调整列宽时,从按下列边界开始全程显示贯穿子表高度的蓝色虚线指示线;边界进入父表可见业务列边界约 6px 范围内会自动吸附,指示线与父表边界重合。离开后恢复自由调整,松开或按 Escape 后隐藏指示线并保留当前宽度。吸附距离随表格缩放换算,并遵守相邻列的最小/最大宽度限制。每个子表独立保存当前实例中的布局,父表和其他子表不受影响;折叠后再展开仍保留,刷新页面不持久化。
- 子表级
resizable: false/draggable: false分别关闭列宽和列顺序拖拽。 - 列级同名配置可禁止指定列参与调整;任一相邻列禁止调整时,该边界不可拖动。
- 列级
minWidth(默认40)/maxWidth限制调整范围,同时满足相邻列的限制。 showHeader: false时不提供拖拽入口。- 同列子表首次手动调整后改用独立布局,不再自动跟随父表列宽和顺序;编辑、排序、过滤仍按原始列定位。
{
type: "table",
resizable: true,
draggable: true,
columns: [
{ title: "名称", dataIndex: "name", width: 220, minWidth: 100 },
{ title: "数量", dataIndex: "quantity", width: 120, minWidth: 60, maxWidth: 240 },
],
dataSource: record.details,
}
分页尺寸缓存
设置表格 storageKey 后,用户选择或输入的每页条数会自动保存到 localStorage,下次创建表格时恢复。可通过 pagination.cachePageSize: true 明确开启;默认沿用开启行为,设置为 false 后禁止读取和写入已有缓存。不同表格应使用不同的键;不缓存页码,不影响滚动加载。无键、缓存无效、缓存超出当前 minPageSize / maxPageSize 范围或浏览器禁用存储时,继续使用默认配置。
显式传入 pagination.pageSize 时以受控值为准。外部负责请求的封装(如 WWISuperTable)应在首次请求前读取缓存,初始化自己的分页状态,避免只改变显示值而请求条数不一致:
import { getListTableStoredPageSize } from "@canvas-components/list-table";
const pagination = { cachePageSize: true, minPageSize: 5, maxPageSize: 500 };
const initialPageSize =
explicitPageSize ?? getListTableStoredPageSize(storageKey, pagination) ?? 10;
// 将 initialPageSize 用于首次请求和受控 pagination.pageSize。
该读取方法也由 @canvas-components/react-list-table 导出。缓存只记录选中的尺寸,不保存自定义候选历史;React 分页器恢复到自定义尺寸后仍可删除,删除当前自定义项会将回退尺寸写入缓存。
本地配置缓存
汇总行右键“汇总方式”中提供“展示汇总方式前缀”,默认勾选。取消后仅显示结果值(如 173),勾选时显示 求和: 173 等前缀;主表、子表和复制文本使用同一展示规则。列配置可使用 summary: { type: "sum", showTypePrefix: false },该开关随汇总配置缓存,自定义汇总标题不受影响。
配置稳定且唯一的 storageKey 后,可在右键通用“缓存到本地”分组中分别设置:列设置、排序、筛选、汇总、对齐、格式(数字、日期、货币等)、表格密度、缩放,以及当前列条件格式,默认全部开启。“列显示”和“条件格式”子菜单不重复提供缓存开关。通用分组中的“列设置”与标题栏“列设置”标题右侧的 Checkbox 共用状态,控制列宽、显隐、固定和顺序;未配置 storageKey 时禁用并提示。
关闭某类缓存不会撤销当前页面的操作,只清除该类持久化内容,刷新后使用传入配置;其他类别不受影响。各缓存开关自身也会保存。冻结行、复制、导出等没有对应持久化数据,不提供缓存开关。
可用 cacheColumnSettings: false 设置列设置缓存默认关闭;其他类别通过 cachePreferences 配置,如 { sort: false, filter: false, summary: false, alignment: false, dataFormat: false, density: false, zoom: false }。未指定默认开启,已保存的用户选择优先。原生实例支持 setColumnSettingsCache(enabled);自定义右键功能列表可通过 cache 控制分类缓存分组。
按列条件格式
规则通过 backgroundColor 和 textColor 分别设置背景色与文本颜色,编辑弹窗使用现有颜色选择器并实时预览。文本颜色覆盖当前单元格文字片段的颜色,选中和悬停时仍生效;不修改其他列、表头或汇总行。不提供旧规则文本颜色迁移逻辑。
通用“缓存到本地”分组中的“当前列条件格式”按列控制规则是否持久化,默认开启。关闭后保留当前页面的规则效果,但保存时移除该列已缓存的规则;刷新后只使用代码中配置的初始规则。可通过列属性 cacheConditionalFormats: false 设置默认关闭。
单条规则支持新增、删除多个条件,相邻条件之间分别选择“且 / 或”,且优先于或。所有条件比较当前列原始值,命中后使用该规则的样式。例如 { id: "score", conditions: [{ operator: "gte", value: 80 }, { relation: "and", operator: "lt", value: 90 }], backgroundColor: "#fff1b8", textColor: "#874d00" }。每项 relation 表示与上一条件的连接关系,首项忽略;删除条件时保留剩余条件携带的关系。未指定时兼容旧的 conditionRelation,默认“且”。conditions 优先于旧的单条件字段;未配置时兼容原有 operator/value/endValue,空条件列表不命中。
右键业务叶子列表头 → 条件格式,二级菜单以“颜色块 + 规则名称”展示当前列规则,未命名时显示“规则 1”等默认名称,悬停可查看完整名称和条件摘要;并提供“新增规则”“重置条件格式”。点击规则打开编辑弹窗,可修改条件、背景色或删除规则;取消不会改动表格。
规则通过 scope 设置作用效果:"cell"(默认)只作用于当前列命中的数据单元格,"row" 则将命中规则的背景色和文字颜色应用于整条数据行。行级规则按可见列顺序检查,首条命中优先;单元格级规则在同一单元格命中时优先于行级样式。两种规则都不修改表头或汇总行。条件背景优先于选中和悬停背景,保留选区边框;校验错误提示仍优先于条件背景。只对绘制中的数据行和单元格计算,不预扫描全量数据。
const columns = [
{
key: "amount",
dataIndex: "amount",
title: "金额",
conditionalFormats: [
{
id: "high",
name: "高金额",
operator: "gte",
value: 1000,
backgroundColor: "#fff1b8",
textColor: "#874d00",
},
{
id: "negative",
name: "负数",
operator: "lt",
value: 0,
backgroundColor: "#ffccc7",
textColor: "#a8071a",
},
{
id: "review",
name: "复核整行",
scope: "row",
operator: "gte",
value: 5000,
backgroundColor: "#e6f4ff",
textColor: "#0958d9",
},
],
},
];
支持 eq / neq(原始值转文本后比较)、gt / gte / lt / lte(数值比较)、between(包含上下界,结束值使用 endValue)、contains(区分大小写)、empty / notEmpty。空值及非数值不参与数值比较。
配置 storageKey 后规则随列设置保存;“重置条件格式”只清空当前列规则,不清除其他样式或其他列规则。自定义右键菜单功能列表时,可通过 conditionalFormat 控制该入口。核心包通过 HeaderContextMenuConfig.conditionalFormats 和 onChangeConditionalFormats 向 UI 适配层提供配置。
分区 CSS 样式
原生 ListTable 和 ReactListTable 均支持 styles。Canvas 区域通过 CSS 属性映射到绘制参数,不创建单元格 DOM,不注入全局样式。
styles: {
cell: { fontSize: '14px', padding: '2px 12px', borderColor: '#d9e1ec' },
header: { backgroundColor: '#f0f9ff', color: '#075985', fontWeight: 700, textAlign: 'center' },
body: { color: '#334155' },
summary: { backgroundColor: '#f0fdf4', color: '#166534', textAlign: 'right' },
activeCell: { backgroundColor: '#fef3c7', borderColor: '#d97706', borderWidth: '2px' },
activeRow: { backgroundColor: '#e0e7ff', color: '#3730a3' },
scrollVertical: { width: '14px', color: '#94a3b8', backgroundColor: '#f1f5f9', borderRadius: 4 },
scrollHorizontal: { height: '12px', color: '#64748b', backgroundColor: '#f8fafc' },
pagination: {
root: { backgroundColor: '#f8fafc', color: '#334155', fontSize: '14px' },
total: { color: '#64748b' },
pageItem: { borderRadius: 4 },
activePageItem: { backgroundColor: '#1677ff', color: '#fff' },
sizeChanger: { width: 140 },
quickJumperInput: { width: 64 },
},
}
cell是表头、表体和汇总单元格的通用默认值;分区覆盖通用值,列级headerStyle/cellStyle/summaryStyle优先。- Canvas 分区支持
backgroundColor、color、fontSize、fontWeight、fontFamily、textAlign、verticalAlign、padding、paddingInline、paddingBlock、borderColor、borderWidth。长度只支持非负数字或px,padding支持一值或“上下 左右”两值;不支持百分比、em或 DOM 布局属性。 activeCell支持背景、文字和边框,activeRow支持背景与文字;仅改变已有选中状态的绘制,不改变选择模式。校验错误提示仍优先。- 滚动条
color对应滑块、backgroundColor对应槽色;纵轴width、横轴height对应厚度。显式scrollbar.vertical/horizontal: false仍禁止对应轴显示。 pagination按节点直接应用 DOM CSS,使用 camelCase 属性名,数字长度按 React DOM 规则处理;仅影响当前实例。通过原生 UI 回调自行渲染分页器时,从PaginationConfig.styles读取并应用样式。当前分页占位高度仍由表格密度决定,避免设置超出该高度的高度或纵向间距。
分页器支持以下节点:
| 节点 | 作用 |
|---|---|
root | 分页器根容器,颜色、字体等可向内部继承 |
total | 总条数文本 |
pages | 页码列表容器 |
pageItem | 普通页码按钮;简易模式下为页码文本 |
activePageItem | 当前页按钮,叠加并覆盖 pageItem |
prevButton / nextButton | 上一页 / 下一页按钮 |
prevIcon / nextIcon | 上一页 / 下一页箭头,通过 borderColor 设置箭头颜色 |
ellipsis | 页码省略号 |
sizeChanger | 分页尺寸选择控件,不包含弹出选项面板 |
quickJumper | 快速跳转区域容器 |
quickJumperText | “跳至”和“页”文本 |
quickJumperInput | 跳转输入控件外框,可设置宽度、背景与边框 |
节点样式优先于默认样式;未配置节点保持原样。独立使用 ReactPagination 时也支持同名 styles,其中 styles.root 优先于 style。
- 更新或移除
styles后即时生效;不额外持久化styles对象,已有列配置缓存中的对齐设置仍优先。嵌套子表格的独立内容样式仍通过其列/内容配置指定。
文档站“主题”示例增加了“启用分区 CSS 样式”开关,可切换验证。
安装方法
pnpm add @canvas-components/list-table
最低环境要求:
Node.js >= 18.12.0pnpm >= 9.0.0
使用方法
import { ListTable } from "@canvas-components/list-table";
const container = document.querySelector("#table") as HTMLElement;
const table = new ListTable(container, {
type: "list",
width: "100%",
height: 480,
rowKey: "id",
columns: [
{ key: "name", title: "姓名", dataIndex: "name", width: 180 },
{ key: "age", title: "年龄", dataIndex: "age", width: 120, align: "right" },
],
dataSource: [
{ id: 1, name: "张三", age: 28 },
{ id: 2, name: "李四", age: 31 },
],
});
table.mount();
属性说明
实例配置 ListTableOptions
| 属性 | 类型 | 说明 |
|---|---|---|
type | "list" | 表格类型,当前稳定支持列表表格 |
rowKey | string | number | Array<string | number> | ((record, index) => string | number) | 行主键解析规则 |
columns | ListTableColumn[] | 列配置集合 |
width | number | string | 表格宽度 |
height | number | string | 表格高度 |
dataSource | RecordType[] | 表格数据源 |
emptyText | string | 空状态文案 |
theme | Partial<ListTableTheme> | 主题覆盖 |
striped | boolean | 是否开启斑马纹 |
highlightMode | "cell" | "row" | "column" | "cross" | 点击高亮模式 |
keyboardNavigation | { tabWrap?: boolean; verticalWrap?: boolean } | 键盘边界导航;两项默认均为 true |
contextMenu | false | ListTableContextMenuOptions | 右键菜单配置 |
grouping | ListTableGroupingOptions | 分组栏、初始分组列与变化回调 |
rowHeight | number | "auto" | 行高 |
headerRowHeight | number | 表头单行高度 |
summaryRowHeight | number | 汇总行高度 |
overscanRowCount | number | 虚拟滚动预渲染行数 |
query | ListTableQueryOptions | 排序、过滤、合并单元格配置 |
rowSelector | boolean | ListTableRowSelectorOptions | 最左侧空白整行选择器配置 |
rowSelection | ListTableRowSelectionOptions | 行选择配置 |
expandable | ListTableExpandableOptions | 树表 / 展开行配置 |
pagination | false | ListTablePaginationOptions | 分页或滚动加载配置 |
scrollbar | ListTableScrollbarOptions | 滚动条配置 |
carouselScroll | ListTableCarouselScrollOptions | 自动轮播滚动配置 |
stretchColumns | boolean | 列总宽不足时是否拉伸补齐 |
resizable | boolean | 是否开启列宽拖拽 |
storageKey | string | 列配置、查询、缩放和密度持久化键 |
draggable | boolean | 是否开启列拖拽排序 |
tableId | string | 表格唯一标识 |
rowDrag | ListTableRowDragOptions | 行拖拽配置 |
debug | ListTableDebugOptions | 调试配置 |
通过 stickToBottom: true 开启动态数据平滑吸底(默认关闭),React 使用 <ReactListTable stickToBottom />。鼠标滚轮、拖动滚动条和触摸滚动均可正常浏览历史;停止滚动后默认等待 3 秒,再平滑滚向最新底部。配置 stickToBottom={{ enabled: true, resumeDelay: 5000 }} 可将等待时间改为 5 秒(resumeDelay 单位毫秒,范围 0~600000)。右键“吸底”二级菜单可切换开关、选择常用等待时间或输入自定义秒数。菜单设置在相同配置的数据更新中保留,显式改变对应配置时同步新值;可用 contextMenu.disabledFeatures: ["stickToBottom"] 隐藏该菜单。
keyboardNavigation.tabWrap 控制 Tab / Shift+Tab 在行尾 / 行首是否进入相邻行;verticalWrap 控制 ↑ / ↓ 在列首 / 列尾是否进入相邻列。设为 false 时到对应边界即停止,不改变当前活动单元格。
分批加载与服务端汇总
数据仍在异步生成或分批加载时,设置表格顶层属性 loading: true。此时汇总行及标题正常显示,统计结果保持空白,也不会计算首批临时数据。完整数据就绪后,将完整 dataSource 与 loading: false 一起更新,表格自动计算汇总并在完成后统一显示结果。默认值为 false,正常的空数据仍可汇总为零。
使用服务端汇总时,通过 query.summaryValues 传入以列 key 为键的对象或 Map。若数据和汇总分别请求,可保持 loading: true,待两者都返回后再设为 false;此配置同样支持 React 适配包,外部无需控制汇总计算状态。
const options = {
dataSource: rows,
loading,
query: {
// 服务端汇总可选;不传则使用本地异步汇总。
summaryValues: serverSummaryValues,
},
};
列配置 ListTableColumn
| 属性 | 类型 | 说明 |
|---|---|---|
key | string | 列唯一标识 |
title | string | 列标题 |
dataIndex | string | number | Array<string | number> | 取值路径 |
children | ListTableColumn[] | 子列,用于多级表头 |
width | number | 列宽 |
minWidth / maxWidth | number | 允许调整的最小 / 最大列宽 |
align | "left" | "center" | "right" | 内容对齐 |
headerAlign | "left" | "center" | "right" | 表头水平对齐,未传时回退到 align |
fixed | "left" | "right" | 固定列 |
hidden | boolean | 是否隐藏 |
ellipsis | boolean | 超出是否省略 |
editable | boolean | (text, record, index) => boolean | 是否允许编辑 |
formItemProps | ListTableFormItemPropsConfig | 编辑器配置 |
resizable | boolean | 单列是否允许调整宽度 |
draggable | boolean | 单列是否允许拖拽 |
allowSort | boolean | 是否开启排序 |
sorter | ListTableSorter<RecordType> | multiple 指定优先级,compare(a, b) 自定义比较 |
defaultSortOrder | "ascend" | "descend" | 默认排序方向,可配合 sorter.multiple |
allowFilter | boolean | 是否开启过滤 |
filterOptions | ListTableFilterOption[] | 精确过滤选项 |
cellType | ListTableCellContent["type"] | 内建单元格类型 |
moneyFormat | ListTableMoneyFormatOptions | 金额格式配置,仅在 cellType: "money" 时生效 |
dataFormat | "default" | "text" | "number" | "thousands" | "decimal" | ... | 右键菜单设置的数据展示格式 |
cellStyle.verticalAlign | "top" | "middle" | "bottom" | 表体垂直对齐 |
headerStyle.verticalAlign | "top" | "middle" | "bottom" | 表头垂直对齐 |
render | (value, record, rowIndex, column) => ... | 自定义渲染 |
onCell | (value, record, rowIndex, column) => { rowSpan?: number; colSpan?: number } | 单元格跨度控制 |
summary | boolean | ListTableSummaryConfig | 汇总配置 |
summaryTitle | string | 汇总标题文本 |
常见嵌套能力
| 配置 | 说明 |
|---|---|
query | 排序、筛选、远程查询、自动合并单元格 |
rowSelector | 空白整行选择器,单击选中一行、纵向拖拽选中连续多行 |
rowSelection | checkbox / radio 行选择 |
expandable | 树形数据、详情展开、展开列 |
pagination | 按钮分页或滚动加载 |
rowDrag | 行拖拽、跨表拖拽、拖放回调 |
contextMenu | 复制、导出、排序、冻结、列显示、分组栏、对齐、数据格式、密度、缩放等菜单能力 |
分组功能默认关闭,只有显式传入 grouping: {} 才会启用右键菜单中的分组入口。grouping.visible 控制 Canvas 顶部分组栏初始显隐,默认 false;grouping.columnKeys 按顺序声明初始分组层级。显示分组栏后可将列头拖入栏内;分组列会从表头和表体隐藏,仅以 Canvas 标签展示,移除标签会立即恢复该列。右键分组栏可隐藏分组栏;grouping.onChange 会在标签增删时收到最新列 key 列表。点击分组标题可折叠或展开该分组,重新设置分组列后会清除折叠状态。
rowSelection.selectRowByClick 控制点击普通表体区域时是否同步切换 checkbox / radio 选择状态,默认关闭。该配置直接复用表格内部选择状态,无需通过外部 key 强制重新创建表格;查看详情等业务点击仍使用 onRow().onClick。
rowSelector 与 rowSelection 相互独立。默认在最左侧展示宽度为 12px 的空白选择器列;传入 { width: 32 } 可自定义宽度,传入 false 可隐藏。该列只建立 Canvas 整行选区,不修改 checkbox / radio 的选中数据。
方法说明
| 方法 | 签名 | 说明 |
|---|---|---|
constructor | new ListTable(container, options) | 创建实例 |
mount | () => void | 挂载表格 |
updateOptions | (options) => void | 更新配置并刷新 |
getDebugSnapshot | () => ListTableDebugSnapshot | null | 获取调试快照 |
validate | () => Promise<RecordType[]> | 手动触发校验,失败时抛出 ListTableValidationFailure |
exportXlsx | (options?: ListTableExportXlsxOptions) => void | 异步导出全部原始数据为 XLSX,可配置文件名和汇总行 |
scrollTo | (options: ListTableScrollToOptions) => void | 滚动到指定行 |
destroy | () => void | 销毁实例 |
列配置可通过 settingTitle、hideInSetting、disableHide 约束列设置中的标题和隐藏权限。hidden 是业务动态隐藏约束,默认 false,不读写缓存;visible 是用户显隐偏好,默认 true,按 storageKey 保存。实际显示为 hidden !== true && visible !== false。旧缓存中的显隐 hidden 不再恢复,列宽、顺序和固定配置继续保留。详见 业务显隐与用户列偏好。
XLSX Worker 导出
导出默认先确认文件名称,输入框默认填入配置的名称,后缀单独显示且不可修改。表格选项 exportOptions: { fileName: "报表", promptFileName: false } 可跳过弹窗;单次 exportXlsx({ promptFileName: false }) 可覆盖默认行为。原生接入通过 ui.onExportNameRequest 自定义弹窗,未提供时使用浏览器输入框。
当前显示汇总行时,XLSX 默认在全部数据末尾追加汇总行(子表格独立判断);显式设置 includeSummary: false 可排除汇总行,true 可强制追加。汇总按导出的全量数据与当前列配置计算。
exportXlsx() 默认对 1 万行及以上数据尝试 Worker;现有调用无需修改。表格加载提示会显示已处理行数与文件生成阶段。取值和 render 回调仍在主线程执行,工作表构建、文件编码交给 Worker。
const controller = new AbortController();
table.exportXlsx({
fileName: "表格数据",
includeSummary: true,
worker: "auto", // true:始终尝试;false:使用主线程
maxRowsPerFile: 100_000, // 默认十万行;20 列以内无需降低每份行数
signal: controller.signal,
onError: (error) => console.error("导出失败", error), // 可替换为业务提示
onProgress: ({ phase, completedRows, totalRows }) => {
console.log(phase, completedRows, totalRows);
},
});
// 需要取消时调用 controller.abort()。
大数据按行数和单元格预算自动拆成多个独立 XLSX,文件名追加 _001、_002。每份保留表头,汇总只追加在全部数据末尾。逐份生成、下载并释放,不同时持有全部工作簿。进度包含 fileIndex、fileCount;可通过 saveFile(blob, fileName, part) 接管保存,等待其返回后才生成下一份。
浏览器可能要求允许下载多个文件。取消或失败会停止后续文件,已经下载的文件保留。每份默认最多 10 万行(含汇总尾行、不含表头),20 列以内按此行数分文件;超过 20 列时受 2,000,020 个单元格预算限制,适当减小每份行数。长文本另有 16M UTF-16 码元安全预算,超限时需调低 maxRowsPerFile。Worker 不可用时回退;CSV、TXT、JSON 不受影响。千万行多列数据仍建议服务端导出。
内建单元格内容
render 支持直接返回结构化内容,内建类型包括:
textmoneyimageqrcodebarcodeprogresschartratebuttontagcoloriconcheckboxradioswitchlink
icon 当前内置 star、sun、moon、check(√)和 close(×)。
开关二次确认
switch 内容可配置 popconfirm,与危险按钮复用同一套确认浮层和文案字段。点击后先确认,确认时才触发 onClick;取消或禁用时不触发回调。未配置 popconfirm 时直接触发回调。开关仍由业务通过 checked 控制,组件不会自动修改记录。
render(value, record) {
return {
type: "switch",
checked: Boolean(value),
popconfirm: {
title: value ? "确认停用?" : "确认启用?",
description: "确认后将更新启用状态。",
okText: "确定",
cancelText: "取消",
},
onClick: () => updateActive(record.id, !value),
};
}
原生包在未提供 ui.onDangerConfirmRequest 时回退使用浏览器 confirm;React 适配包自动展示确认浮层。
金额单元格示例
const columns = [
{
title: "金额",
dataIndex: "amount",
width: 140,
align: "right",
cellType: "money",
moneyFormat: {
symbol: "¥",
thousandsSeparator: true,
decimalPlaces: 2,
},
},
{
title: "结算金额",
dataIndex: "settlementAmount",
width: 160,
align: "right",
render(value) {
return {
type: "money",
value: value as number,
symbol: "$",
thousandsSeparator: ",",
decimalPlaces: 1,
};
},
},
];
行点击事件
onRow 使用统一的回调结构。由于表格由 Canvas 渲染,支持整行点击事件及 style.backgroundColor、style.color。业务可按记录 key 控制持续高亮,初始渲染、冻结列及悬停时均生效;校验错误样式优先。
const table = new ListTable(container, {
columns,
dataSource,
onRow: (record, rowIndex) => ({
style:
record.id === selectedId
? { backgroundColor: "#d6eaff", color: "#003a8c" }
: undefined,
onClick: (event) => {
console.log(record, rowIndex, event);
},
}),
});
推荐搭配
- 原生 DOM 场景:直接使用
ListTable - React 场景:优先使用
@canvas-components/react-list-table - 二维码 / 条形码 / 图表单元格:配合
@canvas-components/qrcode、@canvas-components/barcode、@canvas-components/chart
滚动条配置
scrollbar 同时适用于原生表格与 React 适配组件。通用配置由 horizontal、vertical 单独覆盖;不配置时保留原有外观。
scrollbar: {
thickness: 10,
thumbColor: '#8793a5',
trackColor: '#f1f5f9',
buttonColor: '#475569',
thumbRadius: 4,
horizontal: { thickness: 8, thumbColor: '#1677ff' },
vertical: { thickness: 14, trackColor: '#e2e8f0' },
}
thickness:横向滚动条高度、纵向滚动条宽度,单位 px,默认 10,最小 6。thumbColor/trackColor/buttonColor:滑块、滚动槽、两端箭头颜色。thumbRadius:滑块圆角,0为直角;不传时保持胶囊形状。horizontal: false或vertical: false:单独隐藏某一轴,也可设置该轴的visible: false。visible: false:同时隐藏两轴,优先于单轴设置。隐藏后释放对应槽位,仍可通过滚轮或程序滚动。
配置仅作用于当前 Canvas 表格,不修改全局浏览器滚动条样式。
级联选择编辑(Cascader)
formItemProps.valueType: "cascader" 使用多列展开的单选/多选级联选择器,配置支持级联选择。父表和嵌套子表使用相同配置;React 适配包提供编辑界面,原生包仅提供配置与路径格式化,不引入 React。
{
title: '所属地区', dataIndex: 'region', editable: true, width: 240,
formItemProps: {
valueType: 'cascader', showSearch: true, allowClear: true,
expandTrigger: 'click', changeOnSelect: false,
options: [{ label: '浙江', value: 'zj', children: [
{ label: '杭州', value: 0, children: [{ label: '西湖区', value: 'xh' }] },
{ label: '停用地区', value: 'disabled', disabled: true },
] }],
},
}
// 行数据:{ region: ['zj', 0, 'xh'] }
- 值为
(string | number)[],清空将编辑草稿更新为[],保持编辑状态,可继续选择、失焦提交或按 Escape 取消。默认显示完整标签路径,以/分隔;可用displayRender(labels, selectedOptions)定制。显式format优先级更高。 showSearch默认false,开启后按完整路径搜索;expandTrigger支持click(默认)或hover。changeOnSelect默认false;开启后点击中间级更新编辑草稿,失焦或 Tab 提交,选择末级立即提交。- 方向键在级联菜单中导航,Enter 选择,Escape 取消本次编辑,Tab 切换编辑单元格。
- 支持单选末级、单选任意级、多选父级回填和多选末级回填。使用标准
label/value/children节点;暂不包含异步加载及自定义字段映射。 - 文档站“单元格编辑”示例新增“所属地区(级联)”列。
多选配置如下:
formItemProps: {
valueType: 'cascader',
options: regionOptions,
multiple: true,
showCheckedStrategy: 'SHOW_PARENT', // 或 'SHOW_CHILD'
showSearch: true,
}
// 多选值是二维路径数组,例如:[['zj', 'hz', 0], ['js', 'nj', 1]]
multiple默认false;开启后复选框切换当前节点及其可用后代,父级支持半选。点击分支文案仅展开,点击末级文案可勾选。showCheckedStrategy默认SHOW_PARENT,全部可用子级选中时回填父级路径;SHOW_CHILD回填全部已选末级路径。该策略同时作用于变更回调、输入框和单元格文本。- 多选路径以
;分隔回显,displayRender逐条处理路径。禁用节点不参与父子联动,已有禁用值不会被其他分支操作误删。 - 多选勾选及清空不会退出编辑;失焦或 Tab 提交,Escape 取消。方向键导航,Enter / 空格切换当前选项(搜索输入中的空格仍用于输入)。
changeOnSelect仅影响单选。 - 示例新增“级联单选(任意级)”“级联多选(父级回填)”“级联多选(末级回填)”列。
树选择编辑(TreeSelect)
使用 formItemProps.valueType: 'tree-select',配置支持树形选择。父表和嵌套子表使用同一套编辑配置。
{
title: '所属部门', dataIndex: 'departments', editable: true, width: 280,
formItemProps: {
valueType: 'tree-select',
multiple: true, // 不传则单选
showSearch: true,
allowClear: true,
treeDefaultExpandAll: true,
treeData: [{ title: '组织架构', value: 'root', selectable: false, children: [
{ title: '生产中心', value: 'production', children: [
{ title: '织造车间', value: 0 },
{ title: '检验车间', value: 'inspection' },
] },
{ title: '停用部门', value: 'disabled', disabled: true },
] }],
},
}
// 单选值:0;多选值:[0, 'inspection'],不是 Cascader 的路径数组。
treeData使用title/value/children,value必须在树内唯一;数字0与字符串'0'严格区分。disabled禁止选择当前节点,selectable: false用于只能展开的分组。multiple默认false;多选独立切换各个节点,不进行父子联动。单元格和输入框回显节点标题,多选使用;分隔,显式format可覆盖单元格显示。showSearch单选默认关闭、多选默认开启,按标题搜索并保留祖先上下文;搜索不修改原展开状态。treeDefaultExpandAll默认false;也可使用treeDefaultExpandedKeys指定默认展开节点,依据节点的key(省略时使用value)。已选节点的祖先自动展开。- 单选选择后提交;多选连续选择不提交,失焦或 Tab 提交。清空单选为
null、多选为[],保持焦点和弹层,可继续选择;Escape 取消编辑。 - 上下方向键导航,左右方向键展开/收起,Enter 或空格选择。搜索输入中的空格用于输入。
- 本次实现基础单选与独立多选,不包含
treeCheckable、treeCheckStrictly、异步加载、labelInValue和自定义字段映射。 - “单元格编辑”示例新增“树选择(单选)”“树选择(多选)”列。组件内部索引和值处理放在组件目录,不增加公共工具包接口。
绘制到已有 Canvas 上下文
drawListTableToCanvas({ ctx, width, height, options, requestRender }) 使用现有列配置绘制静态表格,复用布局、单元格、表头与汇总规则,不创建表格 DOM 实例,也不重置目标 Canvas 的尺寸或变换。width、height 为逻辑像素,调用方可通过 ctx.translate/scale 定位和缩放。requestRender 用于图片加载后的重绘通知。内容按指定边界裁剪;不显示排序、筛选和滚动条等交互控件。汇总同步计算,适合打印和有限数据量的离屏绘制。
清空行选择
原生实例调用 table.clearRowSelection()。该方法清空通过选中 key 设置的行高亮、行勾选和 Shift 选择锚点,配置 rowSelection 且选择发生变化时触发 rowSelection.onChange([], [], { type: "none" })。受控模式请在回调中同步 selectedRowKeys。
过滤面板勾选或取消过滤项并即时应用时,会立即清空全部已选行,不保留仍在过滤结果中的勾选。
调用 table.setSelectedRowKeys(["1", "2", "3"]) 可以主动设置行高亮,无需配置 rowSelection,也不会新增 checkbox / radio 列。配置 rowSelection 时同时同步勾选并触发 onChange;单选模式只保留第一个 key。传入空数组等同清空,普通配置更新会保留未受控的选中 key。
普通按钮独立配置 popconfirm 即可在执行 onClick 前确认,无需设置 danger;禁用按钮不发起确认。汇总栏显示继续按现有缓存规则恢复用户选择;仅切换或恢复密度不会主动创建汇总栏,没有汇总配置时重置列设置也不会新增汇总栏。
表头文字区域优先选择并支持复制;拖动表头空白区域调整列顺序。完整交互见文档站“行选择”的业务状态与缓存回归示例。
展开行子表在手动调整列宽或列顺序后使用独立布局。父表配置 storageKey 且开启 cacheColumnSettings 时,子表的列宽与列顺序一同缓存;缓存按父行 rowKey、所在列和内容索引隔离,建议配置稳定的 rowKey。刷新或重新挂载后恢复,关闭列缓存后不再保存这些布局。展开行示例提供“重新挂载验证缓存”按钮。
禁用的原生按钮内容(type: "button", disabled: true)不会触发内容、单元格、行或列的点击回调,也不会改变活动单元格和鼠标选区;同一单元格内其他启用按钮仍可独立交互。
同一单元格编辑多个字段
formItemProps 兼容配置对象、配置数组,以及返回对象或数组的函数。数组每项直接配置 key、dataIndex、valueType、rules、onChange,不需要额外的 valueType。key 在当前单元格内唯一;dataIndex 相对行记录,嵌套属性使用 ['measurement', 'quantity'],写入保留未配置属性。label 提供无障碍名称,width 为固定像素或 fr 比例,默认 1fr;可设置 disabled、readOnly。
formItemProps: (_value, record, _index, context) => [
{
key: "quantity",
dataIndex: "quantity",
label: "数量",
valueType: "number",
width: "1fr",
rules: [{ required: true, message: "请填写数量" }],
onChange: (quantity, { draftRecord, record }) => ({
totalPrice: Number(quantity ?? 0) * (draftRecord ?? record).unitPrice,
}),
},
{
key: "unit",
dataIndex: "unitId",
label: "单位",
valueType: "select",
width: 88,
options:
(context?.draftRecord ?? record).kind === "weight"
? weightUnits
: lengthUnits,
rules: [{ required: true, message: "请选择单位" }],
},
];
函数的 record 保持会话基准快照,可选第四参数的只读 draftRecord 提供尚未提交的候选行。数组内回调接收当前项值,context 的 dataIndex 是该项路径,并增加 itemKey 与可选 draftRecord;同步补丁按操作顺序累计,整组提交时一次写入,不递归触发其他回调。回调返回 false、异常或非法补丁会保持编辑;普通校验失败仍保存整组记录并按项标错,validate() 包含没有独立列的字段。数字 0 与单位编码 0 均为有效值。
Tab/Shift+Tab 在组内遍历,越过首尾才提交并导航;Enter 提交整组,子选择弹层优先处理确认;Escape 先关闭子弹层,再取消整组。非编辑状态也按各项 width 分区绘制,空字段保留位置,长文本仅在本项内省略;进入/退出编辑时各字段位置保持一致。复杂展示使用列 render。列排序、筛选继续使用列自身 dataIndex。空数组不提供编辑,单项数组仍要求显式绑定。
首期数组支持 text、number、select、auto-complete、table-select、year、quarter、month、date、time、datetime、date-range、timerange、datetime-range、input-range、math-interval、number-unit。会话中保持项的顺序、key、路径及类型稳定,不支持嵌套数组、多行布局、动态增删项及 upload/color/tree-select/cascader;这些类型的原有对象配置不受影响。原生宿主须使用声明数组编辑能力的编辑工厂。中文示例位于文档站“单元格编辑 → 多字段组合编辑”。
数组编辑项在非编辑态按各自宽度保留分区,校验背景与 tooltip 仅作用于出错表单,提示锚定该表单区域中间;编辑态不展示tooltip,非编辑态鼠标移入出错表单区域时展示。日期、范围、数学区间及数字单位默认展示沿用编辑控件布局;单元格 padding 传递给表单,避免重复内边距。显式 format 与列 render 的自定义展示仍优先。
紧凑图片与表头操作
图片内容在绘制前按单元格扣除内边距后的可用宽高等比缩小,仅缩小不放大;不会为图片增高固定行高。图片原配置、自动行高测量和原图预览保持原尺寸。
表格和列均支持 showHeaderActions?: boolean(默认 true)。设为 false 时隐藏排序、筛选、固定操作图标及编辑标记,不预留图标空间。表格级关闭优先于列级开启;斜线表头仍需 headerDiagonal.showActions:true 才显示操作。普通表头的排序图标同时遵守 allowSort:false 和 sorter:false。此选项只控制图标显示,右键菜单、拖拽、缩放和编辑能力仍由各自配置决定。
运行时密度、滚动条、列顺序和列配置偏好按 storageKey 隔离。切换 key 时先使用新身份源配置,再读取新身份缓存;业务 hidden 仍不缓存。