mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
Merge pull request #336 from 0xEmberZz/fix-mobile-display-v2
UI : Fix mobile display
This commit is contained in:
120
web/src/App.tsx
120
web/src/App.tsx
@@ -194,8 +194,9 @@ function App() {
|
||||
<div className="min-h-screen" style={{ background: '#0B0E11', color: '#EAECEF' }}>
|
||||
{/* Header - Binance Style */}
|
||||
<header className="glass sticky top-0 z-50 backdrop-blur-xl">
|
||||
<div className="max-w-[1920px] mx-auto px-6 py-4">
|
||||
<div className="relative flex items-center">
|
||||
<div className="max-w-[1920px] mx-auto px-3 md:px-6 py-4">
|
||||
{/* Desktop Layout */}
|
||||
<div className="hidden md:flex relative items-center">
|
||||
{/* Left - Logo and Title */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
@@ -210,7 +211,7 @@ function App() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Center - Page Toggle (absolutely positioned) */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
@@ -244,7 +245,7 @@ function App() {
|
||||
{t('tradingPanel', language)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Right - Actions */}
|
||||
<div className="ml-auto flex items-center gap-3">
|
||||
|
||||
@@ -257,7 +258,7 @@ function App() {
|
||||
<span className="text-sm" style={{ color: '#EAECEF' }}>{user.email}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
{/* Admin Mode Indicator */}
|
||||
{systemConfig?.admin_mode && (
|
||||
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
@@ -302,11 +303,118 @@ function App() {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Layout */}
|
||||
<div className="flex md:hidden flex-col gap-3">
|
||||
{/* Top Row - Logo, Title and Language */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src="/icons/nofx.svg?v=2" alt="NOFX" className="w-7 h-7" />
|
||||
<div>
|
||||
<h1 className="text-base font-bold" style={{ color: '#EAECEF' }}>
|
||||
{t('appTitle', language)}
|
||||
</h1>
|
||||
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||
{t('subtitle', language)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Language Toggle - Right side on mobile */}
|
||||
<div className="flex gap-1 rounded p-0.5" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
onClick={() => setLanguage('zh')}
|
||||
className="px-2 py-1 rounded text-xs font-semibold transition-all"
|
||||
style={language === 'zh'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
中文
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLanguage('en')}
|
||||
className="px-2 py-1 rounded text-xs font-semibold transition-all"
|
||||
style={language === 'en'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
EN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row - Page Toggle */}
|
||||
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
onClick={() => setCurrentPage('competition')}
|
||||
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||
style={currentPage === 'competition'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('aiCompetition', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('traders')}
|
||||
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||
style={currentPage === 'traders'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('aiTraders', language)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('trader')}
|
||||
className={`flex-1 px-2 py-1.5 rounded text-xs font-semibold transition-all`}
|
||||
style={currentPage === 'trader'
|
||||
? { background: '#F0B90B', color: '#000' }
|
||||
: { background: 'transparent', color: '#848E9C' }
|
||||
}
|
||||
>
|
||||
{t('tradingPanel', language)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Third Row - User Info and Logout */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* User Info or Admin Mode */}
|
||||
{!systemConfig?.admin_mode && user && (
|
||||
<div className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<div className="w-5 h-5 rounded-full flex items-center justify-center text-xs font-bold" style={{ background: '#F0B90B', color: '#000' }}>
|
||||
{user.email[0].toUpperCase()}
|
||||
</div>
|
||||
<span className="text-xs truncate" style={{ color: '#EAECEF' }}>{user.email}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{systemConfig?.admin_mode && (
|
||||
<div className="flex-1 flex items-center gap-2 px-2 py-1.5 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
|
||||
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||
<span className="text-xs font-semibold" style={{ color: '#F0B90B' }}>{t('adminMode', language)}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Logout Button */}
|
||||
{!systemConfig?.admin_mode && (
|
||||
<button
|
||||
onClick={logout}
|
||||
className="px-3 py-1.5 rounded text-xs font-semibold transition-all"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }}
|
||||
>
|
||||
{t('logout', language)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
<main className="max-w-[1920px] mx-auto px-6 py-6">
|
||||
<main className="max-w-[1920px] mx-auto px-3 md:px-6 py-4 md:py-6">
|
||||
{currentPage === 'competition' ? (
|
||||
<CompetitionPage />
|
||||
) : currentPage === 'traders' ? (
|
||||
|
||||
@@ -458,22 +458,22 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 animate-fade-in">
|
||||
<div className="space-y-4 md:space-y-6 animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
|
||||
<div className="flex items-center gap-3 md:gap-4">
|
||||
<div className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center" style={{
|
||||
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
|
||||
}}>
|
||||
<Bot className="w-6 h-6" style={{ color: '#000' }} />
|
||||
<Bot className="w-5 h-5 md:w-6 md:h-6" style={{ color: '#000' }} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<h1 className="text-xl md:text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
{t('aiTraders', language)}
|
||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
color: '#F0B90B'
|
||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
color: '#F0B90B'
|
||||
}}>
|
||||
{traders?.length || 0} {t('active', language)}
|
||||
</span>
|
||||
@@ -483,37 +483,37 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
|
||||
<div className="flex gap-2 md:gap-3 w-full md:w-auto overflow-x-auto flex-wrap md:flex-nowrap">
|
||||
<button
|
||||
onClick={handleAddModel}
|
||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<Plus className="w-3 h-3 md:w-4 md:h-4" />
|
||||
{t('aiModels', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={handleAddExchange}
|
||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
|
||||
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'
|
||||
}}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<Plus className="w-3 h-3 md:w-4 md:h-4" />
|
||||
{t('exchanges', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowSignalSourceModal(true)}
|
||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
|
||||
style={{
|
||||
background: '#2B3139',
|
||||
color: '#EAECEF',
|
||||
@@ -522,11 +522,11 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
>
|
||||
📡 {t('signalSource', language)}
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => setShowCreateModal(true)}
|
||||
disabled={configuredModels.length === 0 || configuredExchanges.length === 0}
|
||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
className="px-3 md:px-4 py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1 md:gap-2 whitespace-nowrap"
|
||||
style={{
|
||||
background: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#F0B90B' : '#2B3139',
|
||||
color: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#000' : '#848E9C'
|
||||
@@ -539,30 +539,30 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</div>
|
||||
|
||||
{/* Configuration Status */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
|
||||
{/* AI Models */}
|
||||
<div className="binance-card p-4">
|
||||
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Brain className="w-5 h-5" style={{ color: '#60a5fa' }} />
|
||||
<div className="binance-card p-3 md:p-4">
|
||||
<h3 className="text-base md:text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Brain className="w-4 h-4 md:w-5 md:h-5" style={{ color: '#60a5fa' }} />
|
||||
{t('aiModels', language)}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 md:space-y-3">
|
||||
{configuredModels.map(model => {
|
||||
const inUse = isModelInUse(model.id);
|
||||
return (
|
||||
<div
|
||||
key={model.id}
|
||||
className={`flex items-center justify-between p-3 rounded transition-all ${
|
||||
<div
|
||||
key={model.id}
|
||||
className={`flex items-center justify-between p-2 md:p-3 rounded transition-all ${
|
||||
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
||||
}`}
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||
onClick={() => handleModelClick(model.id)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
{getModelIcon(model.provider || model.id, { width: 32, height: 32 }) || (
|
||||
<div className="w-8 h-8 rounded-full flex items-center justify-center text-sm font-bold"
|
||||
style={{
|
||||
<div className="flex items-center gap-2 md:gap-3">
|
||||
<div className="w-7 h-7 md:w-8 md:h-8 flex items-center justify-center flex-shrink-0">
|
||||
{getModelIcon(model.provider || model.id, { width: 28, height: 28 }) || (
|
||||
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full flex items-center justify-center text-xs md:text-sm font-bold"
|
||||
style={{
|
||||
background: model.id === 'deepseek' ? '#60a5fa' : '#c084fc',
|
||||
color: '#fff'
|
||||
}}>
|
||||
@@ -570,63 +570,63 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(model.name)}</div>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold text-sm md:text-base truncate" style={{ color: '#EAECEF' }}>{getShortName(model.name)}</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{inUse ? t('inUse', language) : model.enabled ? t('enabled', language) : t('configured', language)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-3 h-3 rounded-full ${model.enabled && model.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||
<div className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${model.enabled && model.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{configuredModels.length === 0 && (
|
||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
||||
<Brain className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-sm">{t('noModelsConfigured', language)}</div>
|
||||
<div className="text-center py-6 md:py-8" style={{ color: '#848E9C' }}>
|
||||
<Brain className="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-xs md:text-sm">{t('noModelsConfigured', language)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Exchanges */}
|
||||
<div className="binance-card p-4">
|
||||
<h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Landmark className="w-5 h-5" style={{ color: '#F0B90B' }} />
|
||||
<div className="binance-card p-3 md:p-4">
|
||||
<h3 className="text-base md:text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Landmark className="w-4 h-4 md:w-5 md:h-5" style={{ color: '#F0B90B' }} />
|
||||
{t('exchanges', language)}
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-2 md:space-y-3">
|
||||
{configuredExchanges.map(exchange => {
|
||||
const inUse = isExchangeInUse(exchange.id);
|
||||
return (
|
||||
<div
|
||||
key={exchange.id}
|
||||
className={`flex items-center justify-between p-3 rounded transition-all ${
|
||||
<div
|
||||
key={exchange.id}
|
||||
className={`flex items-center justify-between p-2 md:p-3 rounded transition-all ${
|
||||
inUse ? 'cursor-not-allowed' : 'cursor-pointer hover:bg-gray-700'
|
||||
}`}
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||
onClick={() => handleExchangeClick(exchange.id)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 flex items-center justify-center">
|
||||
{getExchangeIcon(exchange.id, { width: 32, height: 32 })}
|
||||
<div className="flex items-center gap-2 md:gap-3">
|
||||
<div className="w-7 h-7 md:w-8 md:h-8 flex items-center justify-center flex-shrink-0">
|
||||
{getExchangeIcon(exchange.id, { width: 28, height: 28 })}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(exchange.name)}</div>
|
||||
<div className="min-w-0">
|
||||
<div className="font-semibold text-sm md:text-base truncate" style={{ color: '#EAECEF' }}>{getShortName(exchange.name)}</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{exchange.type.toUpperCase()} • {inUse ? t('inUse', language) : exchange.enabled ? t('enabled', language) : t('configured', language)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`w-3 h-3 rounded-full ${exchange.enabled && exchange.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||
<div className={`w-2.5 h-2.5 md:w-3 md:h-3 rounded-full flex-shrink-0 ${exchange.enabled && exchange.apiKey ? 'bg-green-400' : 'bg-gray-500'}`} />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{configuredExchanges.length === 0 && (
|
||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
||||
<Landmark className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-sm">{t('noExchangesConfigured', language)}</div>
|
||||
<div className="text-center py-6 md:py-8" style={{ color: '#848E9C' }}>
|
||||
<Landmark className="w-10 h-10 md:w-12 md:h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-xs md:text-sm">{t('noExchangesConfigured', language)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -634,47 +634,47 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</div>
|
||||
|
||||
{/* Traders List */}
|
||||
<div className="binance-card p-6">
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Users className="w-6 h-6" style={{ color: '#F0B90B' }} />
|
||||
<div className="binance-card p-4 md:p-6">
|
||||
<div className="flex items-center justify-between mb-4 md:mb-5">
|
||||
<h2 className="text-lg md:text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<Users className="w-5 h-5 md:w-6 md:h-6" style={{ color: '#F0B90B' }} />
|
||||
{t('currentTraders', language)}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{traders && traders.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3 md:space-y-4">
|
||||
{traders.map(trader => (
|
||||
<div key={trader.trader_id}
|
||||
className="flex items-center justify-between p-4 rounded transition-all hover:translate-y-[-1px]"
|
||||
className="flex flex-col md:flex-row md:items-center justify-between p-3 md:p-4 rounded transition-all hover:translate-y-[-1px] gap-3 md:gap-4"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-full flex items-center justify-center"
|
||||
<div className="flex items-center gap-3 md:gap-4">
|
||||
<div className="w-10 h-10 md:w-12 md:h-12 rounded-full flex items-center justify-center flex-shrink-0"
|
||||
style={{
|
||||
background: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc',
|
||||
color: '#fff'
|
||||
}}>
|
||||
<Bot className="w-6 h-6" />
|
||||
<Bot className="w-5 h-5 md:w-6 md:h-6" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold text-lg" style={{ color: '#EAECEF' }}>
|
||||
<div className="min-w-0">
|
||||
<div className="font-bold text-base md:text-lg truncate" style={{ color: '#EAECEF' }}>
|
||||
{trader.trader_name}
|
||||
</div>
|
||||
<div className="text-sm" style={{
|
||||
color: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc'
|
||||
<div className="text-xs md:text-sm truncate" style={{
|
||||
color: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc'
|
||||
}}>
|
||||
{getModelDisplayName(trader.ai_model.split('_').pop() || trader.ai_model)} Model • {trader.exchange_id?.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-3 md:gap-4 flex-wrap md:flex-nowrap">
|
||||
{/* Status */}
|
||||
<div className="text-center">
|
||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('status', language)}</div>
|
||||
<div className={`px-3 py-1 rounded text-xs font-bold ${
|
||||
<div className={`px-2 md:px-3 py-1 rounded text-xs font-bold ${
|
||||
trader.is_running ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||
}`} style={trader.is_running
|
||||
}`} style={trader.is_running
|
||||
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||
}>
|
||||
@@ -683,20 +683,20 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-1.5 md:gap-2 flex-wrap md:flex-nowrap">
|
||||
<button
|
||||
onClick={() => onTraderSelect?.(trader.trader_id)}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1"
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1 whitespace-nowrap"
|
||||
style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }}
|
||||
>
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
<BarChart3 className="w-3 h-3 md:w-4 md:h-4" />
|
||||
{t('view', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleEditTrader(trader.trader_id)}
|
||||
disabled={trader.is_running}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed whitespace-nowrap"
|
||||
style={{
|
||||
background: trader.is_running ? 'rgba(132, 142, 156, 0.1)' : 'rgba(255, 193, 7, 0.1)',
|
||||
color: trader.is_running ? '#848E9C' : '#FFC107'
|
||||
@@ -704,10 +704,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
>
|
||||
✏️ {t('edit', language)}
|
||||
</button>
|
||||
|
||||
|
||||
<button
|
||||
onClick={() => handleToggleTrader(trader.trader_id, trader.is_running || false)}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105 whitespace-nowrap"
|
||||
style={trader.is_running
|
||||
? { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||
: { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
||||
@@ -718,10 +718,10 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
|
||||
<button
|
||||
onClick={() => handleDeleteTrader(trader.trader_id)}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
className="px-2 md:px-3 py-1.5 md:py-2 rounded text-xs md:text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
<Trash2 className="w-3 h-3 md:w-4 md:h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -729,15 +729,15 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-16" style={{ color: '#848E9C' }}>
|
||||
<Bot className="w-24 h-24 mx-auto mb-4 opacity-50" />
|
||||
<div className="text-lg font-semibold mb-2">{t('noTraders', language)}</div>
|
||||
<div className="text-sm mb-4">{t('createFirstTrader', language)}</div>
|
||||
<div className="text-center py-12 md:py-16" style={{ color: '#848E9C' }}>
|
||||
<Bot className="w-16 h-16 md:w-24 md:h-24 mx-auto mb-3 md:mb-4 opacity-50" />
|
||||
<div className="text-base md:text-lg font-semibold mb-2">{t('noTraders', language)}</div>
|
||||
<div className="text-xs md:text-sm mb-3 md:mb-4">{t('createFirstTrader', language)}</div>
|
||||
{(configuredModels.length === 0 || configuredExchanges.length === 0) && (
|
||||
<div className="text-sm text-yellow-500">
|
||||
{configuredModels.length === 0 && configuredExchanges.length === 0
|
||||
<div className="text-xs md:text-sm text-yellow-500">
|
||||
{configuredModels.length === 0 && configuredExchanges.length === 0
|
||||
? t('configureModelsAndExchangesFirst', language)
|
||||
: configuredModels.length === 0
|
||||
: configuredModels.length === 0
|
||||
? t('configureModelsFirst', language)
|
||||
: t('configureExchangesFirst', language)
|
||||
}
|
||||
|
||||
@@ -313,24 +313,24 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="mt-6 grid grid-cols-4 gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="mt-6 grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4 pt-5" style={{ borderTop: '1px solid #2B3139' }}>
|
||||
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('comparisonMode', language)}</div>
|
||||
<div className="text-base font-bold" style={{ color: '#EAECEF' }}>PnL %</div>
|
||||
<div className="text-sm md:text-base font-bold" style={{ color: '#EAECEF' }}>PnL %</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('dataPoints', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>{t('count', language, {count: combinedData.length})}</div>
|
||||
<div className="text-sm md:text-base font-bold mono" style={{ color: '#EAECEF' }}>{t('count', language, {count: combinedData.length})}</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentGap', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: currentGap > 1 ? '#F0B90B' : '#EAECEF' }}>
|
||||
<div className="text-sm md:text-base font-bold mono" style={{ color: currentGap > 1 ? '#F0B90B' : '#EAECEF' }}>
|
||||
{currentGap.toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="p-2 md:p-3 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}>
|
||||
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div>
|
||||
<div className="text-base font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
<div className="text-sm md:text-base font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{combinedData.length > MAX_DISPLAY_POINTS
|
||||
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
|
||||
: t('allData', language)}
|
||||
|
||||
@@ -73,13 +73,16 @@ export function CompetitionPage() {
|
||||
return (
|
||||
<div className="space-y-5 animate-fade-in">
|
||||
{/* Competition Header - 精简版 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{ background: 'rgba(240, 185, 11, 0.15)', border: '1px solid rgba(240,185,11,0.3)' }}>
|
||||
<Trophy className="w-6 h-6" style={{ color: '#F0B90B' }} />
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-3 md:gap-0">
|
||||
<div className="flex items-center gap-3 md:gap-4">
|
||||
<div className="w-10 h-10 md:w-12 md:h-12 rounded-xl flex items-center justify-center" style={{
|
||||
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
|
||||
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
|
||||
}}>
|
||||
<Trophy className="w-6 h-6 md:w-7 md:h-7" style={{ color: '#000' }} />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
<h1 className="text-xl md:text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
|
||||
{t('aiCompetition', language)}
|
||||
<span className="text-xs font-normal px-2 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.15)', color: '#F0B90B' }}>
|
||||
{competition.count} {t('traders', language)}
|
||||
@@ -90,9 +93,9 @@ export function CompetitionPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-left md:text-right w-full md:w-auto">
|
||||
<div className="text-xs mb-1" style={{ color: '#848E9C' }}>{t('leader', language)}</div>
|
||||
<div className="text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
||||
<div className="text-base md:text-lg font-bold" style={{ color: '#F0B90B' }}>{leader?.trader_name}</div>
|
||||
<div className="text-sm font-semibold" style={{ color: (leader?.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||
{(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||
</div>
|
||||
@@ -155,20 +158,20 @@ export function CompetitionPage() {
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2 md:gap-3 flex-wrap md:flex-nowrap">
|
||||
{/* Total Equity */}
|
||||
<div className="text-right">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('equity', language)}</div>
|
||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
<div className="text-xs md:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{trader.total_equity?.toFixed(2) || '0.00'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* P&L */}
|
||||
<div className="text-right min-w-[90px]">
|
||||
<div className="text-right min-w-[70px] md:min-w-[90px]">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pnl', language)}</div>
|
||||
<div
|
||||
className="text-lg font-bold mono"
|
||||
className="text-base md:text-lg font-bold mono"
|
||||
style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}
|
||||
>
|
||||
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}
|
||||
@@ -182,7 +185,7 @@ export function CompetitionPage() {
|
||||
{/* Positions */}
|
||||
<div className="text-right">
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>{t('pos', language)}</div>
|
||||
<div className="text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
<div className="text-xs md:text-sm font-bold mono" style={{ color: '#EAECEF' }}>
|
||||
{trader.position_count}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
@@ -242,15 +245,12 @@ export function CompetitionPage() {
|
||||
>
|
||||
<div className="text-center">
|
||||
<div
|
||||
className="text-base font-bold mb-1"
|
||||
className="text-sm md:text-base font-bold mb-2"
|
||||
style={{ color: getTraderColor(sortedTraders, trader.trader_id) }}
|
||||
>
|
||||
{trader.trader_name}
|
||||
</div>
|
||||
<div className="text-xs mono mb-2" style={{ color: '#848E9C' }}>
|
||||
{trader.ai_model.toUpperCase()} + {trader.exchange.toUpperCase()}
|
||||
</div>
|
||||
<div className="text-2xl font-bold mono mb-1" style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||
<div className="text-lg md:text-2xl font-bold mono mb-1" style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }}>
|
||||
{(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
|
||||
</div>
|
||||
{isWinning && gap > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user