npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lensui/lens-table

v0.1.64

Published

A lightweight, canvas-rendered React table component for LensUI.

Readme

@lensui/lens-table

@lensui/lens-table 是一个面向大数据量的 React 表格组件。主体单元格使用 Canvas 绘制,编辑器、筛选器、菜单和可选中文本使用 DOM 承载,适合需要 10 万行级别滚动、固定列、拖拽、编辑和键盘操作的业务表格。

安装

npm install @lensui/lens-table

React 和 React DOM 18 或更高版本为 peer dependencies。

基础使用

import { useState } from 'react';
import {
  Table,
  type CellChange,
  type GridColumn,
} from '@lensui/lens-table';
import '@lensui/lens-table/style.css';

interface Row {
  id: number;
  name: string;
  department: string;
  joinedAt: string;
  amount: number;
}

const columns: GridColumn<Row>[] = [
  { key: 'name', title: '姓名', dataIndex: 'name', width: 180, editable: true },
  {
    key: 'department',
    title: '部门',
    dataIndex: 'department',
    width: 160,
    editable: true,
    editor: {
      type: 'select',
      options: [
        { label: '研发', value: '研发' },
        { label: '设计', value: '设计' },
      ],
    },
  },
  {
    key: 'joinedAt',
    title: '入职日期',
    dataIndex: 'joinedAt',
    width: 160,
    editable: true,
    editor: { type: 'date' },
  },
  { key: 'amount', title: '金额', dataIndex: 'amount', width: 140, align: 'right' },
];

export function Example() {
  const [rows, setRows] = useState<Row[]>([
    { id: 1, name: '用户 1', department: '研发', joinedAt: '2026-01-15', amount: 120 },
  ]);

  const handleCellChange = ({ row, columnKey, value }: CellChange<Row>) => {
    setRows((current) =>
      current.map((item) =>
        item.id === row.id ? { ...item, [columnKey]: value } : item,
      ),
    );
  };

  return (
    <Table
      columns={columns}
      rows={rows}
      height={560}
      onCellChange={handleCellChange}
    />
  );
}

Table 参数

