Skip to content

DataTable 数据表格

新粗野主义风格的数据表格组件,提供完整的排序、筛选、多选、分页和虚拟滚动功能。支持泛型数据源,通过 columns 配置驱动的列定义实现灵活的数据展示。

预览

Preview
姓名
邮箱
角色
状态
张三zhangsan@example.com管理员活跃
李四lisi@example.com编辑活跃
王五wangwu@example.com查看者未活跃
赵六zhaoliu@example.com编辑活跃
孙七sunqi@example.com管理员未活跃

安装

pnpm dlx brutx-vue@latest add data-table

用法

基础用法

vue
<script setup lang="ts">
import { ref } from 'vue'
import { DataTable } from 'brutx-ui-vue'
import type { DataTableColumn } from 'brutx-ui-vue'

interface User {
    id: number
    name: string
    email: string
    role: string
    status: string
}

const columns: DataTableColumn<User>[] = [
    {
        id: 'name',
        header: '姓名',
        accessorKey: 'name',
        sortable: true,
    },
    {
        id: 'email',
        header: '邮箱',
        accessorKey: 'email',
        sortable: true,
    },
    {
        id: 'role',
        header: '角色',
        accessorKey: 'role',
    },
    {
        id: 'status',
        header: '状态',
        accessorKey: 'status',
        align: 'center',
    },
]

const data = ref<User[]>([
    { id: 1, name: '张三', email: 'zhangsan@example.com', role: '管理员', status: '活跃' },
    { id: 2, name: '李四', email: 'lisi@example.com', role: '编辑', status: '活跃' },
    { id: 3, name: '王五', email: 'wangwu@example.com', role: '访客', status: '未激活' },
])
</script>

<template>
    <DataTable
        :data="data"
        :columns="columns"
        :sortable="true"
        row-key="id"
    />
</template>

启用排序

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        :sortable="true"
        row-key="id"
        @sort="handleSort"
    />
</template>

通过 sortable 属性启用排序,在列定义中设置 sortable: false 可禁用特定列的排序。点击表头可在升序 / 降序 / 默认 之间切换。

启用筛选

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        :filterable="true"
        row-key="id"
        @filter="handleFilter"
    />
</template>

启用 filterable 后,表格上方会显示全局搜索输入框,对所有可见列进行模糊匹配。

启用选择

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        :selectable="true"
        row-key="id"
        @select="handleSelect"
    />
</template>

启用 selectable 后,每行左侧会显示复选框,表头复选框支持全选 / 取消全选。选中行后底部会显示已选行数,并可导出 CSV。

启用分页

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        :paginated="true"
        :page-size="10"
        :page-size-options="[10, 20, 50]"
        row-key="id"
        @page-change="handlePageChange"
        @page-size-change="handlePageSizeChange"
    />
</template>

分页器包含首页、上一页、下一页、末页按钮,以及每页条数选择器。

列筛选

支持在每一列头部添加 Popover 筛选器。支持文本搜索、单选(Select)、多选(Multi-Select)以及日期范围(Date Range)筛选。

date-range 筛选会将单元格中的 YYYY-MM-DD 字符串按本地日期解析,并把结束日期包含到当天 23:59:59.999。完整 datetime 字符串仍按浏览器原生 Date / ISO datetime 语义解析;如果数据需要跨时区一致,建议存储为 Date、时间戳,或带时区偏移的完整 ISO 字符串。

vue
<script setup lang="ts">
const columns = [
    { id: 'name', header: '姓名', accessorKey: 'name', filterType: 'text' },
    {
        id: 'role',
        header: '角色',
        accessorKey: 'role',
        filterType: 'select',
        filterOptions: [
            { label: '管理员', value: '管理员' },
            { label: '编辑', value: '编辑' }
        ]
    },
    {
        id: 'status',
        header: '状态',
        accessorKey: 'status',
        filterType: 'multi-select',
        filterOptions: [
            { label: '活跃', value: '活跃' },
            { label: '未激活', value: '未激活' }
        ]
    },
    {
        id: 'date',
        header: '入职日期',
        accessorKey: 'date',
        filterType: 'date-range'
    }
]
</script>

<template>
    <DataTable
        :data="data"
        :columns="columns"
        :filterable="true"
        row-key="id"
    />
</template>

自定义单元格

