---
title: "分页与加载更多"
description: "客户端自动分页、服务端手动分页与加载状态，以及触底加载、无限滚动的实用参数。"
seo_title: "Pagination & Load More"
seo_description: "DataTable pagination and load-more — client-side auto pagination, server-side manual pagination (rowCount / pageCount), page size switching, loading state, infinite scroll with canLoadMore and loadMoreDistance."
canonical_url: "https://nuxt.mhaibaraai.cn/docs/data-table/pagination"
---
# 分页与加载更多

> 客户端自动分页、服务端手动分页与加载状态，以及触底加载、无限滚动的实用参数。

## 客户端自动分页

传入 `v-model:pagination` 即触发自动分页，组件在本地切片数据并渲染默认分页栏：

```vue [DataTableClientPaginationExample.vue]
<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>
```

## 服务端手动分页

`paginationOptions.manualPagination` 设为 `true` 并提供 `rowCount`，由调用方接管分页——只把当前页数据传给 `data`。下例用本地切片模拟服务端，实际场景中应在 `pagination` 变化时请求后端：

```vue [DataTableServerPaginationExample.vue]
<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>
```

> \[\!TIP\]
> 
> 配合 
> 
> :loading
> 
> 、
> 
> :sorting-options="{ manualSorting: true }"
> 
>  与 
> 
> v-model:sorting
> 
>  可实现服务端排序；搜索时把 
> 
> pagination.pageIndex
> 
>  重置为 0。

## 页数形态

后端只返回总页数而非总条数时，改用 `paginationOptions.pageCount` 直接指定总页数：

```vue [DataTablePageCountPaginationExample.vue]
<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>
```

## 每页条数切换

`paginationUi.pageSizes` 长度大于 1 时显示每页条数切换器：

```vue [DataTablePageSizesPaginationExample.vue]
<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>
```

## 分页摘要与文案

`v-model:row-selection` 配合 `row-key` 联动已选计数；`paginationUi` 的 `showSelectedCount`、`showRowRange`、`text` 控制摘要区显示与文案：

```vue [DataTablePaginationSummaryExample.vue]
<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>
```

## 透传分页组件

`paginationUi.paginationProps` 透传 `UPagination` 的 `showEdges`、`siblingCount` 等；`pageSizeSelectProps` 透传每页条数选择器属性：

```vue [DataTablePaginationPassthroughExample.vue]
<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-summary` 与 `#pagination-actions` 分别替换摘要区与操作区：

```vue [DataTablePaginationSlotsExample.vue]
<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>
```

`#pagination` 槽位接收完整分页状态（`page`/`pageCount`/`from`/`to`/`setPage` 等），整条替换默认分页栏：

```vue [DataTablePaginationFullExample.vue]
<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` 透传给底层 `UTable` 显示加载态，适合首屏请求或切页等待：

```vue [DataTableLoadingExample.vue]
<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` 触底加载

传入 `load-more` 回调即启用无限滚动模式（自动隐藏内置分页）。回调返回 Promise 时，组件在加载期间自动派生 `loading`：

```vue [DataTableLoadMoreExample.vue]
<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>
```

> \[\!NOTE\]
> 
> 需要一个受限高度的滚动容器（如 
> 
> ui.root: 'h-\[60vh\]'
> 
> ）才能产生滚动并触发触底加载。

## `canLoadMore` 终止加载

`can-load-more` 为 `false` 时停止触发，可独立于剩余数据强制暂停（如无更多数据、出错后停止）：

```vue [DataTableLoadMoreCanLoadMoreExample.vue]
<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` 触发阈值

`load-more-distance` 设置触发加载的距底像素阈值（默认 100），值越大越提前触发：

```vue [DataTableLoadMoreDistanceExample.vue]
<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>
```

## 调用方接管 loading

若希望由调用方接管 loading，可让 `load-more` 同步返回（不交回 Promise）并自行传入 `:loading`，与自动派生形态对照：

```vue [DataTableLoadMoreManualLoadingExample.vue]
<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>
```


## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