| 参数 | 类型 | 默认值 | 说明 | | ------------------------------ | ------------------------------------------------------------------------------------ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | columns | GridColumn<Row>[] | 必填 | 表格列配置。Canvas 绘制、DOM 编辑器和交互都依赖这份配置。 | | rows | Row[] | 必填 | 表格数据。组件不会直接修改数据,编辑后通过事件通知外部更新。 | | rowKey | keyof Row \| ((row, index) => string \| number) | id | 行唯一标识。默认读取每行的id 字段;如果数据不是 id 主键,请显式传入字段名或函数。 | | width | number \| string | 100% | 表格宽度。数字按像素处理,字符串可传100%、80vw 等。 | | height | number \| string | 100% | 表格最大高度。默认继承父容器高度;如果父容器没有可计算高度,则回落到480px。数据较少时默认按真实内容高度收缩,数据较多时不超过该高度。 | | layout | { rowHeight?: number; headerHeight?: number; headerActionSize?: number } | { rowHeight: 36, headerHeight: 40, headerActionSize: 14 } | 表格布局尺寸配置。headerActionSize 同步控制表头搜索、排序和拖拽图标的尺寸、占位及点击区域;自定义多行表头会以 headerHeight 为最小高度自动撑开。 | | rowHeight | number | 36 | 已兼容保留,建议改用 layout.rowHeight。 | | headerHeight | number | 40 | 已兼容保留,建议改用 layout.headerHeight。 | | fixedHeader | boolean | true | 表头是否固定在顶部。 | | locale | 'zh-CN' \| 'en-US' \| LocaleConfig | 'zh-CN' | 国际化配置。传字符串时使用内置中英文文案;传对象时可通过language 指定语言,并通过 labels 覆盖文案。 | | borderless | boolean | false | 是否弱化竖向边框。开启后隐藏单元格之间的竖线,但保留横向分隔线。 | | verticalBorderless | boolean | false | 已兼容保留,建议改用 borderless。 | | horizontalBorderless | boolean | false | 是否隐藏横向行分隔线及上下边框,同时保留纵向列分隔线。可与 striped 配合使用。 | | frameBorderless | boolean | false | 是否隐藏表格四周外框,同时保留内部行列分隔线。 | | rowStyle | (row, rowIndex) => { color?: string; backgroundColor?: string } | - | 自定义整行绘制样式,覆盖数据列及自动生成的选择列、序号列和拖拽列。 | | striped | boolean \| string | false | 是否显示斑马纹行背景。传true 使用主题变量,传颜色字符串可自定义斑马纹颜色,例如 '#f6faff'。 | | highlight | { editedCells?: boolean \| string; insertedRows?: boolean \| string } | {} | 高亮反馈配置。editedCells 控制编辑过的单元格底色,insertedRows 控制乐观插入行底色;传 true 使用默认色,传颜色字符串可自定义。 | | cellSpans | TableCellSpan[] \| ((ctx) => TableCellSpan \| { rowSpan?: number; colSpan?: number } \| null) | [] | 合并主体单元格配置。可传数组,也可传函数按单元格生成规则。推荐使用 rowKey 定位;横向合并会限制在同一个固定列分区内,工具列不会参与合并。 | | loading | boolean | false | 显示加载态。 | | loadingContent | ReactNode | - | 自定义加载内容。传入后,首次加载和刷新加载不再区分,都会统一展示这个内容。 | | virtualized | boolean \| { enabled?: boolean; overscan?: number } | true | 虚拟渲染配置。传false 时渲染全部行列;传对象时可通过 enabled 开关,并用 overscan 配置可见范围外的缓冲行列数量,默认 4。 | | tooltip | boolean \| { header?: boolean; cell?: boolean } | true | 文本 tooltip 总配置。传true 时表头和单元格 hover 都显示完整文本;传 false 可关闭;传对象可分别控制表头和单元格。 | | summary | boolean \| { position?: 'top' \| 'bottom'; verticalBordered?: boolean; emptyValue?: ReactNode } | false | 表格级合计行配置。默认不显示;传 true 或对象即开启合计行,对象可控制显示在表头下方或底部、是否显示内部竖线,以及无合计值单元格的填充内容。 | | rowNumber | boolean \| TableRowNumberConfig | true | 是否显示序号列。默认开启并自动生成左侧序号列,不需要在columns 中配置;传 false 可关闭。传 { fixed: false } 可取消序号列固定,fixed 默认 true。 | | selectedCell | GridSelectionTarget \| null | 非受控 | 受控单元格选中状态。传 columnKey,行可使用 rowKey 或 rowIndex 定位。 | | defaultSelectedCell | GridSelectionTarget \| null | null | 默认选中单元格。传 columnKey,行可使用 rowKey 或 rowIndex 定位。 | | onSelectedCellChange | (selection) => void | - | 单元格选中变化回调,selection.value 为当前单元格的原始值。 | | rangeSelection | boolean | false | 是否允许鼠标拖动选择多个单元格。开启后支持范围复制、范围右键菜单和右下角拖拽扩展范围。 | | rowSelection | boolean \| RowSelectionConfig | false | 开启行选择。支持 mode、showCheckbox、indicator: 'checkbox' \| 'arrow' \| 'none' 和 columnWidth;none 保留点击区域但不绘制标记。默认自动生成左侧选择列。 | | selectedRowKeys | GridKey[] | 非受控 | 受控行选择 key 列表。 | | defaultSelectedRowKeys | GridKey[] | [] | 非受控模式下默认选中的行 key。 | | onSelectedRowChange | (keys, rows, indices) => void | - | 行选择变化回调。 | | columnSelection | boolean \| AxisSelectionConfig | false | 开启列选择。可传{ mode: 'single' \| 'multiple' }。 | | selectedColumnKeys | string[] | 非受控 | 受控列选择 key 列表。 | | defaultSelectedColumnKeys | string[] | [] | 非受控模式下默认选中的列 key。 | | onSelectedColumnChange | (keys) => void | - | 列选择变化回调。 | | columnDraggable | boolean | true | 是否允许拖拽调整列顺序。默认开启,普通列默认显示表头拖拽入口,传false 可关闭。 | | onColumnsReorder | (columns, detail) => void | - | 列拖拽重排完成回调,返回重排后的完整列配置和索引详情。 | | rowDraggable | boolean | false | 是否允许拖拽调整行顺序。开启后组件自动生成左侧拖拽手柄列,不需要在columns 中配置。 | | onRowsReorder | (rows, detail) => void | - | 行拖拽重排完成回调,返回重排后的完整行数据和索引详情。 | | columnResizable | boolean | true | 是否允许拖拽调整列宽。默认开启,传 false 可关闭。 | | onColumnResize | (columnKey, width) => void | - | 列宽变化回调。未传时组件会在内部维护列宽;传入后可用于持久化或受控同步。 | | onInsertRows | (rows, insertedRows) => void | - | 插入完成后返回新的完整行数组和本次插入的行。 | | onDeleteRows | (rows, deletedRows) => void | - | 删除完成后返回新的完整行数组和本次删除的行。 | | onSortChange | (rows, state) => Row[] | - | 自定义排序方法。排序状态由组件内部管理;返回排序后的行。 | | onFilterChange | (rows, values) => Row[] | - | 自定义筛选方法。筛选值由组件内部管理;返回符合条件的行。 | | onCellChange | (change) => void | - | 单元格编辑提交回调。 | | onCellClick | (cell: TableCellContext<Row>, event: React.MouseEvent<HTMLElement>) => void | - | 数据单元格单击回调;调用 event.preventDefault() 可阻止默认选择行为。 | | onCellDoubleClick | (cell: TableCellContext<Row>, event: React.MouseEvent<HTMLElement>) => void | - | 数据单元格双击回调,在编辑前触发;调用 event.preventDefault() 可阻止进入编辑。 | | onCellContextMenu | (event) => void | - | 单元格右键回调,可用于扩展自定义菜单逻辑。 | | contextMenu | boolean \| { header?, cell?, range? } | true | true 开启默认右键菜单,false 关闭右键菜单,传对象可自定义菜单项、顺序和分割线。 | | emptyContent | ReactNode | 内置空状态 | 自定义空数据内容。 | | className | string | - | 根节点 className。 | | style | CSSProperties | - | 根节点内联样式,可用于传入主题 CSS 变量。 | | ariaLabel | string | Table | 表格区域无障碍名称。 |

