From ce593248fc88008276aca08b68b186a81ea4ca3a Mon Sep 17 00:00:00 2001 From: QuentinHsu Date: Sun, 5 Oct 2025 11:09:03 +0800 Subject: [PATCH 1/3] fix: update layout for Claude app type provider display (#87) * feat: add .node-version file with Node.js version 22.4.1 * fix: update layout for Claude app type provider display --- .node-version | 1 + src/components/ProviderList.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .node-version diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..adb0705 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v22.4.1 diff --git a/src/components/ProviderList.tsx b/src/components/ProviderList.tsx index cd187d5..290c674 100644 --- a/src/components/ProviderList.tsx +++ b/src/components/ProviderList.tsx @@ -199,7 +199,7 @@ const ProviderList: React.FC = ({
{appType === "claude" ? ( -
+
{provider.category !== "official" && isCurrent && (
); diff --git a/src/components/ImportProgressModal.tsx b/src/components/ImportProgressModal.tsx new file mode 100644 index 0000000..d6ad7ab --- /dev/null +++ b/src/components/ImportProgressModal.tsx @@ -0,0 +1,103 @@ +import { useEffect } from "react"; +import { CheckCircle, Loader2, AlertCircle } from "lucide-react"; +import { useTranslation } from "react-i18next"; + +interface ImportProgressModalProps { + status: 'importing' | 'success' | 'error'; + message?: string; + backupId?: string; + onComplete?: () => void; + onSuccess?: () => void; +} + +export function ImportProgressModal({ + status, + message, + backupId, + onComplete, + onSuccess +}: ImportProgressModalProps) { + const { t } = useTranslation(); + + useEffect(() => { + if (status === 'success') { + console.log('[ImportProgressModal] Success detected, starting 2 second countdown'); + // 成功后等待2秒自动关闭并刷新数据 + const timer = setTimeout(() => { + console.log('[ImportProgressModal] 2 seconds elapsed, calling callbacks...'); + if (onSuccess) { + onSuccess(); + } + if (onComplete) { + onComplete(); + } + }, 2000); + + return () => { + console.log('[ImportProgressModal] Cleanup timer'); + clearTimeout(timer); + }; + } + }, [status, onComplete, onSuccess]); + + return ( +
+
+ +
+
+ {status === 'importing' && ( + <> + +

+ {t("settings.importing")} +

+

+ {t("common.loading")} +

+ + )} + + {status === 'success' && ( + <> + +

+ {t("settings.importSuccess")} +

+ {backupId && ( +

+ {t("settings.backupId")}: {backupId} +

+ )} +

+ {t("settings.autoReload")} +

+ + )} + + {status === 'error' && ( + <> + +

+ {t("settings.importFailed")} +

+

+ {message || t("settings.configCorrupted")} +

+ + + )} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index ca727ef..032c625 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -12,6 +12,7 @@ import { Save, } from "lucide-react"; import { getVersion } from "@tauri-apps/api/app"; +import { ImportProgressModal } from "./ImportProgressModal"; import { homeDir, join } from "@tauri-apps/api/path"; import "../lib/tauri-api"; import { relaunchApp } from "../lib/updater"; @@ -22,9 +23,10 @@ import { isLinux } from "../lib/platform"; interface SettingsModalProps { onClose: () => void; + onImportSuccess?: () => void | Promise; } -export default function SettingsModal({ onClose }: SettingsModalProps) { +export default function SettingsModal({ onClose, onImportSuccess }: SettingsModalProps) { const { t, i18n } = useTranslation(); const normalizeLanguage = (lang?: string | null): "zh" | "en" => @@ -63,6 +65,13 @@ export default function SettingsModal({ onClose }: SettingsModalProps) { const { hasUpdate, updateInfo, updateHandle, checkUpdate, resetDismiss } = useUpdate(); + // 导入/导出相关状态 + const [isImporting, setIsImporting] = useState(false); + const [importStatus, setImportStatus] = useState<'idle' | 'importing' | 'success' | 'error'>('idle'); + const [importError, setImportError] = useState(""); + const [importBackupId, setImportBackupId] = useState(""); + const [selectedImportFile, setSelectedImportFile] = useState(''); + useEffect(() => { loadSettings(); loadConfigPath(); @@ -346,6 +355,66 @@ export default function SettingsModal({ onClose }: SettingsModalProps) { } }; + // 导出配置处理函数 + const handleExportConfig = async () => { + try { + const defaultName = `cc-switch-config-${new Date().toISOString().split('T')[0]}.json`; + const filePath = await window.api.saveFileDialog(defaultName); + + if (!filePath) return; // 用户取消了 + + const result = await window.api.exportConfigToFile(filePath); + + if (result.success) { + alert(`${t("settings.configExported")}\n${result.filePath}`); + } + } catch (error) { + console.error("导出配置失败:", error); + alert(`${t("settings.exportFailed")}: ${error}`); + } + }; + + // 选择要导入的文件 + const handleSelectImportFile = async () => { + try { + const filePath = await window.api.openFileDialog(); + if (filePath) { + setSelectedImportFile(filePath); + setImportStatus('idle'); // 重置状态 + setImportError(''); + } + } catch (error) { + console.error('选择文件失败:', error); + alert(`${t("settings.selectFileFailed")}: ${error}`); + } + }; + + // 执行导入 + const handleExecuteImport = async () => { + if (!selectedImportFile || isImporting) return; + + setIsImporting(true); + setImportStatus('importing'); + + try { + const result = await window.api.importConfigFromFile(selectedImportFile); + + if (result.success) { + setImportBackupId(result.backupId || ''); + setImportStatus('success'); + // ImportProgressModal 会在2秒后触发数据刷新回调 + } else { + setImportError(result.message || t("settings.configCorrupted")); + setImportStatus('error'); + } + } catch (error) { + setImportError(String(error)); + setImportStatus('error'); + } finally { + setIsImporting(false); + } + }; + return (
+ {/* 导入导出 */} +
+

+ {t("settings.importExport")} +

+
+
+ {/* 导出按钮 */} + + + {/* 导入区域 */} +
+
+ + +
+ + {/* 显示选择的文件 */} + {selectedImportFile && ( +
+ {selectedImportFile.split('/').pop() || selectedImportFile.split('\\').pop() || selectedImportFile} +
+ )} +
+
+
+
+ {/* 关于 */}

@@ -636,6 +755,28 @@ export default function SettingsModal({ onClose }: SettingsModalProps) {

+ + {/* Import Progress Modal */} + {importStatus !== 'idle' && ( + { + setImportStatus('idle'); + setImportError(''); + setSelectedImportFile(''); + }} + onSuccess={() => { + if (onImportSuccess) { + void onImportSuccess(); + } + void window.api + .updateTrayMenu() + .catch((error) => console.error("[SettingsModal] Failed to refresh tray menu", error)); + }} + /> + )}
); } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 71bd97c..4b86198 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -61,6 +61,19 @@ "title": "Settings", "general": "General", "language": "Language", + "importExport": "Import/Export Config", + "exportConfig": "Export Config to File", + "selectConfigFile": "Select Config File", + "import": "Import", + "importing": "Importing...", + "importSuccess": "Import Successful!", + "importFailed": "Import Failed", + "configExported": "Config exported to:", + "exportFailed": "Export failed", + "selectFileFailed": "Failed to select file", + "configCorrupted": "Config file may be corrupted or invalid", + "backupId": "Backup ID", + "autoReload": "Data will refresh automatically in 2 seconds...", "languageOptionChinese": "中文", "languageOptionEnglish": "English", "windowBehavior": "Window Behavior", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 9ca0b09..20996b2 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -61,6 +61,19 @@ "title": "设置", "general": "通用", "language": "界面语言", + "importExport": "导入导出配置", + "exportConfig": "导出配置到文件", + "selectConfigFile": "选择配置文件", + "import": "导入", + "importing": "导入中...", + "importSuccess": "导入成功!", + "importFailed": "导入失败", + "configExported": "配置已导出到:", + "exportFailed": "导出失败", + "selectFileFailed": "选择文件失败", + "configCorrupted": "配置文件可能已损坏或格式不正确", + "backupId": "备份ID", + "autoReload": "数据将在2秒后自动刷新...", "languageOptionChinese": "中文", "languageOptionEnglish": "English", "windowBehavior": "窗口行为", diff --git a/src/lib/tauri-api.ts b/src/lib/tauri-api.ts index f480ef3..7304ddb 100644 --- a/src/lib/tauri-api.ts +++ b/src/lib/tauri-api.ts @@ -312,6 +312,54 @@ export const tauriAPI = { throw new Error(`检测 Claude 插件配置失败: ${String(error)}`); } }, + + // 导出配置到文件 + exportConfigToFile: async (filePath: string): Promise<{ + success: boolean; + message: string; + filePath: string; + }> => { + try { + return await invoke("export_config_to_file", { filePath }); + } catch (error) { + throw new Error(`导出配置失败: ${String(error)}`); + } + }, + + // 从文件导入配置 + importConfigFromFile: async (filePath: string): Promise<{ + success: boolean; + message: string; + backupId?: string; + }> => { + try { + return await invoke("import_config_from_file", { filePath }); + } catch (error) { + throw new Error(`导入配置失败: ${String(error)}`); + } + }, + + // 保存文件对话框 + saveFileDialog: async (defaultName: string): Promise => { + try { + const result = await invoke("save_file_dialog", { defaultName }); + return result; + } catch (error) { + console.error("打开保存对话框失败:", error); + return null; + } + }, + + // 打开文件对话框 + openFileDialog: async (): Promise => { + try { + const result = await invoke("open_file_dialog"); + return result; + } catch (error) { + console.error("打开文件对话框失败:", error); + return null; + } + }, }; // 创建全局 API 对象,兼容现有代码 diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index c44ff28..4d6968b 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -29,6 +29,18 @@ declare global { getClaudeConfigStatus: () => Promise; getConfigStatus: (app?: AppType) => Promise; getConfigDir: (app?: AppType) => Promise; + saveFileDialog: (defaultName: string) => Promise; + openFileDialog: () => Promise; + exportConfigToFile: (filePath: string) => Promise<{ + success: boolean; + message: string; + filePath: string; + }>; + importConfigFromFile: (filePath: string) => Promise<{ + success: boolean; + message: string; + backupId?: string; + }>; selectConfigDirectory: (defaultPath?: string) => Promise; openConfigFolder: (app?: AppType) => Promise; openExternal: (url: string) => Promise;