Table 数据表格

用于数据表格场景的 MoriUI 组件。

导入

import { Table } from 'moriui'

示例

概览

综合数据表格。

INV-001已支付[email protected]¥316
INV-002已支付[email protected]¥242
INV-003处理中[email protected]¥837
INV-004已支付[email protected]¥874
INV-005失败[email protected]¥721

共 15 条

<script setup lang="ts">
  import type { ColumnDef, ColumnFiltersState, PaginationState, SortingState } from 'moriui'

  import { ref } from 'vue'
  import { Table } from 'moriui'

  interface Invoice {
    invoice: string
    status: string
    email: string
    amount: number
  }

  const columns: ColumnDef<Invoice>[] = [
    {
      accessorKey: 'invoice',
      header: '发票号',
      meta: { filter: { placeholder: '筛选发票' } }
    },
    {
      accessorKey: 'status',
      header: '状态',
      cell: ctx => String(ctx.getValue()),
      meta: { filter: { placeholder: '筛选状态' } }
    },
    {
      accessorKey: 'email',
      header: '邮箱',
      cell: ctx => String(ctx.getValue()),
      meta: { filter: { placeholder: '筛选邮箱' } }
    },
    {
      accessorKey: 'amount',
      header: '金额',
      cell: ctx => `¥${(ctx.getValue() as number).toLocaleString()}`
    }
  ]

  const data: Invoice[] = [
    { invoice: 'INV-001', status: '已支付', email: '[email protected]', amount: 316 },
    { invoice: 'INV-002', status: '已支付', email: '[email protected]', amount: 242 },
    { invoice: 'INV-003', status: '处理中', email: '[email protected]', amount: 837 },
    { invoice: 'INV-004', status: '已支付', email: '[email protected]', amount: 874 },
    { invoice: 'INV-005', status: '失败', email: '[email protected]', amount: 721 },
    { invoice: 'INV-006', status: '待处理', email: '[email protected]', amount: 150 },
    { invoice: 'INV-007', status: '已支付', email: '[email protected]', amount: 450 },
    { invoice: 'INV-008', status: '处理中', email: '[email protected]', amount: 680 },
    { invoice: 'INV-009', status: '失败', email: '[email protected]', amount: 325 },
    { invoice: 'INV-010', status: '已支付', email: '[email protected]', amount: 960 },
    { invoice: 'INV-011', status: '待处理', email: '[email protected]', amount: 180 },
    { invoice: 'INV-012', status: '已支付', email: '[email protected]', amount: 540 },
    { invoice: 'INV-013', status: '失败', email: '[email protected]', amount: 410 },
    { invoice: 'INV-014', status: '处理中', email: '[email protected]', amount: 775 },
    { invoice: 'INV-015', status: '已支付', email: '[email protected]', amount: 633 }
  ]

  const sorting = ref<SortingState>([])
  const columnFilters = ref<ColumnFiltersState>([])
  const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
</script>

<template>
  <Table
    v-model:sorting="sorting"
    v-model:column-filters="columnFilters"
    v-model:pagination="pagination"
    :data="data"
    :columns="columns"
  />
</template>

操作

表格行内操作。

操作
无线鼠标¥29.99128
机械键盘¥129.9956
USB-C 集线器¥49.99203
4K 显示器¥399.9932
降噪耳机¥89.9978

共 5 条

<script setup lang="ts">
  import type { ColumnDef } from 'moriui'

  import { h } from 'vue'
  import { createColumnHelper } from '@tanstack/vue-table'
  import { Copy, Eye, MoreHorizontal, Pencil, Trash2 } from '@lucide/vue'
  import {
    Button,
    DropdownMenu,
    DropdownMenuContent,
    DropdownMenuItem,
    DropdownMenuSeparator,
    DropdownMenuTrigger,
    Table
  } from 'moriui'

  interface Product {
    name: string
    price: number
    stock: number
  }

  const columnHelper = createColumnHelper<Product>()

  const columns: ColumnDef<Product>[] = [
    columnHelper.accessor('name', {
      header: '产品名称',
      cell: ctx => String(ctx.getValue())
    }),
    columnHelper.accessor('price', {
      header: '单价',
      cell: ctx => `¥${(ctx.getValue() as number).toFixed(2)}`
    }),
    columnHelper.accessor('stock', {
      header: '库存',
      cell: ctx => String(ctx.getValue())
    }),
    columnHelper.display({
      id: 'actions',
      header: '操作',
      cell: () => h(DropdownMenu, {}, {
        default: () => [
          h(DropdownMenuTrigger, {
            render: () => h(Button, { 'variant': 'ghost', 'size': 'icon-sm', 'class': 'size-8', 'aria-label': '操作菜单' })
          }, { default: () => h(MoreHorizontal) }),
          h(DropdownMenuContent, { align: 'end' }, {
            default: () => [
              h(DropdownMenuItem, { onClick: () => {} }, { default: () => [h(Eye, { class: 'mr-2 size-4' }), '查看详情'] }),
              h(DropdownMenuItem, { onClick: () => {} }, { default: () => [h(Pencil, { class: 'mr-2 size-4' }), '编辑'] }),
              h(DropdownMenuItem, { onClick: () => {} }, { default: () => [h(Copy, { class: 'mr-2 size-4' }), '复制'] }),
              h(DropdownMenuSeparator),
              h(DropdownMenuItem, { variant: 'destructive', onClick: () => {} }, { default: () => [h(Trash2, { class: 'mr-2 size-4' }), '删除'] })
            ]
          })
        ]
      })
    })
  ]

  const data: Product[] = [
    { name: '无线鼠标', price: 29.99, stock: 128 },
    { name: '机械键盘', price: 129.99, stock: 56 },
    { name: 'USB-C 集线器', price: 49.99, stock: 203 },
    { name: '4K 显示器', price: 399.99, stock: 32 },
    { name: '降噪耳机', price: 89.99, stock: 78 }
  ]