插入和删除的位置计算、空行生成、乐观显示及目标行过滤均由组件处理,外部只需更新最终数组:

<Table
  rows={rows}
  columns={columns}
  onInsertRows={setRows}
  onDeleteRows={setRows}
/>

高亮和范围选择

编辑高亮、插入行高亮和鼠标拖动多单元格选择默认都不开启。需要这些视觉反馈或批量选择能力时,通过表格级配置显式开启:

<Table
  columns={columns}
  rows={rows}
  highlight={{
    editedCells: true,
    insertedRows: '#c8ead4',
  }}
  rangeSelection
/>

highlight.editedCells 和 highlight.insertedRows 传 true 时使用主题默认色;传颜色字符串时只覆盖对应高亮色。rangeSelection 开启后,可以用鼠标拖动选择多个单元格,并在范围内复制或打开范围右键菜单。

合并单元格

通过 cellSpans 配置主体区域的合并单元格。每个合并项以左上角单元格为锚点,rowSpan 和 colSpan 分别控制覆盖的行数和列数:

<Table
  columns={columns}
  rows={rows}
  cellSpans={[
    { rowKey: rows[2].id, columnKey: 'role', rowSpan: 2 },
    { rowKey: rows[7].id, columnKey: 'department', colSpan: 2 },
  ]}
/>

也可以传函数按单元格声明规则。函数返回值会默认使用当前单元格作为锚点,因此只需要返回跨度:

<Table
  columns={columns}
  rows={rows}
  cellSpans={({ rowIndex, column }) => {
    if (column.key === 'department' && rowIndex % 3 === 0) return { rowSpan: 2 };
    return null;
  }}
/>

rowKey 优先于 rowIndex,在排序、过滤或插入删除行后更稳定。被合并覆盖的单元格不会单独绘制或命中,点击覆盖区域会选中合并区域的锚点单元格。横向合并不会跨越固定左列、滚动列、固定右列这三个区域;如果声明跨区,组件会自动收缩到当前区域内。

Column 参数