vue
<script setup lang="ts">
import { h } from 'vue'
import { Badge } from 'brutx-ui-vue'
import type { DataTableColumn } from 'brutx-ui-vue'

const columns: DataTableColumn<User>[] = [
    { id: 'name', header: '姓名', accessorKey: 'name' },
    {
        id: 'status',
        header: '状态',
        accessorKey: 'status',
        cell: ({ value }) => {
            return h(Badge, { variant: value === '活跃' ? 'success' : 'danger' }, () => String(value))
        },
    },
]
</script>

通过 cell 函数可自定义单元格渲染,支持返回 VNode 或字符串。也可使用具名插槽 #cell-{columnId} 进行渲染。

使用插槽

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        row-key="id"
    >
        <template #cell-status="{ row, value }">
            <Badge :variant="value === '活跃' ? 'success' : 'danger'">
                {{ value }}
            </Badge>
        </template>

        <template #toolbar>
            <Button variant="primary" size="sm">
                添加用户
            </Button>
        </template>

        <template #empty>
            <div class="text-center py-8">
                <p class="text-lg font-bold">暂无数据</p>
                <p class="text-sm text-brutal-fg/60">点击上方按钮添加第一条记录</p>
            </div>
        </template>
    </DataTable>
</template>

加载状态

vue
<template>
    <DataTable
        :data="data"
        :columns="columns"
        :loading="isLoading"
        row-key="id"
    />
</template>

设置 loadingtrue 时,表格上方会显示旋转加载动画遮罩。也可通过 loading 具名插槽自定义加载态内容。

尺寸

vue
<template>
    <!-- 小尺寸 + 紧凑密度,适合信息密集型表格 -->
    <DataTable
        :data="data"
        :columns="columns"
        :sortable="true"
        size="sm"
        :dense="true"
        row-key="id"
    />

    <!-- 大尺寸,适合展示型表格 -->
    <DataTable
        :data="data"
        :columns="columns"
        size="lg"
        row-key="id"
    />
</template>

size 控制字体大小与单元格纵向 padding(smpy-2defaultpy-3lgpy-4);dense 进一步压缩行高至 py-1.5,与 size 正交,可单独或组合使用。

条纹控制

vue
<template>
    <!-- 默认开启条纹(偶数行 bg-brutal-muted/50) -->
    <DataTable
        :data="data"
        :columns="columns"
        row-key="id"
    />

    <!-- 显式关闭条纹 -->
    <DataTable
        :data="data"
        :columns="columns"
        :striped="false"
        row-key="id"
    />
</template>

striped 默认 true,偶数行应用 bg-brutal-muted/50 形成奇偶交替。若需要纯净外观或配合自定义行背景,可设为 false

粘性表头

vue
<template>
    <DataTable
        :data="largeData"
        :columns="columns"
        :sticky-header="true"
        row-key="id"
    />
</template>

启用 stickyHeader 后,<thead> 会添加 sticky top-0 z-10,在长表格垂直滚动时表头始终可见。建议配合固定高度的滚动容器使用。

数据类型

DataTableColumn

字段类型说明
idstring列唯一标识,必填
headerstring | ((ctx: DataTableColumnHeaderContext) => string)列标题,必填;函数形式接收 DataTableColumnHeaderContext 上下文
accessorKeykeyof T & string通过 key 访问行数据字段
accessorFn(row: T) => unknown通过函数访问行数据值
cell(props: { row: T; value: unknown }) => VNode | string自定义单元格渲染
sortableboolean是否可排序,默认 true
hiddenboolean是否隐藏
widthnumber | 'auto'列宽度
minWidthnumber最小宽度
maxWidthnumber最大宽度
align'left' | 'center' | 'right'对齐方式
filterType'text' | 'select' | 'multi-select' | 'date-range'筛选类型,开启后表头会显示对应筛选 Popover
filterOptionsArray<{ label: string; value: any }>筛选可选项,供 selectmulti-select 筛选使用

DataTableColumnHeaderContext

header 回调函数接收的上下文对象,用于在函数式表头中获取列的运行时状态。

字段类型说明
idstring当前列的 ID
sortableboolean当前列是否可排序
direction'asc' | 'desc' | null当前列的排序方向(null 表示未排序)
accessorKeyPropertyKey当前列的 accessorKey(如果有)
align'left' | 'center' | 'right'当前列的对齐方式

DataTableVirtualScroll

