bugfix dashboard empty state (#709)

This commit is contained in:
Ember
2025-11-09 14:44:42 +08:00
committed by GitHub
parent b8b8feb5fb
commit a4d3cb41f1
3 changed files with 140 additions and 6 deletions

View File

@@ -101,11 +101,12 @@ function App() {
// }; // };
// 获取trader列表仅在用户登录时 // 获取trader列表仅在用户登录时
const { data: traders } = useSWR<TraderInfo[]>( const { data: traders, error: tradersError } = useSWR<TraderInfo[]>(
user && token ? 'traders' : null, user && token ? 'traders' : null,
api.getTraders, api.getTraders,
{ {
refreshInterval: 10000, refreshInterval: 10000,
shouldRetryOnError: false, // 避免在后端未运行时无限重试
} }
) )
@@ -360,8 +361,14 @@ function App() {
lastUpdate={lastUpdate} lastUpdate={lastUpdate}
language={language} language={language}
traders={traders} traders={traders}
tradersError={tradersError}
selectedTraderId={selectedTraderId} selectedTraderId={selectedTraderId}
onTraderSelect={setSelectedTraderId} onTraderSelect={setSelectedTraderId}
onNavigateToTraders={() => {
window.history.pushState({}, '', '/traders')
setRoute('/traders')
setCurrentPage('traders')
}}
/> />
)} )}
</main> </main>
@@ -426,13 +433,17 @@ function TraderDetailsPage({
lastUpdate, lastUpdate,
language, language,
traders, traders,
tradersError,
selectedTraderId, selectedTraderId,
onTraderSelect, onTraderSelect,
onNavigateToTraders,
}: { }: {
selectedTrader?: TraderInfo selectedTrader?: TraderInfo
traders?: TraderInfo[] traders?: TraderInfo[]
tradersError?: Error
selectedTraderId?: string selectedTraderId?: string
onTraderSelect: (traderId: string) => void onTraderSelect: (traderId: string) => void
onNavigateToTraders: () => void
status?: SystemStatus status?: SystemStatus
account?: AccountInfo account?: AccountInfo
positions?: Position[] positions?: Position[]
@@ -441,6 +452,119 @@ function TraderDetailsPage({
lastUpdate: string lastUpdate: string
language: Language 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) { if (!selectedTrader) {
return ( return (
<div className="space-y-6"> <div className="space-y-6">

View File

@@ -28,6 +28,7 @@ import {
AlertTriangle, AlertTriangle,
BookOpen, BookOpen,
HelpCircle, HelpCircle,
Radio,
} from 'lucide-react' } from 'lucide-react'
// 获取友好的AI模型名称 // 获取友好的AI模型名称
@@ -702,7 +703,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</div> </div>
</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 <button
onClick={handleAddModel} 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" 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 <button
onClick={() => setShowSignalSourceModal(true)} 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={{ style={{
background: '#2B3139', background: '#2B3139',
color: '#EAECEF', color: '#EAECEF',
border: '1px solid #474D57', border: '1px solid #474D57',
}} }}
> >
📡 {t('signalSource', language)} <Radio className="w-3 h-3 md:w-4 md:h-4" />
{t('signalSource', language)}
</button> </button>
<button <button
@@ -793,7 +795,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<strong>{t('solutions', language)}</strong> <strong>{t('solutions', language)}</strong>
</p> </p>
<ul className="list-disc list-inside space-y-1 ml-2 mt-1"> <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>"使用币种池""使用OI Top"</li>
<li></li> <li></li>
</ul> </ul>
@@ -1292,7 +1294,7 @@ function SignalSourceModal({
style={{ background: '#1E2329' }} style={{ background: '#1E2329' }}
> >
<h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}> <h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}>
📡 {t('signalSourceConfig', language)} {t('signalSourceConfig', language)}
</h3> </h3>
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">

View File

@@ -146,6 +146,10 @@ export const translations = {
currentTraders: 'Current Traders', currentTraders: 'Current Traders',
noTraders: 'No AI Traders', noTraders: 'No AI Traders',
createFirstTrader: 'Create your first AI trader to get started', 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', configureModelsFirst: 'Please configure AI models first',
configureExchangesFirst: 'Please configure exchanges first', configureExchangesFirst: 'Please configure exchanges first',
configureModelsAndExchangesFirst: configureModelsAndExchangesFirst:
@@ -915,6 +919,10 @@ export const translations = {
currentTraders: '当前交易员', currentTraders: '当前交易员',
noTraders: '暂无AI交易员', noTraders: '暂无AI交易员',
createFirstTrader: '创建您的第一个AI交易员开始使用', createFirstTrader: '创建您的第一个AI交易员开始使用',
dashboardEmptyTitle: '暂无交易员',
dashboardEmptyDescription:
'您还未创建任何AI交易员创建您的第一个交易员以开始自动化交易。',
goToTradersPage: '前往交易员页面',
configureModelsFirst: '请先配置AI模型', configureModelsFirst: '请先配置AI模型',
configureExchangesFirst: '请先配置交易所', configureExchangesFirst: '请先配置交易所',
configureModelsAndExchangesFirst: '请先配置AI模型和交易所', configureModelsAndExchangesFirst: '请先配置AI模型和交易所',