| 参数 | 类型 | 默认值 | 说明 | | -------------- | -------------------------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------- | | key | string | 必填 | 列唯一标识。排序、筛选、选择、编辑回调都使用它。 | | title | string | 必填 | 表头展示文本。 | | renderHeader | (column) => ReactNode | - | 自定义表头 React 内容。多行内容会自动撑开表头高度;title 仍用于 tooltip、拖拽预览和回退文本。 | | children | GridColumn<Row>[] | - | 子列配置。传入后该列作为多级表头分组,只有叶子列渲染数据单元格。 | | dataIndex | keyof Row | - | 从行数据中读取和写入的字段。工具列可不传。 | | width | number | 140 | 列宽,单位 px。最小宽度由内部布局保护。 | | fixed | 'left' \| 'right' | - | 固定列位置。左/右固定列会覆盖滚动列并显示阴影。 | | align | 'left' \| 'center' \| 'right' | 'left' | 单元格文本和拖拽预览文本对齐方式。 | | editable | boolean \| ((value: unknown, row: Row, rowIndex: number) => boolean) | false | 是否允许双击、Enter 或右键菜单进入编辑。函数按当前单元格动态判断,提交时再次检查权限。 | | editor | EditorConfig | { type: 'text' } | 内置编辑器配置。详见下方编辑器表。未配置时默认使用文本编辑器。 | | sortable | boolean | true | 表头显示排序按钮。普通数据列默认开启,传 false 可关闭。 | | filterable | boolean | true | 表头显示筛选按钮。普通数据列默认开启,传 false 可关闭。 | | formatter | (value, row, rowIndex) => string | - | 自定义展示文本。异常会被隔离并显示渲染错误文案。 | | renderCell | (value, row, rowIndex) => ReactNode | - | 自定义单元格 React 内容。适合标签、徽标、图标组合等内置编辑器无法表达的展示。 | | summary | boolean \| ((rows, column) => ReactNode) | false | 是否在合计行显示该列合计。传true 自动累加数字值;传函数可自定义合计内容。 | | cellStyle | (value, row, rowIndex) => { color?: string; backgroundColor?: string } | - | 自定义单元格绘制样式。支持文本颜色和背景色,适合金额、状态等条件高亮。 |

多级表头

列可以通过 children 组成多级表头。分组列只负责展示表头,数据、排序、筛选、编辑等能力由叶子列承载:

const columns: GridColumn<Person>[] = [
  {
    key: 'basic',
    title: '基础信息',
    children: [
      { key: 'name', title: '姓名', dataIndex: 'name', fixed: 'left' },
      { key: 'department', title: '部门', dataIndex: 'department' },
    ],
  },
  {
    key: 'work',
    title: '工作信息',
    children: [
      { key: 'role', title: '岗位', dataIndex: 'role' },
      { key: 'amount', title: '金额', dataIndex: 'amount', align: 'right' },
    ],
  },
];

列合计

合计行由表格级 summary 和列级 columns[].summary 共同控制:表格级 summary 控制是否显示、位置、内部竖线和空白填充;列级 summary 控制这一列显示什么合计内容。默认不显示合计行;传 summary={true} 或对象即开启合计行。合计行不参与虚拟滚动、排序、筛选、选择和编辑。

<Table
  columns={columns}
  rows={rows}
  summary={{ position: 'bottom' }}
/>

表格级 summary 支持:

| 属性 | 类型 | 默认值 | 说明 | | -------------------- | -------------------- | ------------ | ----------------------------------------------------------------------------------------- | | position | 'top' \| 'bottom' | 'bottom' | 合计行位置。top 显示在表头下方,bottom 显示在表格底部。 | | verticalBordered | boolean | true | 是否显示合计行内部竖线。全局 verticalBorderless 为 true 时优先隐藏竖线。 | | emptyValue | ReactNode | '' | 合计行没有值的单元格填充内容。默认显示为空。 |

const columns: GridColumn<Row>[] = [
  {
    key: 'amount',
    title: '金额',
    dataIndex: 'amount',
    align: 'right',
    formatter: (value) => `¥${Number(value).toLocaleString('zh-CN')}`,
    summary: true,
  },
];

summary: true 会自动累加该列的数字值,非数字值会被忽略;如果列配置了 formatter,合计结果也会复用该 formatter 展示。

需要自定义合计内容时,可以传函数:

const columns: GridColumn<Row>[] = [
  {
    key: 'name',
    title: '姓名',
    dataIndex: 'name',
    summary: (rows) => `共 ${rows.length} 条`,
  },
  {
    key: 'amount',
    title: '金额',
    dataIndex: 'amount',
    summary: (rows) =>
      rows.reduce((total, row) => total + Number(row.amount ?? 0), 0),
  },
];

内置编辑器

