import { ref, reactive, onUnmounted, toRaw } from 'vue' import { useDebounceFn } from '@vueuse/core' import type { BasePaginationResponse, FetchOptions } from '@/types' interface PaginationState { page: number page_size: number total: number pages: number } interface TableLoaderOptions { fetchFn: (page: number, pageSize: number, params: P, options?: FetchOptions) => Promise> initialParams?: P pageSize?: number debounceMs?: number } /** * 通用表格数据加载 Composable * 统一处理分页、筛选、搜索防抖和请求取消 */ export function useTableLoader>(options: TableLoaderOptions) { const { fetchFn, initialParams, pageSize = 20, debounceMs = 300 } = options const items = ref([]) const loading = ref(false) const params = reactive

({ ...(initialParams || {}) } as P) const pagination = reactive({ page: 1, page_size: pageSize, total: 0, pages: 0 }) let abortController: AbortController | null = null const isAbortError = (error: any) => { return error?.name === 'AbortError' || error?.code === 'ERR_CANCELED' || error?.name === 'CanceledError' } const load = async () => { if (abortController) { abortController.abort() } const currentController = new AbortController() abortController = currentController loading.value = true try { const response = await fetchFn( pagination.page, pagination.page_size, toRaw(params) as P, { signal: currentController.signal } ) items.value = response.items || [] pagination.total = response.total || 0 pagination.pages = response.pages || 0 } catch (error) { if (!isAbortError(error)) { console.error('Table load error:', error) throw error } } finally { if (abortController === currentController) { loading.value = false } } } const reload = () => { pagination.page = 1 return load() } const debouncedReload = useDebounceFn(reload, debounceMs) const handlePageChange = (page: number) => { // 确保页码在有效范围内 const validPage = Math.max(1, Math.min(page, pagination.pages || 1)) pagination.page = validPage load() } const handlePageSizeChange = (size: number) => { pagination.page_size = size pagination.page = 1 load() } onUnmounted(() => { abortController?.abort() }) return { items, loading, params, pagination, load, reload, debouncedReload, handlePageChange, handlePageSizeChange } }