"use client" import { ApiError } from "@/types/api" import { MutationCache, QueryCache, QueryClient, QueryClientProvider } from "@tanstack/react-query" import { ReactQueryDevtools } from "@tanstack/react-query-devtools" import { usePathname, useRouter, useSearchParams } from "next/navigation" import React, { useState, useRef, type ReactNode } from "react" import { toast, Toaster } from "sonner" import { tokenManager } from "./auth/token" import { COIN_INSUFFICIENT_ERROR_CODE } from "@/hooks/useWallet" import { walletKeys } from "./query-keys" interface ProvidersProps { children: ReactNode } const ReactQueryDevtoolsProduction = React.lazy(() => import('@tanstack/react-query-devtools/build/modern/production.js').then( (d) => ({ default: d.ReactQueryDevtools, }), ), ) const EXPIRED_ERROR_CODES = ['10050001', '10050002', '10050003', '10050004', '10050005', '10050006']; export function Providers({ children }: ProvidersProps) { const router = useRouter(); // 用于错误去重的引用 const errorTimeoutRef = useRef(null); const lastErrorRef = useRef(''); const [showDevtools, setShowDevtools] = React.useState(false) const [queryClient] = useState( () => new QueryClient({ defaultOptions: { queries: { // 全局查询配置 staleTime: 5 * 60 * 1000, // 5分钟后数据过期 gcTime: 10 * 60 * 1000, // 10分钟后清除缓存 refetchOnWindowFocus: false, // 窗口聚焦时不重新请求 retry: 0, // 失败重试次数 }, mutations: { // 全局修改配置 retry: 0, }, }, queryCache: new QueryCache({ onError: (error) => { handleError(error as ApiError) } }), mutationCache: new MutationCache({ onError: (error) => { handleError(error as ApiError) } }) }) ) const pathname = usePathname(); const searchParams = useSearchParams(); const searchParamsString = searchParams.toString() const redirectURL = `${pathname}${searchParamsString ? `?${searchParamsString}` : ''}` const handleError = (error: ApiError) => { if (error.errorCode === COIN_INSUFFICIENT_ERROR_CODE) { queryClient.invalidateQueries({ queryKey: walletKeys.getWalletBalance() }) } if (EXPIRED_ERROR_CODES.includes(error.errorCode)) { // 清除 cookie 中的 st tokenManager.removeToken(); router.push('/login?redirect=' + encodeURIComponent(redirectURL)); return; // 对于登录过期错误,不显示错误toast,直接跳转 } if (error.ignoreError) { return; } // 错误去重逻辑:只显示最后一次的错误 const errorKey = `${error.errorCode}:${error.errorMsg}`; // 清除之前的定时器 if (errorTimeoutRef.current) { clearTimeout(errorTimeoutRef.current); } // 更新最后一次错误信息 lastErrorRef.current = errorKey; // 设置新的定时器,延迟显示错误 errorTimeoutRef.current = setTimeout(() => { // 只有当前错误仍然是最后一次错误时才显示 if (lastErrorRef.current === errorKey) { toast.error(error.errorMsg) } }, 100) // 100ms 延迟,确保能捕获到快速连续的错误 } React.useEffect(() => { // @ts-ignore window.toggleDevtools = () => setShowDevtools((old) => !old) }, []) return ( {children} , success: , }} toastOptions={{ className: '!bg-surface-base-normal !border-none !px-4 !py-3 !rounded-m !txt-body-m !text-txt-primary-normal', }} /> {showDevtools && ( )} ) }