refactor(settings): simplify settings page layout and auto-save

Reorganize settings page structure and integrate autoSaveSettings.
- Move save button inline within Advanced tab content
- Remove sticky footer for cleaner layout
- Use autoSaveSettings for General tab settings
- Simplify dialog close behavior
- Refactor ImportExportSection layout
This commit is contained in:
YoVinchen
2025-11-22 15:35:08 +08:00
parent cfee4d6fcc
commit 4210b1547c
2 changed files with 71 additions and 114 deletions

View File

@@ -54,86 +54,63 @@ export function ImportExportSection({
</header> </header>
<div className="space-y-4 rounded-xl glass-card p-6 border border-white/10"> <div className="space-y-4 rounded-xl glass-card p-6 border border-white/10">
{/* Export Section */} {/* Import and Export Buttons Side by Side */}
<div className="space-y-3"> <div className="grid grid-cols-2 gap-4 items-stretch">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground"> {/* Import Button */}
<Save className="h-4 w-4" /> <div className="relative">
<span></span>
</div>
<Button <Button
type="button" type="button"
className="w-full bg-primary/90 hover:bg-primary text-primary-foreground shadow-lg shadow-primary/20" className={`w-full h-auto py-3 px-4 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white ${selectedFile && !isImporting ? "flex-col items-start" : "items-center"}`}
onClick={!selectedFile ? onSelectFile : onImport}
disabled={isImporting}
>
<div className="flex items-center gap-2 w-full justify-center">
{isImporting ? (
<Loader2 className="h-4 w-4 animate-spin flex-shrink-0" />
) : selectedFile ? (
<CheckCircle2 className="h-4 w-4 flex-shrink-0" />
) : (
<FolderOpen className="h-4 w-4 flex-shrink-0" />
)}
<span className="font-medium">
{isImporting
? t("settings.importing")
: selectedFile
? t("settings.import")
: t("settings.selectConfigFile")}
</span>
</div>
{selectedFile && !isImporting && (
<div className="mt-2 w-full text-left">
<p className="text-xs font-mono text-white/80 truncate">
📄 {selectedFileName}
</p>
</div>
)}
</Button>
{selectedFile && (
<button
type="button"
onClick={onClear}
className="absolute -top-2 -right-2 h-6 w-6 rounded-full bg-red-500 hover:bg-red-600 text-white flex items-center justify-center shadow-lg transition-colors z-10"
aria-label="Clear selection"
>
<XCircle className="h-4 w-4" />
</button>
)}
</div>
{/* Export Button */}
<div>
<Button
type="button"
className="w-full h-full py-3 px-4 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white items-center"
onClick={onExport} onClick={onExport}
> >
<Save className="mr-2 h-4 w-4" /> <Save className="mr-2 h-4 w-4" />
{t("settings.exportConfig")} {t("settings.exportConfig")}
</Button> </Button>
</div> </div>
{/* Divider */}
<div className="border-t border-white/10" />
{/* Import Section */}
<div className="space-y-3">
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
<FolderOpen className="h-4 w-4" />
<span></span>
</div>
<div className="flex flex-wrap items-center gap-2">
<Button
type="button"
variant="outline"
className="flex-1 min-w-[180px] hover:bg-black/5 dark:hover:bg-white/5 border-white/10"
onClick={onSelectFile}
>
<FolderOpen className="mr-2 h-4 w-4" />
{t("settings.selectConfigFile")}
</Button>
<Button
type="button"
disabled={!selectedFile || isImporting}
className="bg-primary hover:bg-primary/90"
onClick={onImport}
>
{isImporting ? (
<span className="inline-flex items-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
{t("settings.importing")}
</span>
) : (
<>
<CheckCircle2 className="mr-2 h-4 w-4" />
{t("settings.import")}
</>
)}
</Button>
{selectedFile ? (
<Button
type="button"
variant="ghost"
onClick={onClear}
className="hover:bg-black/5 dark:hover:bg-white/5"
>
<XCircle className="mr-2 h-4 w-4" />
{t("common.clear")}
</Button>
) : null}
</div>
{selectedFile ? (
<div className="glass rounded-lg border border-white/10 p-3">
<p className="text-xs font-mono text-foreground/80 truncate">
📄 {selectedFileName}
</p>
</div>
) : (
<div className="glass rounded-lg border border-white/10 p-3">
<p className="text-xs text-muted-foreground italic">
{t("settings.noFileSelected")}
</p>
</div>
)}
</div> </div>
<ImportStatusMessage <ImportStatusMessage

View File

@@ -49,7 +49,7 @@ export function SettingsPage({
resetDirectory, resetDirectory,
resetAppConfigDir, resetAppConfigDir,
saveSettings, saveSettings,
resetSettings, autoSaveSettings,
requiresRestart, requiresRestart,
acknowledgeRestart, acknowledgeRestart,
} = useSettings(); } = useSettings();
@@ -83,21 +83,6 @@ export function SettingsPage({
} }
}, [requiresRestart]); }, [requiresRestart]);
const closeDialog = useCallback(() => {
// 取消/直接关闭:恢复到初始设置(包括语言回滚)
resetSettings();
acknowledgeRestart();
clearSelection();
resetStatus();
onOpenChange(false);
}, [
acknowledgeRestart,
clearSelection,
onOpenChange,
resetSettings,
resetStatus,
]);
const closeAfterSave = useCallback(() => { const closeAfterSave = useCallback(() => {
// 保存成功后关闭:不再重置语言,避免需要“保存两次”才生效 // 保存成功后关闭:不再重置语言,避免需要“保存两次”才生效
acknowledgeRestart(); acknowledgeRestart();
@@ -118,7 +103,7 @@ export function SettingsPage({
} catch (error) { } catch (error) {
console.error("[SettingsPage] Failed to save settings", error); console.error("[SettingsPage] Failed to save settings", error);
} }
}, [closeDialog, saveSettings]); }, [closeAfterSave, saveSettings]);
const handleRestartLater = useCallback(() => { const handleRestartLater = useCallback(() => {
setShowRestartPrompt(false); setShowRestartPrompt(false);
@@ -144,15 +129,13 @@ export function SettingsPage({
}, [closeAfterSave, t]); }, [closeAfterSave, t]);
// 通用设置即时保存(无需手动点击) // 通用设置即时保存(无需手动点击)
// 使用 autoSaveSettings 避免误触发系统 API开机自启、Claude 插件等)
const handleAutoSave = useCallback( const handleAutoSave = useCallback(
async (updates: Partial<SettingsFormState>) => { async (updates: Partial<SettingsFormState>) => {
if (!settings) return; if (!settings) return;
updateSettings(updates); updateSettings(updates);
try { try {
const result = await saveSettings(updates, { silent: true }); await autoSaveSettings(updates);
if (result?.requiresRestart) {
setShowRestartPrompt(true);
}
} catch (error) { } catch (error) {
console.error("[SettingsPage] Failed to autosave settings", error); console.error("[SettingsPage] Failed to autosave settings", error);
toast.error( toast.error(
@@ -162,7 +145,7 @@ export function SettingsPage({
); );
} }
}, },
[saveSettings, settings, t, updateSettings], [autoSaveSettings, settings, t, updateSettings],
); );
const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]); const isBusy = useMemo(() => isLoading && !settings, [isLoading, settings]);
@@ -206,7 +189,7 @@ export function SettingsPage({
) : null} ) : null}
</TabsContent> </TabsContent>
<TabsContent value="advanced" className="space-y-6 mt-0"> <TabsContent value="advanced" className="space-y-6 mt-0 pb-6">
{settings ? ( {settings ? (
<> <>
<DirectorySettings <DirectorySettings
@@ -233,20 +216,10 @@ export function SettingsPage({
onExport={exportConfig} onExport={exportConfig}
onClear={clearSelection} onClear={clearSelection}
/> />
</> <div className="pt-6 border-t border-gray-200 dark:border-white/10">
) : null}
</TabsContent>
<TabsContent value="about" className="mt-0">
<AboutSection isPortable={isPortable} />
</TabsContent>
</div>
{activeTab === "advanced" ? (
<div className="flex-shrink-0 pt-6 border-t border-white/5 sticky bottom-0 bg-background/95 backdrop-blur-sm">
<Button <Button
onClick={handleSave} onClick={handleSave}
className="w-full bg-primary hover:bg-primary/90" className="w-full"
disabled={isSaving} disabled={isSaving}
> >
{isSaving ? ( {isSaving ? (
@@ -262,7 +235,14 @@ export function SettingsPage({
)} )}
</Button> </Button>
</div> </div>
</>
) : null} ) : null}
</TabsContent>
<TabsContent value="about" className="mt-0">
<AboutSection isPortable={isPortable} />
</TabsContent>
</div>
</Tabs> </Tabs>
)} )}