From 10047577e1d09558b5bf94cbdd9bb6dca15cea3f Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Mon, 8 Dec 2025 11:21:09 +0800 Subject: [PATCH] fix: improve UI state updates after form submissions - StrategyStudioPage: auto-select new strategy after creation, clear selection after deletion - ModelConfigModal: add loading state and async onSave handling - ExchangeConfigModal: add loading state to prevent duplicate submissions --- .../traders/ExchangeConfigModal.tsx | 115 ++++++++++-------- .../components/traders/ModelConfigModal.tsx | 33 +++-- web/src/pages/StrategyStudioPage.tsx | 24 ++++ 3 files changed, 106 insertions(+), 66 deletions(-) diff --git a/web/src/components/traders/ExchangeConfigModal.tsx b/web/src/components/traders/ExchangeConfigModal.tsx index 80f944c3..57631499 100644 --- a/web/src/components/traders/ExchangeConfigModal.tsx +++ b/web/src/components/traders/ExchangeConfigModal.tsx @@ -84,6 +84,9 @@ export function ExchangeConfigModal({ null | 'hyperliquid' | 'aster' | 'lighter' >(null) + // 保存中状态 + const [isSaving, setIsSaving] = useState(false) + // 获取当前编辑的交易所信息 const selectedExchange = allExchanges?.find( (e) => e.id === selectedExchangeId @@ -218,59 +221,64 @@ export function ExchangeConfigModal({ const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - if (!selectedExchangeId) return + if (!selectedExchangeId || isSaving) return - // 根据交易所类型验证不同字段 - if (selectedExchange?.id === 'binance') { - if (!apiKey.trim() || !secretKey.trim()) return - await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet) - } else if (selectedExchange?.id === 'okx') { - if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return - await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet) - } else if (selectedExchange?.id === 'hyperliquid') { - if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return // 验证私钥和钱包地址 - await onSave( - selectedExchangeId, - apiKey.trim(), - '', - '', - testnet, - hyperliquidWalletAddr.trim() - ) - } else if (selectedExchange?.id === 'aster') { - if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim()) - return - await onSave( - selectedExchangeId, - '', - '', - '', - testnet, - undefined, - asterUser.trim(), - asterSigner.trim(), - asterPrivateKey.trim() - ) - } else if (selectedExchange?.id === 'lighter') { - if (!lighterWalletAddr.trim() || !lighterPrivateKey.trim()) return - await onSave( - selectedExchangeId, - lighterPrivateKey.trim(), - '', - '', - testnet, - lighterWalletAddr.trim(), - undefined, - undefined, - undefined, - lighterWalletAddr.trim(), - lighterPrivateKey.trim(), - lighterApiKeyPrivateKey.trim() - ) - } else { - // 默认情况(其他CEX交易所) - if (!apiKey.trim() || !secretKey.trim()) return - await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet) + setIsSaving(true) + try { + // 根据交易所类型验证不同字段 + if (selectedExchange?.id === 'binance') { + if (!apiKey.trim() || !secretKey.trim()) return + await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet) + } else if (selectedExchange?.id === 'okx') { + if (!apiKey.trim() || !secretKey.trim() || !passphrase.trim()) return + await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), passphrase.trim(), testnet) + } else if (selectedExchange?.id === 'hyperliquid') { + if (!apiKey.trim() || !hyperliquidWalletAddr.trim()) return // 验证私钥和钱包地址 + await onSave( + selectedExchangeId, + apiKey.trim(), + '', + '', + testnet, + hyperliquidWalletAddr.trim() + ) + } else if (selectedExchange?.id === 'aster') { + if (!asterUser.trim() || !asterSigner.trim() || !asterPrivateKey.trim()) + return + await onSave( + selectedExchangeId, + '', + '', + '', + testnet, + undefined, + asterUser.trim(), + asterSigner.trim(), + asterPrivateKey.trim() + ) + } else if (selectedExchange?.id === 'lighter') { + if (!lighterWalletAddr.trim() || !lighterPrivateKey.trim()) return + await onSave( + selectedExchangeId, + lighterPrivateKey.trim(), + '', + '', + testnet, + lighterWalletAddr.trim(), + undefined, + undefined, + undefined, + lighterWalletAddr.trim(), + lighterPrivateKey.trim(), + lighterApiKeyPrivateKey.trim() + ) + } else { + // 默认情况(其他CEX交易所) + if (!apiKey.trim() || !secretKey.trim()) return + await onSave(selectedExchangeId, apiKey.trim(), secretKey.trim(), '', testnet) + } + } finally { + setIsSaving(false) } } @@ -1000,6 +1008,7 @@ export function ExchangeConfigModal({ diff --git a/web/src/components/traders/ModelConfigModal.tsx b/web/src/components/traders/ModelConfigModal.tsx index 86297ba5..851b7538 100644 --- a/web/src/components/traders/ModelConfigModal.tsx +++ b/web/src/components/traders/ModelConfigModal.tsx @@ -14,8 +14,8 @@ interface ModelConfigModalProps { apiKey: string, baseUrl?: string, modelName?: string - ) => void - onDelete: (modelId: string) => void + ) => Promise + onDelete: (modelId: string) => Promise onClose: () => void language: Language } @@ -48,16 +48,23 @@ export function ModelConfigModal({ } }, [editingModelId, selectedModel]) - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() - if (!selectedModelId || !apiKey.trim()) return + const [isSaving, setIsSaving] = useState(false) - onSave( - selectedModelId, - apiKey.trim(), - baseUrl.trim() || undefined, - modelName.trim() || undefined - ) + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!selectedModelId || !apiKey.trim() || isSaving) return + + setIsSaving(true) + try { + await onSave( + selectedModelId, + apiKey.trim(), + baseUrl.trim() || undefined, + modelName.trim() || undefined + ) + } finally { + setIsSaving(false) + } } // 可选择的模型列表(所有支持的模型) @@ -277,11 +284,11 @@ export function ModelConfigModal({ diff --git a/web/src/pages/StrategyStudioPage.tsx b/web/src/pages/StrategyStudioPage.tsx index 783dd035..d83241d6 100644 --- a/web/src/pages/StrategyStudioPage.tsx +++ b/web/src/pages/StrategyStudioPage.tsx @@ -168,7 +168,25 @@ export function StrategyStudioPage() { }), }) if (!response.ok) throw new Error('Failed to create strategy') + const result = await response.json() await fetchStrategies() + // Auto-select the newly created strategy + if (result.id) { + const now = new Date().toISOString() + const newStrategy = { + id: result.id, + name: language === 'zh' ? '新策略' : 'New Strategy', + description: '', + is_active: false, + is_default: false, + config: defaultConfig, + created_at: now, + updated_at: now, + } + setSelectedStrategy(newStrategy) + setEditingConfig(defaultConfig) + setHasChanges(false) + } } catch (err) { setError(err instanceof Error ? err.message : 'Unknown error') } @@ -195,6 +213,12 @@ export function StrategyStudioPage() { }) if (!response.ok) throw new Error('Failed to delete strategy') notify.success(language === 'zh' ? '策略已删除' : 'Strategy deleted') + // Clear selection if deleted strategy was selected + if (selectedStrategy?.id === id) { + setSelectedStrategy(null) + setEditingConfig(null) + setHasChanges(false) + } await fetchStrategies() } catch (err) { const errorMsg = err instanceof Error ? err.message : 'Unknown error'