Table
useTable connects a page loader to the Table component. Columns are Nuxt UI table columns plus a type for common formats.
Source in the kit: composables/useTable.ts · components/Table/
A table from a loader
examples/table/Basic.vue Column types: TEXT , NUMBER , DATE , DATE_TIME , IMAGE , COMPONENT .
0 จากทั้งหมด 0
| Name | Salary | Joined | |
|---|---|---|---|
ไม่พบข้อมูล! | |||
0 จากทั้งหมด 0
<template>
<Table
:options="table"
@pageChange="users.fetchPageChange"
@search="users.fetchSearch"
/>
</template>
<script lang="ts" setup>
import { type IUser, createUsersLoader } from '~/loaders/users'
const users = createUsersLoader()
// useTable turns a page loader into table options: rows, loading/error/empty states and pagination
const table = useTable<IUser>({
repo: users,
options: {
isEnabledSearch: true,
searchPlaceholder: 'Search users…',
},
columns: () => [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'email',
header: 'Email',
// TEXT truncates with meta.max and shows "-" for empty values
type: COLUMN_TYPES.TEXT,
meta: {
max: 24,
},
},
{
accessorKey: 'salary',
header: 'Salary',
type: COLUMN_TYPES.NUMBER,
},
{
accessorKey: 'createdAt',
header: 'Joined',
// DATE / DATE_TIME follow core.date_format and the Thai-year setting
type: COLUMN_TYPES.DATE,
},
],
})
// The table only renders: fetching is yours. It asks for pages and searches through its events.
onMounted(() =>
users.fetchPage(1, '', {
params: {
limit: 5,
},
})
)
</script>loaders/users.ts
import { defineStore } from 'pinia'
// One file per API resource: its types, then one store per loader.
// `defineStore` makes the loader a singleton, so a list page and its edit form share the same state.
export interface IUser {
id: string
name: string
email: string
role: 'admin' | 'editor' | 'viewer'
salary: number
isActive: boolean
avatar: string
createdAt: string
}
export interface IRole {
id: string
name: string
}
// Paginated CRUD: GET /users, GET /users/:id, POST /users, PUT /users/:id, DELETE /users/:id
export const createUsersLoader = () => {
const options = useRequestOptions()
return usePageLoader<IUser>({
baseURL: '/users',
getBaseRequestOptions: options.getMock,
})
}
// The store is the shared instance: a list page and the form that edits it see the same state.
// Call createUsersLoader() instead when a component wants a private one (each docs example does).
export const useUsersLoader = defineStore('users', createUsersLoader)
// A plain array: GET /roles
export const useRolesLoader = defineStore('roles', () => {
const options = useRequestOptions()
return useListLoader<IRole>({
url: '/roles',
getRequestOptions: options.getMock,
})
})
// A single object, or any one-off request: GET /profile
export const useProfileLoader = defineStore('profile', () => {
const options = useRequestOptions()
return useObjectLoader<IUser & { loadedAt: string }>({
method: 'get',
url: '/profile',
getRequestOptions: options.getMock,
})
})Custom cells and row actions
examples/table/Cells.vue Three ways to draw a cell, from lightest to most flexible: type , a cell function, a #key-cell slot. is-pin-action keeps the actions column visible while scrolling.
0 จากทั้งหมด 0
| User | Role | Salary | Status | |
|---|---|---|---|---|
ไม่พบข้อมูล! | ||||
0 จากทั้งหมด 0
<template>
<Table
:options="table"
is-pin-action
@pageChange="users.fetchPageChange"
>
<!-- 1. A slot named `<accessorKey>-cell` takes over one column -->
<template #name-cell="{ row }">
<div class="flex items-center gap-2">
<Avatar
:src="row.original.avatar"
size="xs"
/>
<span class="font-medium">{{ row.original.name }}</span>
</div>
</template>
<template #isActive-cell="{ row }">
<Badge
:color="row.original.isActive ? 'success' : 'neutral'"
variant="subtle"
:label="row.original.isActive ? 'Active' : 'Inactive'"
/>
</template>
<!-- 2. The column called `actions` is the conventional place for row buttons -->
<template #actions-cell="{ row }">
<DropdownMenu :items="actionsOf(row.original)">
<Button
icon="ph:dots-three-vertical"
color="neutral"
variant="ghost"
size="sm"
/>
</DropdownMenu>
</template>
</Table>
</template>
<script lang="ts" setup>
import { type IUser, createUsersLoader } from '~/loaders/users'
const users = createUsersLoader()
const dialog = useDialog()
const table = useTable<IUser>({
repo: users,
columns: () => [
{
accessorKey: 'name',
header: 'User',
},
{
accessorKey: 'role',
header: 'Role',
// 3. `cell` is a function for small transformations that do not need a template
cell: ({ row }) => row.original.role.toUpperCase(),
},
{
accessorKey: 'salary',
header: 'Salary',
cell: ({ row }) => NumberHelper.toCurrency(row.original.salary),
},
{
accessorKey: 'isActive',
header: 'Status',
},
{
accessorKey: 'actions',
header: '',
},
],
})
const actionsOf = (user: IUser) => [
{
label: 'View',
icon: 'ph:eye',
onSelect: () =>
dialog.info({
title: user.name,
description: user.email,
}),
},
{
label: 'Delete',
icon: 'ph:trash',
color: 'error' as const,
// confirm() rejects when the user cancels, so always add a catch
onSelect: () =>
dialog
.confirm({
title: `Delete ${user.name}?`,
type: DialogType.ERROR,
})
.then(() => users.deleteRun(user.id))
.catch(() => {}),
},
]
onMounted(() =>
users.fetchPage(1, '', {
params: {
limit: 5,
},
})
)
</script>Rows that link somewhere
examples/table/RowLink.vue| Name | |
|---|---|
ไม่พบข้อมูล! | |
0 จากทั้งหมด 0
<template>
<Table
:options="table"
:to="(user: IUser) => `/cookbook/crud?id=${user.id}`"
preserve-query
@pageChange="users.fetchPageChange"
/>
</template>
<script lang="ts" setup>
import { type IUser, createUsersLoader } from '~/loaders/users'
const users = createUsersLoader()
// `to` makes every cell of a row a real link (middle-click and "open in new tab" work).
// A column called `actions` is left out, so its buttons stay clickable.
// `preserve-query` carries the current ?page=…&q=… along; `target="_blank"` opens a new tab.
const table = useTable<IUser>({
repo: users,
options: {
isHidePaginationTop: true,
},
columns: () => [
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'email',
header: 'Email',
},
],
})
onMounted(() =>
users.fetchPage(1, '', {
params: {
limit: 5,
},
})
)
</script>TableSimple: data you already have
examples/table/Simple.vue1 - 2 จากทั้งหมด 3
| Item | Qty | Total |
|---|---|---|
Design | 12 | 36,000 |
Development | 40 | 160,000 |
1 - 2 จากทั้งหมด 3
<template>
<TableSimple :options="table" />
</template>
<script lang="ts" setup>
interface IInvoiceLine {
item: string
quantity: number
total: number
}
const lines = ref<IInvoiceLine[]>([
{
item: 'Design',
quantity: 12,
total: 36000,
},
{
item: 'Development',
quantity: 40,
total: 160000,
},
{
item: 'Hosting',
quantity: 1,
total: 4500,
},
])
// No loader: TableSimple renders an array you already have and pages it in the browser when `limit` is set
const table = useTableSimple<IInvoiceLine>({
items: () => lines.value,
options: {
limit: 2,
},
columns: () => [
{
accessorKey: 'item',
header: 'Item',
},
{
accessorKey: 'quantity',
header: 'Qty',
type: COLUMN_TYPES.NUMBER,
},
{
accessorKey: 'total',
header: 'Total',
type: COLUMN_TYPES.NUMBER,
},
],
})
</script>