mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-01 18:11:20 +08:00
bugfix dashboard empty state (#709)
This commit is contained in:
126
web/src/App.tsx
126
web/src/App.tsx
@@ -101,11 +101,12 @@ function App() {
|
||||
// };
|
||||
|
||||
// 获取trader列表(仅在用户登录时)
|
||||
const { data: traders } = useSWR<TraderInfo[]>(
|
||||
const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
|
||||
user && token ? 'traders' : null,
|
||||
api.getTraders,
|
||||
{
|
||||
refreshInterval: 10000,
|
||||
shouldRetryOnError: false, // 避免在后端未运行时无限重试
|
||||
}
|
||||
)
|
||||
|
||||
@@ -360,8 +361,14 @@ function App() {
|
||||
lastUpdate={lastUpdate}
|
||||
language={language}
|
||||
traders={traders}
|
||||
tradersError={tradersError}
|
||||
selectedTraderId={selectedTraderId}
|
||||
onTraderSelect={setSelectedTraderId}
|
||||
onNavigateToTraders={() => {
|
||||
window.history.pushState({}, '', '/traders')
|
||||
setRoute('/traders')
|
||||
setCurrentPage('traders')
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
@@ -426,13 +433,17 @@ function TraderDetailsPage({
|
||||
lastUpdate,
|
||||
language,
|
||||
traders,
|
||||
tradersError,
|
||||
selectedTraderId,
|
||||
onTraderSelect,
|
||||
onNavigateToTraders,
|
||||
}: {
|
||||
selectedTrader?: TraderInfo
|
||||
traders?: TraderInfo[]
|
||||
tradersError?: Error
|
||||
selectedTraderId?: string
|
||||
onTraderSelect: (traderId: string) => void
|
||||
onNavigateToTraders: () => void
|
||||
status?: SystemStatus
|
||||
account?: AccountInfo
|
||||
positions?: Position[]
|
||||
@@ -441,6 +452,119 @@ function TraderDetailsPage({
|
||||
lastUpdate: string
|
||||
language: Language
|
||||
}) {
|
||||
// If API failed with error, show empty state (likely backend not running)
|
||||
if (tradersError) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
{/* Icon */}
|
||||
<div
|
||||
className="w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.1)',
|
||||
border: '2px solid rgba(240, 185, 11, 0.3)',
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="w-12 h-12"
|
||||
style={{ color: '#F0B90B' }}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h2 className="text-2xl font-bold mb-3" style={{ color: '#EAECEF' }}>
|
||||
{t('dashboardEmptyTitle', language)}
|
||||
</h2>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base mb-6" style={{ color: '#848E9C' }}>
|
||||
{t('dashboardEmptyDescription', language)}
|
||||
</p>
|
||||
|
||||
{/* CTA Button */}
|
||||
<button
|
||||
onClick={onNavigateToTraders}
|
||||
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105 active:scale-95"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||
color: '#0B0E11',
|
||||
boxShadow: '0 4px 12px rgba(240, 185, 11, 0.3)',
|
||||
}}
|
||||
>
|
||||
{t('goToTradersPage', language)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// If traders is loaded and empty, show empty state
|
||||
if (traders && traders.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-[60vh]">
|
||||
<div className="text-center max-w-md mx-auto px-6">
|
||||
{/* Icon */}
|
||||
<div
|
||||
className="w-24 h-24 mx-auto mb-6 rounded-full flex items-center justify-center"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.1)',
|
||||
border: '2px solid rgba(240, 185, 11, 0.3)',
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="w-12 h-12"
|
||||
style={{ color: '#F0B90B' }}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h2 className="text-2xl font-bold mb-3" style={{ color: '#EAECEF' }}>
|
||||
{t('dashboardEmptyTitle', language)}
|
||||
</h2>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base mb-6" style={{ color: '#848E9C' }}>
|
||||
{t('dashboardEmptyDescription', language)}
|
||||
</p>
|
||||
|
||||
{/* CTA Button */}
|
||||
<button
|
||||
onClick={onNavigateToTraders}
|
||||
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105 active:scale-95"
|
||||
style={{
|
||||
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||
color: '#0B0E11',
|
||||
boxShadow: '0 4px 12px rgba(240, 185, 11, 0.3)',
|
||||
}}
|
||||
>
|
||||
{t('goToTradersPage', language)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// If traders is still loading or selectedTrader is not ready, show skeleton
|
||||
if (!selectedTrader) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
AlertTriangle,
|
||||
BookOpen,
|
||||
HelpCircle,
|
||||
Radio,
|
||||
} from 'lucide-react'
|
||||
|
||||
// 获取友好的AI模型名称
|
||||
@@ -702,7 +703,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-x-auto flex-wrap md:flex-nowrap">
|
||||
<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-hidden flex-wrap md:flex-nowrap">
|
||||
<button
|
||||
onClick={handleAddModel}
|
||||
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||
@@ -731,14 +732,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
<button
|
||||
onClick={() => setShowSignalSourceModal(true)}
|
||||
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
|
||||
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||
style={{
|
||||
background: '#2B3139',
|
||||
color: '#EAECEF',
|
||||
border: '1px solid #474D57',
|
||||
}}
|
||||
>
|
||||
📡 {t('signalSource', language)}
|
||||
<Radio className="w-3 h-3 md:w-4 md:h-4" />
|
||||
{t('signalSource', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -793,7 +795,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
<strong>{t('solutions', language)}</strong>
|
||||
</p>
|
||||
<ul className="list-disc list-inside space-y-1 ml-2 mt-1">
|
||||
<li>点击"📡 {t('signalSource', language)}"按钮配置API地址</li>
|
||||
<li>点击"{t('signalSource', language)}"按钮配置API地址</li>
|
||||
<li>或在交易员配置中禁用"使用币种池"和"使用OI Top"</li>
|
||||
<li>或在交易员配置中设置自定义币种列表</li>
|
||||
</ul>
|
||||
@@ -1292,7 +1294,7 @@ function SignalSourceModal({
|
||||
style={{ background: '#1E2329' }}
|
||||
>
|
||||
<h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}>
|
||||
📡 {t('signalSourceConfig', language)}
|
||||
{t('signalSourceConfig', language)}
|
||||
</h3>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
|
||||
@@ -146,6 +146,10 @@ export const translations = {
|
||||
currentTraders: 'Current Traders',
|
||||
noTraders: 'No AI Traders',
|
||||
createFirstTrader: 'Create your first AI trader to get started',
|
||||
dashboardEmptyTitle: 'No Traders Configured',
|
||||
dashboardEmptyDescription:
|
||||
"You haven't created any AI traders yet. Create your first trader to start automated trading.",
|
||||
goToTradersPage: 'Go to Traders Page',
|
||||
configureModelsFirst: 'Please configure AI models first',
|
||||
configureExchangesFirst: 'Please configure exchanges first',
|
||||
configureModelsAndExchangesFirst:
|
||||
@@ -915,6 +919,10 @@ export const translations = {
|
||||
currentTraders: '当前交易员',
|
||||
noTraders: '暂无AI交易员',
|
||||
createFirstTrader: '创建您的第一个AI交易员开始使用',
|
||||
dashboardEmptyTitle: '暂无交易员',
|
||||
dashboardEmptyDescription:
|
||||
'您还未创建任何AI交易员,创建您的第一个交易员以开始自动化交易。',
|
||||
goToTradersPage: '前往交易员页面',
|
||||
configureModelsFirst: '请先配置AI模型',
|
||||
configureExchangesFirst: '请先配置交易所',
|
||||
configureModelsAndExchangesFirst: '请先配置AI模型和交易所',
|
||||
|
||||
Reference in New Issue
Block a user