feat(界面): 优化分页跳转与页大小显示

分页组件支持隐藏每页条数选择器并新增跳转页输入
清理任务列表启用跳转页并固定每页 5 条
补充中英文分页文案
This commit is contained in:
yangjianbo
2026-01-18 14:31:22 +08:00
parent bd18f4b8ef
commit 771baa66ee
4 changed files with 85 additions and 6 deletions

View File

@@ -66,6 +66,19 @@
</div>
</div>
</div>
<Pagination
v-if="tasksTotal > tasksPageSize"
class="mt-4"
:total="tasksTotal"
:page="tasksPage"
:page-size="tasksPageSize"
:page-size-options="[5]"
:show-page-size-selector="false"
:show-jump="true"
@update:page="handleTaskPageChange"
@update:pageSize="handleTaskPageSizeChange"
/>
</div>
</div>
@@ -108,6 +121,7 @@ import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import BaseDialog from '@/components/common/BaseDialog.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import Pagination from '@/components/common/Pagination.vue'
import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
import { adminUsageAPI } from '@/api/admin/usage'
import type { AdminUsageQueryParams, UsageCleanupTask, CreateUsageCleanupTaskRequest } from '@/api/admin/usage'
@@ -131,6 +145,9 @@ const localEndDate = ref('')
const tasks = ref<UsageCleanupTask[]>([])
const tasksLoading = ref(false)
const tasksPage = ref(1)
const tasksPageSize = ref(5)
const tasksTotal = ref(0)
const submitting = ref(false)
const confirmVisible = ref(false)
const cancelConfirmVisible = ref(false)
@@ -146,6 +163,8 @@ const resetFilters = () => {
localEndDate.value = props.endDate
localFilters.value.start_date = localStartDate.value
localFilters.value.end_date = localEndDate.value
tasksPage.value = 1
tasksTotal.value = 0
}
const startPolling = () => {
@@ -219,8 +238,18 @@ const loadTasks = async () => {
if (!props.show) return
tasksLoading.value = true
try {
const res = await adminUsageAPI.listCleanupTasks({ page: 1, page_size: 5 })
const res = await adminUsageAPI.listCleanupTasks({
page: tasksPage.value,
page_size: tasksPageSize.value
})
tasks.value = res.items || []
tasksTotal.value = res.total || 0
if (res.page) {
tasksPage.value = res.page
}
if (res.page_size) {
tasksPageSize.value = res.page_size
}
} catch (error) {
console.error('Failed to load cleanup tasks:', error)
appStore.showError(t('admin.usage.cleanup.loadFailed'))
@@ -229,6 +258,18 @@ const loadTasks = async () => {
}
}
const handleTaskPageChange = (page: number) => {
tasksPage.value = page
loadTasks()
}
const handleTaskPageSizeChange = (size: number) => {
if (!Number.isFinite(size) || size <= 0) return
tasksPageSize.value = size
tasksPage.value = 1
loadTasks()
}
const openConfirm = () => {
confirmVisible.value = true
}

View File

@@ -37,7 +37,7 @@
</p>
<!-- Page size selector -->
<div class="flex items-center space-x-2">
<div v-if="showPageSizeSelector" class="flex items-center space-x-2">
<span class="text-sm text-gray-700 dark:text-gray-300"
>{{ t('pagination.perPage') }}:</span
>
@@ -49,6 +49,22 @@
/>
</div>
</div>
<div v-if="showJump" class="flex items-center space-x-2">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ t('pagination.jumpTo') }}</span>
<input
v-model="jumpPage"
type="number"
min="1"
:max="totalPages"
class="input w-20 text-sm"
:placeholder="t('pagination.jumpPlaceholder')"
@keyup.enter="submitJump"
/>
<button type="button" class="btn btn-ghost btn-sm" @click="submitJump">
{{ t('pagination.jumpAction') }}
</button>
</div>
</div>
<!-- Desktop pagination buttons -->
@@ -102,7 +118,7 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import Icon from '@/components/icons/Icon.vue'
import Select from './Select.vue'
@@ -114,6 +130,8 @@ interface Props {
page: number
pageSize: number
pageSizeOptions?: number[]
showPageSizeSelector?: boolean
showJump?: boolean
}
interface Emits {
@@ -122,7 +140,9 @@ interface Emits {
}
const props = withDefaults(defineProps<Props>(), {
pageSizeOptions: () => [10, 20, 50, 100]
pageSizeOptions: () => [10, 20, 50, 100],
showPageSizeSelector: true,
showJump: false
})
const emit = defineEmits<Emits>()
@@ -146,6 +166,8 @@ const pageSizeSelectOptions = computed(() => {
}))
})
const jumpPage = ref('')
const visiblePages = computed(() => {
const pages: (number | string)[] = []
const maxVisible = 7
@@ -196,6 +218,16 @@ const handlePageSizeChange = (value: string | number | boolean | null) => {
const newPageSize = typeof value === 'string' ? parseInt(value) : value
emit('update:pageSize', newPageSize)
}
const submitJump = () => {
const value = jumpPage.value.trim()
if (!value) return
const pageNum = Number.parseInt(value, 10)
if (Number.isNaN(pageNum)) return
const nextPage = Math.min(Math.max(pageNum, 1), totalPages.value)
jumpPage.value = ''
goToPage(nextPage)
}
</script>
<style scoped>

View File

@@ -571,7 +571,10 @@ export default {
previous: 'Previous',
next: 'Next',
perPage: 'Per page',
goToPage: 'Go to page {page}'
goToPage: 'Go to page {page}',
jumpTo: 'Jump to',
jumpPlaceholder: 'Page',
jumpAction: 'Go'
},
// Errors

View File

@@ -567,7 +567,10 @@ export default {
previous: '上一页',
next: '下一页',
perPage: '每页',
goToPage: '跳转到第 {page} 页'
goToPage: '跳转到第 {page} 页',
jumpTo: '跳转页',
jumpPlaceholder: '页码',
jumpAction: '跳转'
},
// Errors