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:
@@ -54,86 +54,63 @@ export function ImportExportSection({
|
||||
</header>
|
||||
|
||||
<div className="space-y-4 rounded-xl glass-card p-6 border border-white/10">
|
||||
{/* Export Section */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||
<Save className="h-4 w-4" />
|
||||
<span>导出配置</span>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full bg-primary/90 hover:bg-primary text-primary-foreground shadow-lg shadow-primary/20"
|
||||
onClick={onExport}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{t("settings.exportConfig")}
|
||||
</Button>
|
||||
</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">
|
||||
{/* Import and Export Buttons Side by Side */}
|
||||
<div className="grid grid-cols-2 gap-4 items-stretch">
|
||||
{/* Import Button */}
|
||||
<div className="relative">
|
||||
<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}
|
||||
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}
|
||||
>
|
||||
<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")}
|
||||
<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>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle2 className="mr-2 h-4 w-4" />
|
||||
{t("settings.import")}
|
||||
</>
|
||||
</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
|
||||
{selectedFile && (
|
||||
<button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onClear}
|
||||
className="hover:bg-black/5 dark:hover:bg-white/5"
|
||||
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="mr-2 h-4 w-4" />
|
||||
{t("common.clear")}
|
||||
</Button>
|
||||
) : null}
|
||||
<XCircle className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</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>
|
||||
)}
|
||||
{/* 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}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{t("settings.exportConfig")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ImportStatusMessage
|
||||
|
||||
@@ -49,7 +49,7 @@ export function SettingsPage({
|
||||
resetDirectory,
|
||||
resetAppConfigDir,
|
||||
saveSettings,
|
||||
resetSettings,
|
||||
autoSaveSettings,
|
||||
requiresRestart,
|
||||
acknowledgeRestart,
|
||||
} = useSettings();
|
||||
@@ -83,21 +83,6 @@ export function SettingsPage({
|
||||
}
|
||||
}, [requiresRestart]);
|
||||
|
||||
const closeDialog = useCallback(() => {
|
||||
// 取消/直接关闭:恢复到初始设置(包括语言回滚)
|
||||
resetSettings();
|
||||
acknowledgeRestart();
|
||||
clearSelection();
|
||||
resetStatus();
|
||||
onOpenChange(false);
|
||||
}, [
|
||||
acknowledgeRestart,
|
||||
clearSelection,
|
||||
onOpenChange,
|
||||
resetSettings,
|
||||
resetStatus,
|
||||
]);
|
||||
|
||||
const closeAfterSave = useCallback(() => {
|
||||
// 保存成功后关闭:不再重置语言,避免需要“保存两次”才生效
|
||||
acknowledgeRestart();
|
||||
@@ -118,7 +103,7 @@ export function SettingsPage({
|
||||
} catch (error) {
|
||||
console.error("[SettingsPage] Failed to save settings", error);
|
||||
}
|
||||
}, [closeDialog, saveSettings]);
|
||||
}, [closeAfterSave, saveSettings]);
|
||||
|
||||
const handleRestartLater = useCallback(() => {
|
||||
setShowRestartPrompt(false);
|
||||
@@ -144,15 +129,13 @@ export function SettingsPage({
|
||||
}, [closeAfterSave, t]);
|
||||
|
||||
// 通用设置即时保存(无需手动点击)
|
||||
// 使用 autoSaveSettings 避免误触发系统 API(开机自启、Claude 插件等)
|
||||
const handleAutoSave = useCallback(
|
||||
async (updates: Partial<SettingsFormState>) => {
|
||||
if (!settings) return;
|
||||
updateSettings(updates);
|
||||
try {
|
||||
const result = await saveSettings(updates, { silent: true });
|
||||
if (result?.requiresRestart) {
|
||||
setShowRestartPrompt(true);
|
||||
}
|
||||
await autoSaveSettings(updates);
|
||||
} catch (error) {
|
||||
console.error("[SettingsPage] Failed to autosave settings", 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]);
|
||||
@@ -206,7 +189,7 @@ export function SettingsPage({
|
||||
) : null}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="advanced" className="space-y-6 mt-0">
|
||||
<TabsContent value="advanced" className="space-y-6 mt-0 pb-6">
|
||||
{settings ? (
|
||||
<>
|
||||
<DirectorySettings
|
||||
@@ -233,6 +216,25 @@ export function SettingsPage({
|
||||
onExport={exportConfig}
|
||||
onClear={clearSelection}
|
||||
/>
|
||||
<div className="pt-6 border-t border-gray-200 dark:border-white/10">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
className="w-full"
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t("settings.saving")}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{t("common.save")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</TabsContent>
|
||||
@@ -241,28 +243,6 @@ export function SettingsPage({
|
||||
<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
|
||||
onClick={handleSave}
|
||||
className="w-full bg-primary hover:bg-primary/90"
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? (
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{t("settings.saving")}
|
||||
</span>
|
||||
) : (
|
||||
<>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{t("common.save")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user