统一管理后台分页样式

- 新建 AdminTablePagination 组件,统一分页 UI
- 改造 6 个前端页面使用统一分页组件:
  - 管理员管理
  - 用户管理
  - 申诉管理
  - 审计日志
  - 公告管理
  - 钱包流水
- 订单管理前后端完整改造:
  - 后端:添加 PaginatedResult 结构,支持 page/page_size 参数
  - 前端:API 支持分页参数,页面使用统一分页组件
- 所有列表页分页样式统一为商品管理的 .table-pagination 设计

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
yml
2026-06-06 00:13:59 +08:00
co-authored by Claude Opus 4.8
parent abb2204e56
commit ec9baada67
13 changed files with 226 additions and 90 deletions
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
currentPage: number
pageSize: number
total: number
loading?: boolean
}
interface Emits {
(e: 'update:currentPage', value: number): void
(e: 'update:pageSize', value: number): void
(e: 'pageChange'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const totalPages = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))
const currentCount = computed(() => {
if (!props.total) return 0
const start = (props.currentPage - 1) * props.pageSize
return Math.min(props.pageSize, props.total - start)
})
function handleSizeChange(size: number) {
emit('update:pageSize', size)
emit('update:currentPage', 1)
emit('pageChange')
}
function prevPage() {
if (props.currentPage <= 1) return
emit('update:currentPage', props.currentPage - 1)
emit('pageChange')
}
function nextPage() {
if (props.currentPage >= totalPages.value) return
emit('update:currentPage', props.currentPage + 1)
emit('pageChange')
}
</script>
<template>
<div class="table-pagination">
<span class="pagination-summary">当前页 {{ currentCount }} {{ total }} </span>
<div class="pagination-controls">
<span class="pagination-size-label">每页</span>
<el-select
:model-value="pageSize"
class="page-size-select"
size="small"
@update:model-value="handleSizeChange"
>
<el-option :value="10" label="10条" />
<el-option :value="20" label="20条" />
<el-option :value="50" label="50条" />
<el-option :value="100" label="100条" />
</el-select>
<span class="pagination-page">{{ currentPage }}/{{ totalPages }}</span>
<el-button size="small" :disabled="currentPage <= 1 || loading" @click="prevPage">上页</el-button>
<el-button size="small" :disabled="currentPage >= totalPages || loading" @click="nextPage">下页</el-button>
</div>
</div>
</template>
@@ -15,6 +15,7 @@ import {
import type { Announcement } from '@/features/announcement'
import { formatDateTime } from '@/utils/time'
import AnnouncementDialog from '../components/AnnouncementDialog.vue'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const announcements = ref<Announcement[]>([])
@@ -146,9 +147,13 @@ function handleFilterChange() {
loadAnnouncements()
}
function handleSizeChange() {
async function handlePageChange() {
await loadAnnouncements()
}
async function handleSizeChange() {
currentPage.value = 1
loadAnnouncements()
await loadAnnouncements()
}
function getCategoryLabel(category: string) {
@@ -261,17 +266,14 @@ function getStatusType(status: string): '' | 'success' | 'info' | 'warning' {
</el-table-column>
</el-table>
<div class="pagination-wrap">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadAnnouncements"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="handlePageChange"
/>
</div>
<AnnouncementDialog
@@ -4,6 +4,7 @@ import { computed, onMounted, reactive, ref } from 'vue'
import { fetchAdminAuditLogs, type AdminAuditLog } from '@/features/admin/api/adminAudit'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const logs = ref<AdminAuditLog[]>([])
@@ -45,9 +46,13 @@ function resetFilters() {
void loadLogs()
}
function handleSizeChange() {
async function handlePageChange() {
await loadLogs()
}
async function handleSizeChange() {
currentPage.value = 1
loadLogs()
await loadLogs()
}
function actorName(row: AdminAuditLog) {
@@ -141,17 +146,14 @@ function actionType(action: string) {
</el-table-column>
</el-table>
<div class="pagination-wrap" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadLogs"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="handlePageChange"
/>
<el-dialog :model-value="!!activeLog" title="审计明细" width="720px" @update:model-value="activeLog = null">
<div v-if="activeLog" class="dialog-body">
@@ -6,6 +6,7 @@ import { arbitrateDispute, fetchAdminDisputes, type Dispute } from '@/features/d
import { fetchAdminFileBlob } from '@/shared/api/files'
import { disputeStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const submitting = ref(false)
@@ -32,9 +33,13 @@ async function loadDisputes() {
}
}
function handleSizeChange() {
async function handlePageChange() {
await loadDisputes()
}
async function handleSizeChange() {
currentPage.value = 1
loadDisputes()
await loadDisputes()
}
function openArbitration(row: Dispute) {
@@ -132,17 +137,14 @@ function readError(error: unknown, fallback: string) {
</el-table-column>
</el-table>
<div class="pagination-wrap" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadDisputes"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="handlePageChange"
/>
<el-dialog :model-value="!!activeDispute" title="申诉仲裁" width="560px" @update:model-value="activeDispute = null">
<div v-if="activeDispute" class="dialog-body">
@@ -8,6 +8,7 @@ import { formatDateTime } from '@/utils/time'
import AdminUserDialog from '../components/AdminUserDialog.vue'
import AssignRolesDialog from '../components/AssignRolesDialog.vue'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const showDialog = ref(false)
const editingAdmin = ref<AdminMgrUser | null>(null)
@@ -141,17 +142,14 @@ const statusLabel: Record<string, string> = {
</el-table-column>
</el-table>
<div class="pagination-wrap" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadAdmins"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="loadAdmins"
/>
<!-- 新建/编辑对话框 -->
<AdminUserDialog
@@ -1,17 +1,34 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { fetchAdminOrders, type Order } from '@/features/orders'
import { useAdminTable } from '@/features/admin/composables/useAdminTable'
import { handoffStatusLabel, orderStatusLabel, settlementStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const status = ref('')
const loading = ref(false)
const orders = ref<Order[]>([])
const total = ref(0)
const currentPage = ref(1)
const currentPageSize = ref(20)
const { loading, data: orders, load: loadOrders } = useAdminTable<Order[]>({
fetchFn: fetchAdminOrders,
initialData: [],
})
onMounted(loadOrders)
async function loadOrders() {
loading.value = true
try {
const result = await fetchAdminOrders(currentPage.value, currentPageSize.value)
orders.value = result.items
total.value = result.total
} finally {
loading.value = false
}
}
async function handlePageChange() {
await loadOrders()
}
const filteredOrders = computed(() => {
if (!status.value) return orders.value
@@ -74,5 +91,14 @@ const filteredOrders = computed(() => {
</template>
</el-table-column>
</el-table>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="handlePageChange"
/>
</section>
</template>
@@ -6,6 +6,7 @@ import { fetchAdminUsers, freezeAdminUser, unfreezeAdminUser, type AdminUserItem
import { useAdminPaginatedTable } from '@/features/admin/composables/useAdminPaginatedTable'
import { userStatusLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const submitting = ref(false)
const activeUser = ref<AdminUserItem | null>(null)
@@ -88,17 +89,14 @@ function readError(error: unknown, fallback: string) {
</el-table-column>
</el-table>
<div class="pagination-wrap" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadUsers"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="loadUsers"
/>
<el-dialog :model-value="!!activeUser" title="冻结用户" width="560px" @update:model-value="activeUser = null">
<div v-if="activeUser" class="dialog-body">
@@ -5,6 +5,7 @@ import { computed, onMounted, reactive, ref } from 'vue'
import { fetchAdminWalletLedger, type AdminWalletLedger } from '@/features/admin/api/adminWallet'
import { balanceTypeLabel, ledgerDirectionLabel } from '@/utils/statusLabels'
import { formatDateTime } from '@/utils/time'
import AdminTablePagination from '../components/AdminTablePagination.vue'
const loading = ref(false)
const ledger = ref<AdminWalletLedger[]>([])
@@ -50,9 +51,13 @@ function resetFilters() {
void loadLedger()
}
function handleSizeChange() {
async function handlePageChange() {
await loadLedger()
}
async function handleSizeChange() {
currentPage.value = 1
loadLedger()
await loadLedger()
}
function money(value: number) {
@@ -149,16 +154,13 @@ function directionLabel(direction: string) {
</el-table-column>
</el-table>
<div class="pagination-wrap" v-if="total > 0">
<el-pagination
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next"
@current-change="loadLedger"
@size-change="handleSizeChange"
/>
</div>
<AdminTablePagination
v-if="total > 0"
v-model:current-page="currentPage"
v-model:page-size="currentPageSize"
:total="total"
:loading="loading"
@page-change="handlePageChange"
/>
</section>
</template>