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> </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>
</div> </div>
@@ -108,6 +121,7 @@ import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app' import { useAppStore } from '@/stores/app'
import BaseDialog from '@/components/common/BaseDialog.vue' import BaseDialog from '@/components/common/BaseDialog.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue' import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import Pagination from '@/components/common/Pagination.vue'
import UsageFilters from '@/components/admin/usage/UsageFilters.vue' import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
import { adminUsageAPI } from '@/api/admin/usage' import { adminUsageAPI } from '@/api/admin/usage'
import type { AdminUsageQueryParams, UsageCleanupTask, CreateUsageCleanupTaskRequest } 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 tasks = ref<UsageCleanupTask[]>([])
const tasksLoading = ref(false) const tasksLoading = ref(false)
const tasksPage = ref(1)
const tasksPageSize = ref(5)
const tasksTotal = ref(0)
const submitting = ref(false) const submitting = ref(false)
const confirmVisible = ref(false) const confirmVisible = ref(false)
const cancelConfirmVisible = ref(false) const cancelConfirmVisible = ref(false)
@@ -146,6 +163,8 @@ const resetFilters = () => {
localEndDate.value = props.endDate localEndDate.value = props.endDate
localFilters.value.start_date = localStartDate.value localFilters.value.start_date = localStartDate.value
localFilters.value.end_date = localEndDate.value localFilters.value.end_date = localEndDate.value
tasksPage.value = 1
tasksTotal.value = 0
} }
const startPolling = () => { const startPolling = () => {
@@ -219,8 +238,18 @@ const loadTasks = async () => {
if (!props.show) return if (!props.show) return
tasksLoading.value = true tasksLoading.value = true
try { 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 || [] 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) { } catch (error) {
console.error('Failed to load cleanup tasks:', error) console.error('Failed to load cleanup tasks:', error)
appStore.showError(t('admin.usage.cleanup.loadFailed')) 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 = () => { const openConfirm = () => {
confirmVisible.value = true confirmVisible.value = true
} }

View File

@@ -37,7 +37,7 @@
</p> </p>
<!-- Page size selector --> <!-- 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" <span class="text-sm text-gray-700 dark:text-gray-300"
>{{ t('pagination.perPage') }}:</span >{{ t('pagination.perPage') }}:</span
> >
@@ -49,6 +49,22 @@
/> />
</div> </div>
</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> </div>
<!-- Desktop pagination buttons --> <!-- Desktop pagination buttons -->
@@ -102,7 +118,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import Icon from '@/components/icons/Icon.vue' import Icon from '@/components/icons/Icon.vue'
import Select from './Select.vue' import Select from './Select.vue'
@@ -114,6 +130,8 @@ interface Props {
page: number page: number
pageSize: number pageSize: number
pageSizeOptions?: number[] pageSizeOptions?: number[]
showPageSizeSelector?: boolean
showJump?: boolean
} }
interface Emits { interface Emits {
@@ -122,7 +140,9 @@ interface Emits {
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
pageSizeOptions: () => [10, 20, 50, 100] pageSizeOptions: () => [10, 20, 50, 100],
showPageSizeSelector: true,
showJump: false
}) })
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
@@ -146,6 +166,8 @@ const pageSizeSelectOptions = computed(() => {
})) }))
}) })
const jumpPage = ref('')
const visiblePages = computed(() => { const visiblePages = computed(() => {
const pages: (number | string)[] = [] const pages: (number | string)[] = []
const maxVisible = 7 const maxVisible = 7
@@ -196,6 +218,16 @@ const handlePageSizeChange = (value: string | number | boolean | null) => {
const newPageSize = typeof value === 'string' ? parseInt(value) : value const newPageSize = typeof value === 'string' ? parseInt(value) : value
emit('update:pageSize', newPageSize) 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> </script>
<style scoped> <style scoped>

View File

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

View File

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