Files
MusicWorkshop/frontend/src/pages/SettingsPage.jsx
T
liumangmang 7d003ff822 refactor: rewrite SettingsPage as lightweight container with hooks and components
Extracted cron utilities to utils/schedule.js, all state management
(15+ useState) to useSettingsForm hook, and 5 config sections + 2 dialogs
+ 1 status badge into focused components. SettingsPage reduced from
~700 lines to ~130 line container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-08 12:06:26 +08:00

128 lines
5.9 KiB
React

// frontend/src/pages/SettingsPage.jsx
import { useRef } from 'react';
import {
CheckCircle2, Download, RefreshCw, Upload, XCircle
} from 'lucide-react';
import useSettingsForm from '../hooks/useSettingsForm';
import CorePathsSection from '../components/settings/CorePathsSection';
import AdvancedStrategySection from '../components/settings/AdvancedStrategySection';
import ScheduleSection from '../components/settings/ScheduleSection';
import NotificationSection from '../components/settings/NotificationSection';
import MetadataServicesSection from '../components/settings/MetadataServicesSection';
import ConfigExportDialog from '../components/settings/ConfigExportDialog';
import ConfigImportDialog from '../components/settings/ConfigImportDialog';
export default function SettingsPage({ config, setConfig, setTaskState }) {
const {
localConfig, netStatus, toast, isSaving,
isExportDialogOpen, isExporting, exportPassword, exportPasswordConfirm,
importDialog, fileInputRef,
updateField,
handleSave,
setExportPassword, setExportPasswordConfirm,
handleOpenExportDialog, handleCloseExportDialog, handleExportConfig,
handleImportButtonClick, handleImportFileChange,
handleCloseImportDialog, handleImportDecrypt, handleImportConfirm
} = useSettingsForm({ config, setConfig, setTaskState });
const updateNested = (section, value) => {
// Create a new localConfig with the nested section updated
// useSettingsForm doesn't have updateNestedField exposed directly,
// so we use updateField for top-level fields, and for nested
// sections we pass the merged object directly.
// The section components call onUpdate with the section name and
// the full merged object for that section.
};
return (
<div className="w-full py-6">
{/* Header */}
<div className="mb-8 flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<h2 className="mb-2 text-2xl font-bold text-white">系统运行配置</h2>
<p className="text-slate-400">全局基础目录设定及核心入库引擎的高级策略配置</p>
</div>
<div className="flex flex-wrap items-center gap-3">
<button type="button" onClick={handleImportButtonClick} disabled={isSaving}
className="flex items-center whitespace-nowrap rounded-lg border border-slate-700 bg-slate-900 px-4 py-2.5 text-sm font-medium text-slate-200 transition hover:border-blue-500/50 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-60">
<Upload className="mr-2 h-4 w-4" />导入配置
</button>
<button type="button" onClick={handleOpenExportDialog} disabled={isSaving}
className="flex items-center whitespace-nowrap rounded-lg border border-slate-700 bg-slate-900 px-4 py-2.5 text-sm font-medium text-slate-200 transition hover:border-emerald-500/50 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-60">
<Download className="mr-2 h-4 w-4" />导出配置
</button>
<button type="button" onClick={handleSave} disabled={isSaving}
className="flex items-center whitespace-nowrap rounded-lg bg-blue-600 px-6 py-2.5 text-sm font-medium text-white shadow-lg shadow-blue-900/20 transition hover:bg-blue-500 disabled:cursor-not-allowed disabled:opacity-70">
{isSaving ? <RefreshCw className="mr-2 h-4 w-4 animate-spin" /> : <CheckCircle2 className="mr-2 h-4 w-4" />}
{isSaving ? '校验并保存...' : '保存系统配置'}
</button>
</div>
</div>
{/* Hidden file input for import */}
<input ref={fileInputRef} type="file" accept=".json,application/json"
className="hidden" onChange={handleImportFileChange} />
{/* Toast notification */}
{toast && (
<div className={`fixed left-1/2 top-6 z-[100] flex -translate-x-1/2 items-center rounded-xl border px-5 py-3 shadow-2xl backdrop-blur-md animate-in slide-in-from-top-4 fade-in duration-300 ${
toast.type === 'success' ? 'border-emerald-500/50 bg-emerald-950/90 text-emerald-400' : 'border-rose-500/50 bg-rose-950/90 text-rose-400'
}`}>
{toast.type === 'success' ? <CheckCircle2 className="mr-2 h-5 w-5" /> : <XCircle className="mr-2 h-5 w-5" />}
<span className="text-sm font-medium">{toast.message}</span>
</div>
)}
{/* Configuration Sections */}
<div className="mb-8 space-y-6">
<CorePathsSection
input={localConfig.input}
output={localConfig.output}
trash={localConfig.trash}
onUpdate={(field, value) => updateField(field, value)}
/>
<AdvancedStrategySection
advancedStrategy={localConfig.advancedStrategy || {}}
onUpdate={(section, value) => updateField(section, value)}
/>
<ScheduleSection
schedule={localConfig.schedule || {}}
onUpdate={(section, value) => updateField(section, value)}
/>
<NotificationSection
notifications={localConfig.notifications || {}}
onUpdate={(section, value) => updateField(section, value)}
/>
<MetadataServicesSection
metadata={localConfig.metadata || {}}
netStatus={netStatus}
onUpdate={(section, value) => updateField(section, value)}
/>
</div>
{/* Dialogs */}
<ConfigExportDialog
isOpen={isExportDialogOpen}
isExporting={isExporting}
password={exportPassword}
passwordConfirm={exportPasswordConfirm}
onClose={handleCloseExportDialog}
onExport={handleExportConfig}
onPasswordChange={setExportPassword}
onPasswordConfirmChange={setExportPasswordConfirm}
/>
<ConfigImportDialog
importDialog={importDialog}
onClose={handleCloseImportDialog}
onDecrypt={handleImportDecrypt}
onConfirm={handleImportConfirm}
/>
</div>
);
}