TablePro 表格组件
基于 TDesign Vue Next 的增强表格组件,提供搜索表单、分页、排序、过滤等高级功能。
功能特性
- 🔍 智能搜索表单 - 自动根据列配置生成搜索表单
- 📄 分页支持 - 内置分页功能,支持前端和后端分页
- 🔄 数据请求 - 支持异步数据请求和自动加载状态
- 📊 排序过滤 - 支持列排序和数据过滤
- 🎨 插槽扩展 - 丰富的插槽支持,灵活定制表格头部和内容
- 🔧 类型安全 - 完整的 TypeScript 类型定义
- 📱 响应式布局 - 搜索表单自适应容器宽度
基础使用
Center Header
Left Header
Right Header
ID | Name | Age | Address | Email | Phone | Actions |
|---|---|---|---|---|---|---|
| 1 | John Doe | 30 | 123 Main St | - | - | - |
| 2 | Jane Smith | 25 | 456 Elm St | - | - | - |
| 3 | Alice Johnson | 28 | 789 Maple Ave | - | - | - |
| 4 | Bob Brown | 35 | 101 Pine Rd | - | - | - |
| 5 | Charlie White | 22 | 202 Oak St | - | - | - |
| 6 | Diana Green | 27 | 303 Birch Blvd | - | - | - |
| 7 | Ethan Blue | 32 | 404 Cedar Ct | - | - | - |
| 8 | Fiona Black | 29 | 505 Spruce Dr | - | - | - |
| 9 | George Gray | 31 | 606 Willow Way | - | - | - |
| 10 | Hannah Yellow | 26 | 707 Cherry Ln | - | - | - |
| 11 | Ian Purple | 34 | 808 Ash St | - | - | - |
| 12 | Julia Orange | 23 | 909 Palm Pl | - | - | - |
| 13 | Kevin Pink | 30 | 1010 Magnolia Ave | - | - | - |
| 14 | Laura Cyan | 28 | 1111 Peach St | - | - | - |
| 15 | Mike Red | 33 | 1212 Maple St | - | - | - |
| 16 | Nina Teal | 24 | 1313 Cedar St | - | - | - |
| 17 | Oscar Silver | 36 | 1414 Oak St | - | - | - |
| 18 | Paula Gold | 21 | 1515 Pine St | - | - | - |
| 19 | Quinn Bronze | 29 | 1616 Birch St | - | - | - |
| 20 | Rita Copper | 32 | 1717 Spruce St | - | - | - |
共 0 条数据
50 条/页
- 1
查看代码
vue
<template>
<div class="base-table-container">
<TablePro :columns="columns" row-key="id" :data="data">
<template #tableHeaderCenter>
<div>Center Header</div>
</template>
<template #tableHeaderLeft>
<div>Left Header</div>
</template>
<template #tableHeaderRight>
<div>Right Header</div>
</template>
</TablePro>
</div>
</template>
<script setup lang="ts">
import type { ProTableCol } from 't-design-pro'
import { TablePro } from 't-design-pro'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute(
'theme-mode',
newVal ? 'dark' : 'light'
)
},
{
immediate: true
}
)
const columns = ref<ProTableCol[]>([
{
title: 'ID',
colKey: 'id',
width: 100,
search: true,
fixed: 'left'
},
{
title: 'Name',
colKey: 'name',
width: 200,
search: true
},
{
title: 'Age',
colKey: 'age',
width: 100,
search: true
},
{
title: 'Address',
colKey: 'address',
width: 300,
search: true
},
{
title: 'Email',
colKey: 'email',
width: 200,
search: true
},
{
title: 'Phone',
colKey: 'phone',
width: 150,
search: true
},
{
title: 'Actions',
colKey: 'operation',
width: 150,
fixed: 'right'
}
])
const data = ref([
{ id: 1, name: 'John Doe', age: 30, address: '123 Main St' },
{ id: 2, name: 'Jane Smith', age: 25, address: '456 Elm St' },
{ id: 3, name: 'Alice Johnson', age: 28, address: '789 Maple Ave' },
{ id: 4, name: 'Bob Brown', age: 35, address: '101 Pine Rd' },
{ id: 5, name: 'Charlie White', age: 22, address: '202 Oak St' },
{ id: 6, name: 'Diana Green', age: 27, address: '303 Birch Blvd' },
{ id: 7, name: 'Ethan Blue', age: 32, address: '404 Cedar Ct' },
{ id: 8, name: 'Fiona Black', age: 29, address: '505 Spruce Dr' },
{ id: 9, name: 'George Gray', age: 31, address: '606 Willow Way' },
{ id: 10, name: 'Hannah Yellow', age: 26, address: '707 Cherry Ln' },
{ id: 11, name: 'Ian Purple', age: 34, address: '808 Ash St' },
{ id: 12, name: 'Julia Orange', age: 23, address: '909 Palm Pl' },
{ id: 13, name: 'Kevin Pink', age: 30, address: '1010 Magnolia Ave' },
{ id: 14, name: 'Laura Cyan', age: 28, address: '1111 Peach St' },
{ id: 15, name: 'Mike Red', age: 33, address: '1212 Maple St' },
{ id: 16, name: 'Nina Teal', age: 24, address: '1313 Cedar St' },
{ id: 17, name: 'Oscar Silver', age: 36, address: '1414 Oak St' },
{ id: 18, name: 'Paula Gold', age: 21, address: '1515 Pine St' },
{ id: 19, name: 'Quinn Bronze', age: 29, address: '1616 Birch St' },
{ id: 20, name: 'Rita Copper', age: 32, address: '1717 Spruce St' }
])
</script>
<style scoped>
.base-table-container {
height: 700px;
}
</style>动态字段搜索
动态字段搜索示例
ID | 姓名 | 手机号 | 邮箱 | 状态 | 创建时间 | 操作 | 综合搜索 |
|---|---|---|---|---|---|---|---|
暂无数据 | |||||||
共 0 条数据
50 条/页
- 1
查看代码
vue
<template>
<div class="dynamic-fields-container">
<TablePro :columns="columns" :request="fetchData" row-key="id">
<template #tableHeaderCenter>
<div>动态字段搜索示例</div>
</template>
</TablePro>
</div>
</template>
<script setup lang="ts">
import type { ProTableCol } from 't-design-pro'
import { TablePro } from 't-design-pro'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute(
'theme-mode',
newVal ? 'dark' : 'light'
)
},
{
immediate: true
}
)
const columns = ref<ProTableCol[]>([
{
title: 'ID',
colKey: 'id',
width: 100,
fixed: 'left'
},
{
title: '姓名',
colKey: 'name',
width: 150
},
{
title: '手机号',
colKey: 'phone',
width: 150
},
{
title: '邮箱',
colKey: 'email',
width: 200
},
{
title: '状态',
colKey: 'status',
width: 100,
cellContentEnum: {
'1': '启用',
'0': '禁用'
}
},
{
title: '创建时间',
colKey: 'createTime',
width: 180
},
{
title: '操作',
colKey: 'operation',
width: 120,
fixed: 'right'
},
{
title: '综合搜索',
colKey: 'dynamicSearch',
search: {
defaultFieldKey: 'phone',
dynamicFields: [
{
key: 'name',
label: '姓名',
valueType: 't-input',
fieldProps: {
placeholder: '请输入姓名',
clearable: true
}
},
{
key: 'phone',
label: '手机号',
valueType: 't-input',
fieldProps: {
placeholder: '请输入手机号',
maxlength: 11
}
},
{
key: 'email',
label: '邮箱',
valueType: 't-input',
fieldProps: {
placeholder: '请输入邮箱'
}
},
{
key: 'status',
label: '状态',
valueType: 't-select',
valueEnum: {
'1': '启用',
'0': '禁用'
},
fieldProps: {
placeholder: '请选择状态'
}
}
]
}
}
])
// 模拟数据
const mockData = [
{ id: 1, name: '张三', phone: '13800138000', email: 'zhangsan@example.com', status: '1', createTime: '2024-01-15 10:30:00' },
{ id: 2, name: '李四', phone: '13900139000', email: 'lisi@example.com', status: '0', createTime: '2024-01-16 14:20:00' },
{ id: 3, name: '王五', phone: '13700137000', email: 'wangwu@example.com', status: '1', createTime: '2024-01-17 09:15:00' },
{ id: 4, name: '赵六', phone: '13600136000', email: 'zhaoliu@example.com', status: '1', createTime: '2024-01-18 16:45:00' },
{ id: 5, name: '钱七', phone: '13500135000', email: 'qianqi@example.com', status: '0', createTime: '2024-01-19 11:30:00' },
{ id: 6, name: '孙八', phone: '13400134000', email: 'sunba@example.com', status: '1', createTime: '2024-01-20 13:20:00' },
{ id: 7, name: '周九', phone: '13300133000', email: 'zhoujiu@example.com', status: '1', createTime: '2024-01-21 15:10:00' },
{ id: 8, name: '吴十', phone: '13200132000', email: 'wushi@example.com', status: '0', createTime: '2024-01-22 10:00:00' }
]
const fetchData = async (params: any) => {
// 模拟异步请求
await new Promise(resolve => setTimeout(resolve, 500))
const { current, pageSize, ...searchParams } = params
// 过滤数据
let filteredData = [...mockData]
// 处理动态字段搜索
Object.keys(searchParams).forEach(key => {
const value = searchParams[key]
if (value) {
const [fieldKey, subKey] = key.split('.')
if (fieldKey === 'dynamicSearch') {
if (subKey === 'name') {
filteredData = filteredData.filter(item => item.name.includes(value))
} else if (subKey === 'phone') {
filteredData = filteredData.filter(item => item.phone.includes(value))
} else if (subKey === 'email') {
filteredData = filteredData.filter(item => item.email.includes(value))
} else if (subKey === 'status') {
filteredData = filteredData.filter(item => item.status === value)
}
}
}
})
// 分页处理
const start = (current - 1) * pageSize
const end = start + pageSize
const pageData = filteredData.slice(start, end)
return {
data: pageData,
total: filteredData.length
}
}
</script>
<style scoped>
.dynamic-fields-container {
height: 700px;
}
</style>动态请求数据
Name | Age | Address |
|---|---|---|
暂无数据 | ||
共 0 条数据
50 条/页
- 1
查看代码
vue
<template>
<div>
<TablePro
ref="tableRef"
:columns="columns"
row-key="id"
:loading="loading"
:request="request"
>
<template #tableHeaderLeft>
<TButton @click="refresh">刷新</TButton>
</template>
</TablePro>
</div>
</template>
<script setup lang="ts">
import { TablePro } from 't-design-pro'
import { Button as TButton } from 'tdesign-vue-next'
import { useData } from 'vitepress'
import { ref, watch } from 'vue'
import type { ProTableCol } from '@/components/Table/types'
const { isDark } = useData()
watch(
isDark,
(newVal) => {
document.documentElement.setAttribute(
'theme-mode',
newVal ? 'dark' : 'light'
)
},
{
immediate: true
}
)
const columns = ref<ProTableCol[]>([
{
title: 'Name',
colKey: 'name',
width: 200
},
{
title: 'Age',
colKey: 'age',
width: 100
},
{
title: 'Address',
colKey: 'address',
width: 300
}
])
const data = [
{ id: 1, name: 'John Doe', age: 30, address: '123 Main St' },
{ id: 2, name: 'Jane Smith', age: 25, address: '456 Elm St' },
{ id: 3, name: 'Alice Johnson', age: 28, address: '789 Maple Ave' },
{ id: 4, name: 'Bob Brown', age: 35, address: '101 Pine Rd' }
]
const getData = (params: { name: string }) => {
console.log('params', params)
return new Promise<{ data: any[]; total: number }>((resolve) => {
setTimeout(() => {
resolve({
data,
total: data.length
})
}, 1500)
})
}
const loading = ref(false)
const request = async (params) => {
try {
loading.value = true
const result = await getData(params)
return {
data: result.data,
total: result.total
}
} catch {
return null
} finally {
loading.value = false
}
}
const tableRef = ref<InstanceType<typeof TablePro> | null>(null)
const refresh = () => {
tableRef.value?.refresh()
}
</script>
<style></style>API
Props
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| request | 数据请求方法 | ProTableRequestMethod | - |
| extendParams | 请求扩展参数 | ExtendParams | - |
| columns | 表格列配置 | ProTableCol[] | [] |
| data | 静态数据 | Array<any> | - |
| rowKey | 行数据的唯一标识字段名 | string | 'id' |
| cellEmptyContent | 空单元格显示内容 | string | '-' |
| pagination | 分页配置 | PaginationProps | null | { defaultCurrent: 1, defaultPageSize: 50 } |
| resetType | 表单重置类型 | 'initial' | 'empty' | 'initial' |
| tableProps | 透传给 EnhancedTable 的属性 | ProTablePropsOmitKey | {} |
ProTableCol 列配置
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| colKey | 列标识符 | string | - |
| title | 列标题 | string | TNode | - |
| width | 列宽度 | number | string | - |
| fixed | 固定列位置 | 'left' | 'right' | - |
| hidden | 是否隐藏列 | boolean | false |
| search | 搜索配置 | ProTableColSearchType | boolean | - |
| cellContentEnum | 单元格内容枚举映射 | Record<string, any> | - |
| cell | 自定义单元格渲染 | TNode | - |
ProTableColSearchType 搜索配置
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 搜索字段名(默认使用 colKey) | string | - |
| label | 搜索表单标签 | string | - |
| valueType | 搜索组件类型 | SearchValueType | 't-input' |
| valueEnum | 选择器选项枚举 | Record<string, any> | - |
| fieldProps | 组件属性 | Record<string, any> | - |
| render | 自定义渲染搜索组件 | () => VNode | - |
| dynamicFields | 动态字段配置 | DynamicFieldItem[] | - |
| defaultFieldKey | 默认选中的动态字段key | string | - |
DynamicFieldItem 动态字段配置
| 参数 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| key | 字段标识符 | string | - |
| label | 字段显示名称 | string | - |
| valueType | 该字段的输入类型 | SearchValueType | 't-input' |
| fieldProps | 该字段的输入属性 | Record<string, any> | - |
| valueEnum | 该字段的枚举值 | Record<string, any> | - |
SearchValueType 搜索组件类型
支持的搜索组件类型:
't-input'- 输入框't-select'- 选择器't-date-picker'- 日期选择器't-date-range-picker'- 日期范围选择器VNode- 自定义组件
方法
| 方法名 | 说明 | 参数 | 返回值 |
|---|---|---|---|
| refresh | 刷新表格数据(不重置分页) | - | - |
| reset | 重置表格(重置分页到第一页) | - | - |
| restSearchForm | 重置搜索表单 | FormResetParams | - |
| appendTo | 新增数据到指定位置 | (key: string, newData: any) | - |
插槽
| 插槽名 | 说明 | 参数 |
|---|---|---|
| tableHeaderCenter | 表格头部中心区域 | - |
| tableHeaderLeft | 表格头部左侧区域 | - |
| tableHeaderRight | 表格头部右侧区域 | - |
| empty | 空数据状态 | - |
事件
继承 TDesign EnhancedTable 的所有事件,主要包括:
change- 分页、排序、过滤变化时触发select-change- 选择变化时触发expand-change- 展开变化时触发
高级用法
搜索表单配置
vue
<template>
<TablePro :columns="columns" :request="fetchData" />
</template>
<script setup>
const columns = [
{
title: '用户名',
colKey: 'username',
search: true // 简单搜索配置
},
{
title: '状态',
colKey: 'status',
search: {
valueType: 't-select',
valueEnum: {
active: '活跃',
inactive: '非活跃'
}
}
},
{
title: '创建时间',
colKey: 'createTime',
search: {
valueType: 't-date-range-picker',
fieldProps: {
enableTimePicker: true
}
}
}
]
</script>自定义搜索组件
vue
<script setup>
const columns = [
{
title: '自定义搜索',
colKey: 'custom',
search: {
render: () =>
h(CustomComponent, {
placeholder: '请输入关键词'
})
}
}
]
</script>动态字段搜索
支持在一个搜索项中动态切换不同的字段进行搜索,适用于需要灵活搜索的场景。
vue
<script setup>
const columns = [
{
title: '动态搜索',
colKey: 'dynamicSearch',
search: {
dynamicFields: [
{ key: 'name', label: '姓名', valueType: 't-input' },
{ key: 'phone', label: '手机号', valueType: 't-input' },
{ key: 'email', label: '邮箱', valueType: 't-input' },
{ key: 'status', label: '状态', valueType: 't-select', valueEnum: { '1': '启用', '0': '禁用' } }
]
}
}
]
// 搜索时,参数格式为:{ 'dynamicSearch.name': '张三' }
const fetchData = async (params) => {
// params 包含:{ 'dynamicSearch.name': '张三' } 或 { 'dynamicSearch.phone': '13800138000' }
const response = await api.search({
...params
})
return {
data: response.list,
total: response.total
}
}
</script>说明:
- 左侧为字段选择器(select),右侧为对应的输入框
- 默认选中
dynamicFields中的第一个字段 - 搜索时,参数格式为
{ 'colKey.fieldKey': value },例如{ 'dynamicSearch.name': '张三' } - 切换字段时,输入框的值会自动清空
动态字段搜索(带默认值)
vue
<script setup>
const columns = [
{
title: '高级搜索',
colKey: 'advancedSearch',
search: {
// 设置默认选中的字段
defaultFieldKey: 'phone',
dynamicFields: [
{
key: 'name',
label: '姓名',
valueType: 't-input',
fieldProps: {
placeholder: '请输入姓名',
clearable: true
}
},
{
key: 'phone',
label: '手机号',
valueType: 't-input',
fieldProps: {
placeholder: '请输入手机号',
maxlength: 11
}
},
{
key: 'email',
label: '邮箱',
valueType: 't-input',
fieldProps: {
placeholder: '请输入邮箱'
}
},
{
key: 'status',
label: '状态',
valueType: 't-select',
valueEnum: {
'1': '启用',
'0': '禁用'
},
fieldProps: {
placeholder: '请选择状态'
}
}
]
}
}
]
const fetchData = async (params) => {
// params 包含:{ 'advancedSearch.phone': '13800138000' }
const response = await api.search({
...params
})
return {
data: response.list,
total: response.total
}
}
</script>说明:
- 使用
defaultFieldKey指定默认选中的字段(示例中默认选中"手机号") - 可以为每个字段配置独立的
fieldProps,如placeholder、maxlength等 - 字段切换时,输入框的值会自动清空
动态字段搜索(复杂场景)
vue
<script setup>
const columns = [
{
title: '综合搜索',
colKey: 'complexSearch',
search: {
dynamicFields: [
{
key: 'dateRange',
label: '时间范围',
valueType: 't-date-range-picker',
fieldProps: {
enableTimePicker: true,
format: 'YYYY-MM-DD HH:mm:ss'
}
},
{
key: 'amount',
label: '金额范围',
valueType: 't-input',
fieldProps: {
placeholder: '请输入金额',
type: 'number'
}
},
{
key: 'category',
label: '分类',
valueType: 't-select',
valueEnum: {
'1': '产品A',
'2': '产品B',
'3': '产品C'
}
},
{
key: 'region',
label: '地区',
valueType: 't-select',
valueEnum: {
'north': '华北',
'south': '华南',
'east': '华东',
'west': '西南'
}
}
]
}
}
]
const fetchData = async (params) => {
// 处理不同字段类型的参数
const { current, pageSize, ...searchParams } = params
// 示例:处理日期范围
if (searchParams['complexSearch.dateRange']) {
const [startTime, endTime] = searchParams['complexSearch.dateRange']
searchParams.startTime = startTime
searchParams.endTime = endTime
delete searchParams['complexSearch.dateRange']
}
const response = await api.search({
page: current,
size: pageSize,
...searchParams
})
return {
data: response.list,
total: response.total
}
}
</script>说明:
- 支持多种输入类型:日期范围、数字输入、选择器等
- 可以根据业务需求自定义参数处理逻辑
- 适用于需要灵活组合多种搜索条件的复杂场景
数据请求方法
javascript
const fetchData = async (params) => {
const { current, pageSize, ...searchParams } = params
const response = await api.getTableData({
page: current,
size: pageSize,
...searchParams
})
return {
data: response.list,
total: response.total
}
}表格头部插槽
vue
<template>
<TablePro :columns="columns">
<template #tableHeaderLeft>
<t-button theme="primary">新增</t-button>
</template>
<template #tableHeaderRight>
<t-button theme="default">导出</t-button>
<t-button theme="default">设置</t-button>
</template>
</TablePro>
</template>注意事项
- 数据请求方法:
request方法需要返回{ data: Array, total: number }格式的数据 - 列标识符:每列必须设置唯一的
colKey - 搜索表单:只有设置了
search属性的列才会出现在搜索表单中 - 分页处理:组件会自动处理分页参数传递和页码管理
- 响应式布局:搜索表单会根据容器宽度自动调整列数和布局