Files
rustfs-console/components/data-table/data-table.vue
T
2025-10-29 22:37:28 +08:00

142 lines
5.5 KiB
Vue

<script setup lang="ts" generic="TData">
import EmptyState from '@/components/empty-state.vue'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Spinner } from '@/components/ui/spinner'
import {
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Table as UiTable,
} from '@/components/ui/table'
import { cn } from '@/lib/utils'
import type { Column, Table } from '@tanstack/vue-table'
import { FlexRender } from '@tanstack/vue-table'
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
table: Table<TData>
isLoading?: boolean
emptyTitle?: string
emptyDescription?: string
class?: string
tableClass?: string
stickyHeader?: boolean
bodyHeight?: string
}>(),
{
isLoading: false,
emptyTitle: 'No data',
emptyDescription: 'There is nothing to display yet.',
class: undefined,
tableClass: undefined,
stickyHeader: false,
bodyHeight: undefined,
}
)
const visibleColumnCount = computed(() => props.table.getVisibleLeafColumns().length)
const hasRows = computed(() => props.table.getRowModel().rows.length > 0)
const resolveMaxWidth = (value?: string | number) => {
if (value == null) return undefined
return typeof value === 'number' ? `${value}px` : value
}
const getColumnStyles = (column: Column<TData, unknown>) => {
const maxWidth = resolveMaxWidth(column.columnDef.meta?.maxWidth)
return maxWidth ? { maxWidth } : undefined
}
</script>
<template>
<div :class="cn('flex flex-col gap-4', props.class)">
<ScrollArea v-if="bodyHeight" :class="cn('rounded-md border', bodyHeight)">
<UiTable :class="tableClass">
<TableHeader :class="stickyHeader ? 'sticky top-0 z-10 bg-muted/40 backdrop-blur' : ''">
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id" :class="stickyHeader ? 'bg-muted/40 backdrop-blur' : undefined">
<TableHead v-for="header in headerGroup.headers" :key="header.id" :style="getColumnStyles(header.column)">
<template v-if="!header.isPlaceholder">
<FlexRender :render="header.column.columnDef.header" :props="header.getContext()" />
</template>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="isLoading">
<TableRow>
<TableCell :colspan="visibleColumnCount" class="h-48 text-center align-middle">
<div class="flex flex-col items-center gap-2">
<Spinner class="size-6" />
<span class="text-sm text-muted-foreground">Loading...</span>
</div>
</TableCell>
</TableRow>
</template>
<template v-else-if="hasRows">
<TableRow v-for="row in table.getRowModel().rows" :key="row.id" :data-state="row.getIsSelected() ? 'selected' : undefined" class="transition-colors hover:bg-muted/40">
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id" :style="getColumnStyles(cell.column)">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<template v-else>
<TableRow>
<TableCell :colspan="visibleColumnCount" class="h-48">
<EmptyState :title="emptyTitle" :description="emptyDescription">
<slot name="empty" />
</EmptyState>
</TableCell>
</TableRow>
</template>
</TableBody>
</UiTable>
</ScrollArea>
<template v-else>
<UiTable :class="cn('border rounded-md', tableClass)">
<TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id" class="py-2" :style="getColumnStyles(header.column)">
<template v-if="!header.isPlaceholder">
<FlexRender :render="header.column.columnDef.header" :props="header.getContext()" />
</template>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="isLoading">
<TableRow>
<TableCell :colspan="visibleColumnCount" class="h-48 text-center align-middle">
<div class="flex flex-col items-center gap-2">
<Spinner class="size-6" />
<span class="text-sm text-muted-foreground">Loading...</span>
</div>
</TableCell>
</TableRow>
</template>
<template v-else-if="hasRows">
<TableRow v-for="row in table.getRowModel().rows" :key="row.id" :data-state="row.getIsSelected() ? 'selected' : undefined" class="transition-colors hover:bg-muted/40">
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id" class="py-2" :style="getColumnStyles(cell.column)">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<template v-else>
<TableRow>
<TableCell :colspan="visibleColumnCount" class="h-48">
<EmptyState :title="emptyTitle" :description="emptyDescription">
<slot name="empty" />
</EmptyState>
</TableCell>
</TableRow>
</template>
</TableBody>
</UiTable>
</template>
</div>
</template>