feat(settings): 新增设置页重启按钮,区分保存后触发与手动触发
状态从 showRestartDialog: bool 改为 restartDialogMode: 'saved' | 'manual' | null,三态清晰区分触发来源。 改动:- 保存后自动弹出 'saved' 模式对话框(标题 '配置已保存',按钮 '稍后') - 新增独立 '重启' 按钮,点击弹出 'manual' 模式对话框(标题 '重启服务',按钮 '取消'),dirty 时提示未保存更改 - 重启中禁用保存与重启按钮,handleClose 阻断关闭并提示 - ESC 在对话框打开时仅关闭对话框,不关闭整个页面 - 重启按钮 loading 状态与 restarting 同步
This commit is contained in:
parent
2255d2e774
commit
235f57c9ae
@ -49,7 +49,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [toast, setToastState] = useState('');
|
const [toast, setToastState] = useState('');
|
||||||
const [dirty, setDirty] = useState(false);
|
const [dirty, setDirty] = useState(false);
|
||||||
const [showRestartDialog, setShowRestartDialog] = useState(false);
|
const [restartDialogMode, setRestartDialogMode] = useState<'saved' | 'manual' | null>(null);
|
||||||
const [restarting, setRestarting] = useState(false);
|
const [restarting, setRestarting] = useState(false);
|
||||||
|
|
||||||
// toast 自动清除:每次设置非空 toast 时启动 3s 计时器
|
// toast 自动清除:每次设置非空 toast 时启动 3s 计时器
|
||||||
@ -77,9 +77,13 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClose = useCallback(() => {
|
const handleClose = useCallback(() => {
|
||||||
|
if (restarting) {
|
||||||
|
showToast('服务正在重启,请等待完成');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (dirty && !confirm('有未保存的更改,确定要关闭吗?')) return;
|
if (dirty && !confirm('有未保存的更改,确定要关闭吗?')) return;
|
||||||
onClose();
|
onClose();
|
||||||
}, [dirty, onClose]);
|
}, [dirty, restarting, showToast, onClose]);
|
||||||
|
|
||||||
// Load config
|
// Load config
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -93,11 +97,18 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
// ESC 关闭(子模态框的 ESC 在 capture 阶段已 stopPropagation,不会触发此处)
|
// ESC 关闭(子模态框的 ESC 在 capture 阶段已 stopPropagation,不会触发此处)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const h = (e: KeyboardEvent) => {
|
const h = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Escape') handleClose();
|
if (e.key === 'Escape') {
|
||||||
|
// 重启确认对话框打开时,ESC 仅关闭对话框,不关闭整个页面
|
||||||
|
if (restartDialogMode !== null) {
|
||||||
|
setRestartDialogMode(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener('keydown', h);
|
document.addEventListener('keydown', h);
|
||||||
return () => document.removeEventListener('keydown', h);
|
return () => document.removeEventListener('keydown', h);
|
||||||
}, [handleClose]);
|
}, [handleClose, restartDialogMode]);
|
||||||
|
|
||||||
const update = useCallback(<K extends keyof AppConfig>(key: K, value: AppConfig[K]) => {
|
const update = useCallback(<K extends keyof AppConfig>(key: K, value: AppConfig[K]) => {
|
||||||
setConfig((prev) => (prev ? { ...prev, [key]: value } : prev));
|
setConfig((prev) => (prev ? { ...prev, [key]: value } : prev));
|
||||||
@ -115,13 +126,13 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
// Config is now synced to both disk and in-memory state,
|
// Config is now synced to both disk and in-memory state,
|
||||||
// so the local state is already correct. No need to re-fetch.
|
// so the local state is already correct. No need to re-fetch.
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
setShowRestartDialog(true);
|
setRestartDialogMode('saved');
|
||||||
}
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRestart = async () => {
|
const handleRestart = async () => {
|
||||||
setShowRestartDialog(false);
|
setRestartDialogMode(null);
|
||||||
setRestarting(true);
|
setRestarting(true);
|
||||||
try {
|
try {
|
||||||
const { status, data } = await restartGateway();
|
const { status, data } = await restartGateway();
|
||||||
@ -289,12 +300,21 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={saving || !dirty}
|
disabled={saving || restarting || !dirty}
|
||||||
className="flex items-center gap-2 px-5 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
|
className="flex items-center gap-2 px-5 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
{saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
|
||||||
{saving ? '保存中...' : '保存配置'}
|
{saving ? '保存中...' : '保存配置'}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setRestartDialogMode('manual')}
|
||||||
|
disabled={restarting || saving}
|
||||||
|
title="重启网关服务"
|
||||||
|
className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-[var(--text-primary)] bg-[var(--bg-tertiary)] border border-[var(--border-color)] hover:bg-[var(--overlay-hover)] transition-all disabled:opacity-40 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
{restarting ? <Loader2 className="h-4 w-4 animate-spin" /> : <RefreshCw className="h-4 w-4" />}
|
||||||
|
{restarting ? '重启中...' : '重启'}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Toast */}
|
{/* Toast */}
|
||||||
@ -310,7 +330,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Restart confirmation dialog */}
|
{/* Restart confirmation dialog */}
|
||||||
{showRestartDialog && (
|
{restartDialogMode && (
|
||||||
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm rounded-2xl">
|
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm rounded-2xl">
|
||||||
<div className="bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl p-6 max-w-sm mx-4 shadow-2xl animate-[scaleIn_0.2s_ease-out]">
|
<div className="bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl p-6 max-w-sm mx-4 shadow-2xl animate-[scaleIn_0.2s_ease-out]">
|
||||||
<div className="flex items-center gap-3 mb-4">
|
<div className="flex items-center gap-3 mb-4">
|
||||||
@ -318,16 +338,24 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
|
|||||||
<RefreshCw className="h-5 w-5 text-[var(--accent-cyan)]" />
|
<RefreshCw className="h-5 w-5 text-[var(--accent-cyan)]" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-[var(--text-primary)]">配置已保存</h3>
|
<h3 className="text-sm font-semibold text-[var(--text-primary)]">
|
||||||
<p className="text-xs text-[var(--text-muted)]">是否立即重启服务使配置生效?</p>
|
{restartDialogMode === 'saved' ? '配置已保存' : '重启服务'}
|
||||||
|
</h3>
|
||||||
|
<p className="text-xs text-[var(--text-muted)]">
|
||||||
|
{restartDialogMode === 'saved'
|
||||||
|
? '是否立即重启服务使配置生效?'
|
||||||
|
: dirty
|
||||||
|
? '有未保存的更改,重启后将丢失。确定要重启吗?'
|
||||||
|
: '确定要重启服务吗?'}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3 justify-end">
|
<div className="flex gap-3 justify-end">
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowRestartDialog(false)}
|
onClick={() => setRestartDialogMode(null)}
|
||||||
className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
|
className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
|
||||||
>
|
>
|
||||||
稍后
|
{restartDialogMode === 'saved' ? '稍后' : '取消'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleRestart}
|
onClick={handleRestart}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user