Appearance
DataTable 表格
配置驱动的数据表格组件,通过 columns 配置列、query 提供数据查询。支持搜索栏、分页、列类型渲染、操作按钮、Tab 切换、多选等。
在线演示
一个包含搜索栏、下拉筛选、金额/状态/日期列、操作按钮、分页的完整表格:
tsx
import { defineComponent, ref, h } from 'vue'
import { ElMessage as message } from 'element-plus'
import DataTable from '@/components/DataTable'
export default defineComponent({
name: 'DemoDataTableFull',
setup() {
const selection = ref<any>()
const mockRows = Array.from({ length: 86 }, (_, i) => ({
id: i + 1,
name: ['张三', '李四', '王五', '赵六', '陈七', '周八'][i % 6],
dept: ['技术部', '市场部', '财务部', '人事部', '运营部'][i % 5],
amount: +(Math.random() * 100000).toFixed(2),
status: (i % 4) as 0 | 1 | 2 | 3,
role: ['管理员', '普通用户', '访客'][i % 3],
email: `user${i + 1}@company.com`,
createdAt: new Date(Date.now() - i * 86400000 * 3).toISOString(),
enabled: i % 2 === 0,
}))
const columns: any[] = [
{ label: '姓名', field: 'name', width: 100, fixed: 'left' },
{
label: '自定义列',
field: 'name',
width: 130,
render: (val: string, row: any) =>
h('div', { style: 'display:flex;align-items:center;gap:4px' }, [
h('span', {
style: `display:inline-block;width:8px;height:8px;border-radius:50%;background:${row.status === 1 ? '#67c23a' : '#909399'}`,
}),
h('span', val),
]),
},
{ label: '部门', field: 'dept', width: 100 },
{ label: '邮箱', field: 'email', minWidth: 180 },
{
label: '金额(money)',
field: 'amount',
type: 'money',
width: 140,
},
{
label: '状态(enum)',
field: 'status',
type: 'enum',
width: 110,
code: 'sys_status',
colors: { 0: 'info', 1: 'success', 2: 'warning', 3: 'danger' },
},
{
label: '创建时间(date)',
field: 'createdAt',
type: 'date',
width: 120,
},
{
label: '操作',
type: 'operate',
width: 180,
fixed: 'right',
buttons: [
{
label: '编辑',
type: 'primary',
click: ({ row }: any) => message.success(`编辑 ${row.name}`),
},
{
label: '删除',
type: 'danger',
confirm: ({ row }: any) => `确认删除用户「${row.name}」?`,
click: ({ row }: any) => message.success(`已删除 ${row.name}`),
},
],
},
]
const fields: any[] = [
{ label: '姓名', field: 'name', type: 'el-input', props: { placeholder: '请输入姓名' } },
{
label: '部门',
field: 'dept',
type: 'data-picker',
props: {
type: 'select',
query: () =>
Promise.resolve([
{ label: '全部', value: '' },
{ label: '技术部', value: '技术部' },
{ label: '市场部', value: '市场部' },
{ label: '财务部', value: '财务部' },
]),
},
},
]
const query = (params: any) => {
const { page, pageSize } = params
return new Promise<any>((resolve) => {
setTimeout(() => {
const filtered = params.name
? mockRows.filter((r) => r.name.includes(params.name))
: params.dept
? mockRows.filter((r) => r.dept === params.dept)
: mockRows
resolve({
totalRows: filtered.length,
rows: filtered.slice((page - 1) * pageSize, page * pageSize),
})
}, 200)
})
}
return () => (
<div class="demo-block">
<DataTable
columns={columns}
query={query}
fields={fields}
pageSize={5}
selection={selection.value ? {} : false}
actions={[
{
label: '新增',
type: 'primary',
click: () => message.success('新增用户'),
},
{
label: '批量操作',
type: 'dropdown',
buttons: [
{ label: '批量导出', click: () => message.success('导出中…') },
{ label: '批量删除', confirm: '确认批量删除?', click: () => {} },
],
},
]}
/>
</div>
)
},
})Props
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| query | (params) => Promise<Result> | - | 数据查询函数,接收 { page, pageSize, activeKey, ...搜索字段 },返回 { rows, totalRows } |
| columns | TableColumn[] | [] | 列定义 |
| fields | FormColumn[] | [] | 搜索栏字段配置,基于 DataForm |
| actions | Button[] | [] | 顶部操作按钮 |
| model | Record<string, any> | {} | 搜索参数初始值 |
| data | any[] | - | 直接传入数据源(替代 query) |
| rowKey | string | 'id' | 行唯一标识字段 |
| title | string | - | 表格左上角标题 |
| tabs | { label, value }[] | - | 页签,切换时 query 会收到 activeKey |
| activeTab | any | - | 当前激活 tab(支持 v-model) |
| selection | RowSelection | - | 多选配置 |
| fillPage | boolean | false | 自动撑满父容器高度 |
| nullValue | any | '/' | 空数据占位文本 |
| props | Partial<TableProps> | - | 透传给 ElTable 的 props(如 border、lazy、tree-props) |
| pageSize | number | 20 | 每页条数 |
| pageNo | number | 1 | 初始页码 |
| showButton | boolean | true | 是否显示搜索/重置按钮 |
| hideOnSinglePage | boolean | false | 只有一页时隐藏分页 |
| fieldsPosition | 'top' | 'left' | 'top' | 搜索栏位置 |
| labelWidth | string | number | '80px' | 搜索栏标签宽度 |
| onPageChange | (page, size) => void | - | 分页变化回调 |
列配置 TableColumn
基础属性
| 属性名 | 类型 | 说明 |
|---|---|---|
| label | string | 表头标题 |
| field | string | 字段名,支持嵌套路径 |
| width | number | 列宽 |
| minWidth | number | 最小列宽 |
| fixed | 'left' | 'right' | 固定列 |
| align | string | 对齐方式,默认 'center' |
| visible | boolean | 是否显示,默认 true |
| tabs | any[] | 指定该列在哪些 tab 下显示 |
| className | string | 自定义列 class |
| tooltip | boolean | 内容超长时显示 tooltip |
| labelTooltip | string | 表头问号提示 |
增强类型 type
通过 type 快速格式化数据,无需手写 render:
| type | 说明 | 额外属性 |
|---|---|---|
| money | 金额千分位 | params |
| date | 日期 YYYY-MM-DD | params:自定义格式,如 'YYYY/MM/DD' |
| time | 日期时间 YYYY-MM-DD HH:mm:ss | params:自定义格式 |
| whether | 布尔值 → 是/否(ElTag) | - |
| enum | 字典值翻译 | code:字典编码,colors:颜色映射 |
| percentage | 百分比(×100 + %) | - |
| img | 图片展示(支持预览) | params:透传 ElImage props |
| index | 序号列 | - |
| selection | 多选列(自动添加) | - |
| operate | 操作列 | buttons:按钮数组 |
| expand | 展开行 | 配合 slots.expand |
ts
// 字典列
{ label: '状态', field: 'status', type: 'enum', code: 'order_status', colors: { 1: 'success', 2: 'danger' } }
// 金额列
{ label: '金额', field: 'amount', type: 'money', align: 'right' }
// 图片列
{ label: '头像', field: 'avatar', type: 'img' }
// 日期列
{ label: '创建时间', field: 'createdAt', type: 'date', width: 120 }
// 自定义格式日期
{ label: '时间', field: 'time', type: 'date', params: 'YYYY/MM/DD', width: 120 }自定义渲染 render
ts
{
label: '状态',
field: 'status',
render: (value, row, rowIndex) => {
return h(ElTag, { type: value === 1 ? 'success' : 'danger' }, value === 1 ? '启用' : '禁用')
}
}嵌套表头
ts
{
label: '用户信息',
columns: [
{ label: '姓名', field: 'name', width: 100 },
{ label: '邮箱', field: 'email', width: 200 },
]
}操作列
ts
{
label: '操作',
type: 'operate',
width: 200,
fixed: 'right',
buttons: [
{ label: '编辑', type: 'primary', click: ({ row }) => editRow(row) },
{ label: '删除', type: 'danger', confirm: '确认删除?', click: ({ row }) => deleteRow(row) },
],
}按钮配置 Button
| 属性名 | 类型 | 说明 |
|---|---|---|
| label | string | ({ row, column, params }) => string | 按钮文字,支持函数动态计算 |
| click | ({ row, column, params }) => void | 点击回调,返回 Promise 自动 loading |
| type | 'primary' | 'danger' | 'default' | 按钮类型 |
| show | (row, column, params) => boolean | 动态显示/隐藏。注意是三个独立参数,非解构对象 |
| confirm | string | ({ row, column, params }) => string | 二次确认文案 |
| permission | string | string[] | 权限标识 |
| visited | boolean | 点击后标记已读样式 |
| tabs | any[] | 指定在哪些 tab 下显示 |
按钮支持 ElButton 的所有 props(
size、plain、text等)。
下拉菜单按钮
当操作按钮超过全局配置的 actionsCount(默认 3)时,超出的按钮会自动折叠到"更多"下拉菜单中。也可手动设置:
ts
{
label: '更多操作',
type: 'dropdown',
buttons: [
{ label: '导出', click: () => {} },
{ label: '打印', click: () => {} },
],
}顶部操作按钮 (actions)
ts
const actions = [
{
label: '新增',
type: 'primary',
click: () => openCreateModal(),
},
{
label: '批量删除',
type: 'danger',
confirm: '确认批量删除?',
click: () => batchDelete(),
},
// 下拉分组
{
label: '更多',
type: 'dropdown',
buttons: [
{ label: '导出 Excel', click: () => {} },
{ label: '打印列表', click: () => {} },
],
},
]搜索栏 fields
通过 fields 配置搜索表单,支持所有 FormColumn 字段类型:
ts
const fields = [
{ label: '关键词', field: 'keyword', type: 'el-input', props: { placeholder: '搜索姓名/邮箱' } },
{ label: '部门', field: 'deptId', type: 'data-picker', props: { type: 'select', query: fetchDepts } },
{ label: '状态', field: 'status', type: 'data-picker', props: { type: 'select', query: fetchStatuses } },
{ label: '创建时间', field: 'dateRange', type: 'date-picker', props: { type: 'daterange' } },
]字段也支持 tabs 属性控制在哪些 tab 下显示:
ts
{ label: '部门', field: 'deptId', tabs: ['tab1', 'tab2'] }当 showButton={false} 时,搜索表单值变化会自动触发查询(无搜索按钮)。
Tab 切换
ts
const tabs = [
{ label: '全部', value: 'all' },
{ label: '待审核', value: 'pending' },
{ label: '已审核', value: 'approved' },
]
// query 参数中会收到 activeKey
const query = (params) => {
console.log(params.activeKey) // 'all' | 'pending' | 'approved'
return fetchList(params)
}列、按钮、搜索字段都可以通过 tabs 属性控制在不同 tab 下的显示。
多选 Selection
ts
const selection = {
onChange: (rows) => console.log('选中行:', rows),
selectable: (row) => row.status !== 'disabled',
reserveSelection: true,
}
<DataTable selection={selection} columns={columns} query={query} />开启后可通过 getData().exposed.getSelectionRows() 获取选中行。
实例方法
| 方法 | 说明 |
|---|---|
refresh() | 刷新当前页(保留搜索条件和页码) |
reload() | 重置搜索、回到第一页 |
nextPage() | 下一页 |
prevPage() | 上一页 |
getData() | 返回 { total, rows, pageInfo, exposed }(exposed 为 ElTable 实例) |
ts
const tableRef = ref()
// 获取选中行
const rows = tableRef.value.getData().exposed.getSelectionRows()
// 刷新
tableRef.value.refresh()Slots
| 插槽 | 说明 |
|---|---|
bar | 搜索栏右侧自定义区域 |
tableTop | 表格上方(tabbar 和表格之间) |
table | 完全自定义表格渲染,接收 { data, loading } |
empty | 空数据状态自定义 |
tsx
<DataTable columns={columns} query={query}>
{{
bar: () => <Button type="primary" onClick={() => {}}>快捷操作</Button>,
tableTop: () => <div style="padding:8px 0">自定义提示区域</div>,
empty: () => <div>暂无数据</div>,
}}
</DataTable>全局配置
通过安装选项可配置表格默认行为:
ts
app.use(yoyooUi, {
table: {
page: { page: 1, limit: 20 }, // 默认分页
actionsCount: 3, // 操作按钮最多显示个数,超出折叠
typeRender: { // 自定义列类型渲染
customType: ({ text, row, column }) => h('span', text),
},
renderByType: ({ text, type, row, column }) => h('span', text),
actionResult: (type, tableRef, item, resp) => {
// 操作结果回调
ElMessage.success('操作成功')
},
},
})完整示例
vue
<script setup>
import DataTable from 'yoyoo-ui'
import { ElMessage as message } from 'element-plus'
import { ref } from 'vue'
const columns = [
{ label: 'ID', field: 'id', width: 70 },
{ label: '姓名', field: 'name', width: 100 },
{ label: '金额', field: 'amount', type: 'money', width: 120 },
{ label: '状态', field: 'status', type: 'whether', width: 80 },
{ label: '创建时间', field: 'createdAt', type: 'date', width: 120 },
{
label: '操作',
type: 'operate',
width: 160,
fixed: 'right',
buttons: [
{ label: '编辑', click: ({ row }) => message.success(`编辑 ${row.name}`) },
{ label: '删除', confirm: '确认?', click: ({ row }) => message.success(`已删 ${row.name}`) },
],
},
]
const fields = [
{ label: '姓名', field: 'name' },
{ label: '状态', field: 'status', type: 'data-picker', props: { type: 'select', query: () => Promise.resolve([{ label: '启用', value: true }, { label: '禁用', value: false }]) } },
]
const query = (params) => fetch('/api/list', { body: JSON.stringify(params) }).then(r => r.json())
const tableRef = ref()
</script>
<template>
<DataTable
ref="tableRef"
v-bind="{ columns, query, fields, pageSize: 10 }"
title="用户列表"
/>
</template>