Skip to content

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 }
columnsTableColumn[][]列定义
fieldsFormColumn[][]搜索栏字段配置,基于 DataForm
actionsButton[][]顶部操作按钮
modelRecord<string, any>{}搜索参数初始值
dataany[]-直接传入数据源(替代 query)
rowKeystring'id'行唯一标识字段
titlestring-表格左上角标题
tabs{ label, value }[]-页签,切换时 query 会收到 activeKey
activeTabany-当前激活 tab(支持 v-model)
selectionRowSelection-多选配置
fillPagebooleanfalse自动撑满父容器高度
nullValueany'/'空数据占位文本
propsPartial<TableProps>-透传给 ElTable 的 props(如 borderlazytree-props
pageSizenumber20每页条数
pageNonumber1初始页码
showButtonbooleantrue是否显示搜索/重置按钮
hideOnSinglePagebooleanfalse只有一页时隐藏分页
fieldsPosition'top' | 'left''top'搜索栏位置
labelWidthstring | number'80px'搜索栏标签宽度
onPageChange(page, size) => void-分页变化回调

列配置 TableColumn

基础属性

属性名类型说明
labelstring表头标题
fieldstring字段名,支持嵌套路径
widthnumber列宽
minWidthnumber最小列宽
fixed'left' | 'right'固定列
alignstring对齐方式,默认 'center'
visibleboolean是否显示,默认 true
tabsany[]指定该列在哪些 tab 下显示
classNamestring自定义列 class
tooltipboolean内容超长时显示 tooltip
labelTooltipstring表头问号提示

增强类型 type

通过 type 快速格式化数据,无需手写 render:

type说明额外属性
money金额千分位params
date日期 YYYY-MM-DDparams:自定义格式,如 'YYYY/MM/DD'
time日期时间 YYYY-MM-DD HH:mm:ssparams:自定义格式
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

属性名类型说明
labelstring | ({ row, column, params }) => string按钮文字,支持函数动态计算
click({ row, column, params }) => void点击回调,返回 Promise 自动 loading
type'primary' | 'danger' | 'default'按钮类型
show(row, column, params) => boolean动态显示/隐藏。注意是三个独立参数,非解构对象
confirmstring | ({ row, column, params }) => string二次确认文案
permissionstring | string[]权限标识
visitedboolean点击后标记已读样式
tabsany[]指定在哪些 tab 下显示

按钮支持 ElButton 的所有 props(sizeplaintext 等)。

下拉菜单按钮

当操作按钮超过全局配置的 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>