From b6ff721d67ca8f7cc68844c201e041bde5bf85f6 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 19 Dec 2025 11:18:22 +0800 Subject: [PATCH] fix(import): refresh all providers immediately after SQL import - Remove setTimeout delay that could be cancelled on component unmount - Invalidate all providers cache (not just current app) since import affects all apps - Call onImportSuccess before sync to ensure UI refresh even if sync fails - Update i18n: "Data refreshed" (past tense, reflecting immediate action) --- src/App.tsx | 17 ++++++++++++++++- src/hooks/useImportExport.ts | 19 +++++-------------- src/i18n/locales/en.json | 2 +- src/i18n/locales/ja.json | 2 +- src/i18n/locales/zh.json | 2 +- tests/hooks/useImportExport.test.tsx | 6 ------ 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 32fbf8d5..15255766 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import { useEffect, useMemo, useState, useRef } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { invoke } from "@tauri-apps/api/core"; +import { useQueryClient } from "@tanstack/react-query"; import { Plus, Settings, @@ -47,6 +48,7 @@ type View = "providers" | "settings" | "prompts" | "skills" | "mcp" | "agents"; function App() { const { t } = useTranslation(); + const queryClient = useQueryClient(); const [activeApp, setActiveApp] = useState("claude"); const [currentView, setCurrentView] = useState("providers"); @@ -276,7 +278,20 @@ function App() { // 导入配置成功后刷新 const handleImportSuccess = async () => { - await refetch(); + try { + // 导入会影响所有应用的供应商数据:刷新所有 providers 缓存 + await queryClient.invalidateQueries({ + queryKey: ["providers"], + refetchType: "all", + }); + await queryClient.refetchQueries({ + queryKey: ["providers"], + type: "all", + }); + } catch (error) { + console.error("[App] Failed to refresh providers after import", error); + await refetch(); + } try { await providersApi.updateTrayMenu(); } catch (error) { diff --git a/src/hooks/useImportExport.ts b/src/hooks/useImportExport.ts index 3fa573aa..62703df6 100644 --- a/src/hooks/useImportExport.ts +++ b/src/hooks/useImportExport.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { settingsApi } from "@/lib/api"; @@ -39,15 +39,6 @@ export function useImportExport( const [errorMessage, setErrorMessage] = useState(null); const [backupId, setBackupId] = useState(null); const [isImporting, setIsImporting] = useState(false); - const successTimerRef = useRef(null); - - useEffect(() => { - return () => { - if (successTimerRef.current) { - window.clearTimeout(successTimerRef.current); - } - }; - }, []); const clearSelection = useCallback(() => { setSelectedFile(""); @@ -105,6 +96,10 @@ export function useImportExport( } setBackupId(result.backupId ?? null); + // 导入成功后立即触发外部刷新(与 live 同步结果解耦) + // - 避免 sync 失败时 UI 不刷新 + // - 避免依赖 setTimeout(组件卸载会取消) + void onImportSuccess?.(); const syncResult = await syncCurrentProvidersLiveSafe(); if (syncResult.ok) { @@ -115,10 +110,6 @@ export function useImportExport( }), { closeButton: true }, ); - - successTimerRef.current = window.setTimeout(() => { - void onImportSuccess?.(); - }, 1500); } else { console.error( "[useImportExport] Failed to sync live config", diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index c1049742..03d8f3cf 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -166,7 +166,7 @@ "selectFileFailed": "Please choose a valid SQL backup file", "configCorrupted": "SQL file may be corrupted or invalid", "backupId": "Backup ID", - "autoReload": "Data will refresh automatically in 2 seconds...", + "autoReload": "Data refreshed", "languageOptionChinese": "中文", "languageOptionEnglish": "English", "languageOptionJapanese": "日本語", diff --git a/src/i18n/locales/ja.json b/src/i18n/locales/ja.json index d8cde7d1..36076dbf 100644 --- a/src/i18n/locales/ja.json +++ b/src/i18n/locales/ja.json @@ -166,7 +166,7 @@ "selectFileFailed": "有効な SQL バックアップファイルを選択してください", "configCorrupted": "SQL ファイルが壊れているか形式が無効な可能性があります", "backupId": "バックアップ ID", - "autoReload": "2 秒後に自動で再読み込みします...", + "autoReload": "データを更新しました", "languageOptionChinese": "中文", "languageOptionEnglish": "English", "languageOptionJapanese": "日本語", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 9e5e7bec..5e6e458f 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -166,7 +166,7 @@ "selectFileFailed": "请选择有效的 SQL 备份文件", "configCorrupted": "SQL 文件可能已损坏或格式不正确", "backupId": "备份ID", - "autoReload": "数据将在2秒后自动刷新...", + "autoReload": "数据已刷新", "languageOptionChinese": "中文", "languageOptionEnglish": "English", "languageOptionJapanese": "日本語", diff --git a/tests/hooks/useImportExport.test.tsx b/tests/hooks/useImportExport.test.tsx index 030c228a..b3523792 100644 --- a/tests/hooks/useImportExport.test.tsx +++ b/tests/hooks/useImportExport.test.tsx @@ -110,12 +110,6 @@ describe("useImportExport Hook", () => { expect(result.current.status).toBe("success"); expect(result.current.backupId).toBe("backup-123"); expect(toastSuccessMock).toHaveBeenCalledTimes(1); - - // Skip delay to execute callback - await act(async () => { - vi.runOnlyPendingTimers(); - }); - expect(onImportSuccess).toHaveBeenCalledTimes(1); });