feat(table): 表格排序与搜索改为后端处理

This commit is contained in:
IanShaw027
2026-04-09 18:14:28 +08:00
parent 66e15a54a4
commit 5f8e60a1b7
79 changed files with 2282 additions and 240 deletions

View File

@@ -21,7 +21,15 @@
</button>
</div>
<DataTable :columns="columns" :data="items" :loading="loading">
<DataTable
:columns="columns"
:data="items"
:loading="loading"
:server-side-sort="true"
default-sort-key="email"
default-sort-order="asc"
@sort="handleSort"
>
<template #cell-email="{ value }">
<span class="font-medium text-gray-900 dark:text-white">{{ value }}</span>
</template>
@@ -62,7 +70,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue'
import { computed, onUnmounted, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { adminAPI } from '@/api/admin'
@@ -98,23 +106,54 @@ const pagination = reactive({
pages: 0
})
const sortState = reactive({
sort_by: 'email',
sort_order: 'asc' as 'asc' | 'desc'
})
const items = ref<AnnouncementUserReadStatus[]>([])
const columns = computed<Column[]>(() => [
{ key: 'email', label: t('common.email') },
{ key: 'username', label: t('admin.users.columns.username') },
{ key: 'balance', label: t('common.balance') },
{ key: 'email', label: t('common.email'), sortable: true },
{ key: 'username', label: t('admin.users.columns.username'), sortable: true },
{ key: 'balance', label: t('common.balance'), sortable: true },
{ key: 'eligible', label: t('admin.announcements.eligible') },
{ key: 'read_at', label: t('admin.announcements.readAt') }
])
let currentController: AbortController | null = null
let searchDebounceTimer: number | null = null
function resetDialogState() {
loading.value = false
search.value = ''
items.value = []
pagination.page = 1
pagination.total = 0
pagination.pages = 0
sortState.sort_by = 'email'
sortState.sort_order = 'asc'
}
function cancelPendingLoad(resetState = false) {
if (searchDebounceTimer) {
window.clearTimeout(searchDebounceTimer)
searchDebounceTimer = null
}
currentController?.abort()
currentController = null
if (resetState) {
resetDialogState()
}
}
async function load() {
if (!props.show || !props.announcementId) return
if (currentController) currentController.abort()
currentController = new AbortController()
currentController?.abort()
const requestController = new AbortController()
currentController = requestController
const { signal } = requestController
try {
loading.value = true
@@ -122,20 +161,37 @@ async function load() {
props.announcementId,
pagination.page,
pagination.page_size,
search.value
{
search: search.value,
sort_by: sortState.sort_by,
sort_order: sortState.sort_order
},
{ signal }
)
if (signal.aborted || currentController !== requestController) return
items.value = res.items
pagination.total = res.total
pagination.pages = res.pages
pagination.page = res.page
pagination.page_size = res.page_size
} catch (error: any) {
if (currentController.signal.aborted || error?.name === 'AbortError') return
if (
signal.aborted ||
currentController !== requestController ||
error?.name === 'AbortError' ||
error?.code === 'ERR_CANCELED'
) {
return
}
console.error('Failed to load read status:', error)
appStore.showError(error.response?.data?.detail || t('admin.announcements.failedToLoadReadStatus'))
} finally {
loading.value = false
if (currentController === requestController) {
loading.value = false
currentController = null
}
}
}
@@ -150,7 +206,13 @@ function handlePageSizeChange(pageSize: number) {
load()
}
let searchDebounceTimer: number | null = null
function handleSort(key: string, order: 'asc' | 'desc') {
sortState.sort_by = key
sortState.sort_order = order
pagination.page = 1
load()
}
function handleSearch() {
if (searchDebounceTimer) window.clearTimeout(searchDebounceTimer)
searchDebounceTimer = window.setTimeout(() => {
@@ -160,13 +222,17 @@ function handleSearch() {
}
function handleClose() {
cancelPendingLoad(true)
emit('close')
}
watch(
() => props.show,
(v) => {
if (!v) return
if (!v) {
cancelPendingLoad(true)
return
}
pagination.page = 1
load()
}
@@ -181,7 +247,7 @@ watch(
}
)
onMounted(() => {
// noop
onUnmounted(() => {
cancelPendingLoad()
})
</script>