| editor.type | 数据格式示例 | 说明 | | ------------------- | ----------------------------------------- | ------------------------------------------------------------------- | | text | '' | 默认文本编辑器。editable: true 且不传 editor 时也会使用它。 | | select | '' | 下拉选择。需要传options: Array<{ label; value }>。 | | date | '2026-01-15' | 日期选择器。 | | time | '09:30' 或 '09:30:15' | 时间选择器。是否显示秒由format 或当前值推断。 | | date-time | '2026-01-15 09:30' | 日期时间选择器。日期和时间默认使用空格连接。 | | year | '2026' | 年选择器。 | | month | '2026-01' | 年月选择器。 | | date-range | '2026-01-01 ~ 2026-01-15' | 日期范围选择器。 | | time-range | '09:00 ~ 18:00' | 时间范围选择器。 | | date-time-range | '2026-01-01 09:00 ~ 2026-01-01 18:00' | 日期时间范围选择器,包含日期和时间面板。 |

可选 format 用于控制提交到数据里的字符串格式,例如:

{
  key: 'startAt',
  title: '开始时间',
  dataIndex: 'startAt',
  editable: true,
  editor: { type: 'date-time', format: 'yyyy-MM-dd HH:mm:ss' },
}

右键菜单

contextMenu 支持三种模式:

  • true:开启默认右键菜单。不传时也是这个行为。
  • false:关闭组件右键菜单。
  • 对象:自定义表头、单元格和范围选择菜单。对象里的 header、cell、range 可以传 true 使用该区域默认菜单,传 false 关闭该区域菜单,传数组自定义该区域菜单,也可以传 { exclude } 基于默认菜单关闭部分内置项。

对象配置里的数组顺序就是菜单显示顺序,移除某个内置 key 就会关闭该项,'|' 会渲染分割线。也可以插入自定义菜单项。直接写 key 使用内置行为;传对象且 key 命中内置项时,可以覆盖 label、icon、shortcut、disabled、hidden,传 onClick 时会替换该内置行为。

<Table
  columns={columns}
  rows={rows}
  contextMenu={{
    header: true,
    cell: [
      'edit',
      'copy',
      'undo',
      'clear',
      '|',
      'select-column',
      {
        key: 'inspect',
        label: '查看详情',
        onClick: ({ row }) => console.log(row),
      },
    ],
    range: false,
  }}
/>

如果只是想关闭默认菜单中的一两个内置项,可以用 exclude:

<Table
  columns={columns}
  rows={rows}
  contextMenu={{
    cell: { exclude: ['undo', 'delete-row'] },
  }}
/>

对象菜单项支持这些属性:

| 属性 | 说明 | | ------------ | ------------------------------------------------------------------------------------------------- | | key | 菜单项唯一标识。命中内置 key 时覆盖内置菜单项;未命中时表示自定义菜单项。 | | label | 菜单主文本,支持ReactNode。内置项不传时使用默认文案;自定义项建议必传。 | | icon | 菜单左侧图标,支持ReactNode。内置项不传时使用默认图标;传入后会覆盖默认图标。 | | shortcut | 菜单右侧快捷键提示,支持ReactNode,只负责展示,不会自动绑定键盘事件。 | | disabled | 是否禁用菜单项。可以传布尔值,也可以传(context) => boolean 按当前表头、单元格或范围动态判断。 | | danger | 是否使用危险操作样式。 | | hidden | 是否隐藏菜单项。可以传布尔值,也可以传(context) => boolean 按当前表头、单元格或范围动态判断。 | | onClick | 点击回调。命中内置 key 时会替换内置行为;自定义菜单项通常需要提供。 | | children | 子菜单配置,只支持数组。数组项同样支持对象菜单项属性,也可以继续嵌套children。 | | render | 自定义右侧面板内容,支持ReactNode 或 (context) => ReactNode。 |

children 用于配置多级菜单:

<Table
  columns={columns}
  rows={rows}
  contextMenu={{
    cell: [
      'copy',
      {
        key: 'more',
        label: '更多操作',
        children: [
          { key: 'copy-id', label: '复制 ID', onClick: ({ row }) => console.log(row.id) },
          '|',
          {
            key: 'export',
            label: '导出',
            children: [
              { key: 'export-json', label: '导出 JSON', onClick: ({ row }) => console.log(row) },
            ],
          },
        ],
      },
    ],
  }}
/>

render 用于渲染自定义面板:

