Table
用法
该 Table 组件构建于TanStack Table之上,并由useVueTable组合函数提供支持,提供灵活且完全类型安全的 API。TanStack Table 的部分功能尚不支持,我们将随着时间推移添加更多功能。
# | 日期 | 状态 | 金额 | |||
---|---|---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | ||
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | ||
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | ||
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | ||
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 | ||
#4595 | 3月10日 13:40 | 已退款 | ava.thomas@example.com | €428.00 | ||
#4594 | 3月10日 09:15 | 已支付 | michael.wilson@example.com | €683.00 | ||
#4593 | 3月9日 20:25 | 失败 | olivia.taylor@example.com | €947.00 | ||
#4592 | 3月9日 18:45 | 已支付 | benjamin.jackson@example.com | €851.00 | ||
#4591 | 3月9日 16:05 | 已支付 | sophia.miller@example.com | €762.00 | ||
#4590 | 3月9日 14:20 | 已支付 | noah.clark@example.com | €573.00 | ||
#4589 | 3月9日 11:35 | 失败 | isabella.lee@example.com | €389.00 | ||
#4588 | 3月8日 22:50 | 已退款 | liam.walker@example.com | €701.00 | ||
#4587 | 3月8日 20:15 | 已支付 | charlotte.hall@example.com | €856.00 | ||
#4586 | 3月8日 17:40 | 已支付 | mason.young@example.com | €492.00 | ||
#4585 | 3月8日 14:55 | 失败 | amelia.king@example.com | €637.00 | ||
#4584 | 3月8日 12:30 | 已支付 | elijah.wright@example.com | €784.00 | ||
#4583 | 3月8日 09:45 | 已退款 | harper.scott@example.com | €345.00 | ||
#4582 | 3月7日 23:10 | 已支付 | evelyn.green@example.com | €918.00 | ||
#4581 | 3月7日 20:25 | 已支付 | logan.baker@example.com | €567.00 |
数据
将 data
prop 用作对象数组,列将根据对象的键生成。
ID | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
4600 | 2024-03-11T15:30:00 | 已支付 | james.anderson@example.com | 594 |
4599 | 2024-03-11T10:10:00 | 失败 | mia.white@example.com | 276 |
4598 | 2024-03-11T08:50:00 | 已退款 | william.brown@example.com | 315 |
4597 | 2024-03-10T19:45:00 | 已支付 | emma.davis@example.com | 529 |
4596 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
</script>
<template>
<UTable :data="data" class="flex-1" />
</template>
列
将 columns
prop 用作ColumnDef对象的数组,其中包含诸如
accessorKey
:提取列值时要使用的行对象的键。header
:要为列显示的标题。如果传递的是字符串,则可以用作列 ID 的默认值。如果传递的是函数,则会向该函数传递一个标题的 props 对象,并且它应该返回渲染后的标题值(确切类型取决于所使用的适配器)。footer
:要为列显示的页脚。功能与 header 完全相同,但显示在表格下方。cell
:要为列显示的每行单元格。如果传递的是函数,则会向该函数传递一个单元格的 props 对象,并且它应该返回渲染后的单元格值(确切类型取决于所使用的适配器)。meta
:列的额外属性。class
:td
:要应用于td
元素的类。th
:要应用于th
元素的类。
style
:td
:要应用于td
元素的样式。th
:要应用于th
元素的样式。
为了渲染组件或其他 HTML 元素,您需要使用 Vue 的h
函数在 header
和 cell
props 中。这与其他使用插槽的组件不同,但提供了更大的灵活性。
# | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>
h
渲染组件时,您可以使用 resolveComponent
函数或从 #components
导入。Meta
将 meta
prop 用作对象(TableMeta)来传递诸如
class
:tr
:要应用于tr
元素的类。
style
:tr
:要应用于tr
元素的样式。
ID | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
4600 | 3月11日 下午3:30 | 已支付 | james.anderson@example.com | $594.00 |
4599 | 3月11日 上午10:10 | 失败 | mia.white@example.com | $276.00 |
4598 | 3月11日 上午8:50 | 已退款 | william.brown@example.com | $315.00 |
4597 | 3月10日 下午7:45 | 已支付 | emma.davis@example.com | $529.00 |
4596 | 3月10日 下午3:55 | 已支付 | ethan.harris@example.com | $639.00 |
<script setup lang="ts">
import type { ColumnDef } from '@tanstack/vue-table'
interface Payment {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data: Payment[] = [
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
]
const columns: ColumnDef<Payment>[] = [
{
accessorKey: 'id',
header: 'ID',
meta: {
class: {
th: 'text-center font-semibold',
td: 'text-center font-mono'
}
}
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit'
})
}
},
{
accessorKey: 'status',
header: 'Status',
meta: {
class: {
th: 'text-center',
td: 'text-center'
}
},
cell: ({ row }) => {
const status = row.getValue('status') as string
const colorMap = {
paid: 'text-success',
failed: 'text-error',
refunded: 'text-warning'
}
return h(
'span',
{
class: `font-semibold capitalize ${colorMap[status as keyof typeof colorMap]}`
},
status
)
}
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
th: 'text-left',
td: 'text-left'
}
}
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right font-bold text-primary',
td: 'text-right font-mono'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
return h(
'span',
{
class: 'font-semibold text-success'
},
formatted
)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="w-full" />
</template>
加载中
使用 loading
prop 来显示加载状态,使用 loading-color
prop 来更改其颜色,使用 loading-animation
prop 来更改其动画。
ID | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
4600 | 2024-03-11T15:30:00 | 已支付 | james.anderson@example.com | 594 |
4599 | 2024-03-11T10:10:00 | 失败 | mia.white@example.com | 276 |
4598 | 2024-03-11T08:50:00 | 已退款 | william.brown@example.com | 315 |
4597 | 2024-03-10T19:45:00 | 已支付 | emma.davis@example.com | 529 |
4596 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
</script>
<template>
<UTable loading loading-color="primary" loading-animation="carousel" :data="data" class="flex-1" />
</template>
粘滞
使用 sticky
prop 来使标题或页脚粘滞。
ID | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
4600 | 2024-03-11T15:30:00 | 已支付 | james.anderson@example.com | 594 |
4599 | 2024-03-11T10:10:00 | 失败 | mia.white@example.com | 276 |
4598 | 2024-03-11T08:50:00 | 已退款 | william.brown@example.com | 315 |
4597 | 2024-03-10T19:45:00 | 已支付 | emma.davis@example.com | 529 |
4596 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | 639 |
4595 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | 639 |
4594 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4595',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4594',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
</script>
<template>
<UTable sticky :data="data" class="flex-1 max-h-[312px]" />
</template>
示例
带行操作
您可以在 cell
中添加一个渲染 DropdownMenu 组件的新列,以渲染行操作。
# | 日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Row } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
},
{
id: 'actions',
cell: ({ row }) => {
return h(
'div',
{ class: 'text-right' },
h(
UDropdownMenu,
{
content: {
align: 'end'
},
items: getRowItems(row),
'aria-label': 'Actions dropdown'
},
() =>
h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto',
'aria-label': 'Actions dropdown'
})
)
)
}
}
]
function getRowItems(row: Row<Payment>) {
return [
{
type: 'label',
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
type: 'separator'
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>
带可展开行
您可以在 cell
中添加一个渲染 Button 组件的新列,使用 TanStack Table 的Expanding APIs.
#expanded
插槽来渲染展开的内容,该插槽将接收行作为参数。# | 日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
{ "id": "4599", "date": "2024-03-11T10:10:00", "status": "failed", "email": "mia.white@example.com", "amount": 276 } | |||||
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'expand',
cell: ({ row }) =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
'aria-label': 'Expand',
ui: {
leadingIcon: [
'transition-transform',
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
]
},
onClick: () => row.toggleExpanded()
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const expanded = ref({ 1: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
class="flex-1"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</template>
expanded
prop 来控制行的可展开状态(可以与 v-model
绑定)。actions
列中的 DropdownMenu 组件。带分组行
您可以根据给定的列值对行进行分组,并通过添加到单元格的某个按钮来显示/隐藏子行,使用 TanStack Table 的Grouping APIs.
重要部分
- 添加
grouping
prop,并传入要分组的列 ID 数组。 - 添加
grouping-options
prop。它必须包含getGroupedRowModel
,您可以从@tanstack/vue-table
导入它,或实现自己的。 - 通过行上的任何单元格的
row.toggleExpanded()
方法来展开行。请记住,这也会切换#expanded
插槽。 - 在列定义中使用
aggregateFn
来定义如何聚合行。 - 列定义中的
agregatedCell
渲染器只有在没有cell
渲染器时才起作用。
项目 | # | 日期 | 邮箱 | 金额 |
---|---|---|---|---|
账户 1 | 3 条记录 | 3月11日 15:30 | 3 位客户 | €1,548.00 |
账户 2 | 2 条记录 | 3月11日 10:10 | 2 位客户 | €805.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
type Account = {
id: string
name: string
}
type PaymentStatus = 'paid' | 'failed' | 'refunded'
type Payment = {
id: string
date: string
status: PaymentStatus
email: string
amount: number
account: Account
}
const getColorByStatus = (status: PaymentStatus) => {
return {
paid: 'success',
failed: 'error',
refunded: 'neutral'
}[status]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639,
account: {
id: '1',
name: 'Account 1'
}
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'title',
header: 'Item'
},
{
id: 'account_id',
accessorKey: 'account.id'
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('id')} records` : `#${row.getValue('id')}`,
aggregationFn: 'count'
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
},
aggregationFn: 'max'
},
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
td: 'w-full'
}
},
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('email')} customers` : row.getValue('email'),
aggregationFn: 'uniqueCount'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
},
aggregationFn: 'sum'
}
]
const grouping_options = ref<GroupingOptions>({
groupedColumnMode: 'remove',
getGroupedRowModel: getGroupedRowModel()
})
</script>
<template>
<UTable
:data="data"
:columns="columns"
:grouping="['account_id', 'status']"
:grouping-options="grouping_options"
:ui="{
root: 'min-w-full',
td: 'empty:p-0' // helps with the colspaned row added for expand slot
}"
>
<template #title-cell="{ row }">
<div v-if="row.getIsGrouped()" class="flex items-center">
<span class="inline-block" :style="{ width: `calc(${row.depth} * 1rem)` }" />
<UButton
variant="outline"
color="neutral"
class="mr-2"
size="xs"
:icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
@click="row.toggleExpanded()"
/>
<strong v-if="row.groupingColumnId === 'account_id'">{{
row.original.account.name
}}</strong>
<UBadge
v-else-if="row.groupingColumnId === 'status'"
:color="getColorByStatus(row.original.status)"
class="capitalize"
variant="subtle"
>
{{ row.original.status }}
</UBadge>
</div>
</template>
</UTable>
</template>
带行选择
您可以在 header
和 cell
中添加一个渲染 Checkbox 组件的新列,以使用 TanStack Table 的Row Selection APIs.
日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|
3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref({ 1: true })
</script>
<template>
<div class="flex-1 w-full">
<UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>
row-selection
prop 来控制行的选择状态(可以与 v-model
绑定)。带行选择事件
您可以添加一个 @select
监听器,使行可点击(带或不带复选框列)。
Event
和 TableRow
实例分别作为第一个和第二个参数接收。日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|
3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref<Record<string, boolean>>({})
function onSelect(e: Event, row: TableRow<Payment>) {
/* If you decide to also select the column you can do this */
row.toggleSelected(!row.getIsSelected())
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<div class="flex-1">
<UTable
ref="table"
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
@select="onSelect"
/>
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</div>
</template>
带行右键菜单事件
您可以添加一个 @contextmenu
监听器,使行可右键单击,并将表格包装在 ContextMenu 组件中,以显示行操作等。
Event
和 TableRow
实例分别作为第一个和第二个参数接收。# | 日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const items = ref<ContextMenuItem[]>([])
function getRowItems(row: TableRow<Payment>) {
return [
{
type: 'label' as const,
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
},
{
type: 'separator' as const
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
function onContextmenu(_e: Event, row: TableRow<Payment>) {
items.value = getRowItems(row)
}
</script>
<template>
<UContextMenu :items="items">
<UTable :data="data" :columns="columns" class="flex-1" @contextmenu="onContextmenu">
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</UContextMenu>
</template>
带行悬停事件
您可以添加一个 @hover
监听器,使行可悬停,并使用 Popover 或 Tooltip 组件来显示行详细信息等。
Event
和 TableRow
实例分别作为第一个和第二个参数接收。# | 日期 | 状态 | 邮箱 | 金额 | |
---|---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 | |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 | |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 | |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 | |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
}) as DOMRect
}))
const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)
function onHover(_e: Event, row: TableRow<Payment> | null) {
selectedRow.value = row
open.value = !!row
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<UTable
:data="data"
:columns="columns"
class="flex-1"
@pointermove="
(ev: PointerEvent) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}
"
@hover="onHover"
/>
<UPopover
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
:open="openDebounced"
:reference="reference"
>
<template #content>
<div class="p-4">
{{ selectedRow?.original?.id }}
</div>
</template>
</UPopover>
</div>
</template>
refDebounced
来防止在将光标从一行移到另一行时 Popover 过快地打开和关闭。带列页脚
您可以在列定义中添加 footer
属性来为该列渲染页脚。
# | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
总计:€2,353.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
footer: ({ column }) => {
const total = column
.getFacetedRowModel()
.rows.reduce(
(acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')),
0
)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return h('div', { class: 'text-right font-medium' }, `Total: ${formatted}`)
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>
带列排序
您可以通过更新列 header
来渲染 Button 组件,以使用 TanStack Table 的Sorting APIs.
# | 日期 | 状态 | 金额 | |
---|---|---|---|---|
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: ({ column }) => {
const isSorted = column.getIsSorted()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label: 'Email',
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
})
}
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const sorting = ref([
{
id: 'email',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
sorting
prop 来控制列的排序状态(可以与 v-model
绑定)。您还可以创建一个可重用组件来使任何列标题可排序。
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
const UDropdownMenu = resolveComponent('UDropdownMenu')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: ({ column }) => getHeader(column, 'ID'),
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: ({ column }) => getHeader(column, 'Date'),
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: ({ column }) => getHeader(column, 'Status'),
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: ({ column }) => getHeader(column, 'Email')
},
{
accessorKey: 'amount',
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, 'Amount')),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted()
return h(
UDropdownMenu,
{
content: {
align: 'start'
},
'aria-label': 'Actions dropdown',
items: [
{
label: 'Asc',
type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide',
checked: isSorted === 'asc',
onSelect: () => {
if (isSorted === 'asc') {
column.clearSorting()
} else {
column.toggleSorting(false)
}
}
},
{
label: 'Desc',
icon: 'i-lucide-arrow-down-wide-narrow',
type: 'checkbox',
checked: isSorted === 'desc',
onSelect: () => {
if (isSorted === 'desc') {
column.clearSorting()
} else {
column.toggleSorting(true)
}
}
}
]
},
() =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
label,
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5 data-[state=open]:bg-elevated',
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
})
)
}
const sorting = ref([
{
id: 'id',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>
带列固定
您可以通过更新列 header
来渲染 Button 组件,以使用 TanStack Table 的Pinning APIs.
#46000000000000000000000000000000000000000 | 2024-03-11T15:30:00 | 已支付 | james.anderson@example.com | €594,000.00 |
#45990000000000000000000000000000000000000 | 2024-03-11T10:10:00 | 失败 | mia.white@example.com | €276,000.00 |
#45980000000000000000000000000000000000000 | 2024-03-11T08:50:00 | 已退款 | william.brown@example.com | €315,000.00 |
#45970000000000000000000000000000000000000 | 2024-03-10T19:45:00 | 已支付 | emma.davis@example.com | €5,290,000.00 |
#45960000000000000000000000000000000000000 | 2024-03-10T15:55:00 | 已支付 | ethan.harris@example.com | €639,000.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '46000000000000000000000000000000000000000',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594000
},
{
id: '45990000000000000000000000000000000000000',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276000
},
{
id: '45980000000000000000000000000000000000000',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315000
},
{
id: '45970000000000000000000000000000000000000',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 5290000
},
{
id: '45960000000000000000000000000000000000000',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639000
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: ({ column }) => getHeader(column, 'ID', 'left'),
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: ({ column }) => getHeader(column, 'Date', 'left')
},
{
accessorKey: 'status',
header: ({ column }) => getHeader(column, 'Status', 'left'),
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: ({ column }) => getHeader(column, 'Email', 'left')
},
{
accessorKey: 'amount',
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, 'Amount', 'right')),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
function getHeader(column: Column<Payment>, label: string, position: 'left' | 'right') {
const isPinned = column.getIsPinned()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label,
icon: isPinned ? 'i-lucide-pin-off' : 'i-lucide-pin',
class: '-mx-2.5',
onClick() {
column.pin(isPinned === position ? false : position)
}
})
}
const columnPinning = ref({
left: [],
right: ['amount']
})
</script>
<template>
<UTable v-model:column-pinning="columnPinning" :data="data" :columns="columns" class="flex-1" />
</template>
column-pinning
prop 来控制列的固定状态(可以与 v-model
绑定)。带列可见性
您可以使用 DropdownMenu 组件来切换列的可见性,使用 TanStack Table 的Column Visibility APIs.
日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|
3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const table = useTemplateRef('table')
const columnVisibility = ref({
id: false
})
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex justify-end px-4 py-3.5 border-b border-accented">
<UDropdownMenu
:items="
table?.tableApi
?.getAllColumns()
.filter((column) => column.getCanHide())
.map((column) => ({
label: upperFirst(column.id),
type: 'checkbox' as const,
checked: column.getIsVisible(),
onUpdateChecked(checked: boolean) {
table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
},
onSelect(e: Event) {
e.preventDefault()
}
}))
"
:content="{ align: 'end' }"
>
<UButton
label="Columns"
color="neutral"
variant="outline"
trailing-icon="i-lucide-chevron-down"
/>
</UDropdownMenu>
</div>
<UTable
ref="table"
v-model:column-visibility="columnVisibility"
:data="data"
:columns="columns"
/>
</div>
</template>
v-model
绑定)。带列筛选
您可以使用 Input 组件按列筛选行,使用 TanStack Table 的Column Filtering APIs.
# | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
#4600 | 3月11日 15:30 | 已支付 | james.anderson@example.com | €594.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const table = useTemplateRef('table')
const columnFilters = ref([
{
id: 'email',
value: 'james'
}
])
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented">
<UInput
:model-value="table?.tableApi?.getColumn('email')?.getFilterValue() as string"
class="max-w-sm"
placeholder="Filter emails..."
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
/>
</div>
<UTable ref="table" v-model:column-filters="columnFilters" :data="data" :columns="columns" />
</div>
</template>
v-model
绑定)。带全局筛选
您可以使用 Input 组件来筛选行,使用 TanStack Table 的Global Filtering APIs.
# | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
#4599 | 3月11日 10:10 | 失败 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | 已退款 | william.brown@example.com | €315.00 |
#4597 | 3月10日 19:45 | 已支付 | emma.davis@example.com | €529.00 |
#4596 | 3月10日 15:55 | 已支付 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const globalFilter = ref('45')
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented">
<UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..." />
</div>
<UTable ref="table" v-model:global-filter="globalFilter" :data="data" :columns="columns" />
</div>
</template>
v-model
绑定)。带分页
您可以使用 Pagination 组件来控制分页状态,使用Pagination APIs.
分页有不同的方法,如Pagination Guide中所述。在此示例中,我们使用客户端分页,因此需要手动传入 getPaginationRowModel()
函数。
# | 日期 | 邮箱 | 金额 |
---|---|---|---|
#4600 | 3月11日 15:30 | james.anderson@example.com | €594.00 |
#4599 | 3月11日 10:10 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | william.brown@example.com | €315.00 |
#4597 | 3月10日 19:45 | emma.davis@example.com | €529.00 |
#4596 | 3月10日 15:55 | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'
const table = useTemplateRef('table')
type Payment = {
id: string
date: string
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
email: 'emma.davis@example.com',
amount: 529
},
{
id: '4596',
date: '2024-03-10T15:55:00',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4595',
date: '2024-03-10T13:20:00',
email: 'sophia.miller@example.com',
amount: 428
},
{
id: '4594',
date: '2024-03-10T11:05:00',
email: 'noah.wilson@example.com',
amount: 673
},
{
id: '4593',
date: '2024-03-09T22:15:00',
email: 'olivia.jones@example.com',
amount: 382
},
{
id: '4592',
date: '2024-03-09T20:30:00',
email: 'liam.taylor@example.com',
amount: 547
},
{
id: '4591',
date: '2024-03-09T18:45:00',
email: 'ava.thomas@example.com',
amount: 291
},
{
id: '4590',
date: '2024-03-09T16:20:00',
email: 'lucas.martin@example.com',
amount: 624
},
{
id: '4589',
date: '2024-03-09T14:10:00',
email: 'isabella.clark@example.com',
amount: 438
},
{
id: '4588',
date: '2024-03-09T12:05:00',
email: 'mason.rodriguez@example.com',
amount: 583
},
{
id: '4587',
date: '2024-03-09T10:30:00',
email: 'sophia.lee@example.com',
amount: 347
},
{
id: '4586',
date: '2024-03-09T08:15:00',
email: 'ethan.walker@example.com',
amount: 692
},
{
id: '4585',
date: '2024-03-08T23:40:00',
email: 'amelia.hall@example.com',
amount: 419
},
{
id: '4584',
date: '2024-03-08T21:25:00',
email: 'oliver.young@example.com',
amount: 563
},
{
id: '4583',
date: '2024-03-08T19:50:00',
email: 'aria.king@example.com',
amount: 328
},
{
id: '4582',
date: '2024-03-08T17:35:00',
email: 'henry.wright@example.com',
amount: 647
},
{
id: '4581',
date: '2024-03-08T15:20:00',
email: 'luna.lopez@example.com',
amount: 482
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const pagination = ref({
pageIndex: 0,
pageSize: 5
})
</script>
<template>
<div class="w-full space-y-4 pb-4">
<UTable
ref="table"
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
class="flex-1"
/>
<div class="flex justify-center border-t border-default pt-4">
<UPagination
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
:total="table?.tableApi?.getFilteredRowModel().rows.length"
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
/>
</div>
</div>
</template>
pagination
prop 来控制分页状态(可以与 v-model
绑定)。带获取的数据
您可以从 API 获取数据并在表格中使用它们。
ID | 名称 | 邮箱 | 公司 |
---|---|---|---|
无数据 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
name: string
username: string
email: string
avatar: { src: string }
company: { name: string }
}
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
key: 'table-users',
transform: (data) => {
return (
data?.map((user) => ({
...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
})) || []
)
},
lazy: true
})
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => {
return h('div', { class: 'flex items-center gap-3' }, [
h(UAvatar, {
...row.original.avatar,
size: 'lg'
}),
h('div', undefined, [
h('p', { class: 'font-medium text-highlighted' }, row.original.name),
h('p', { class: '' }, `@${row.original.username}`)
])
])
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'company',
header: 'Company',
cell: ({ row }) => row.original.company.name
}
]
</script>
<template>
<UTable :data="data" :columns="columns" :loading="status === 'pending'" class="flex-1 h-80" />
</template>
带无限滚动
如果您使用服务器端分页,则可以使用useInfiniteScroll
组合函数来加载更多数据。
ID | Avatar | 名字 | 邮箱 | 用户名 |
---|---|---|---|---|
无数据 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useInfiniteScroll } from '@vueuse/core'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
firstName: string
username: string
email: string
image: string
}
type UserResponse = {
users: User[]
total: number
skip: number
limit: number
}
const skip = ref(0)
const { data, status, execute } = await useFetch(
'https://dummyjson.com/users?limit=10&select=firstName,username,email,image',
{
key: 'table-users-infinite-scroll',
params: { skip },
transform: (data?: UserResponse) => {
return data?.users
},
lazy: true,
immediate: false
}
)
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'image',
header: 'Avatar',
cell: ({ row }) => h(UAvatar, { src: row.original.image })
},
{
accessorKey: 'firstName',
header: 'First name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'username',
header: 'Username'
}
]
const users = ref<User[]>([])
watch(data, () => {
users.value = [...users.value, ...(data.value || [])]
})
execute()
const table = useTemplateRef('table')
onMounted(() => {
useInfiniteScroll(
table.value?.rootRef,
() => {
skip.value += 10
},
{
distance: 200,
canLoadMore: () => {
return status.value !== 'pending'
}
}
)
})
</script>
<template>
<UTable
ref="table"
:data="users"
:columns="columns"
:loading="status === 'pending'"
sticky
class="flex-1 h-80"
/>
</template>
带拖放
您可以使用useSortable
组合函数来自@vueuse/integrations
来启用表格上的拖放功能。此集成封装了Sortable.js以提供无缝的拖放体验。
:ui
prop 为其添加一个唯一的类,以用 useSortable
(例如 :ui="{ tbody: 'my-table-tbody' }"
)来定位它。# | 日期 | 邮箱 | 金额 |
---|---|---|---|
#4600 | 3月11日 15:30 | james.anderson@example.com | €594.00 |
#4599 | 3月11日 10:10 | mia.white@example.com | €276.00 |
#4598 | 3月11日 08:50 | william.brown@example.com | €315.00 |
#4597 | 3月10日 19:45 | emma.davis@example.com | €529.00 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable.mjs'
type Payment = {
id: string
date: string
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594
},
{
id: '4599',
date: '2024-03-11T10:10:00',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
email: 'emma.davis@example.com',
amount: 529
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
useSortable('.my-table-tbody', data, {
animation: 150
})
</script>
<template>
<UTable
ref="table"
:data="data"
:columns="columns"
:ui="{
tbody: 'my-table-tbody'
}"
class="flex-1"
/>
</template>
即将推出 虚拟化
使用 virtualize
prop 来启用大数据集的虚拟化,可以传入布尔值或一个包含选项的对象,例如 { estimateSize: 65, overscan: 12 }
。您还可以传递其他TanStack Virtual options来定制虚拟化行为。
# | 日期 | 状态 | 邮箱 | 金额 |
---|---|---|---|---|
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>(
Array(1000)
.fill(0)
.map((_, i) => ({
id: `4600-${i}`,
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}))
)
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'status',
header: 'Status',
cell: ({ row }) => {
const color = {
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
}[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () =>
row.getValue('status')
)
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
</script>
<template>
<UTable virtualize :data="data" :columns="columns" class="flex-1 h-80" />
</template>
class="h-[400px]"
)。带树形数据
您可以使用 get-sub-rows
prop 在表格中显示层级(树形)数据。例如,如果您的数据对象有一个 children
数组,请设置 :get-sub-rows="row => row.children"
来启用可展开行。
# | 日期 | 邮箱 | 金额 | |
---|---|---|---|---|
4600 | 3月11日 15:30 | james.anderson@example.com | €594.00 | |
4599 | 3月11日 10:10 | mia.white@example.com | €276.00 | |
4598 | 3月11日 08:50 | william.brown@example.com | €315.00 | |
4597 | 3月10日 19:45 | emma.davis@example.com | €529.00 | |
4589 | 3月9日 11:35 | isabella.lee@example.com | €389.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
email: string
amount: number
children?: Payment[]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594,
children: [
{
id: '4599',
date: '2024-03-11T10:10:00',
email: 'mia.white@example.com',
amount: 276
},
{
id: '4598',
date: '2024-03-11T08:50:00',
email: 'william.brown@example.com',
amount: 315
},
{
id: '4597',
date: '2024-03-10T19:45:00',
email: 'emma.davis@example.com',
amount: 529,
children: [
{
id: '4592',
date: '2024-03-09T18:45:00',
email: 'benjamin.jackson@example.com',
amount: 851
},
{
id: '4591',
date: '2024-03-09T16:05:00',
email: 'sophia.miller@example.com',
amount: 762
},
{
id: '4590',
date: '2024-03-09T14:20:00',
email: 'noah.clark@example.com',
amount: 573,
children: [
{
id: '4596',
date: '2024-03-10T15:55:00',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4595',
date: '2024-03-10T13:40:00',
email: 'ava.thomas@example.com',
amount: 428
}
]
}
]
}
]
},
{
id: '4589',
date: '2024-03-09T11:35:00',
email: 'isabella.lee@example.com',
amount: 389
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected() ? true : row.getIsSomeSelected() ? 'indeterminate' : false,
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => {
return h(
'div',
{
style: {
paddingLeft: `${row.depth}rem`
},
class: 'flex items-center gap-2'
},
[
h(UButton, {
color: 'neutral',
variant: 'outline',
size: 'xs',
icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
class: !row.getCanExpand() && 'invisible',
ui: {
base: 'p-0 rounded-sm',
leadingIcon: 'size-4'
},
onClick: row.getToggleExpandedHandler()
}),
row.getValue('id') as string
]
)
}
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}
]
const expanded = ref({ 0: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:get-sub-rows="(row) => row.children"
class="flex-1"
:ui="{
base: 'border-separate border-spacing-0',
tbody: '[&>tr]:last:[&>td]:border-b-0',
tr: 'group',
td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
}"
/>
</template>
使用插槽
您可以使用插槽来定制表格的标题和数据单元格。
使用 #<column>-header
插槽来自定义列的标题。您将能够访问插槽作用域中的 column
、header
和 table
属性。
使用 #<column>-cell
插槽来自定义列的单元格。您将能够访问插槽作用域中的 cell
、column
、getValue
、renderValue
、row
和 table
属性。
ID | 名称 | 邮箱 | 角色 | |
---|---|---|---|---|
1 | Lindsay Walton 前端开发工程师 | lindsay.walton@example.com | 会员 | |
2 | Courtney Henry 设计师 | courtney.henry@example.com | 管理员 | |
3 | Tom Cook 产品总监 | tom.cook@example.com | 会员 | |
4 | Whitney Francis 文案撰写员 | whitney.francis@example.com | 管理员 | |
5 | Leonard Krasner 高级设计师 | leonard.krasner@example.com | 所有者 | |
6 | Floyd Miles 首席设计师 | floyd.miles@example.com | 会员 |
<script setup lang="ts">
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
interface User {
id: number
name: string
position: string
email: string
role: string
}
const toast = useToast()
const { copy } = useClipboard()
const data = ref<User[]>([
{
id: 1,
name: 'Lindsay Walton',
position: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
},
{
id: 2,
name: 'Courtney Henry',
position: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
},
{
id: 3,
name: 'Tom Cook',
position: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
},
{
id: 4,
name: 'Whitney Francis',
position: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
},
{
id: 5,
name: 'Leonard Krasner',
position: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
},
{
id: 6,
name: 'Floyd Miles',
position: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}
])
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'role',
header: 'Role'
},
{
id: 'action'
}
]
function getDropdownActions(user: User): DropdownMenuItem[][] {
return [
[
{
label: 'Copy user Id',
icon: 'i-lucide-copy',
onSelect: () => {
copy(user.id.toString())
toast.add({
title: 'User ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}
],
[
{
label: 'Edit',
icon: 'i-lucide-edit'
},
{
label: 'Delete',
icon: 'i-lucide-trash',
color: 'error'
}
]
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar
:src="`https://i.pravatar.cc/120?img=${row.original.id}`"
size="lg"
:alt="`${row.original.name} avatar`"
/>
<div>
<p class="font-medium text-highlighted">
{{ row.original.name }}
</p>
<p>
{{ row.original.position }}
</p>
</div>
</div>
</template>
<template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)">
<UButton
icon="i-lucide-ellipsis-vertical"
color="neutral"
variant="ghost"
aria-label="Actions"
/>
</UDropdownMenu>
</template>
</UTable>
</template>
API
属性
属性 | 默认值 | 类型 |
---|---|---|
as |
|
此组件应渲染为的元素或组件。 |
data |
| |
columns |
| |
caption |
| |
meta |
您可以将任何对象传递给 | |
virtualize |
|
为大数据集启用虚拟化。注意:启用后,行之间的分隔符和粘滞属性不受支持。
|
空 |
|
表格为空时显示的文本。 |
粘滞 |
|
表格是否应具有粘滞的标题或页脚。True 表示两者都有,'header' 表示仅标题,'footer' 表示仅页脚。注意:当 |
loading |
表格是否应处于加载状态。 | |
加载颜色 |
|
|
加载动画 |
|
|
watchOptions |
|
使用 |
globalFilterOptions |
| |
columnFiltersOptions |
| |
columnPinningOptions |
| |
columnSizingOptions |
| |
visibilityOptions |
| |
sortingOptions |
| |
groupingOptions |
| |
expandedOptions |
| |
rowSelectionOptions |
| |
rowPinningOptions |
| |
paginationOptions |
| |
facetedOptions |
| |
onSelect |
| |
onHover |
| |
onContextmenu |
| |
state |
| |
onStateChange |
| |
renderFallbackValue |
| |
_features |
您可以添加到 table 实例的额外功能的数组。 | |
autoResetAll |
设置此选项以覆盖任何 | |
debugAll |
将此选项设置为 | |
debugCells |
将此选项设置为 | |
debugColumns |
将此选项设置为 | |
debugHeaders |
将此选项设置为 | |
debugRows |
将此选项设置为 | |
debugTable |
将此选项设置为 | |
defaultColumn |
为提供的所有列定义提供默认列选项。 | |
getRowId |
这个可选函数用于为任何给定的行派生一个唯一的 ID。如果未提供,则使用行的索引(嵌套的行通过 | |
getSubRows |
这个可选函数用于访问任何给定行的子行。如果您正在使用嵌套行,您将需要使用此函数从行中返回子行对象(或 undefined)。 | |
initialState |
使用此选项可选择性地将初始状态传递给表。当表自动(例如 当此对象更改时,表状态不会重置,这也意味着初始状态对象不需要是稳定的。
| |
mergeOptions |
此选项用于可选地实现表格选项的合并。 | |
globalFilter |
|
|
columnFilters |
|
|
columnOrder |
|
|
columnVisibility |
|
|
columnPinning |
|
|
columnSizing |
|
|
columnSizingInfo |
|
|
rowSelection |
|
|
rowPinning |
|
|
sorting |
|
|
grouping |
|
|
expanded |
|
|
pagination |
|
|
ui |
|
插槽
插槽 | 类型 |
---|---|
expanded |
|
空 |
|
loading |
|
caption |
|
body-top |
|
body-bottom |
|
可访问属性
您可以使用useTemplateRef
.
<script setup lang="ts">
const table = useTemplateRef('table')
</script>
<template>
<UTable ref="table" />
</template>
这将使您能够访问以下内容
名称 | 类型 |
---|---|
rootRef | Ref<HTMLDivElement | null> |
tableRef | Ref<HTMLTableElement | null> |
tableApi | Ref<Table | null> |
主题
export default defineAppConfig({
ui: {
table: {
slots: {
root: 'relative overflow-auto',
base: 'min-w-full',
caption: 'sr-only',
thead: 'relative',
tbody: '[&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:focus-visible:outline-primary',
tfoot: 'relative',
tr: 'data-[selected=true]:bg-elevated/50',
th: 'px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
separator: 'absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)',
empty: 'py-6 text-center text-sm text-muted',
loading: 'py-6 text-center'
},
variants: {
virtualize: {
false: {
base: 'overflow-clip',
tbody: 'divide-y divide-default'
}
},
pinned: {
true: {
th: 'sticky bg-default/75 data-[pinned=left]:left-0 data-[pinned=right]:right-0',
td: 'sticky bg-default/75 data-[pinned=left]:left-0 data-[pinned=right]:right-0'
}
},
sticky: {
true: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur',
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
header: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
footer: {
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
}
},
loading: {
true: {
thead: 'after:absolute after:z-[1] after:h-px'
}
},
loadingAnimation: {
carousel: '',
'carousel-inverse': '',
swing: '',
elastic: ''
},
loadingColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
}
},
compoundVariants: [
{
loading: true,
loadingColor: 'primary',
class: {
thead: 'after:bg-primary'
}
},
{
loading: true,
loadingColor: 'neutral',
class: {
thead: 'after:bg-inverted'
}
},
{
loading: true,
loadingAnimation: 'carousel',
class: {
thead: 'after:animate-[carousel_2s_ease-in-out_infinite] rtl:after:animate-[carousel-rtl_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'carousel-inverse',
class: {
thead: 'after:animate-[carousel-inverse_2s_ease-in-out_infinite] rtl:after:animate-[carousel-inverse-rtl_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'swing',
class: {
thead: 'after:animate-[swing_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'elastic',
class: {
thead: 'after:animate-[elastic_2s_ease-in-out_infinite]'
}
}
],
defaultVariants: {
loadingColor: 'primary',
loadingAnimation: 'carousel'
}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
table: {
slots: {
root: 'relative overflow-auto',
base: 'min-w-full',
caption: 'sr-only',
thead: 'relative',
tbody: '[&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:focus-visible:outline-primary',
tfoot: 'relative',
tr: 'data-[selected=true]:bg-elevated/50',
th: 'px-4 py-3.5 text-sm text-highlighted text-left rtl:text-right font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
separator: 'absolute z-[1] left-0 w-full h-px bg-(--ui-border-accented)',
empty: 'py-6 text-center text-sm text-muted',
loading: 'py-6 text-center'
},
variants: {
virtualize: {
false: {
base: 'overflow-clip',
tbody: 'divide-y divide-default'
}
},
pinned: {
true: {
th: 'sticky bg-default/75 data-[pinned=left]:left-0 data-[pinned=right]:right-0',
td: 'sticky bg-default/75 data-[pinned=left]:left-0 data-[pinned=right]:right-0'
}
},
sticky: {
true: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur',
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
header: {
thead: 'sticky top-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
},
footer: {
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 z-[1] backdrop-blur'
}
},
loading: {
true: {
thead: 'after:absolute after:z-[1] after:h-px'
}
},
loadingAnimation: {
carousel: '',
'carousel-inverse': '',
swing: '',
elastic: ''
},
loadingColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
}
},
compoundVariants: [
{
loading: true,
loadingColor: 'primary',
class: {
thead: 'after:bg-primary'
}
},
{
loading: true,
loadingColor: 'neutral',
class: {
thead: 'after:bg-inverted'
}
},
{
loading: true,
loadingAnimation: 'carousel',
class: {
thead: 'after:animate-[carousel_2s_ease-in-out_infinite] rtl:after:animate-[carousel-rtl_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'carousel-inverse',
class: {
thead: 'after:animate-[carousel-inverse_2s_ease-in-out_infinite] rtl:after:animate-[carousel-inverse-rtl_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'swing',
class: {
thead: 'after:animate-[swing_2s_ease-in-out_infinite]'
}
},
{
loading: true,
loadingAnimation: 'elastic',
class: {
thead: 'after:animate-[elastic_2s_ease-in-out_infinite]'
}
}
],
defaultVariants: {
loadingColor: 'primary',
loadingAnimation: 'carousel'
}
}
}
})
]
})
更新日志
9526a
— fix!: consistent args order in select event
fd6a6
— chore: use tsdoc @see
instead of @link
5cb65
— 特性:导入 @nuxt/ui-pro
组件
f903e
— feat: add row hover
event
f62c5
— feat: add support for context menu
7a2bd
— feat: expose trigger refs
e6e51
— fix:class
应该优先于 ui
属性
4ebb9
— fix: wrong condition on caption
slot
afff5
— feat: add empty
prop