diff --git a/admin/src/hooks/core/useTable.ts b/admin/src/hooks/core/useTable.ts index d9def4f..3506519 100644 --- a/admin/src/hooks/core/useTable.ts +++ b/admin/src/hooks/core/useTable.ts @@ -197,6 +197,9 @@ function useTableImpl Promise>( // 请求取消控制器 let abortController: AbortController | null = null + // 请求序号令牌(竞态守卫):请求被更新的请求取代后,不得再写数据/清 loading/置错误态 + let fetchSeq = 0 + // 缓存清理定时器 let cacheCleanupTimer: NodeJS.Timeout | null = null @@ -288,6 +291,7 @@ function useTableImpl Promise>( // 创建新的取消控制器 const currentController = new AbortController() abortController = currentController + const currentSeq = ++fetchSeq // 状态机:进入 loading 状态 loadingState.value = 'loading' @@ -391,15 +395,24 @@ function useTableImpl Promise>( return standardResponse } catch (err) { + // 竞态守卫:已被更新请求取代的旧请求不再改动任何状态 + // (防旧 finally 提前清 loading、旧失败响应清空新请求的数据) + const superseded = currentSeq !== fetchSeq if (err instanceof Error && err.message === '请求已取消') { - // 请求被取消,回到 idle 状态 - loadingState.value = 'idle' + // 请求被取消,回到 idle 状态(仅当它仍是最新请求,如显式 cancelRequest) + if (!superseded) loadingState.value = 'idle' return { records: [], total: 0, current: 1, size: 10 } } - // 状态机:请求失败,进入 error 状态 - loadingState.value = 'error' - data.value = [] + if (!superseded) { + // 状态机:请求失败,进入 error 状态 + loadingState.value = 'error' + data.value = [] + } + if (superseded) { + // 旧请求的失败交给新请求兜底,不触发错误回调 + throw err instanceof Error ? err : new Error(String(err)) + } const tableError = handleError(err, '获取表格数据失败') throw tableError } finally {