分页与加载更多
客户端自动分页
传入 v-model:pagination 即触发自动分页,组件在本地切片数据并渲染默认分页栏:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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>
服务端手动分页
paginationOptions.manualPagination 设为 true 并提供 rowCount,由调用方接管分页——只把当前页数据传给 data。下例用本地切片模拟服务端,实际场景中应在 pagination 变化时请求后端:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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 }" 与 v-model:sorting 可实现服务端排序;搜索时把 pagination.pageIndex 重置为 0。页数形态
后端只返回总页数而非总条数时,改用 paginationOptions.pageCount 直接指定总页数:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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>
每页条数切换
paginationUi.pageSizes 长度大于 1 时显示每页条数切换器:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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>
分页摘要与文案
v-model:row-selection 配合 row-key 联动已选计数;paginationUi 的 showSelectedCount、showRowRange、text 控制摘要区显示与文案:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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>
透传分页组件
paginationUi.paginationProps 透传 UPagination 的 showEdges、siblingCount 等;pageSizeSelectProps 透传每页条数选择器属性:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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-summary 与 #pagination-actions 分别替换摘要区与操作区:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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>
#pagination 槽位接收完整分页状态(page/pageCount/from/to/setPage 等),整条替换默认分页栏:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
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 透传给底层 UTable 显示加载态,适合首屏请求或切页等待:
| 工号 | 姓名 | 岗位 |
|---|---|---|
| 没有数据 | ||
<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:
| 工号 | 姓名 | 部门 | 岗位 |
|---|---|---|---|
| 没有数据 | |||
<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]')才能产生滚动并触发触底加载。canLoadMore 终止加载
can-load-more 为 false 时停止触发,可独立于剩余数据强制暂停(如无更多数据、出错后停止):
| 工号 | 姓名 | 部门 |
|---|---|---|
| 没有数据 | ||
<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),值越大越提前触发:
| 工号 | 姓名 | 部门 |
|---|---|---|
| 没有数据 | ||
<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,与自动派生形态对照:
| 工号 | 姓名 | 部门 |
|---|---|---|
| 没有数据 | ||
<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>