字段类型默认值说明
enabledboolean是否启用虚拟滚动
rowHeightnumber | 'auto'48每行预估高度(像素),'auto' 为根据 DOM 自动测量动态高度(要求所有列必须显式配置宽度)

DataTableFilterState

字段类型说明
globalstring全局筛选关键字
columnsRecord<string, any>按列 ID 索引的列级筛选值

程序化控制

DataTable 通过 defineExpose 暴露了排序、筛选、选择、分页四组方法与状态,可通过组件 ref 在父组件中直接调用,实现"工具栏外置"、"外部按钮触发排序"、"批量操作按钮"等场景。

vue
<script setup lang="ts">
import { ref } from 'vue'
import { DataTable, Button } from 'brutx-ui-vue'
import type { DataTableColumn } from 'brutx-ui-vue'

interface User {
    id: number
    name: string
    email: string
    role: string
    status: string
}

const tableRef = ref<InstanceType<typeof DataTable> | null>(null)

const columns: DataTableColumn<User>[] = [
    { id: 'name', header: '姓名', accessorKey: 'name', sortable: true },
    { id: 'email', header: '邮箱', accessorKey: 'email' },
    { id: 'role', header: '角色', accessorKey: 'role' },
    { id: 'status', header: '状态', accessorKey: 'status' },
]

const data: User[] = [
    { id: 1, name: '张三', email: 'zhangsan@example.com', role: '管理员', status: '活跃' },
    { id: 2, name: '李四', email: 'lisi@example.com', role: '编辑', status: '未激活' },
    { id: 3, name: '王五', email: 'wangwu@example.com', role: '访客', status: '活跃' },
]

// 程序化触发按 name 升序
function sortByNameAsc() {
    tableRef.value?.sort.toggleSort('name')
}

// 程序化设置全局筛选关键字
function filterByAdmin() {
    tableRef.value?.filter.setGlobalFilter('管理员')
}

// 全选当前页
function selectAll() {
    tableRef.value?.selection.toggleAllRows()
}

// 跳转到下一页
function goNextPage() {
    tableRef.value?.pagination.nextPage()
}
</script>

<template>
    <div class="flex flex-wrap items-center gap-2 mb-4">
        <Button variant="primary" size="sm" @click="sortByNameAsc">按姓名排序</Button>
        <Button variant="default" size="sm" @click="filterByAdmin">筛选"管理员"</Button>
        <Button variant="default" size="sm" @click="selectAll">全选当前页</Button>
        <Button variant="default" size="sm" @click="goNextPage">下一页</Button>
    </div>

    <DataTable
        ref="tableRef"
        :data="data"
        :columns="columns"
        :sortable="true"
        :filterable="true"
        :selectable="true"
        :paginated="true"
        :page-size="2"
        row-key="id"
    />
</template>

暴露的 API

通过 ref 访问 tableRef 后,可使用以下四组命名空间:

sort

成员类型说明
toggleSort(columnId)(columnId: string) => void切换指定列的排序方向,循环顺序为 asc → desc → 取消
sortStateRef<{ column: string; direction: 'asc' | 'desc' | null }>当前排序状态(响应式)

filter

成员类型说明
setGlobalFilter(value)(value: string) => void设置全局筛选关键字
filterStateRef<DataTableFilterState>当前筛选状态(响应式),包含 global 字段

selection

成员类型说明
toggleRow(row)(row: T) => void切换指定行的选中状态
toggleAllRows()() => void切换当前页全选 / 取消全选
clearSelection()() => void清空所有选中行
getSelectedRows()() => T[]获取当前选中的完整行数据数组
selectedRowsRef<Set<string | number>>选中行的 key 集合(响应式)
isAllSelectedComputedRef<boolean>当前页是否全选

pagination

成员类型说明
goToPage(page)(page: number) => boolean跳转到指定页码,返回是否实际切换
nextPage()() => void跳转到下一页
previousPage()() => void跳转到上一页
setPageSize(size)(size: number) => void设置每页条数(会重置到第 1 页)
pageIndexRef<number>当前页码(响应式,从 1 开始)
pageCountComputedRef<number>总页数(响应式)

注意:程序化调用 toggleSort / setGlobalFilter / toggleRow 等方法不会自动触发 sort / filter / select 事件。如需同步通知父组件,请自行监听对应状态变化或显式调用后处理。

Props

