更新记录
1.0.0(2026-10-10)
u-table 是面向 uni-app 的通用表格组件,配套 u-table-column子组件。提供多表头、固定列、列宽拖拽、树形数据、分页、排序、筛选、合计、大屏滚动、大数据虚拟滚动、单行预览、单元格类型化渲染等企业级表格能力。
平台兼容性
uni-app(5.0)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | √ | √ | √ | √ | √ | √ | √ |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| √ | - | - | - | - | - | - | - | - | - | - | - |
u-table 通用表格组件
跨端通用表格组件,支持 Vue2 / Vue3、H5 / Chrome、微信 / 支付宝 / 抖音小程序。
目录
一、组件简介
u-table 是面向 uni-app 的通用表格组件,配套 u-table-column 子组件。提供多表头、固定列、列宽拖拽、树形数据、分页、排序、筛选、合计、单行预览、单元格类型化渲染等企业级表格能力。
设计目标:
- 跨端:H5 / Chrome、微信、支付宝、抖音小程序原生支持。
- 跨框架:Vue2 与 Vue3 双兼容,统一 Options API 风格。
- 性能:树形懒加载、防抖测量、滚动加载分页、虚拟滚动预留接口。
- 易用:JSON columns 与声明式
<u-table-column>双写法;命名插槽灵活自定义。 - 主题:CSS 变量 +
rpx/px混排,支持深色主题与自定义样式。
优先级实现状态:
| 优先级 | 能力 | 状态 |
|---|---|---|
| P0 | 基础表格、多表头、固定列、列宽拖拽、高度适配、JSON/子组件列、插槽、格式化、排序、分页、选择、索引、合计、树形、斑马纹、边框、事件、跨端 | ✅ 已实现 |
| P1 | 虚拟滚动、CSV 导出、单行预览、图片预览 | ✅ 已实现 |
| P1 | 列设置、行拖拽、右键菜单、可访问性 | ⏳ 预留接口 |
| P2 | 编辑、打印、复杂合并、多语言、暗黑主题完整化 | ⏳ 预留接口 |
二、架构设计
2.1 分层架构
┌────────────────────────────────────────────────────┐
│ 使用层(页面/父组件) │
│ <u-table :columns="..." :data="..."> │
│ <u-table-column field="..." title="..." /> │
│ </u-table> │
└───────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ u-table.vue(主组件) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ 状态层 │ │ 事件层 │ │ 方法层 │ │
│ │ data/props │ │ emit/on │ │ ref 调用│ │
│ └──────┬───────┘ └──────┬───────┘ └────┬─────┘ │
│ │ │ │ │
│ └─────────────────┴───────────────┘ │
│ │ │
│ ┌───────────────────────┼────────────────────┐ │
│ │ 渲染层(template + style) │ │
│ │ 表头矩阵 → 数据行 → 合计 → 分页 → 预览 │ │
│ └───────────────────────┬────────────────────┘ │
└───────────────────────────┼────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ utils.js(纯函数工具层) types.js(类型/常量) │
│ 列归一化、表头矩阵、树形扁平化、格式化、 │
│ 排序、筛选、分页、CSV 导出、尺寸解析 │
└────────────────────────────────────────────────────┘
2.2 核心流程
初始化流程:
1. mounted 钩子
│
├─→ rebuildColumns()
│ ├─→ normalizeColumns(columns/childColumns)
│ ├─→ buildHeaderMatrix() ← 递归构建多表头矩阵 + 叶子列 + 分区
│ └─→ computeFixedOffsets() ← 计算左右固定列偏移
│
├─→ expandAll() (若 defaultExpandAll)
│
└─→ measureRowHeights() (防抖) ← uni.createSelectorQuery 测量行高
数据渲染流程:
props.data → processedData (本地筛选 + 排序) → flattenTree (树形扁平化)
→ slicePage (客户端分页,仅按钮分页模式) → renderRows → 渲染
拖拽流程:
表头 cell 右侧 handle → Start (mouse/touch) → Move (更新 __width)
└─→ End (重算固定列偏移 + emit)
2.3 关键设计决策
| 决策点 | 方案 | 原因 |
|---|---|---|
| 布局策略 | 单 scroll-view (scroll-x + scroll-y) + position: sticky | 跨端兼容性最佳;CSS Grid 在多端兼容性差异较大;多 scroll-view 同步方案复杂且性能差 |
| 多表头实现 | flex 行 + rowSpan/colSpan 控制高度/宽度 | 简化实现;保证 H5 与小程序渲染一致 |
| 固定列实现 | position: sticky; left/right + z-index | 微信/支付宝/抖音小程序 2.x+ 均支持 |
| 拖拽事件 | 条件编译:H5 mouse / 小程序 touch | 小程序无 mouse 事件;H5 触屏可选用 touch |
| 测量 API | uni.createSelectorQuery().in(this) | 跨端统一;不用 getBoundingClientRect/ResizeObserver |
| 插槽兼容 | this.$scopedSlots \|\| this.$slots |
Vue2 scopedSlots 与 Vue3 $slots 统一访问 |
| 列声明 | provide/inject + u-table-column 注册 | 解耦父子组件;便于属性响应式更新 |
三、目录结构
本组件采用 uni_modules 标准结构,便于 easycom 自动注册:
uni_modules/u-table/
├── package.json
├── readme.md
├── changelog.md
└── components/
├── u-table/ # 主组件
│ ├── u-table.vue # 主组件源码
│ ├── utils.js # 工具函数(纯函数)
│ └── types.js # 类型常量、默认配置
└── u-table-column/ # 声明式列子组件(独立目录,便于 easycom)
└── u-table-column.vue
说明:
u-table-column单独成目录是为了让 easycom 自动注册机制可在用户页面模板中直接使用<u-table-column>标签(默认 easycom 规则要求组件名 = 文件夹名 = 文件名)。组件完整文档即本文件(readme.md);版本变更见 changelog.md。
四、安装与配置
4.1 easycom 自动注册
uni_modules 下的组件默认按 easycom 自动注册,无需手动 import。在模板中直接使用即可:
<u-table :columns="columns" :data="data" />
4.2 手动注册(可选)
若需要禁用 easycom 或自定义路径,可在 pages.json 中配置:
{
"easycom": {
"autoscan": true,
"custom": {
"^u-table$": "@/uni_modules/u-table/components/u-table/u-table.vue",
"^u-table-column$": "@/uni_modules/u-table/components/u-table-column/u-table-column.vue"
}
}
}
4.3 全局主题样式(可选)
可通过覆盖 CSS 变量自定义主题:
/* 全局样式中 */
page {
--u-table-border-color: #ebeef5;
--u-table-header-bg: #f5f7fa;
--u-table-row-bg: #ffffff;
--u-table-stripe-bg: #fafafa;
--u-table-hover-bg: #f5f7fa;
--u-table-text-color: #303133;
--u-table-text-secondary: #909399;
--u-table-header-text: #303133;
}
五、快速上手
5.1 最简示例
<template>
<view class="container">
<u-table :columns="columns" :data="data" border stripe />
</view>
</template>
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '姓名', width: 120 },
{ field: 'age', title: '年龄', width: 100, align: 'right' },
{ field: 'gender', title: '性别', width: 100, filters: { 1: '男', 2: '女' }, filterable: true }
],
data: [
{ id: 1, name: '张三', age: 28, gender: 1 },
{ id: 2, name: '李四', age: 24, gender: 2 },
{ id: 3, name: '王五', age: 32, gender: 1 }
]
};
}
};
</script>
5.2 声明式写法(u-table-column)
<u-table :data="data" border>
<u-table-column field="name" title="姓名" :width="120" />
<u-table-column field="age" title="年龄" :width="100" align="right" />
<u-table-column field="gender" title="性别" :width="100" :filters="{ 1: '男', 2: '女' }" filterable />
</u-table>
六、API 参考
Table Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
columns |
Array |
[] |
JSON 列定义数组;优先级高于 <u-table-column> |
data |
Array |
[] |
表格数据 |
rowKey |
String \| Function |
'id' |
行唯一标识字段或函数 |
minWidth |
Number |
40 |
全局最小列宽(px);初始化时会将列显式宽度低于该值的拉升到该值,避免移动端表头与列错位;也可在单列用 minWidth 覆盖 |
autoWidth |
Boolean |
false |
是否启用自适应列宽:未设宽度的列按剩余空间均分,所有可见列都设宽时按比例铺满填满容器;默认关闭则列宽由初始化宽度决定 |
height |
Number \| String |
0 |
表格高度(px),0 表示自适应 |
maxHeight |
Number \| String |
0 |
最大高度(px) |
rowHeight |
Number \| String |
0 |
行高(px),0 表示自适应 |
size |
String |
'default' |
尺寸:large / default / small / mini |
stripe |
Boolean |
false |
斑马纹 |
border |
Boolean |
false |
边框 |
showHeader |
Boolean |
true |
显示表头 |
showSummary |
Boolean |
false |
显示合计行 |
sumText |
String |
'合计' |
合计行首列文案 |
summaryMethod |
Function |
null |
自定义合计方法:({ columns, data }) => Array |
rowStyle |
Object \| Function |
null |
行样式 |
rowClassName |
String \| Function |
null |
行类名 |
cellStyle |
Object \| Function |
null |
单元格样式 |
cellClassName |
String \| Function |
null |
单元格类名 |
cellHeaderStyle |
Object \| Function |
null |
表头单元格样式 |
headerRowClassName |
String \| Function |
null |
表头行类名 |
cellPadding |
String |
'4px 8px 4rpx 8px' |
单元格内边距 |
loading |
Boolean |
false |
加载中状态 |
error |
Boolean |
false |
错误状态 |
emptyText |
String |
'暂无数据' |
空数据提示文案 |
errorText |
String |
'加载失败' |
错误提示文案 |
loadMoreText |
String |
'加载中...' |
加载更多文案 |
noMoreText |
String |
'没有更多了' |
没有更多文案 |
isShowLoadMore |
Boolean |
false |
滚动加载中显示 |
isNoMore |
Boolean |
false |
没有更多 |
sortMode |
String |
'local' |
排序模式:local / remote |
remoteSort |
Boolean |
false |
远程排序(仅触发事件,不本地排序) |
defaultSort |
Object |
null |
默认排序 { field, order } |
filterMode |
String |
'local' |
筛选模式:local / remote |
remoteFilter |
Boolean |
false |
远程筛选 |
pagination |
Boolean |
false |
启用分页 |
page |
Number |
1 |
当前页(支持 .sync 风格,配合 page-change) |
pageSize |
Number |
10 |
每页条数 |
total |
Number |
0 |
数据总数(远程分页必填) |
paginationMode |
String |
'button' |
分页模式:button / scroll |
showPagination |
Boolean |
true |
显示分页栏 |
pagerCount |
Number |
5 |
按钮分页下最多显示的页码数 |
remote |
Boolean |
false |
远程模式总开关 |
remotePagination |
Boolean |
false |
远程分页(不分页切片,依赖 total) |
virtual |
Boolean |
false |
启用虚拟滚动(预留) |
bufferSize |
Number |
5 |
虚拟滚动缓冲行数 |
scrollScreen |
Boolean |
false |
大屏滚动开关:数据总高超过可视高度时自动滚动/翻页;与 virtual 互斥(开启时强制禁用虚拟滚动) |
scrollScreenMode |
String |
'continuous' |
滚动模式:continuous 单页自动上滚(跑马灯);paged 多页整屏翻页 |
scrollScreenSpeed |
Number |
40 |
速率:continuous 为每秒滚动像素(px/s);paged 为翻页间隔(ms,默认示例用 4000) |
scrollScreenLoop |
Boolean |
true |
滚到末尾是否循环(continuous 回顶端 / paged 回首页);false 则到末页后停止 |
scrollScreenControl |
Boolean |
true |
是否显示上方播放/暂停控制条 |
treeProps |
Object |
{ children: 'children', hasChildren: 'hasChildren' } |
树形配置 |
lazy |
Boolean |
false |
树形懒加载 |
load |
Function |
null |
懒加载函数:(row) => Promise<Array> |
defaultExpandAll |
Boolean |
false |
默认展开全部 |
expandKeys |
Array |
[] |
默认展开的 keys |
selectedKeys |
Array |
[] |
默认选中行的 keys |
currentRowKey |
String \| Number |
null |
当前高亮行 key |
reserveSelection |
Boolean |
false |
跨页保留选择 |
preview |
Boolean |
false |
点击行触发单行预览弹窗 |
previewTitle |
String |
'详情' |
预览弹窗标题 |
previewWidth |
String |
'80%' |
预览弹窗宽度 |
previewFormatter |
Function |
null |
自定义预览值:({ row, column, value }) => String |
theme |
String |
'default' |
主题(预留多主题) |
dark |
Boolean |
false |
深色模式 |
barSetting |
Boolean |
false |
是否显示内置工具栏的「列设置」按钮 |
barClear |
String \| Boolean |
false |
是否显示内置工具栏的「清除过滤/排序」按钮(仅在已设置过滤或排序时真正可见);传字符串时作为按钮的自定义名称 |
barReselt |
String \| Boolean |
false |
是否显示内置工具栏的「恢复默认」按钮;传字符串时作为按钮的自定义名称 |
colors |
Object \| Function |
null |
自定义主题颜色,可覆盖字段:borderColor、headerBg、rowBg、stripeBg、hoverBg(选中/悬停行)、textColor、textSecondary、headerText、cellPadding、headerRowHeight。传对象逐字段浅覆盖;或传 (baseColors) => colors 依据默认色派生。示例::colors="{ hoverBg: '#ffe7a3' }" |
spanMethod |
Function |
null |
合并行/列方法(预留) |
rowSpanMethod |
Function |
null |
行合并方法(预留) |
colSpanMethod |
Function |
null |
列合并方法(预留) |
columnConfig |
Object |
{} |
列配置(预留) |
persistColumnWidth |
Boolean |
false |
持久化列宽(预留) |
indexMethod |
Function |
null |
自定义索引:(index) => String |
Table Methods
通过 ref 调用:
| 方法 | 参数 | 说明 |
|---|---|---|
clearSelection |
— | 清空所有选择 |
toggleRowSelection |
(row, selected?) |
切换某行选中状态 |
toggleAllSelection |
— | 切换全选 |
setCurrentRow |
(row) |
设置当前高亮行 |
clearSort |
— | 清空排序 |
clearFilter |
(field?) |
清空筛选 |
doLayout |
— | 触发重新测量布局 |
refresh |
— | 重建列 + 测量 |
reload |
— | 重置至首页 + 清空选择/排序 + refresh |
scrollTo |
(scrollTop) |
滚动到指定位置 |
scrollToRow |
(rowKey) |
滚动到指定行 |
setColumnWidth |
(field, width) |
设置列宽 |
getColumn |
(field) |
获取列对象 |
exportCsv |
(filename?) |
导出 CSV(H5 触发下载;小程序返回字符串) |
Table Events
| 事件名 | 回调参数 | 说明 |
|---|---|---|
row-click |
(row, column, event) |
行点击 |
cell-click |
(row, column, value, event) |
单元格点击 |
header-click |
(column, event) |
表头点击 |
sort-change |
({ column, field, order, prop }) |
排序变化 |
filter-change |
({ column, values }) |
筛选变化 |
selection-change |
(selectedRows) |
选择变化 |
select |
(row, selected) |
单行选择 |
select-all |
(selected) |
全选 |
expand-change |
(row, expanded) |
树形展开/折叠 |
current-change |
(currentRow, oldRow) |
当前高亮行变化 |
column-resize |
({ column, width }) |
列宽拖拽结束 |
scroll |
({ scrollLeft, scrollTop }) |
滚动 |
load |
— | 加载(预留) |
load-more |
— | 滚动加载分页触底 |
page-change |
({ page, pageSize }) |
分页变化 |
preview |
(row) |
触发预览 |
retry |
— | 错误状态下重试 |
Table Slots
| 插槽名 | 作用域参数 | 说明 |
|---|---|---|
toolbar |
— | 顶部工具栏。与内置工具栏(由 barClear/barReselt/barSetting 控制)同处一行 flex 布局:插槽内容居左,内置按钮靠右展示 |
empty |
— | 空数据自定义 |
loading |
— | 加载中自定义 |
error |
— | 错误状态自定义 |
summary |
{ columns, data } |
合计行自定义 |
cellSlot |
{ row, column, rowIndex, value } |
单元格固定命名插槽,适配小程序(列配置 slot: true或slot: 'cellSlot'时渲染到该槽) |
headerSlot |
{ column } |
表头固定命名插槽(列配置 headerSlot: true或headerSlot: 'headerSlot' 时渲染到该槽) |
[field] |
{ row, column, rowIndex, value } |
单元格自定义插槽(默认插槽名等于列 field;仅H5/APP 端适配)⚠️ 部分支持 |
header-[field] |
{ column } |
表头单元格自定义插槽(默认表头插槽名等于列 header-field;仅H5/APP 端适配)⚠️ 部分支持 |
preview |
{ row, columns, values } |
单行预览弹窗内容自定义。若提供该插槽则完全替换默认渲染;未提供时使用内置字段列表展示 |
Column Props
columns 数组中每一项或 <u-table-column> 上可设置的属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
field |
String |
— | 列字段名 |
title |
String |
— | 列标题 |
width |
Number \| String |
0 |
列宽(数字或 '100px' / '100rpx',0 自动等分) |
minWidth |
Number \| String |
40 |
最小列宽 |
type |
String |
'' |
列类型,见下表 |
align |
String |
'left' |
对齐:left / center / right;selection / checkbox / radio / index 列默认居中(未显式设置时) |
fixed |
String |
'' |
固定:left / right / '' |
emptyString |
String |
'--' |
空值占位 |
formatter |
Function |
null |
格式化:(row, column, rowIndex) => String |
slot |
String \| Boolean |
'' |
单元格插槽名(字符串=动态具名插槽,默认等于 field;true=固定 cellSlot,适配小程序)。⚠️ 该属性仅用于 JSON columns 配置;在 <u-table-column> 声明式写法中对应 prop 为 cellSlot(slot 是 Vue 保留属性,不能作为组件 prop) |
headerSlot |
String \| Boolean |
'' |
表头插槽名(字符串=动态具名插槽;true=固定 headerSlot) |
children |
Array |
null |
多表头子列 |
hidden |
Boolean |
false |
是否隐藏;为 true 时表头与表体均不渲染该列,且不参与自适应列宽计算 |
filters |
Object \| Array |
null |
过滤器,见下 |
filterMultiple |
Boolean |
true |
筛选是否多选 |
onFilter |
Function |
null |
远程异步筛选分页:(page, callback) => callback(labels, hasMore),见"远程筛选分页" |
sortable |
Boolean |
false |
是否可排序 |
sorter |
Function \| String |
null |
排序函数或 'remote' |
filterable |
Boolean |
false |
是否可筛选 |
resizable |
Boolean |
true |
是否可拖拽 |
ellipsis |
Boolean |
true |
溢出省略 |
overflowTips |
Boolean \| String \| Array |
false |
溢出提示:文字被省略时查看完整内容。false 关闭;true 默认三种都有(悬停 + 双击 + 长按);也可指定触发方式:'hover'(悬停,H5/App)、'double'/'dbltap'(双击)、'long'/'longpress'(长按),支持字符串(如 'double,long')或数组(如 ['double','long'])单独/组合设置 |
className |
String |
'' |
单元格类名 |
headerClassName |
String |
'' |
表头单元格类名 |
cellStyle |
Object \| Function |
null |
单元格样式 |
headerStyle |
Object \| Function |
null |
表头单元格样式 |
indexMethod |
Function |
null |
自定义索引方法 |
selectable |
Function |
null |
是否可选:(row, index) => Boolean(仅 type='selection') |
reserveSelection |
Boolean |
false |
跨页保留选择 |
filters 取值:
- 对象:
{ 1: '启用', 0: '停用' } - 数组:
[{ value: 1, label: '启用' }, { value: 0, label: '停用' }] - 也可省略
filters:当filterable: true且未提供filters时,自动从当前data对应field去重生成选项(空值显示为column.emptyString,默认'--')。
远程筛选分页(onFilter):
当列设置了 onFilter(page, callback) 时,打开筛选弹层会通过该函数远程分页拉取标签,不再本地生成。选项列表底部有"加载更多"按钮,点击追加下一页;每列独立缓存已加载进度,关闭再开不重复请求。
{
title: '姓名', field: 'name', filterable: true,
onFilter: (page, callback) => {
// 一次加载 5 条,共 30 条(6 页);hasMore 表示是否还有下一页
const perPage = 5, total = 30;
const start = (page - 1) * perPage;
const names = Array.from({ length: total }, (_, i) => '远程用户' + (i + 1));
callback(names.slice(start, start + perPage), page * perPage < total);
}
}
labels可为['张三','李四']字符串数组,或[{ value, label }]对象数组(内部自动识别转换)。hasMore为true时底部显示"加载更多"按钮;false且已加载 >1 页时显示"没有更多了"。- 筛选弹层顶部提供"全部"选框,用于一键全选 / 取消全选下方标签。
Column type 取值
| type 值 | 渲染方式 | 说明 |
|---|---|---|
selection |
checkbox | 多选选择列(表头含全选框) |
checkbox |
checkbox | 同 selection 别名 |
radio |
radio | 单选选择列(单选语义:点选某行时清空其他选中;表头不显示全选) |
index |
行号 | 索引列(支持 indexMethod 自定义) |
img |
image | 图片列,点击触发 uni.previewImage;字段为数组 ['url1','url2'] 时渲染多图并支持整体预览滑动 |
year |
text | 年:yyyy |
month |
text | 月:MM月(只显示月份) |
year-month |
text | 年月:yyyy-MM |
date |
text | 年月日:yyyy-MM-dd(原 year-month-day) |
datetime |
text | 完整时间:yyyy-MM-dd HH:mm:ss(原 year-month-day-hour-minute-second) |
time |
text | 时分秒:HH:mm:ss(原 hour-minute-second) |
minute-second |
text | 分秒:mm:ss |
second |
text | 秒:ss |
列宽分配说明:
autoWidth(默认false)控制是否启用自适应列宽。开启时:设定了宽度的列使用固定宽度;未设宽度的列在容器有剩余空间时按比例铺满;当所有列都设置宽度且容器有剩余宽度时,按设置宽度比例均分填满容器。关闭时列宽仅由初始化宽度决定。checkbox/radio/index列的列宽仅由初始化宽度决定,不参与自适应分配,但可手动拖拽调整。hidden: true的列不参与任何列宽计算。minWidth表头/表体同源,保证对齐。纵向滚动条占位:H5 端在固定(
height)或受限(maxHeight)高度且内容超出时出现的右侧纵向滚动条会被计入列宽分配——均分与比例铺满时从容器宽度先减去滚动条占位宽,避免自适应后列宽总和超出可视宽而额外多出一条横向滚动条。
七、使用示例
7.1 JSON columns 示例
<template>
<u-table :columns="columns" :data="data" border stripe />
</template>
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '姓名', width: 120 },
{ field: 'age', title: '年龄', width: 100, align: 'right', sortable: true }
],
data: [
{ id: 1, name: '张三', age: 28 },
{ id: 2, name: '李四', age: 24 }
]
};
}
};
</script>
7.2 u-table-column 声明式示例
<u-table :data="data" border>
<u-table-column type="selection" :width="50" />
<u-table-column type="index" title="#" :width="60" />
<u-table-column field="name" title="姓名" :width="120" />
<u-table-column field="age" title="年龄" :width="100" align="right" sortable />
</u-table>
7.3 多表头示例
<u-table :columns="columns" :data="data" border />
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '姓名', width: 120, fixed: 'left' },
{
title: '基本信息',
children: [
{ field: 'age', title: '年龄', width: 100 },
{ field: 'gender', title: '性别', width: 100, filters: { 1: '男', 2: '女' }, filterable: true }
]
},
{
title: '联系方式',
children: [
{ field: 'phone', title: '电话', width: 150 },
{ field: 'email', title: '邮箱', width: 200 }
]
}
],
data: [
{ id: 1, name: '张三', age: 28, gender: 1, phone: '***', email: 'zhang@example.com' }
]
};
}
};
</script>
7.4 固定列示例
<u-table :columns="columns" :data="data" border height="400" />
<script>
export default {
data() {
return {
columns: [
{ field: 'id', title: 'ID', width: 80, fixed: 'left' },
{ field: 'name', title: '姓名', width: 120, fixed: 'left' },
{ field: 'age', title: '年龄', width: 100 },
{ field: 'address', title: '地址', width: 300 },
{ field: 'phone', title: '电话', width: 150 },
{ field: 'email', title: '邮箱', width: 200 },
{ field: 'actions', title: '操作', width: 100, fixed: 'right', slot: 'actions' }
]
};
}
};
</script>
7.5 树形懒加载示例
<u-table
:columns="columns"
:data="data"
row-key="id"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:lazy="true"
:load="loadChildren"
border
/>
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '名称', width: 240 },
{ field: 'count', title: '数量', width: 120, align: 'right' }
],
data: [
{ id: 1, name: '父节点 1', count: 0, hasChildren: true },
{ id: 2, name: '父节点 2', count: 0, hasChildren: true }
]
};
},
methods: {
// 懒加载:返回子节点数组
loadChildren(row) {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{ id: row.id + '_a', name: row.name + '-子A', count: 10 },
{ id: row.id + '_b', name: row.name + '-子B', count: 20 }
]);
}, 500);
});
}
}
};
</script>
7.6 分页示例
按钮分页(客户端分页):
<u-table
:columns="columns"
:data="data"
:pagination="true"
:page-size="10"
:pager-count="5"
@page-change="onPageChange"
/>
<script>
export default {
methods: {
onPageChange({ page, pageSize }) {
console.log('切换到第', page, '页');
}
}
};
</script>
远程分页:
<u-table
:columns="columns"
:data="remoteData"
:pagination="true"
:remote="true"
:remote-pagination="true"
:total="total"
:page="page"
:page-size="pageSize"
@page-change="fetchData"
/>
滚动加载分页:
<u-table
:columns="columns"
:data="data"
:pagination="true"
pagination-mode="scroll"
:is-show-load-more="loading"
:is-no-more="noMore"
@load-more="loadMore"
/>
7.7 插槽示例
<u-table :columns="columns" :data="data">
<!-- 默认单元格插槽:插槽名等于 field -->
<template #gender="{ row, value }">
<text :style="{ color: value === 1 ? '#409eff' : '#f56c6c' }">
{{ value === 1 ? '男' : '女' }}
</text>
</template>
<!-- 自定义列插槽名 -->
<template #actions="{ row }">
<view class="row-actions">
<text @click="onEdit(row)">编辑</text>
<text @click="onDelete(row)">删除</text>
</view>
</template>
<!-- 表头插槽 -->
<template #header-age="{ column }">
<view>
<text>{{ column.title }}</text>
<text style="font-size: 20rpx; color: #909399;">(岁)</text>
</view>
</template>
<!-- 空数据自定义 -->
<template #empty>
<view style="padding: 60rpx; text-align: center;">
<image src="/static/empty.png" mode="aspectFit" style="width: 200rpx; height: 200rpx;" />
<text>暂无数据,去添加一条吧</text>
</view>
</template>
</u-table>
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '姓名', width: 120 },
{ field: 'gender', title: '性别', width: 100 },
{ field: 'age', title: '年龄', width: 100, headerSlot: 'header-age' },
{ field: 'actions', title: '操作', width: 160, slot: 'actions' }
]
};
}
};
</script>
小程序插槽降级:微信/支付宝/抖音小程序的动态插槽名支持有限,优先使用固定命名插槽
cellSlot(见第八节:跨端与兼容性说明);如遇编译失败,参考该节中的降级方案。
7.8 合计示例
<u-table
:columns="columns"
:data="data"
:show-summary="true"
sum-text="总计"
:border="true"
/>
<script>
export default {
data() {
return {
columns: [
{ field: 'name', title: '商品', width: 200 },
{ field: 'price', title: '单价', width: 120, align: 'right' },
{ field: 'quantity', title: '数量', width: 100, align: 'right' },
{ field: 'total', title: '小计', width: 140, align: 'right' }
],
data: [
{ id: 1, name: '苹果', price: 5, quantity: 10, total: 50 },
{ id: 2, name: '香蕉', price: 3, quantity: 8, total: 24 }
]
};
}
};
</script>
自定义合计方法:
<u-table
:columns="columns"
:data="data"
:show-summary="true"
:summary-method="summaryMethod"
/>
<script>
export default {
methods: {
summaryMethod({ columns, data }) {
return columns.map((col, i) => {
if (i === 0) return '总计';
if (col.field === 'quantity') {
return '共 ' + data.reduce((a, b) => a + b.quantity, 0) + ' 件';
}
const sum = data.reduce((a, b) => a + Number(b[col.field] || 0), 0);
return isNaN(sum) ? '' : '¥' + sum;
});
}
}
};
</script>
7.9 单行预览示例
<u-table
:columns="columns"
:data="data"
:preview="true"
preview-title="用户详情"
preview-width="90%"
@preview="onPreview"
/>
<script>
export default {
methods: {
onPreview(row) {
console.log('预览行:', row);
}
}
};
</script>
通过 preview 插槽可整体替换预览弹窗内容(未提供时使用内置字段列表渲染):
<u-table
:columns="columns"
:data="data"
:preview="true"
preview-title="用户详情"
@preview="onPreview"
>
<template #preview="{ row, columns }">
<view class="custom-preview">
<text>{{ row.name }}</text>
<text>{{ row.address }}</text>
</view>
</template>
</u-table>
7.9.1 大屏滚动示例(continuous 单页滚动 / paged 多页翻页)
开启
scrollScreen后,仅当数据行总高度超过可视高度时才启用自动滚动/翻页;数据少则保持静态。 悬停 / 触摸滚动区域即暂停,移开 / 松开继续;上方控制条可手动播放/暂停。 注意:scrollScreen与virtual互斥,开启大屏滚动会自动禁用虚拟滚动,也不会再走前端分页切片(需展示全量数据流动)。
<!-- continuous:单页自动上滚(跑马灯),每秒 40px,循环 -->
<u-table
:columns="columns"
:data="data"
row-key="id"
height="400"
scroll-screen
scroll-screen-mode="continuous"
:scroll-screen-speed="40"
border
/>
<!-- paged:多页整屏翻页,每 4000ms 翻一页,循环 -->
<u-table
:columns="columns"
:data="data"
row-key="id"
height="400"
scroll-screen
scroll-screen-mode="paged"
:scroll-screen-speed="4000"
border
/>
<!-- 关闭控制条 / 不循环(到末页停止) -->
<u-table
:columns="columns"
:data="data"
row-key="id"
height="400"
scroll-screen
scroll-screen-mode="continuous"
:scroll-screen-loop="false"
:scroll-screen-control="false"
/>
7.10 综合示例(多特性叠加)
<u-table
ref="tableRef"
:columns="columns"
:data="data"
row-key="id"
height="600"
border
stripe
:pagination="true"
:page-size="10"
:show-summary="true"
:preview="true"
:default-expand-all="false"
:expand-keys="['1']"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
@row-click="onRowClick"
@selection-change="ionChange"
@sort-change="onSortChange"
@page-change="onPageChange"
>
<template #toolbar>
<view class="toolbar">
<button size="mini" @click="exportData">导出 CSV</button>
<button size="mini" @click="reload">刷新</button>
</view>
</template>
<template #status="{ row }">
<text :style="{ color: row.status === 1 ? '#67c23a' : '#f56c6c' }">
{{ row.status === 1 ? '启用' : '停用' }}
</text>
</template>
<template #actions="{ row }">
<text style="color: #409eff; margin-right: 20rpx;" @click.stop="onEdit(row)">编辑</text>
<text style="color: #f56c6c;" @click.stop="onDelete(row)">删除</text>
</template>
</u-table>
<script>
export default {
data() {
return {
columns: [
{ type: 'selection', width: 50 },
{ type: 'index', title: '#', width: 60 },
{ field: 'name', title: '名称', width: 200, fixed: 'left' },
{ field: 'status', title: '状态', width: 100, slot: 'status', filters: { 1: '启用', 0: '停用' }, filterable: true },
{ field: 'createTime', title: '创建时间', width: 180, type: 'datetime', sortable: true },
{ field: 'amount', title: '金额', width: 120, align: 'right', sortable: true },
{ field: 'actions', title: '操作', width: 160, fixed: 'right', slot: 'actions' }
],
data: []
};
},
methods: {
onRowClick(row) { /* ... */ },
ionChange(rows) { /* ... */ },
onSortChange({ field, order }) { /* ... */ },
onPageChange({ page }) { /* ... */ },
exportData() { this.$refs.tableRef.exportCsv('用户数据'); },
reload() { this.$refs.tableRef.reload(); }
}
};
</script>
八、跨端与兼容性说明
8.1 端能力矩阵
| 能力 | H5/Chrome | 微信小程序 | 支付宝小程序 | 抖音小程序 |
|---|---|---|---|---|
| 基础表格 | ✅ | ✅ | ✅ | ✅ |
| 多表头 | ✅ | ✅ | ✅ | ✅ |
| 固定列(左右) | ✅ sticky | ✅ sticky | ✅ sticky | ✅ sticky |
| 列宽拖拽 | ✅ mouse | ✅ touch | ✅ touch | ✅ touch |
| 高度自适应 | ✅ | ✅ | ✅ | ✅ |
| JSON columns | ✅ | ✅ | ✅ | ✅ |
| u-table-column 声明式 | ✅ | ✅ | ✅ | ✅ |
| 静态具名插槽 | ✅ | ✅ | ✅ | ✅ |
固定插槽(cellSlot) |
✅ | ✅ | ✅ | ✅ |
动态插槽名(#<field>) |
✅ | ⚠️ 不支持或部分支持 | ⚠️ 部分支持 | ⚠️ 部分支持 |
| 排序(前后台) | ✅ | ✅ | ✅ | ✅ |
| 筛选 | ✅ | ✅ | ✅ | ✅ |
| 树形 / 懒加载 | ✅ | ✅ | ✅ | ✅ |
| 分页(按钮) | ✅ | ✅ | ✅ | ✅ |
| 分页(滚动加载) | ✅ | ✅ | ✅ | ✅ |
| 合计 | ✅ | ✅ | ✅ | ✅ |
| 选择(单/多/全) | ✅ | ✅ | ✅ | ✅ |
| 单行预览 | ✅ | ✅ | ✅ | ✅ |
| CSV 导出 | ✅ 自动下载 | ⚠️ 返回字符串需自存 | ⚠️ 返回字符串 | ⚠️ 返回字符串 |
| 图片预览 | ✅ | ✅ uni.previewImage | ✅ | ✅ |
| 虚拟滚动 | ⏳ 预留 | ⏳ 预留 | ⏳ 预留 | ⏳ 预留 |
8.2 条件编译策略
| 平台 | 关键 API/事件 | 编译指令 |
|---|---|---|
| H5 | window mouse 事件、Blob 下载 |
#ifdef H5 |
| 微信小程序 | touch 事件、uni.createSelectorQuery |
#ifdef MP-WEIXIN |
| 支付宝小程序 | 同上 | #ifdef MP-ALIPAY |
| 抖音小程序 | 同上 | #ifdef MP-TOUTIAO |
| App(nvue 除外) | uni.previewImage、uni.createSelectorQuery |
#ifdef APP-PLUS |
8.3 动态插槽名降级方案
微信/支付宝/抖音小程序对动态插槽名(如 #<field>)支持有限,遇到编译错误时按以下顺序降级:
- 优先(推荐):单元格列配置
slot: true,表头列配置headerSlot: true,组件将对应列渲染到固定命名插槽cellSlot/headerSlot(作用域:cellSlot为{ row, column, rowIndex, value },headerSlot为{ column }),可在父级模板中按row/column分发自定义内容;未配置对应属性的列保持默认文本渲染,不受插槽影响:<u-table :columns="columns" :data="data"> <template #cellSlot="{ row, column, value }"> <text v-if="column.field === 'status'" :style="{ color: value === 1 ? '#67c23a' : '#f56c6c' }"> {{ value === 1 ? '启用' : '停用' }} </text> </template> </u-table>// columns 中:status、actions 两列走 cellSlot,其余列默认渲染 columns: [ { field: 'age', title: '年龄', align: 'right' }, // 默认文本,右对齐 { field: 'status', title: '状态', slot: true }, // 走 cellSlot { field: 'actions', title: '操作', slot: true }, // 走 cellSlot ] - 其次:在
<u-table-column>上使用cell-slotprop 显式声明具名插槽(⚠️ 不能写成slot,slot是 Vue 保留属性):<u-table-column field="status" title="状态" cell-slot="status" />列对象内部统一映射为
slot字段(与 JSONcolumns写法一致),仅组件 prop 名为cellSlot。 - H5/App 可使用
column.render函数(待后续版本支持)。 - 最后:使用
formatter替代自定义插槽。 - 详见已知限制。
8.4 Vue2 / Vue3 兼容
- 使用 Options API 风格,避免 Composition API 专属语法。
- 插槽访问统一:
this.$scopedSlots || this.$slots。 - 生命周期兼容:同时实现
beforeDestroy(Vue2)与beforeUnmount(Vue3)。 $set/$delete在 Vue3 中已废弃但 Vue2 中必需;本组件已使用并在 Vue3 中视为 no-op。
九、已知限制
| # | 限制 | 影响端 | 应对建议 |
|---|---|---|---|
| 1 | 单 scroll-view 同时 scroll-x + scroll-y 在部分低端机型滚动惯性略有差异 | 小程序 | 已开启 scroll-anchoring;可后续切换为多 scroll-view 同步方案 |
| 2 | 多表头复杂 rowSpan/colSpan 合并场景未完整实现(预留 spanMethod) |
全部 | 当前为简化版多表头;后续按需扩展 |
| 3 | 虚拟滚动接口预留,未实际启用大列表优化 | 全部 | 数据量 < 500 行无需开启;超大数据集请等服务端分页 |
| 4 | 列拖拽 handle 在小程序中触控区域较小 | 小程序 | 后续将扩大命中区域到 16rpx |
| 5 | CSV 导出在小程序中仅返回字符串,无法直接保存到相册 | 小程序 | 需用户配合 uni.saveFile 或剪贴板 |
| 6 | 行拖拽、右键菜单、可访问性(ARIA)尚未实现 | 全部 | 计划 P1 后续版本 |
| 7 | 暗黑主题仅实现基础色,未覆盖所有交互态 | 全部 | 计划 P2 完整化 |
| 8 | 多语言未实现 | 全部 | 预留 P2 |
| 9 | 动态插槽名在小程序中支持不稳定 | 微信/支付宝/抖音 | 使用固定命名插槽 cellSlot 统一分发 |
| 10 | <u-table-column> 嵌套子列(多表头)在小程序中通过 $children 收集不可靠 |
小程序 | 推荐改用 columns JSON 数组的 children 字段配置多表头 |
十、测试清单
10.1 Vue2 测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 + 数据行正常显示 | ☐ |
| 插槽访问 | $scopedSlots 兼容访问成功 |
☐ |
beforeDestroy 钩子 |
卸载时正常注销监听 | ☐ |
$set 修改列宽 |
响应式更新生效 | ☐ |
| 子组件注册 | <u-table-column> 通过 provide/inject 注册成功 |
☐ |
10.2 Vue3 测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 + 数据行正常显示 | ☐ |
| 插槽访问 | $slots 兼容访问成功 |
☐ |
beforeUnmount 钩子 |
卸载时正常注销监听 | ☐ |
| 选项式 API | Options API 写法无报错 | ☐ |
10.3 Chrome 测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 sticky 在顶部;固定列 sticky 在左右 | ☐ |
| 列宽拖拽 | mouse 按下拖拽 handle 实时调整宽度 | ☐ |
| 排序 | 点击表头切换 asc/desc/none | ☐ |
| 筛选 | 弹层选择并确认后筛选生效 | ☐ |
| 树形懒加载 | 点击展开图标触发 load,子节点正确挂载 |
☐ |
| 分页 | 按钮分页与滚动加载分页均生效 | ☐ |
| 合计 | 数值列正确求和 | ☐ |
| 预览 | 点击行触发预览弹窗 | ☐ |
| CSV 导出 | 触发浏览器下载 .csv 文件 | ☐ |
10.4 微信小程序测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 sticky + 固定列 sticky 正常 | ☐ |
| 列宽拖拽 | touch 拖拽 handle 调整宽度 | ☐ |
| 排序 | 表头点击切换排序 | ☐ |
| 筛选 | 弹层选择并确认 | ☐ |
| 树形懒加载 | load 返回 Promise 子节点挂载 |
☐ |
| 分页(按钮) | 上一页/下一页/页码切换 | ☐ |
| 分页(滚动加载) | 滚动到底触发 load-more |
☐ |
| 合计 | 数值列正确求和 | ☐ |
| 具名插槽 | 静态具名插槽正常渲染 | ☐ |
| 动态插槽名 | 小程序端需降级为固定插槽 cellSlot / headerSlot |
☐ |
| 图片预览 | uni.previewImage 正常调用 |
☐ |
10.5 支付宝小程序测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 + 固定列正常 | ☐ |
| 列宽拖拽 | touch 拖拽生效 | ☐ |
| 排序 / 筛选 | 切换/筛选生效 | ☐ |
| 树形懒加载 | load 异步挂载 |
☐ |
| 分页 | 按钮与滚动加载均生效 | ☐ |
| 合计 / 预览 | 合计行 + 预览弹窗 | ☐ |
| 插槽 | 静态具名插槽可用 | ☐ |
10.6 抖音小程序测试清单
| 测试项 | 期望结果 | 通过 |
|---|---|---|
| 基础渲染 | 表头 + 固定列正常 | ☐ |
| 列宽拖拽 | touch 拖拽生效 | ☐ |
| 排序 / 筛选 | 切换/筛选生效 | ☐ |
| 树形懒加载 | load 异步挂载 |
☐ |
| 分页 | 按钮与滚动加载均生效 | ☐ |
| 合计 / 预览 | 合计行 + 预览弹窗 | ☐ |
| 插槽 | 静态具名插槽可用 | ☐ |
附:版本与变更日志
参见 changelog.md。

收藏人数:
购买源码授权版(
试用
赞赏(0)
下载 59
赞赏 0
下载 12667119
赞赏 1955
赞赏
京公网安备:11010802035340号