/** * TanStack Query 接入:手动触发的 endpoint 请求 * * 语义(设计文档 §3.3 定案): * - queryKey = [endpointPath, skKey]——同 tab 换 key 是新查询,不同 tab(不同 key)缓存天然隔离 * - staleTime: Infinity——调试工具的响应不该自己变,切走再切回不自动重发 * - 手动触发:sendRequest() 置位 enabled,首次发送后才拉数据;再次点击强制 refetch */ import { useQuery, type UseQueryReturnType } from '@tanstack/vue-query'; import { readonly, ref, type Ref } from 'vue'; import { requestGatewayEndpoint, type GatewayResponse } from './gateway-client'; /** 手动请求的返回契约 */ export interface ManualEndpointQuery { /** 响应数据(GatewayResponse,含 status/ms/bytes/text/json/networkError) */ data: Readonly>; /** 请求进行中 */ isFetching: Readonly>; /** 是否已发送过至少一次(区分「未发送」与「发送中」的空态) */ hasSent: Readonly>; /** 触发请求(key 为空时由调用方负责拦截,本层不校验) */ sendRequest: () => void; } /** * 手动触发的 endpoint 查询。 * endpointPath 与 skKey 均为响应式 Ref:换接口 / 换 key 时 queryKey 随之变化(新缓存槽)。 */ export function useManualEndpointQuery( endpointPath: Ref, skKey: Ref, ): ManualEndpointQuery { const requestEnabled = ref(false); const hasSent = ref(false); const query = useQuery({ queryKey: ['endpoint', endpointPath, skKey], queryFn: () => requestGatewayEndpoint(endpointPath.value, skKey.value), enabled: requestEnabled, staleTime: Infinity, gcTime: Infinity, retry: false, // 调试工具要看到真实的失败,不重试 refetchOnWindowFocus: false, refetchOnMount: false, refetchOnReconnect: false, }); function sendRequest(): void { hasSent.value = true; requestEnabled.value = true; // 已有缓存(如换回旧 key)时强制重新请求——「发送」按钮的语义就是真的发一次 void query.refetch(); } return { data: query.data, isFetching: query.isFetching, hasSent: readonly(hasSent), sendRequest, }; } /** 具名导出便于类型引用(避免调用方直接依赖 tanstack 内部类型) */ export type { UseQueryReturnType };