Pagination & Load More
Client-Side Auto Pagination
Passing v-model:pagination triggers automatic client-side pagination; the component slices the data locally and renders the default pagination bar:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(80)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="data"
:columns="columns"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
Server-Side Manual Pagination
Set paginationOptions.manualPagination to true and provide rowCount to hand off pagination to the caller — pass only the current page's data via data. The example below uses local slicing to simulate a server; in a real scenario you should request the backend whenever pagination changes:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
// 用本地切片模拟服务端:仅持有当前页数据 + 已知总条数 rowCount
const all = makePeople(80)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const rowCount = all.length
const pageData = computed(() => {
const start = pagination.value.pageIndex * pagination.value.pageSize
return all.slice(start, start + pagination.value.pageSize)
})
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="pageData"
:columns="columns"
:pagination-options="{ manualPagination: true, rowCount }"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
:loading, :sorting-options="{ manualSorting: true }" and v-model:sorting for server-side sorting; reset pagination.pageIndex to 0 when a search is triggered.Page-Count Mode
When the backend returns only the total page count rather than total rows, use paginationOptions.pageCount to specify the total pages directly:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
// 服务端只返回页数(pageCount),不返回总条数
const all = makePeople(80)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const pageCount = computed(() => Math.ceil(all.length / pagination.value.pageSize))
const pageData = computed(() => {
const start = pagination.value.pageIndex * pagination.value.pageSize
return all.slice(start, start + pagination.value.pageSize)
})
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="pageData"
:columns="columns"
:pagination-options="{ manualPagination: true, pageCount }"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
Page Size Switching
When paginationUi.pageSizes has more than one item, a page-size switcher is shown:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-ui="{ pageSizes: [5, 10, 20, 50] }"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
Pagination Summary and Labels
v-model:row-selection combined with row-key syncs the selected count; paginationUi's showSelectedCount, showRowRange and text control the summary area's display and labels:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
P0006 | 黄涛 | 产品 | 产品经理 |
P0007 | 赵伟 | 市场 | 前端工程师 |
P0008 | 吴敏 | 设计 | UI 设计师 |
P0009 | 周强 | 运营 | 运营专员 |
P0010 | 徐洋 | 研发 | 全栈工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState, RowSelectionState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 10 })
const selection = ref<RowSelectionState>({})
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
v-model:row-selection="selection"
:data="data"
:columns="columns"
row-key="id"
select-on-row-click
:pagination-ui="{
showSelectedCount: true,
showRowRange: true,
text: { total: '总计', item: '人', range: '当前', selected: '勾选' }
}"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
Passing Props to the Pagination Component
paginationUi.paginationProps passes through UPagination props such as showEdges and siblingCount; pageSizeSelectProps passes through page-size selector props:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(80)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-ui="{
pageSizes: [5, 10, 20],
paginationProps: { showEdges: true, siblingCount: 1, color: 'success' },
pageSizeSelectProps: { size: 'sm', variant: 'subtle' }
}"
:ui="{ root: 'max-h-[60vh]' }"
/>
</template>
Pagination Slots
#pagination-summary and #pagination-actions replace the summary area and actions area respectively:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-ui="{ show: true }"
:ui="{ root: 'max-h-[60vh]' }"
>
<template #pagination-summary="{ page, pageCount, rowCount }">
<span class="text-sm text-muted">
第 {{ page }} / {{ pageCount }} 页,共 {{ rowCount }} 条
</span>
</template>
<template #pagination-actions="{ page, pageCount, setPage }">
<UButton size="xs" variant="ghost" icon="i-lucide-chevron-left" :disabled="page <= 1" @click="setPage(page - 1)" />
<UButton size="xs" variant="ghost" icon="i-lucide-chevron-right" :disabled="page >= pageCount" @click="setPage(page + 1)" />
</template>
</MDataTable>
</template>
The #pagination slot receives the full pagination state (page, pageCount, from, to, setPage, etc.) and replaces the entire default pagination bar:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
P0001 | 李勇 | 产品 | UI 设计师 |
P0002 | 王超 | 市场 | 运营专员 |
P0003 | 陈娜 | 设计 | 全栈工程师 |
P0004 | 刘丽 | 运营 | 数据分析师 |
P0005 | 杨军 | 研发 | 后端工程师 |
<script setup lang="ts">
import type { DataTableColumn, PaginationState } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const data = makePeople(40)
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
</script>
<template>
<MDataTable
v-model:pagination="pagination"
:data="data"
:columns="columns"
:ui="{ root: 'max-h-[60vh]' }"
>
<template #pagination="{ page, pageCount, from, to, setPage }">
<div class="flex items-center justify-between rounded-md border border-default px-3 py-2">
<span class="text-sm text-muted">显示 {{ from }}-{{ to }}</span>
<div class="flex items-center gap-2">
<UButton size="xs" variant="soft" :disabled="page <= 1" @click="setPage(page - 1)">
上一页
</UButton>
<span class="text-sm tabular-nums">{{ page }} / {{ pageCount }}</span>
<UButton size="xs" variant="soft" :disabled="page >= pageCount" @click="setPage(page + 1)">
下一页
</UButton>
</div>
</div>
</template>
</MDataTable>
</template>
loading Loading State
loading is passed through to the underlying UTable to display a loading indicator, suitable for initial data fetch or page-turn waits:
| 工号 | 姓名 | 岗位 |
|---|---|---|
| No data | ||
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号', size: 100 },
{ accessorKey: 'name', header: '姓名', size: 120 },
{ accessorKey: 'role', header: '岗位', size: 160 }
]
</script>
<template>
<MDataTable :columns="columns" :data="[]" loading />
</template>
loadMore Bottom-Triggered Load
Passing a load-more callback enables infinite scroll mode (the built-in pagination bar is automatically hidden). When the callback returns a Promise, the component automatically derives loading during the load:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
| No data | |||
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const PAGE_SIZE = 15
const TOTAL = 60
const items = ref<Person[]>([])
const loaded = ref(0)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' },
{ accessorKey: 'role', header: '岗位' }
]
// 模拟异步分页:每次追加一页,返回 Promise 让组件自动派生 loading
async function loadMore() {
await new Promise(r => setTimeout(r, 600))
const next = makePeople(Math.min(PAGE_SIZE, TOTAL - loaded.value), loaded.value)
items.value = [...items.value, ...next]
loaded.value += next.length
}
const canLoadMore = computed(() => loaded.value < TOTAL)
function reset() {
items.value = []
loaded.value = 0
}
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<UButton size="xs" variant="outline" icon="i-lucide-rotate-ccw" @click="reset">
重置
</UButton>
<span class="text-xs text-muted tabular-nums">已加载 {{ loaded }} / {{ TOTAL }}</span>
</div>
<MDataTable
:data="items"
:columns="columns"
:load-more="loadMore"
:can-load-more="canLoadMore"
:load-more-distance="100"
:ui="{ root: 'h-[60vh]' }"
/>
</div>
</template>
ui.root: 'h-[60vh]') is required to produce scrolling and trigger the bottom-load callback.canLoadMore Stop Loading
When can-load-more is false, triggering stops — you can force a pause independently of remaining data (e.g. no more data, or stop after an error):
| 工号 | 姓名 | 部门 |
|---|---|---|
| No data | ||
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const PAGE_SIZE = 12
const TOTAL = 48
const items = ref<Person[]>([])
const loaded = ref(0)
const paused = ref(false)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' }
]
async function loadMore() {
await new Promise(r => setTimeout(r, 500))
const next = makePeople(Math.min(PAGE_SIZE, TOTAL - loaded.value), loaded.value)
items.value = [...items.value, ...next]
loaded.value += next.length
}
const canLoadMore = computed(() => !paused.value && loaded.value < TOTAL)
function reset(): void {
items.value = []
loaded.value = 0
}
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<USwitch v-model="paused" label="暂停加载" />
<UButton size="xs" variant="outline" icon="i-lucide-rotate-ccw" @click="reset">
重置
</UButton>
<span class="text-xs text-muted tabular-nums">已加载 {{ loaded }} / {{ TOTAL }}</span>
</div>
<MDataTable
:data="items"
:columns="columns"
:load-more="loadMore"
:can-load-more="canLoadMore"
:ui="{ root: 'h-[50vh]' }"
/>
</div>
</template>
loadMoreDistance Trigger Threshold
load-more-distance sets the distance-from-bottom threshold in pixels (default 100) — a larger value triggers loading earlier:
| 工号 | 姓名 | 部门 |
|---|---|---|
| No data | ||
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const PAGE_SIZE = 12
const TOTAL = 60
const items = ref<Person[]>([])
const loaded = ref(0)
const distance = ref(100)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' }
]
async function loadMore() {
await new Promise(r => setTimeout(r, 500))
const next = makePeople(Math.min(PAGE_SIZE, TOTAL - loaded.value), loaded.value)
items.value = [...items.value, ...next]
loaded.value += next.length
}
const canLoadMore = computed(() => loaded.value < TOTAL)
function reset(): void {
items.value = []
loaded.value = 0
}
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<USelect
v-model="distance"
:items="[
{ label: 'distance 40(贴底触发)', value: 40 },
{ label: 'distance 100(默认)', value: 100 },
{ label: 'distance 240(提前触发)', value: 240 }
]"
value-key="value"
size="xs"
class="w-52"
/>
<UButton size="xs" variant="outline" icon="i-lucide-rotate-ccw" @click="reset">
重置
</UButton>
<span class="text-xs text-muted tabular-nums">已加载 {{ loaded }} / {{ TOTAL }}</span>
</div>
<MDataTable
:data="items"
:columns="columns"
:load-more="loadMore"
:can-load-more="canLoadMore"
:load-more-distance="distance"
:ui="{ root: 'h-[50vh]' }"
/>
</div>
</template>
Caller-Managed Loading
If you want the caller to manage loading state, make load-more return synchronously (without handing back a Promise) and pass :loading manually, in contrast to the auto-derived form:
| 工号 | 姓名 | 部门 |
|---|---|---|
| No data | ||
<script setup lang="ts">
import type { DataTableColumn } from '@movk/nuxt'
import type { Person } from '~/composables/useTableMock'
const PAGE_SIZE = 12
const TOTAL = 48
const items = ref<Person[]>([])
const loaded = ref(0)
const loading = ref(false)
const columns: DataTableColumn<Person>[] = [
{ accessorKey: 'id', header: '工号' },
{ accessorKey: 'name', header: '姓名' },
{ accessorKey: 'department', header: '部门' }
]
// 同步返回(不交回 Promise),由调用方自行接管 loading
function loadMore(): void {
if (loading.value) return
loading.value = true
setTimeout(() => {
const next = makePeople(Math.min(PAGE_SIZE, TOTAL - loaded.value), loaded.value)
items.value = [...items.value, ...next]
loaded.value += next.length
loading.value = false
}, 600)
}
const canLoadMore = computed(() => loaded.value < TOTAL)
function reset(): void {
items.value = []
loaded.value = 0
loading.value = false
}
</script>
<template>
<div class="flex flex-col gap-3">
<div class="flex items-center gap-3">
<UButton size="xs" variant="outline" icon="i-lucide-rotate-ccw" @click="reset">
重置
</UButton>
<UBadge :color="loading ? 'primary' : 'neutral'" variant="subtle" size="sm">
{{ loading ? 'loading…' : 'idle' }}
</UBadge>
<span class="text-xs text-muted tabular-nums">已加载 {{ loaded }} / {{ TOTAL }}</span>
</div>
<MDataTable
:data="items"
:columns="columns"
:load-more="loadMore"
:can-load-more="canLoadMore"
:loading="loading"
:ui="{ root: 'h-[50vh]' }"
/>
</div>
</template>