更新记录

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>)支持有限,遇到编译错误时按以下顺序降级:

  1. 优先(推荐):单元格列配置 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
    ]
  2. 其次:在 <u-table-column> 上使用 cell-slot prop 显式声明具名插槽(⚠️ 不能写成 slot,slot 是 Vue 保留属性):
    <u-table-column field="status" title="状态" cell-slot="status" />

    列对象内部统一映射为 slot 字段(与 JSON columns 写法一致),仅组件 prop 名为 cellSlot。

  3. H5/App 可使用 column.render 函数(待后续版本支持)。
  4. 最后:使用 formatter 替代自定义插槽。
  5. 详见已知限制。

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。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

无

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。