fix: replace project pagination footer with scroll-to-load batches
This commit is contained in:
@@ -1,7 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, h, ref, watch } from 'vue'
|
import { computed, h, nextTick, ref, watch } from 'vue'
|
||||||
import { RouterLink, useRouter } from 'vue-router'
|
import { RouterLink, useRouter } from 'vue-router'
|
||||||
import { NAlert, NButton, NDataTable, NInput, NRadioButton, NRadioGroup, type DataTableColumns } from 'naive-ui'
|
import {
|
||||||
|
NAlert,
|
||||||
|
NButton,
|
||||||
|
NDataTable,
|
||||||
|
NInput,
|
||||||
|
NRadioButton,
|
||||||
|
NRadioGroup,
|
||||||
|
type DataTableColumns,
|
||||||
|
type DataTableInst
|
||||||
|
} from 'naive-ui'
|
||||||
import { RefreshCw, Search } from '@lucide/vue'
|
import { RefreshCw, Search } from '@lucide/vue'
|
||||||
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
import WorkspacePage from '../../components/ui/WorkspacePage.vue'
|
||||||
import { usePolling } from '../../composables/usePolling'
|
import { usePolling } from '../../composables/usePolling'
|
||||||
@@ -13,11 +22,14 @@ import CreateProjectDialog from './components/CreateProjectDialog.vue'
|
|||||||
|
|
||||||
/** 项目表格使用固定表头与独立滚动;搜索筛选仍作用于真实接口数据。 */
|
/** 项目表格使用固定表头与独立滚动;搜索筛选仍作用于真实接口数据。 */
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
/** 列表已有刷新按钮,只在进入、筛选切换和手动刷新时读取。 */
|
/** 列表已有刷新按钮,只在进入和手动刷新时读取;筛选在完整结果中进行。 */
|
||||||
const query = usePolling(ref('projects'), (_, signal) => projectsApi.list(signal), false)
|
const query = usePolling(ref('projects'), (_, signal) => projectsApi.list(signal), false)
|
||||||
const search = ref('')
|
const search = ref('')
|
||||||
const filter = ref('all')
|
const filter = ref('all')
|
||||||
const page = ref(1)
|
/** 接口暂未分页:对完整查询结果分批展示,滚动时追加而非切换页码。 */
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
const visibleCount = ref(PAGE_SIZE)
|
||||||
|
const table = ref<DataTableInst | null>(null)
|
||||||
const projects = computed(() => query.data.value ?? [])
|
const projects = computed(() => query.data.value ?? [])
|
||||||
const filtered = computed(() =>
|
const filtered = computed(() =>
|
||||||
projects.value.filter(item => {
|
projects.value.filter(item => {
|
||||||
@@ -34,9 +46,20 @@ const filters = [
|
|||||||
{ value: 'need_review', label: '待审核' },
|
{ value: 'need_review', label: '待审核' },
|
||||||
{ value: 'failed', label: '失败' }
|
{ value: 'failed', label: '失败' }
|
||||||
]
|
]
|
||||||
watch([search, filter], () => {
|
const visibleProjects = computed(() => filtered.value.slice(0, visibleCount.value))
|
||||||
page.value = 1
|
/** 条件或接口数据更新后从首批重新展示,避免保留旧列表的滚动位置。 */
|
||||||
|
watch([search, filter, query.data], async () => {
|
||||||
|
visibleCount.value = PAGE_SIZE
|
||||||
|
await nextTick()
|
||||||
|
table.value?.scrollTo({ top: 0 })
|
||||||
})
|
})
|
||||||
|
/** 靠近表格底部时追加下一批,保留已有行与当前位置。 */
|
||||||
|
function loadMore(event: Event) {
|
||||||
|
const scroller = event.target as HTMLElement | null
|
||||||
|
if (!scroller || query.loading.value || visibleCount.value >= filtered.value.length) return
|
||||||
|
if (scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight > 120) return
|
||||||
|
visibleCount.value = Math.min(visibleCount.value + PAGE_SIZE, filtered.value.length)
|
||||||
|
}
|
||||||
/** 表格列通过正式项目 ID 跳转,不依赖列表索引。 */
|
/** 表格列通过正式项目 ID 跳转,不依赖列表索引。 */
|
||||||
const columns: DataTableColumns<Project> = [
|
const columns: DataTableColumns<Project> = [
|
||||||
{
|
{
|
||||||
@@ -58,7 +81,7 @@ const columns: DataTableColumns<Project> = [
|
|||||||
function openProject(id: string) {
|
function openProject(id: string) {
|
||||||
void router.push('/projects/' + encodeURIComponent(id) + '/create-drama')
|
void router.push('/projects/' + encodeURIComponent(id) + '/create-drama')
|
||||||
}
|
}
|
||||||
/** 清空条件后返回第一页,不改变后端数据。 */
|
/** 清空条件后返回首批,不改变后端数据。 */
|
||||||
function clearFilters() {
|
function clearFilters() {
|
||||||
search.value = ''
|
search.value = ''
|
||||||
filter.value = 'all'
|
filter.value = 'all'
|
||||||
@@ -115,18 +138,19 @@ function clearFilters() {
|
|||||||
>
|
>
|
||||||
<div class="project-table-area">
|
<div class="project-table-area">
|
||||||
<NDataTable
|
<NDataTable
|
||||||
|
ref="table"
|
||||||
class="project-data-table"
|
class="project-data-table"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
:bottom-bordered="false"
|
:bottom-bordered="false"
|
||||||
striped
|
striped
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="filtered"
|
:data="visibleProjects"
|
||||||
:row-key="row => row.id"
|
:row-key="row => row.id"
|
||||||
:loading="query.loading.value"
|
:loading="query.loading.value"
|
||||||
flex-height
|
flex-height
|
||||||
table-layout="fixed"
|
table-layout="fixed"
|
||||||
:scroll-x="785"
|
:scroll-x="785"
|
||||||
:pagination="{ page, pageSize: 20, onUpdatePage: (value: number) => (page = value) }"
|
@scroll="loadMore"
|
||||||
>
|
>
|
||||||
<template #empty
|
<template #empty
|
||||||
><div>
|
><div>
|
||||||
@@ -144,11 +168,6 @@ function clearFilters() {
|
|||||||
>
|
>
|
||||||
</NDataTable>
|
</NDataTable>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-muted">
|
|
||||||
共 {{ filtered.length }} 个项目<span v-if="query.updatedAt.value">
|
|
||||||
· 更新于 {{ formatDate(query.updatedAt.value) }}</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</WorkspacePage>
|
</WorkspacePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user