<Table
  columns={columns}
  rows={rows}
  contextMenu={{
    cell: [
      {
        key: 'custom-panel',
        label: '自定义面板',
        render: ({ row }) => <div style={{ padding: 12 }}>{row.name}</div>,
      },
    ],
  }}
/>

内置 key:

| 区域 | key | | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 表头 | copy, select-column | | 单元格 | edit, copy, annotation, select-row, select-column, insert-above, insert-below, move-up, move-down, undo, clear, delete-row | | 范围选择 | copy, annotation, clear | | 分割线 | \|,可写在 header、cell、range 数组中,用于渲染菜单分割线。 |

表头默认能力

普通数据列默认显示三个表头入口:拖拽排序、正序/倒序排序、筛选。大多数列不需要显式写 sortable: true 或 filterable: true。

const columns: GridColumn<Row>[] = [
  { key: 'name', title: '姓名', dataIndex: 'name' },
  { key: 'amount', title: '金额', dataIndex: 'amount', align: 'right' },
];

<Table columns={columns} rows={rows} />;

如果某一列不需要排序或筛选,显式传 false:

{
  key: 'remark',
  title: '备注',
  dataIndex: 'remark',
  sortable: false,
  filterable: false,
}

排序和筛选

排序和筛选状态均由组件内部管理。默认排序会比较数值或单元格显示文本;默认筛选按照显示文本执行不区分大小写的包含匹配。需要其他规则时,分别通过 onSortChange 和 onFilterChange 返回处理后的行。

<Table
  columns={columns}
  rows={rows}
  onSortChange={(currentRows, state) => {
    const direction = state.direction === 'asc' ? 1 : -1;
    return currentRows.slice().sort((left, right) =>
      String(left[state.columnKey as keyof Row]).localeCompare(
        String(right[state.columnKey as keyof Row]),
      ) * direction,
    );
  }}
  onFilterChange={(currentRows, values) =>
    currentRows.filter((row) =>
      Object.entries(values).every(([key, value]) =>
        String(row[key as keyof Row] ?? '').startsWith(value),
      ),
    )
  }
/>;

拖拽和列宽

拖拽后组件会在内部维护新的行列顺序,因此排序回调不是必需的。需要持久化或同步结果时,可以通过回调直接取得排序后的 rows 或 columns。列宽调整默认开启并支持非受控使用,传 columnResizable={false} 可关闭。

columnDraggable 默认开启,普通列会自动显示表头拖拽入口。行选择和行拖拽是表格级配置:开启 rowSelection 后默认自动生成左侧选择列(showCheckbox: false 时隐藏),开启 rowDraggable 后自动生成左侧拖拽手柄列,不需要在 columns 里配置 rowSelection 或 rowDragHandle。

<Table
  columns={columns}
  rows={rows}
  rowSelection={{ mode: 'multiple' }}
  rowDraggable
  onColumnsReorder={(nextColumns) => saveColumns(nextColumns)}
  onRowsReorder={(nextRows) => saveRows(nextRows)}
  onColumnResize={(columnKey, width) => {
    setColumns((current) =>
      current.map((column) =>
        column.key === columnKey ? { ...column, width } : column,
      ),
    );
  }}
/>;

隐藏选择框时不生成选择列,表头全选框也会隐藏;行选择状态、回调和右键菜单仍然可用。多选模式支持 Ctrl / Command 点击追加或取消选择,以及 Shift 点击连续选择。

<Table
  columns={columns}
  rows={rows}
  rowNumber={{ fixed: false }}
  rowSelection={{ mode: 'multiple', showCheckbox: false }}
  onSelectedRowChange={(keys, selectedRows) => console.log(keys, selectedRows)}
/>

rowNumber={true} 或 rowNumber={{ fixed: true }} 固定序号列;rowNumber={false} 隐藏序号列。

虚拟渲染

默认开启虚拟渲染,只绘制可见区域和少量缓冲区域。缓冲数量默认是 4,可以通过对象形式调整。

<Table
  columns={columns}
  rows={rows}
  virtualized={{ enabled: true, overscan: 6 }}
/>

如果数据量很小,或需要一次性渲染全部 DOM 文本层,可以关闭虚拟渲染:

<Table
  columns={columns}
  rows={rows}
  virtualized={false}
/>

加载状态

默认加载态会区分两种场景:首次加载显示骨架层,已有数据刷新时显示 spinner 遮罩。如果传入 loadingContent,组件不会再区分这两种场景,所有加载状态都会统一展示自定义内容。