属性类型默认值说明
dataT[]—(必填)表格数据源
columnsDataTableColumn<T>[]—(必填)列定义配置
rowKeykeyof T | ((row: T) => string | number)—(必填)行唯一标识,用于选择和虚拟滚动
sortablebooleanfalse是否启用排序
filterablebooleanfalse是否启用筛选
filterPlaceholderstringlocale: dataTable.filterPlaceholder全局搜索输入框占位文案
selectablebooleanfalse是否启用行选择
paginatedbooleanfalse是否启用分页
pageSizenumber10每页显示条数
pageSizeOptionsnumber[][10, 20, 50, 100]每页条数选项
loadingbooleanfalse是否显示加载状态
emptyMessagestringlocale: dataTable.noData数据为空时的提示信息
virtualScrollDataTableVirtualScroll虚拟滚动配置
size'sm' | 'default' | 'lg''default'表格尺寸
densebooleanfalse是否启用紧凑密度
stripedbooleantrue是否显示条纹
stickyHeaderbooleanfalse是否启用粘性表头
expandablebooleanfalse是否启用展开行
expandRowKeysSet<string | number>受控的展开行 key 集合
spanMethod(params) => [number, number] | void单元格合并方法,返回 [rowspan, colspan]
classstring自定义 CSS 类名

DataTableColumn 属性

属性类型默认值说明
idstring—(必填)列唯一标识
headerstring | ((ctx) => string)—(必填)列头文本或渲染函数
accessorKeykeyof T & string数据字段键
accessorFn(row: T) => unknown自定义取值函数
cell(props) => VNode | string自定义单元格渲染函数
sortableboolean该列是否可排序
hiddenboolean是否隐藏该列
widthnumber | 'auto'列宽
minWidthnumber最小列宽
maxWidthnumber最大列宽
align'left' | 'center' | 'right'列内容对齐方式
fixed'left' | 'right'固定列方向
type'default' | 'expand''default'列类型(expand 为展开列)

事件

事件参数说明
sort[column: string, direction: 'asc' | 'desc' | null]排序变化时触发
filter[filters: DataTableFilterState]筛选条件变化时触发
select[rows: T[]]选中行变化时触发
page-change[page: number]页码变化时触发
page-size-change[size: number]每页条数变化时触发
expand-change[row: T, expanded: boolean]行展开状态变化时触发
export[format: 'csv' | 'json', selectedRows?: T[]]导出操作触发,携带选中行数据(需启用 selectable)

插槽

插槽作用域说明
toolbar工具栏区域,位于筛选框右侧
cell-{columnId}{ row: T; value: unknown }自定义特定列的单元格渲染
expanded-row{ row: T; index: number }自定义展开行内容
empty数据为空时的自定义空状态
loading自定义加载态内容,仅在 loadingtrue 时渲染

可访问性

DataTable 组件遵循 WAI-ARIA 表格模式,提供以下可访问性支持:

  • 使用语义化的 <table><thead><tbody><tr><th><td> 元素
  • 排序状态通过 aria-sort 属性通知屏幕阅读器
  • 选择状态通过 aria-selected 属性标记选中行
  • 筛选输入框和分页控件均提供适当的 aria-label
  • 支持键盘导航:Tab 键在交互元素间切换,Enter/Space 键触发排序和选择

常见问题

Q: 为什么设置了 selectable 但选中状态没有生效?

A: 请确保正确设置了 row-key 属性。row-key 用于标识每一行的唯一性,选择功能依赖它来追踪选中行。如果没有提供 row-key,选择功能将无法正常工作。建议使用数据中具有唯一性的字段,如 id

Q: 同时启用排序、筛选和分页时,它们之间的执行顺序是什么?

A: 组件内部按"筛选 → 排序 → 分页"的顺序处理数据。即先根据筛选条件过滤数据,再对过滤后的结果进行排序,最后进行分页。如果同时使用了程序化 API(如 setGlobalFiltertoggleSort),也会遵循相同的处理顺序。

Q: 数据量很大时如何优化表格性能?

A: 当数据量超过数百行时,建议启用虚拟滚动功能。通过配置 :virtual-scroll="{ enabled: true }" 属性,表格将只渲染可视区域内的行,大幅减少 DOM 节点数量。同时可以设置 :dense="true" 来压缩行高,进一步提升可视区域内的信息密度。

蛮力铸就。