Appearance
Table 表格
用于数据收集展示、分析整理和操作处理,适合中后台列表、配置项矩阵、数据明细和可批量处理的记录集合。
何时使用
- 需要展示结构化数据,并支持排序、筛选、分页、滚动或固定列。
- 需要对数据行进行选择、展开、拖拽、合并或汇总。
- 需要通过插槽或渲染函数定制表头、单元格、空态、分页两侧内容或内部表格元素。
基础用法
vue
<template>
<x-table :columns="columns" :data="data" />
</template>
<script setup lang="ts">
const columns = [
{ title: '名称', dataIndex: 'name' },
{ title: '类型', dataIndex: 'type' },
{ title: '状态', dataIndex: 'status' },
];
const data = [
{ key: '1', name: 'Button', type: '通用', status: '稳定' },
{ key: '2', name: 'Select', type: '表单', status: '维护中' },
{ key: '3', name: 'Table', type: '数据展示', status: '改造中' },
];
</script>声明式列
vue
<template>
<x-table :data="data" :pagination="false">
<x-table-column title="名称" data-index="name" />
<x-table-column title="状态" data-index="status" />
</x-table>
</template>
<script setup lang="ts">
const data = [
{ key: '1', name: 'Button', status: '稳定' },
{ key: '2', name: 'Table', status: '维护中' },
];
</script>尺寸和边框
vue
<template>
<x-table :columns="columns" :data="data" size="small" :pagination="false" />
<x-table
:columns="columns"
:data="data"
size="mini"
:bordered="{ wrapper: true, cell: true }"
:pagination="false"
/>
</template>行选择器
vue
<template>
<x-table
v-model:selected-keys="selectedKeys"
:columns="columns"
:data="data"
:row-selection="{ type: 'checkbox', showCheckedAll: true, onlyCurrent: false }"
:pagination="{ pageSize: 3 }"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const selectedKeys = ref(['1', '2']);
</script>单选
vue
<template>
<x-table
v-model:selected-keys="selectedKeys"
:columns="columns"
:data="data"
:row-selection="{ type: 'radio' }"
:pagination="false"
/>
</template>展开行
vue
<template>
<x-table
v-model:expanded-keys="expandedKeys"
:columns="columns"
:data="data"
:expandable="{ title: '展开', width: 72 }"
>
<template #expand-row="{ record }">
{{ record.expand || `${record.name} 暂无额外说明。` }}
</template>
</x-table>
</template>树形数据
vue
<template>
<x-table
:columns="columns"
:data="data"
:default-expanded-keys="['1']"
:pagination="false"
/>
</template>排序和筛选
vue
<template>
<x-table
:columns="columns"
:data="data"
:filter-icon-align-left="alignLeft"
@change="handleChange"
/>
</template>
<script setup lang="ts">
const columns = [
{
title: '姓名',
dataIndex: 'name',
sortable: { sortDirections: ['ascend', 'descend'] },
},
{
title: '薪资',
dataIndex: 'salary',
filterable: {
filters: [{ text: '20000 以上', value: '20000' }],
filter: (values, record) => values.some((value) => record.salary > Number(value)),
multiple: true,
},
},
];
</script>可排序表头可通过 Tab 聚焦,并使用 Enter 或 Space 切换排序;读屏软件可从 aria-sort 获得当前顺序。筛选图标为原生按钮,会通过 aria-expanded 和 aria-controls 关联筛选弹层。
分组表头、滚动和固定列
vue
<template>
<x-table
:columns="columns"
:data="data"
:scroll="{ x: 900, y: 220 }"
:pagination="false"
/>
</template>单元格合并
vue
<template>
<x-table
:columns="columns"
:data="data"
:span-method="spanMethod"
:bordered="{ wrapper: true, cell: true }"
/>
</template>
<script setup lang="ts">
const spanMethod = ({ rowIndex, columnIndex }) => {
if (rowIndex === 1 && columnIndex === 1) {
return { rowspan: 2, colspan: 2 };
}
};
</script>总结行
vue
<template>
<x-table
:columns="columns"
:data="data"
:summary="summary"
summary-text="合计"
/>
</template>
<script setup lang="ts">
const summary = ({ data }) => {
const total = data.reduce((acc, item) => {
acc.salary += item.salary;
return acc;
}, { name: '合计', salary: 0 });
return [total];
};
</script>省略与文本提示
列配置 ellipsis: true 时超出宽度会截断。再开启 tooltip 后,悬浮会显示完整内容;tooltip 也可传入对象以配置 Tooltip 的属性。
vue
<template>
<x-table :columns="columns" :data="data" :pagination="false" />
</template>
<script setup lang="ts">
const columns = [
{ title: '名称', dataIndex: 'name', width: 160 },
// 截断并在悬浮时显示完整内容
{ title: '描述', dataIndex: 'desc', ellipsis: true, tooltip: true },
// 只截断,不显示提示
{ title: '备注', dataIndex: 'remark', width: 120, ellipsis: true },
];
const data = [{ key: '1', name: 'Button', desc: '很长的描述文本……', remark: '备注' }];
</script>吸顶表头
stickyHeader 让表头在页面滚动时吸顶;传入数字可指定距离顶部的偏移量。需要纵向滚动时同时设置 scroll.y。
vue
<template>
<x-table
:columns="columns"
:data="data"
:sticky-header="48"
:scroll="{ y: 220 }"
:pagination="false"
/>
</template>懒加载子树
传入 loadMore 后展开箭头会在需要时异步拉取子节点。函数的第二个参数 done 用于回传子数据。
vue
<template>
<x-table :columns="columns" :data="data" :load-more="loadMore" :pagination="false" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const data = ref([{ key: 'dept-1', name: '研发中心', type: '部门' }]);
// done 回传子节点;子节点标记 isLeaf 后不再显示展开箭头
const loadMore = (record, done) => {
setTimeout(() => {
done([
{ key: `${record.key}-1`, name: `${record.name} · 小组 1`, type: '小组', isLeaf: true },
]);
}, 400);
};
</script>调整列宽
vue
<template>
<x-table
:columns="columns"
:data="data"
column-resizable
:bordered="{ wrapper: true, cell: true }"
/>
</template>拖拽排序
vue
<template>
<x-table
:columns="columns"
:data="data"
:draggable="{ type: 'handle', width: 40 }"
@change="handleChange"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const data = ref([...]);
const handleChange = (nextData, extra) => {
if (extra.type === 'drag') data.value = nextData;
};
</script>行拖拽
draggable.type 为 'row' 时整行可拖动;为 'handle' 时只通过手柄拖动(见上一节)。
vue
<template>
<x-table :columns="columns" :data="data" :draggable="{ type: 'row' }" @change="onChange" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const data = ref([...]);
const onChange = (nextData, extra) => {
if (extra.type === 'drag') data.value = nextData;
};
</script>可编辑单元格
Table 没有内置的编辑态,用列的 cell 插槽即可实现:双击切换到输入控件,失焦或回车后写回数据。
vue
<template>
<x-table :columns="columns" :data="data" :pagination="false">
<template #statusCell="{ record }">
<!-- 非编辑态:双击进入编辑 -->
<span v-if="!record.editing" @dblclick="startEdit(record)">{{ record.status }}</span>
<!-- 编辑态:写入后退出 -->
<x-select
v-else
:model-value="record.status"
:options="options"
@change="(value) => apply(record, value)"
/>
</template>
</x-table>
</template>
<script setup lang="ts">
import { ref } from 'vue';
const columns = [
{ title: '名称', dataIndex: 'name' },
// slotName 与 template 名一致
{ title: '状态', dataIndex: 'status', slotName: 'statusCell' },
];
const data = ref([{ key: '1', name: 'Button', status: '待处理', editing: false }]);
const startEdit = (record) => {
data.value = data.value.map((item) => ({ ...item, editing: item.key === record.key }));
};
const apply = (record, value) => {
data.value = data.value.map((item) =>
item.key === record.key ? { ...item, status: value, editing: false } : item,
);
};
</script>虚拟列表
vue
<template>
<x-table
:columns="columns"
:data="data"
:virtual-list-props="{ height: 260, fixedSize: true, estimatedSize: 48 }"
:pagination="false"
/>
</template>自定义渲染和插槽
vue
<template>
<x-table :columns="columns" :data="data">
<template #nameCell="{ record }">
<strong>{{ record.name }}</strong>
</template>
</x-table>
</template>空态和加载
vue
<template>
<x-table :columns="columns" :data="[]" :pagination="false">
<template #empty>暂无匹配数据</template>
</x-table>
<x-table :columns="columns" :data="data" loading />
</template>命令式方法
通过 ref 调用 select / expand / resetFilters / clearSorters 等方法操作表格状态。
vue
<template>
<x-button @click="tableRef?.select('2', true)">选中第 2 行</x-button>
<x-button @click="tableRef?.selectAll(true)">全选</x-button>
<x-table ref="tableRef" :columns="columns" :data="data" :row-selection="{ type: 'checkbox' }" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
const tableRef = ref();
const data = [{ key: '1', name: 'Button' }, { key: '2', name: 'Table' }];
</script>自定义筛选弹层
filterable.slotName 指定弹层内容所用的具名插槽,插槽参数含 filterValue、setFilterValue、handleFilterConfirm、handleFilterReset。
vue
<template>
<x-table :columns="columns" :data="data" @filter-change="onFilterChange">
<template #statusFilter="{ filterValue, setFilterValue, handleFilterConfirm, handleFilterReset }">
<x-checkbox-group :model-value="filterValue" @change="setFilterValue">
<x-checkbox value="待处理">待处理</x-checkbox>
<x-checkbox value="处理中">处理中</x-checkbox>
</x-checkbox-group>
<x-button size="mini" @click="handleFilterReset">重置</x-button>
<x-button size="mini" type="primary" @click="handleFilterConfirm">确认</x-button>
</template>
</x-table>
</template>
<script setup lang="ts">
const columns = [
{ title: '名称', dataIndex: 'name' },
{
title: '状态',
dataIndex: 'status',
filterable: {
filters: [{ text: '待处理', value: '待处理' }],
filter: (values, record) => values.length === 0 || values.includes(record.status),
multiple: true,
// 与 template 名一致
slotName: 'statusFilter',
},
},
];
</script>自定义展开图标
expand-icon 插槽可替换默认的展开箭头,参数为 { expanded, record }。
vue
<template>
<x-table :columns="columns" :data="data" :expandable="{ expandedRowRender: render }">
<template #expand-icon="{ expanded }">
<span>{{ expanded ? '−' : '+' }}</span>
</template>
</x-table>
</template>分页两侧与总结行插槽
pagination-left / pagination-right 用于在分页器两侧补充内容;summary-cell 用于自定义总结行的单元格。
共 3 条记录
vue
<template>
<x-table :columns="columns" :data="data" :pagination="{ pageSize: 2 }" :summary="true">
<template #pagination-left>
<span>共 {{ data.length }} 条记录</span>
</template>
<template #pagination-right>
<x-button size="mini">刷新</x-button>
</template>
</x-table>
</template>vue
<template>
<x-table :columns="columns" :data="data" :summary="true">
<!-- 参数为 { column, record, rowIndex } -->
<template #summary-cell="{ column }">
<strong>{{ summaryText(column) }}</strong>
</template>
</x-table>
</template>事件回调
vue
<template>
<x-table
:columns="columns"
:data="data"
@row-click="handleRowClick"
@cell-click="handleCellClick"
@header-click="handleHeaderClick"
/>
</template>按需导入
ts
import { Table, TableColumn } from 'x-next';样式按需引入(base.css 为共享基础层,多个组件只需引入一次):
ts
import 'x-next/style/base.css';
import 'x-next/style/table.css';Table Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
columns | 表格列配置 | TableColumnData[] | [] |
data | 表格数据 | TableData[] | [] |
bordered | 是否显示边框 | boolean | TableBorder | false |
hoverable | 是否显示行悬浮效果 | boolean | true |
stripe | 是否显示斑马纹 | boolean | false |
size | 表格尺寸 | 'mini' | 'small' | 'medium' | 'large' | 'large' |
tableLayoutFixed | 是否使用 fixed 表格布局 | boolean | false |
loading | 是否加载中,object 会透传给 Spin | boolean | object | false |
rowSelection | 行选择配置 | TableRowSelection | - |
expandable | 展开行配置 | TableExpandable | - |
scroll | 滚动配置 | { x?: number | string; y?: number | string; minWidth?: number | string; maxHeight?: number | string } | - |
pagination | 分页配置,传 false 可关闭分页 | boolean | PaginationProps | true |
pagePosition | 分页位置 | 'tl' | 'top' | 'tr' | 'bl' | 'bottom' | 'br' | 'br' |
indentSize | 树形表格缩进距离 | number | 16 |
rowKey | 表格行 key 字段 | string | 'key' |
showHeader | 是否显示表头 | boolean | true |
virtualListProps | 虚拟列表配置,传入后开启虚拟滚动 | VirtualListProps | - |
spanMethod | 单元格合并方法 | (data) => { rowspan?: number; colspan?: number } | void | - |
spanAll | 合并方法索引是否包含操作列 | boolean | false |
components | 自定义内部操作列组织方式 | TableComponents | - |
loadMore | 树形数据懒加载函数 | (record, done) => void | - |
filterIconAlignLeft | 筛选图标是否左对齐 | boolean | false |
hideExpandButtonOnEmpty | 子树为空时是否隐藏展开按钮 | boolean | false |
rowClass | 行 class,支持函数 | string | any[] | object | ((record, rowIndex) => any) | - |
draggable | 拖拽排序配置 | TableDraggable | - |
columnResizable | 是否允许调整列宽 | boolean | false |
summary | 是否显示总结行,或自定义总结数据 | boolean | ((params) => TableData[]) | - |
summaryText | 默认总结行首列文字 | string | 'Summary' |
summarySpanMethod | 总结行单元格合并方法 | (data) => { rowspan?: number; colspan?: number } | void | - |
selectedKeys | 已选择行 key,受控模式 | (string | number)[] | - |
defaultSelectedKeys | 默认已选择行 key | (string | number)[] | - |
expandedKeys | 已展开行 key,受控模式 | (string | number)[] | - |
defaultExpandedKeys | 默认展开行 key | (string | number)[] | - |
defaultExpandAllRows | 是否默认展开全部行 | boolean | false |
stickyHeader | 是否开启表头吸顶,数字表示 top 偏移 | boolean | number | false |
scrollbar | 是否使用自定义滚动条,或传入 Scrollbar 配置 | boolean | ScrollbarProps | true |
showEmptyTree | 是否展示空子树 | boolean | false |
Table Events
| 事件名 | 说明 | 回调参数 |
|---|---|---|
update:selectedKeys | 选中行变化时触发 | rowKeys |
update:expandedKeys | 展开行变化时触发 | rowKeys |
expand | 点击展开行时触发 | rowKey, record |
expanded-change | 展开行变化时触发 | rowKeys |
select | 点击行选择器时触发 | rowKeys, rowKey, record |
select-all | 点击全选选择器时触发 | checked |
selection-change | 已选择行变化时触发 | rowKeys |
sorter-change | 排序规则变化时触发 | dataIndex, direction |
filter-change | 筛选项变化时触发 | dataIndex, filteredValues |
page-change | 分页页码变化时触发 | page |
page-size-change | 每页数量变化时触发 | pageSize |
change | 分页、排序、筛选、拖拽变化时触发 | data, extra, currentData |
cell-mouse-enter | 鼠标进入单元格时触发 | record, column, event |
cell-mouse-leave | 鼠标离开单元格时触发 | record, column, event |
cell-click | 点击单元格时触发 | record, column, event |
row-click | 点击行时触发 | record, event |
header-click | 点击表头时触发 | column, event |
column-resize | 调整列宽时触发 | dataIndex, width |
row-dblclick | 双击行时触发 | record, event |
cell-dblclick | 双击单元格时触发 | record, column, event |
row-contextmenu | 右击行时触发 | record, event |
cell-contextmenu | 右击单元格时触发 | record, column, event |
Table Methods
| 方法名 | 说明 | 参数 |
|---|---|---|
selectAll | 设置全选状态 | checked?: boolean |
select | 设置指定行选择状态 | rowKey, checked?: boolean |
expandAll | 设置全部展开状态 | checked?: boolean |
expand | 设置指定行展开状态 | rowKey, checked?: boolean |
resetFilters | 重置列筛选器到默认值 | dataIndex?: string | string[] |
clearFilters | 清空列筛选器 | dataIndex?: string | string[] |
resetSorters | 重置列排序到默认值 | - |
clearSorters | 清空列排序 | - |
Table Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
default | TableColumn 声明式列,或自定义 table 内容 | - |
columns | TableColumn 声明式列 | - |
expand-icon | 自定义展开图标 | { expanded, record } |
expand-row | 展开行内容 | { record } |
footer | 表格底部内容 | - |
drag-handle-icon | 拖拽手柄图标 | - |
tbody | 自定义 tbody 元素 | - |
tr | 自定义 tr 元素 | { record, rowIndex } |
td | 自定义 td 元素 | { record, column, rowIndex } |
pagination-left | 分页器左侧内容 | - |
pagination-right | 分页器右侧内容 | - |
summary-cell | 总结行单元格 | { column, record, rowIndex } |
empty | 空态内容 | - |
thead | 自定义 thead 元素 | - |
th | 自定义 th 元素 | { column } |
TableColumn Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
dataIndex | 列字段名 | string | - |
title | 列标题 | string | - |
width | 列宽 | number | - |
minWidth | 最小列宽 | number | - |
align | 对齐方式 | 'left' | 'center' | 'right' | - |
fixed | 固定位置 | 'left' | 'right' | - |
ellipsis | 是否显示省略 | boolean | false |
tooltip | 省略时是否显示提示,或 Tooltip 配置 | boolean | object | false |
sortable | 排序配置 | TableSortable | - |
filterable | 筛选配置 | TableFilterable | - |
cellClass | 单元格 class | ClassName | - |
headerCellClass | 表头单元格 class | ClassName | - |
bodyCellClass | 内容单元格 class | ClassName | ((record) => ClassName) | - |
summaryCellClass | 总结单元格 class | ClassName | ((record) => ClassName) | - |
cellStyle | 单元格样式 | CSSProperties | - |
headerCellStyle | 表头单元格样式 | CSSProperties | - |
bodyCellStyle | 内容单元格样式 | CSSProperties | ((record) => CSSProperties) | - |
summaryCellStyle | 总结单元格样式 | CSSProperties | ((record) => CSSProperties) | - |
index | 手动指定列序号,通常无需使用 | number | - |
TableColumn Slots
| 插槽名 | 说明 | 参数 |
|---|---|---|
cell | 单元格内容 | { record, column, rowIndex } |
title | 表头内容 | - |
filter-content | 自定义筛选弹层内容 | { filterValue, setFilterValue, handleFilterConfirm, handleFilterReset } |
filter-icon | 自定义筛选图标 | - |
类型说明
| 类型 | 说明 |
|---|---|
TableData | 行数据,默认用 key 作为唯一标识,可包含 children、expand、disabled、isLeaf |
TableSortable | 排序配置,包含 sortDirections、sorter、sortOrder、defaultSortOrder |
TableFilterable | 筛选配置,包含 filters、filter、multiple、filteredValue、defaultFilteredValue、renderContent、icon、triggerProps、alignLeft、slotName |
TableRowSelection | 行选择配置,包含 type、selectedRowKeys、defaultSelectedRowKeys、showCheckedAll、title、width、fixed、checkStrictly、onlyCurrent |
TableExpandable | 展开行配置,包含 expandedRowKeys、defaultExpandedRowKeys、defaultExpandAllRows、expandedRowRender、icon、title、width、fixed |
TableDraggable | 拖拽配置,包含 type、title、width、fixed |
TableChangeExtra | change 事件附加信息,包含 type、page、pageSize、sorter、filters、dragTarget |
元素插槽
tbody、thead、tr、td、th 是内部元素插槽,可替换表格的原生元素(例如给 tbody 加原生属性、给 td 挂自定义指令)。
使用契约:插槽内只提供单一空元素,让 Table 把原有的 children、class、style 和事件合并到该元素上。若自行写死内容,会覆盖表格原本的行、列和事件结构。
vue
<template>
<x-table :columns="columns" :data="data" :pagination="false">
<!-- 只写单一空元素,Table 会把 children / class / 事件合并进来 -->
<!-- tbody 插槽不接收参数:Table 会把 children/attrs 合并到返回的元素上 -->
<template #tbody>
<tbody data-custom-body="true" />
</template>
</x-table>
</template>也可用于 tr / td / th / thead。tr / td / th 会传入行与列信息(见上方 Slots 表);tbody 与 thead 不传参数——children、attrs 与事件统一由 Table 合并到插槽返回的元素上。
已知限制
- 拖拽排序仍是鼠标 / 触摸优先能力,暂未提供键盘拖拽替代路径。
- 非受控分页下筛选、排序和每页数量变化会自动回到第一页;受控分页需要业务同步
pagination.current。