<Table
  columns={columns}
  rows={rows}
  loading={loading}
  loadingContent={<div className="table-loading">加载中...</div>}
/>

主题适配

样式通过 CSS 变量开放。可以在全局、页面容器或单个表格上覆盖变量。

<Table
  columns={columns}
  rows={rows}
  style={{
    '--rvg-color-primary': '#10b981',
    '--rvg-color-bg': '#0f172a',
    '--rvg-color-text': '#e5e7eb',
    '--rvg-color-grid': '#334155',
    '--rvg-font-size-body': '12px',
  } as React.CSSProperties}
/>

常用变量:

| 变量 | 说明 | | ----------------------------------- | ---------------------------------------------------------------------------------- | | --rvg-font-size-body | 数据正文及单元格编辑器字号,默认 13px;不影响表头字号。 | | --rvg-color-bg | 表格背景色。 | | --rvg-color-header-bg | 表头背景色。 | | --rvg-color-text | 主文本颜色。 | | --rvg-color-muted | 次级文本颜色。 | | --rvg-color-icon | 图标颜色。 | | --rvg-color-grid | Canvas 网格线颜色。 | | --rvg-color-border | 弹层、输入框等 DOM 边框颜色。 | | --rvg-color-primary | 选中、按钮、焦点等强调色。 | | --rvg-color-selection-fill | 单元格选中背景。 | | --rvg-color-axis-selection-fill | 行/列选中背景。 | | --rvg-color-axis-selection-text | 行/列选中后的正文和选择标记颜色,默认继承主文本色。 | | --rvg-color-edited-fill | 已编辑单元格背景。 | | --rvg-color-stripe | striped={true} 时的斑马纹背景。也可以直接通过 striped="#f6faff" 单独指定。 |

国际化

locale 是统一的国际化入口。直接传 'zh-CN' 或 'en-US' 时会使用组件内置文案;传对象时通过 language 指定基础语言,并在 labels 里覆盖你关心的字段。

目前组件内置文案只提供中文和英文两套:zh-CN、en-US。日期选择器的底层库可以接受更多地区格式,但组件菜单、按钮、提示文案需要你通过对象模式自行覆盖。

<Table
  columns={columns}
  rows={rows}
  locale="en-US"
/>
<Table
  columns={columns}
  rows={rows}
  locale={{
    language: 'en-US',
    labels: {
      choose: 'Choose',
      confirm: 'OK',
      reset: 'Reset',
      search: 'Search',
      empty: 'No data',
      deleteConfirmDescription: (count) => `Delete ${count} row(s)?`,
    },
  }}
/>

如果项目里已经有自己的 i18n 封装,可以把翻译函数的结果映射到 locale 对象里传入:

import { Table, type TableLocaleConfig } from '@lensui/lens-table';
import { useTranslation } from 'react-i18next';

function UserTable() {
  const { t, i18n } = useTranslation();

  const gridLocale: TableLocaleConfig = {
    language: i18n.language.startsWith('en') ? 'en-US' : 'zh-CN',
    labels: {
      choose: t('grid.choose'),
      confirm: t('grid.confirm'),
      reset: t('grid.reset'),
      search: t('grid.search'),
      empty: t('grid.empty'),
      filterWithValue: (value) => t('grid.filterWithValue', { value }),
      filterColumn: (title) => t('grid.filterColumn', { title }),
      deleteConfirmDescription: (count) =>
        t('grid.deleteConfirmDescription', { count }),
    },
  };

  return (
    <Table
      columns={columns}
      rows={rows}
      locale={gridLocale}
    />
  );
}

也可以只覆盖部分字段,未传的字段会从 language 对应的内置中英文文案里补齐:

<Table
  locale={{
    language: 'zh-CN',
    labels: {
      empty: i18n.t('common.empty'),
      confirm: i18n.t('common.confirm'),
    },
  }}
/>

常用交互

| 操作 | 行为 | | ---------------- | ---------------------------------------- | | 单击单元格 | 选中单元格。 | | 拖拽单元格 | 创建多单元格范围选择。 | | 双击可编辑单元格 | 进入编辑。 | | Enter | 进入编辑或提交当前编辑。 | | Escape | 取消编辑或关闭弹层。 | | 表头排序按钮 | 切换升序、降序、无排序。 | | 表头筛选按钮 | 打开当前列筛选输入。 | | 右键单元格 | 打开复制、清空、编辑、插入、删除等菜单。 |

