feat(界面): 优化分页跳转与页大小显示
分页组件支持隐藏每页条数选择器并新增跳转页输入 清理任务列表启用跳转页并固定每页 5 条 补充中英文分页文案
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -567,7 +567,10 @@ export default {
|
|||||||
previous: '上一页',
|
previous: '上一页',
|
||||||
next: '下一页',
|
next: '下一页',
|
||||||
perPage: '每页',
|
perPage: '每页',
|
||||||
goToPage: '跳转到第 {page} 页'
|
goToPage: '跳转到第 {page} 页',
|
||||||
|
jumpTo: '跳转页',
|
||||||
|
jumpPlaceholder: '页码',
|
||||||
|
jumpAction: '跳转'
|
||||||
},
|
},
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
|||||||
Reference in New Issue
Block a user