</script>

<template>
  <Table :data="data" :columns="columns" />
</template>

底部

表格底部分页。

INV-001已支付¥250.00
INV-002待支付¥150.00
INV-003未支付¥350.00
INV-004已支付¥450.00
INV-005已支付¥550.00
合计:¥2250.00 共 7 条
<script setup lang="ts">
  import type { ColumnDef, PaginationState, SortingState } from 'moriui'

  import { Table } from 'moriui'
  import { computed, ref } from 'vue'

  interface Invoice {
    invoice: string
    status: string
    amount: number
  }

  const columns: ColumnDef<Invoice>[] = [
    { accessorKey: 'invoice', header: '发票号', cell: ctx => String(ctx.getValue()) },
    {
      accessorKey: 'status',
      header: '状态',
      cell: ctx => String(ctx.getValue())
    },
    {
      accessorKey: 'amount',
      header: '金额',
      cell: ctx => `¥${(ctx.getValue() as number).toFixed(2)}`
    }
  ]

  const data: Invoice[] = [
    { invoice: 'INV-001', status: '已支付', amount: 250 },
    { invoice: 'INV-002', status: '待支付', amount: 150 },
    { invoice: 'INV-003', status: '未支付', amount: 350 },
    { invoice: 'INV-004', status: '已支付', amount: 450 },
    { invoice: 'INV-005', status: '已支付', amount: 550 },
    { invoice: 'INV-006', status: '待支付', amount: 200 },
    { invoice: 'INV-007', status: '未支付', amount: 300 }
  ]

  const sorting = ref<SortingState>([])
  const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })

  const filteredTotal = computed(() =>
    data.reduce((sum, inv) => sum + inv.amount, 0)
  )
</script>

<template>
  <Table
    v-model:sorting="sorting"
    v-model:pagination="pagination"
    :data="data"
    :columns="columns"
    class="max-w-lg"
  >
    <template #pagination="{ table }">
      <div class="flex w-full items-center justify-between px-4 py-3 text-sm">
        <span class="text-muted-foreground">
          合计:<strong class="text-foreground">¥{{ filteredTotal.toFixed(2) }}</strong>
        </span>
        <span class="text-muted-foreground">
          共 {{ table.getFilteredRowModel().rows.length }} 条
        </span>
      </div>
    </template>
  </Table>
</template>

RTL

从右到左的表格布局。

INV-001مدفوع$316
INV-002معلق$242
INV-003قيد المعالجة$837
INV-004مدفوع$874
INV-005فشل$721

共 12 条

<script setup lang="ts">
  import type { ColumnDef, PaginationState, SortingState } from 'moriui'

  import { ref } from 'vue'
  import { DirectionProvider, Table } from 'moriui'

  interface Invoice {
    invoice: string
    status: string
    amount: number
  }

  const columns: ColumnDef<Invoice>[] = [
    {
      accessorKey: 'invoice',
      header: 'رقم الفاتورة',
      cell: ctx => String(ctx.getValue())
    },
    {
      accessorKey: 'status',
      header: 'الحالة',
      cell: ctx => String(ctx.getValue())
    },
    {
      accessorKey: 'amount',
      header: 'المبلغ',
      cell: ctx => `$${(ctx.getValue() as number).toLocaleString()}`
    }
  ]

  const data: Invoice[] = [
    { invoice: 'INV-001', status: 'مدفوع', amount: 316 },
    { invoice: 'INV-002', status: 'معلق', amount: 242 },
    { invoice: 'INV-003', status: 'قيد المعالجة', amount: 837 },
    { invoice: 'INV-004', status: 'مدفوع', amount: 874 },
    { invoice: 'INV-005', status: 'فشل', amount: 721 },
    { invoice: 'INV-006', status: 'معلق', amount: 150 },
    { invoice: 'INV-007', status: 'مدفوع', amount: 450 },
    { invoice: 'INV-008', status: 'قيد المعالجة', amount: 680 },
    { invoice: 'INV-009', status: 'فشل', amount: 325 },
    { invoice: 'INV-010', status: 'مدفوع', amount: 960 },
    { invoice: 'INV-011', status: 'معلق', amount: 180 },
    { invoice: 'INV-012', status: 'مدفوع', amount: 540 }
  ]

  const sorting = ref<SortingState>([])
  const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
</script>

<template>
  <DirectionProvider dir="rtl">
    <Table
      v-model:sorting="sorting"
      v-model:pagination="pagination"
      :data="data"
      :columns="columns"
    />
  </DirectionProvider>
</template>

使用说明

组合方式

Table 基于 TanStack Table 构建,接收 data 和 columns 作为输入,内置排序、筛选、分页和行选择能力。

尺寸

size 支持 default 和 sm,控制行的密度。

行选择

selectable 启用行选择复选框。

RTL

表格内容沿 dir 排列。

变体

Table 的 size 支持 default 与 sm;支持排序、列筛选、全局搜索、分页和行选择。

可访问性

Table 输出原生 table 元素,配合 TanStack Table 的排序、选择和分页 ARIA。表头使用 scope="col",排序按钮有 aria-sort。

API 参考

属性类型默认值说明
Table dataTData[]必填,表格数据数组。
Table columnsColumnDef<TData>[]必填,列定义数组。
Table size'default' | 'sm''default'表格行密度。
Table selectablebooleanfalse是否显示行选择复选框。
Table pageSizeOptionsnumber[]undefined可选的每页条数选项。
Table searchPlaceholderstringundefined全局搜索占位提示。
Table emptyTextstringundefined空数据显示文本。