开发

pnpm install
pnpm dev
pnpm check

发布

  1. 在 package.json 中确认 npm 包名、版本和仓库信息。
  2. 执行 pnpm check,确保类型检查、测试和构建通过。
  3. 更新版本号并创建发布。

License

MIT

单元格事件与动态编辑权限

const columns: TableColumn<MyRow>[] = [
  {
    key: 'name',
    title: '姓名',
    dataIndex: 'name',
    editable: (_value, row, _rowIndex) => row.status === 'draft',
  },
];

<Table
  columns={columns}
  rows={rows}
  onCellClick={(cell, event) => {
    console.log(cell.row, cell.column, cell.value, cell.rowIndex, cell.columnIndex);
  }}
  onCellDoubleClick={(cell, event) => {
    console.log(cell.rowKey, cell.columnKey);
    // event.preventDefault(); // 阻止本次双击进入编辑
  }}
/>

事件只针对数据单元格,表头、序号列、选择列和拖拽手柄不触发;只读单元格也会触发。双击时先触发一次单击回调,再触发双击回调,不延迟单击。合并单元格返回其起始单元格,索引基于当前显示顺序,columnIndex 包含工具列。

editable 保持兼容布尔值;函数接收当前值、行数据和当前行索引,返回 true 才允许编辑。编辑期间权限变为 false 时,提交会关闭编辑器并丢弃草稿,不触发 onCellChange。

Ref 方法

支持 React 18+ 的对象 ref 和回调 ref,保留行数据的泛型推导;VelocityGrid 别名同样支持。可导入 TableRef<Row>(或 VelocityGridRef<Row>)。

import { useRef } from 'react';
import { Table, type TableRef } from '@lensui/lens-table';

function Example() {
  const tableRef = useRef<TableRef<MyRow>>(null);
  return (
    <>
      <button onClick={() => tableRef.current?.scrollToCell({ rowKey: 1, columnKey: 'name' })}>
        定位
      </button>
      <button onClick={() => tableRef.current?.startEdit({ rowKey: 1, columnKey: 'name' })}>
        编辑
      </button>
      <Table ref={tableRef} columns={columns} rows={rows} rowSelection />
    </>
  );
}

| 方法 | 说明 | | --- | --- | | focus() | 聚焦表格,启用键盘操作。 | | scrollToCell(target) | 将目标数据单元格滚入可见区域,不改变选择;目标不存在时返回 false。 | | getSelectedCell() | 返回当前单元格的 TableCellContext<Row>,未选择时返回 null。 | | setSelectedCell(target \| null) | 设置或清除单元格选择,同时清除范围选择;无效目标返回 false 并保留原选择。 | | getSelectedRowKeys() | 返回所选行 key 的数组副本。 | | getSelectedRows() | 返回当前筛选和排序结果中选中的行数据,按显示顺序排列。 | | setSelectedRowKeys(keys) | 设置行选择并触发现有回调;需要开启 rowSelection,单选模式只取第一个 key。 | | getSelectedColumnKeys() | 返回所选列 key 的数组副本。 | | setSelectedColumnKeys(keys) | 设置数据列选择,忽略无效列;需要开启 columnSelection,单选模式只取第一个有效 key。 | | clearSelection() | 清空单元格、范围、行和列选择,并取消编辑。 | | startEdit(target?) | 选择并编辑目标单元格;省略参数时编辑当前选择。目标无效或 editable 不允许时返回 false。 | | commitEdit() | 提交当前草稿并关闭编辑器,遵循 editable 动态权限和 onCellChange。 | | cancelEdit() | 丢弃当前草稿并关闭编辑器。 |

target 使用 { rowKey, columnKey } 或 { rowIndex, columnKey };rowKey 优先,rowIndex 为当前显示顺序中的零基索引。工具列不能作为目标;合并单元格自动定位到起始单元格。

选择方法遵守受控/非受控约定:传入 selectedCell、selectedRowKeys 或 selectedColumnKeys 后,需要在相应回调中更新这些 props。尤其是受控单元格选择下的 startEdit,外部需要接受新的选择才能保持编辑器打开。读取方法反映最近一次 React 渲染完成后的状态,不保证在调用设置方法的同一同步代码段内立即更新;返回的行和列对象是原数据引用。