Merge pull request #138 from zhoujunhehe/dev

UI feat: enhance UI with Lucide icons & add LOGO
This commit is contained in:
tinkle-community
2025-11-01 02:21:44 +08:00
committed by GitHub
12 changed files with 391 additions and 221 deletions

10
web/package-lock.json generated
View File

@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"lucide-react": "^0.552.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"recharts": "^2.15.2", "recharts": "^2.15.2",
@@ -2156,6 +2157,15 @@
"yallist": "^3.0.2" "yallist": "^3.0.2"
} }
}, },
"node_modules/lucide-react": {
"version": "0.552.0",
"resolved": "https://registry.npmmirror.com/lucide-react/-/lucide-react-0.552.0.tgz",
"integrity": "sha512-g9WCjmfwqbexSnZE+2cl21PCfXOcqnGeWeMTNAOGEfpPbm/ZF4YIq77Z8qWrxbu660EKuLB4nSLggoKnCb+isw==",
"license": "ISC",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

View File

@@ -8,22 +8,23 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"lucide-react": "^0.552.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"zustand": "^5.0.2",
"swr": "^2.2.5",
"recharts": "^2.15.2", "recharts": "^2.15.2",
"date-fns": "^4.1.0", "swr": "^2.2.5",
"clsx": "^2.1.1" "zustand": "^5.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.17", "@types/react": "^18.3.17",
"@types/react-dom": "^18.3.5", "@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.8.3", "autoprefixer": "^10.4.20",
"vite": "^6.0.7",
"tailwindcss": "^3.4.17",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"autoprefixer": "^10.4.20" "tailwindcss": "^3.4.17",
"typescript": "^5.8.3",
"vite": "^6.0.7"
} }
} }

BIN
web/public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -11,6 +11,7 @@ import { LanguageProvider, useLanguage } from './contexts/LanguageContext';
import { AuthProvider, useAuth } from './contexts/AuthContext'; import { AuthProvider, useAuth } from './contexts/AuthContext';
import { t, type Language } from './i18n/translations'; import { t, type Language } from './i18n/translations';
import { useSystemConfig } from './hooks/useSystemConfig'; import { useSystemConfig } from './hooks/useSystemConfig';
import { Bot, RefreshCw, TrendingUp, BarChart3, Brain, Download, Upload, Check, X, AlertCircle, Zap, TrendingUp as ArrowUp, TrendingDown as ArrowDown } from 'lucide-react';
import type { import type {
SystemStatus, SystemStatus,
AccountInfo, AccountInfo,
@@ -175,10 +176,7 @@ function App() {
return ( return (
<div className="min-h-screen flex items-center justify-center" style={{ background: '#0B0E11' }}> <div className="min-h-screen flex items-center justify-center" style={{ background: '#0B0E11' }}>
<div className="text-center"> <div className="text-center">
<div className="w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center text-3xl animate-spin" <img src="/images/logo.png" alt="NoFx Logo" className="w-16 h-16 mx-auto mb-4 animate-pulse" />
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
</div>
<p style={{ color: '#EAECEF' }}>...</p> <p style={{ color: '#EAECEF' }}>...</p>
</div> </div>
</div> </div>
@@ -201,9 +199,7 @@ function App() {
<div className="relative flex items-center"> <div className="relative flex items-center">
{/* Left - Logo and Title */} {/* Left - Logo and Title */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full flex items-center justify-center text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}> <img src="/images/logo.png" alt="NoFx Logo" className="w-8 h-8" />
</div>
<div> <div>
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}> <h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
{t('appTitle', language)} {t('appTitle', language)}
@@ -264,7 +260,8 @@ function App() {
{/* Admin Mode Indicator */} {/* Admin Mode Indicator */}
{systemConfig?.admin_mode && ( {systemConfig?.admin_mode && (
<div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}> <div className="flex items-center gap-2 px-3 py-2 rounded" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}> </span> <Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
<span className="text-sm font-semibold" style={{ color: '#F0B90B' }}></span>
</div> </div>
)} )}
@@ -429,9 +426,9 @@ function TraderDetailsPage({
<div className="mb-6 rounded p-6 animate-scale-in" style={{ background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%)', border: '1px solid rgba(240, 185, 11, 0.2)', boxShadow: '0 0 30px rgba(240, 185, 11, 0.15)' }}> <div className="mb-6 rounded p-6 animate-scale-in" style={{ background: 'linear-gradient(135deg, rgba(240, 185, 11, 0.15) 0%, rgba(252, 213, 53, 0.05) 100%)', border: '1px solid rgba(240, 185, 11, 0.2)', boxShadow: '0 0 30px rgba(240, 185, 11, 0.15)' }}>
<div className="flex items-start justify-between mb-3"> <div className="flex items-start justify-between mb-3">
<h2 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}> <h2 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
<span className="w-10 h-10 rounded-full flex items-center justify-center text-xl" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}> <div className="w-10 h-10 rounded-full flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
🤖 <Bot className="w-6 h-6" style={{ color: '#000' }} />
</span> </div>
{selectedTrader.trader_name} {selectedTrader.trader_name}
</h2> </h2>
@@ -470,8 +467,9 @@ function TraderDetailsPage({
{/* Debug Info */} {/* Debug Info */}
{account && ( {account && (
<div className="mb-4 p-3 rounded text-xs font-mono" style={{ background: '#1E2329', border: '1px solid #2B3139' }}> <div className="mb-4 p-3 rounded text-xs font-mono" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div style={{ color: '#848E9C' }}> <div className="flex items-center gap-2" style={{ color: '#848E9C' }}>
🔄 Last Update: {lastUpdate} | Total Equity: {account?.total_equity?.toFixed(2) || '0.00'} | <RefreshCw className="w-3 h-3" />
Last Update: {lastUpdate} | Total Equity: {account?.total_equity?.toFixed(2) || '0.00'} |
Available: {account?.available_balance?.toFixed(2) || '0.00'} | P&L: {account?.total_pnl?.toFixed(2) || '0.00'}{' '} Available: {account?.available_balance?.toFixed(2) || '0.00'} | P&L: {account?.total_pnl?.toFixed(2) || '0.00'}{' '}
({account?.total_pnl_pct?.toFixed(2) || '0.00'}%) ({account?.total_pnl_pct?.toFixed(2) || '0.00'}%)
</div> </div>
@@ -517,7 +515,8 @@ function TraderDetailsPage({
<div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.15s' }}> <div className="binance-card p-6 animate-slide-in" style={{ animationDelay: '0.15s' }}>
<div className="flex items-center justify-between mb-5"> <div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}> <h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
📈 {t('currentPositions', language)} <TrendingUp className="w-5 h-5" style={{ color: '#0ECB81' }} />
{t('currentPositions', language)}
</h2> </h2>
{positions && positions.length > 0 && ( {positions && positions.length > 0 && (
<div className="text-xs px-3 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}> <div className="text-xs px-3 py-1 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', color: '#F0B90B', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
@@ -581,7 +580,9 @@ function TraderDetailsPage({
</div> </div>
) : ( ) : (
<div className="text-center py-16" style={{ color: '#848E9C' }}> <div className="text-center py-16" style={{ color: '#848E9C' }}>
<div className="text-6xl mb-4 opacity-50">📊</div> <div className="mb-4 flex justify-center opacity-50">
<BarChart3 className="w-16 h-16" />
</div>
<div className="text-lg font-semibold mb-2">{t('noPositions', language)}</div> <div className="text-lg font-semibold mb-2">{t('noPositions', language)}</div>
<div className="text-sm">{t('noActivePositions', language)}</div> <div className="text-sm">{t('noActivePositions', language)}</div>
</div> </div>
@@ -594,11 +595,11 @@ function TraderDetailsPage({
<div className="binance-card p-6 animate-slide-in h-fit lg:sticky lg:top-24 lg:max-h-[calc(100vh-120px)]" style={{ animationDelay: '0.2s' }}> <div className="binance-card p-6 animate-slide-in h-fit lg:sticky lg:top-24 lg:max-h-[calc(100vh-120px)]" style={{ animationDelay: '0.2s' }}>
{/* 标题 */} {/* 标题 */}
<div className="flex items-center gap-3 mb-5 pb-4 border-b" style={{ borderColor: '#2B3139' }}> <div className="flex items-center gap-3 mb-5 pb-4 border-b" style={{ borderColor: '#2B3139' }}>
<div className="w-10 h-10 rounded-xl flex items-center justify-center text-xl" style={{ <div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{
background: 'linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%)', background: 'linear-gradient(135deg, #6366F1 0%, #8B5CF6 100%)',
boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)' boxShadow: '0 4px 14px rgba(99, 102, 241, 0.4)'
}}> }}>
🧠 <Brain className="w-6 h-6" style={{ color: '#FFF' }} />
</div> </div>
<div> <div>
<h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>{t('recentDecisions', language)}</h2> <h2 className="text-xl font-bold" style={{ color: '#EAECEF' }}>{t('recentDecisions', language)}</h2>
@@ -618,7 +619,9 @@ function TraderDetailsPage({
)) ))
) : ( ) : (
<div className="py-16 text-center"> <div className="py-16 text-center">
<div className="text-6xl mb-4 opacity-30">🧠</div> <div className="mb-4 flex justify-center opacity-30">
<Brain className="w-16 h-16" style={{ color: '#8B5CF6' }} />
</div>
<div className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}>{t('noDecisionsYet', language)}</div> <div className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}>{t('noDecisionsYet', language)}</div>
<div className="text-sm" style={{ color: '#848E9C' }}>{t('aiDecisionsWillAppear', language)}</div> <div className="text-sm" style={{ color: '#848E9C' }}>{t('aiDecisionsWillAppear', language)}</div>
</div> </div>
@@ -657,10 +660,11 @@ function StatCard({
{change !== undefined && ( {change !== undefined && (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<div <div
className="text-sm mono font-bold" className="text-sm mono font-bold flex items-center gap-1"
style={{ color: positive ? '#0ECB81' : '#F6465D' }} style={{ color: positive ? '#0ECB81' : '#F6465D' }}
> >
{positive ? '▲' : '▼'} {positive ? '+' : ''} {positive ? <ArrowUp className="w-3 h-3" /> : <ArrowDown className="w-3 h-3" />}
{positive ? '+' : ''}
{change.toFixed(2)}% {change.toFixed(2)}%
</div> </div>
</div> </div>
@@ -704,7 +708,8 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua
className="flex items-center gap-2 text-sm transition-colors" className="flex items-center gap-2 text-sm transition-colors"
style={{ color: '#60a5fa' }} style={{ color: '#60a5fa' }}
> >
<span className="font-semibold">📥 {t('inputPrompt', language)}</span> <Download className="w-4 h-4" />
<span className="font-semibold">{t('inputPrompt', language)}</span>
<span className="text-xs">{showInputPrompt ? t('collapse', language) : t('expand', language)}</span> <span className="text-xs">{showInputPrompt ? t('collapse', language) : t('expand', language)}</span>
</button> </button>
{showInputPrompt && ( {showInputPrompt && (
@@ -723,7 +728,8 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua
className="flex items-center gap-2 text-sm transition-colors" className="flex items-center gap-2 text-sm transition-colors"
style={{ color: '#F0B90B' }} style={{ color: '#F0B90B' }}
> >
<span className="font-semibold">📤 {t('aiThinking', language)}</span> <Upload className="w-4 h-4" />
<span className="font-semibold">{t('aiThinking', language)}</span>
<span className="text-xs">{showCoT ? t('collapse', language) : t('expand', language)}</span> <span className="text-xs">{showCoT ? t('collapse', language) : t('expand', language)}</span>
</button> </button>
{showCoT && ( {showCoT && (
@@ -753,9 +759,11 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua
{action.price > 0 && ( {action.price > 0 && (
<span className="font-mono text-xs" style={{ color: '#848E9C' }}>@{action.price.toFixed(4)}</span> <span className="font-mono text-xs" style={{ color: '#848E9C' }}>@{action.price.toFixed(4)}</span>
)} )}
<span style={{ color: action.success ? '#0ECB81' : '#F6465D' }}> {action.success ? (
{action.success ? '✓' : '✗'} <Check className="w-4 h-4" style={{ color: '#0ECB81' }} />
</span> ) : (
<X className="w-4 h-4" style={{ color: '#F6465D' }} />
)}
{action.error && <span className="text-xs ml-2" style={{ color: '#F6465D' }}>{action.error}</span>} {action.error && <span className="text-xs ml-2" style={{ color: '#F6465D' }}>{action.error}</span>}
</div> </div>
))} ))}
@@ -789,8 +797,9 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua
{/* Error Message */} {/* Error Message */}
{decision.error_message && ( {decision.error_message && (
<div className="text-sm rounded px-3 py-2 mt-3" style={{ color: '#F6465D', background: 'rgba(246, 70, 93, 0.1)' }}> <div className="text-sm rounded px-3 py-2 mt-3 flex items-center gap-2" style={{ color: '#F6465D', background: 'rgba(246, 70, 93, 0.1)' }}>
{decision.error_message} <AlertCircle className="w-4 h-4" />
{decision.error_message}
</div> </div>
)} )}
</div> </div>

View File

@@ -2,6 +2,7 @@ import useSWR from 'swr';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations'; import { t } from '../i18n/translations';
import { api } from '../lib/api'; import { api } from '../lib/api';
import { Brain, BarChart3, TrendingUp, TrendingDown, Sparkles, Coins, Trophy, ScrollText, Lightbulb } from 'lucide-react';
interface TradeOutcome { interface TradeOutcome {
symbol: string; symbol: string;
@@ -72,7 +73,9 @@ export default function AILearning({ traderId }: AILearningProps) {
if (!performance) { if (!performance) {
return ( return (
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}> <div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div style={{ color: '#848E9C' }}>📊 {t('loading', language)}</div> <div className="flex items-center gap-2" style={{ color: '#848E9C' }}>
<BarChart3 className="w-4 h-4" /> {t('loading', language)}
</div>
</div> </div>
); );
} }
@@ -81,7 +84,7 @@ export default function AILearning({ traderId }: AILearningProps) {
return ( return (
<div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}> <div className="rounded p-6" style={{ background: '#1E2329', border: '1px solid #2B3139' }}>
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<span className="text-xl">🧠</span> <Brain className="w-5 h-5" style={{ color: '#8B5CF6' }} />
<h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>{t('aiLearning', language)}</h2> <h2 className="text-lg font-bold" style={{ color: '#EAECEF' }}>{t('aiLearning', language)}</h2>
</div> </div>
<div style={{ color: '#848E9C' }}> <div style={{ color: '#848E9C' }}>
@@ -109,12 +112,12 @@ export default function AILearning({ traderId }: AILearningProps) {
filter: 'blur(60px)' filter: 'blur(60px)'
}} /> }} />
<div className="relative flex items-center gap-4"> <div className="relative flex items-center gap-4">
<div className="w-16 h-16 rounded-2xl flex items-center justify-center text-3xl" style={{ <div className="w-16 h-16 rounded-2xl flex items-center justify-center" style={{
background: 'linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%)', background: 'linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%)',
boxShadow: '0 8px 24px rgba(139, 92, 246, 0.5)', boxShadow: '0 8px 24px rgba(139, 92, 246, 0.5)',
border: '2px solid rgba(255, 255, 255, 0.1)' border: '2px solid rgba(255, 255, 255, 0.1)'
}}> }}>
🧠 <Brain className="w-8 h-8" style={{ color: '#FFF' }} />
</div> </div>
<div> <div>
<h2 className="text-3xl font-bold mb-1" style={{ <h2 className="text-3xl font-bold mb-1" style={{
@@ -149,7 +152,9 @@ export default function AILearning({ traderId }: AILearningProps) {
<div className="text-4xl font-bold mono mb-1" style={{ color: '#E0E7FF' }}> <div className="text-4xl font-bold mono mb-1" style={{ color: '#E0E7FF' }}>
{performance.total_trades} {performance.total_trades}
</div> </div>
<div className="text-xs" style={{ color: '#6366F1' }}>📊 Trades</div> <div className="text-xs flex items-center gap-1" style={{ color: '#6366F1' }}>
<BarChart3 className="w-3 h-3" /> Trades
</div>
</div> </div>
</div> </div>
@@ -199,7 +204,9 @@ export default function AILearning({ traderId }: AILearningProps) {
<div className="text-4xl font-bold mono mb-1" style={{ color: '#10B981' }}> <div className="text-4xl font-bold mono mb-1" style={{ color: '#10B981' }}>
+{(performance.avg_win || 0).toFixed(2)} +{(performance.avg_win || 0).toFixed(2)}
</div> </div>
<div className="text-xs" style={{ color: '#6EE7B7' }}>📈 USDT Average</div> <div className="text-xs flex items-center gap-1" style={{ color: '#6EE7B7' }}>
<TrendingUp className="w-3 h-3" /> USDT Average
</div>
</div> </div>
</div> </div>
@@ -220,7 +227,9 @@ export default function AILearning({ traderId }: AILearningProps) {
<div className="text-4xl font-bold mono mb-1" style={{ color: '#F87171' }}> <div className="text-4xl font-bold mono mb-1" style={{ color: '#F87171' }}>
{(performance.avg_loss || 0).toFixed(2)} {(performance.avg_loss || 0).toFixed(2)}
</div> </div>
<div className="text-xs" style={{ color: '#FCA5A5' }}>📉 USDT Average</div> <div className="text-xs flex items-center gap-1" style={{ color: '#FCA5A5' }}>
<TrendingDown className="w-3 h-3" /> USDT Average
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -239,11 +248,11 @@ export default function AILearning({ traderId }: AILearningProps) {
}} /> }} />
<div className="relative"> <div className="relative">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{ <div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
background: 'rgba(139, 92, 246, 0.3)', background: 'rgba(139, 92, 246, 0.3)',
border: '1px solid rgba(139, 92, 246, 0.5)' border: '1px solid rgba(139, 92, 246, 0.5)'
}}> }}>
🧬 <Sparkles className="w-6 h-6" style={{ color: '#A78BFA' }} />
</div> </div>
<div> <div>
<div className="text-lg font-bold" style={{ color: '#C4B5FD' }}></div> <div className="text-lg font-bold" style={{ color: '#C4B5FD' }}></div>
@@ -307,11 +316,11 @@ export default function AILearning({ traderId }: AILearningProps) {
}} /> }} />
<div className="relative"> <div className="relative">
<div className="flex items-center gap-3 mb-4"> <div className="flex items-center gap-3 mb-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{ <div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
background: 'rgba(240, 185, 11, 0.3)', background: 'rgba(240, 185, 11, 0.3)',
border: '1px solid rgba(240, 185, 11, 0.5)' border: '1px solid rgba(240, 185, 11, 0.5)'
}}> }}>
💰 <Coins className="w-6 h-6" style={{ color: '#FCD34D' }} />
</div> </div>
<div> <div>
<div className="text-lg font-bold" style={{ color: '#FCD34D' }}> <div className="text-lg font-bold" style={{ color: '#FCD34D' }}>
@@ -373,7 +382,7 @@ export default function AILearning({ traderId }: AILearningProps) {
boxShadow: '0 4px 16px rgba(16, 185, 129, 0.1)' boxShadow: '0 4px 16px rgba(16, 185, 129, 0.1)'
}}> }}>
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-2xl">🏆</span> <Trophy className="w-6 h-6" style={{ color: '#10B981' }} />
<span className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>{t('bestPerformer', language)}</span> <span className="text-sm font-semibold" style={{ color: '#6EE7B7' }}>{t('bestPerformer', language)}</span>
</div> </div>
<div className="text-3xl font-bold mono mb-1" style={{ color: '#10B981' }}> <div className="text-3xl font-bold mono mb-1" style={{ color: '#10B981' }}>
@@ -395,7 +404,7 @@ export default function AILearning({ traderId }: AILearningProps) {
boxShadow: '0 4px 16px rgba(248, 113, 113, 0.1)' boxShadow: '0 4px 16px rgba(248, 113, 113, 0.1)'
}}> }}>
<div className="flex items-center gap-2 mb-3"> <div className="flex items-center gap-2 mb-3">
<span className="text-2xl">📉</span> <TrendingDown className="w-6 h-6" style={{ color: '#F87171' }} />
<span className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>{t('worstPerformer', language)}</span> <span className="text-sm font-semibold" style={{ color: '#FCA5A5' }}>{t('worstPerformer', language)}</span>
</div> </div>
<div className="text-3xl font-bold mono mb-1" style={{ color: '#F87171' }}> <div className="text-3xl font-bold mono mb-1" style={{ color: '#F87171' }}>
@@ -428,7 +437,7 @@ export default function AILearning({ traderId }: AILearningProps) {
backdropFilter: 'blur(10px)' backdropFilter: 'blur(10px)'
}}> }}>
<h3 className="font-bold flex items-center gap-2 text-lg" style={{ color: '#E0E7FF' }}> <h3 className="font-bold flex items-center gap-2 text-lg" style={{ color: '#E0E7FF' }}>
📊 {t('symbolPerformance', language)} <BarChart3 className="w-5 h-5" /> {t('symbolPerformance', language)}
</h3> </h3>
</div> </div>
<div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 280px)' }}> <div className="overflow-y-auto" style={{ maxHeight: 'calc(100vh - 280px)' }}>
@@ -488,7 +497,7 @@ export default function AILearning({ traderId }: AILearningProps) {
backdropFilter: 'blur(10px)' backdropFilter: 'blur(10px)'
}}> }}>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-2xl">📜</span> <ScrollText className="w-6 h-6" style={{ color: '#FCD34D' }} />
<div> <div>
<h3 className="font-bold text-lg" style={{ color: '#FCD34D' }}>{t('tradeHistory', language)}</h3> <h3 className="font-bold text-lg" style={{ color: '#FCD34D' }}>{t('tradeHistory', language)}</h3>
<p className="text-xs" style={{ color: '#94A3B8' }}> <p className="text-xs" style={{ color: '#94A3B8' }}>
@@ -631,7 +640,9 @@ export default function AILearning({ traderId }: AILearningProps) {
}) })
) : ( ) : (
<div className="p-6 text-center"> <div className="p-6 text-center">
<div className="text-4xl mb-2 opacity-50">📜</div> <div className="mb-2 flex justify-center opacity-50">
<ScrollText className="w-10 h-10" style={{ color: '#94A3B8' }} />
</div>
<div style={{ color: '#94A3B8' }}>{t('noCompletedTrades', language)}</div> <div style={{ color: '#94A3B8' }}>{t('noCompletedTrades', language)}</div>
</div> </div>
)} )}
@@ -646,11 +657,11 @@ export default function AILearning({ traderId }: AILearningProps) {
boxShadow: '0 4px 16px rgba(240, 185, 11, 0.1)' boxShadow: '0 4px 16px rgba(240, 185, 11, 0.1)'
}}> }}>
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="w-10 h-10 rounded-lg flex items-center justify-center text-xl flex-shrink-0" style={{ <div className="w-10 h-10 rounded-lg flex items-center justify-center flex-shrink-0" style={{
background: 'rgba(240, 185, 11, 0.2)', background: 'rgba(240, 185, 11, 0.2)',
border: '1px solid rgba(240, 185, 11, 0.3)' border: '1px solid rgba(240, 185, 11, 0.3)'
}}> }}>
💡 <Lightbulb className="w-5 h-5" style={{ color: '#FCD34D' }} />
</div> </div>
<div> <div>
<h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>{t('howAILearns', language)}</h3> <h3 className="font-bold mb-3 text-base" style={{ color: '#FCD34D' }}>{t('howAILearns', language)}</h3>

View File

@@ -6,6 +6,7 @@ import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations'; import { t } from '../i18n/translations';
import { getExchangeIcon } from './ExchangeIcons'; import { getExchangeIcon } from './ExchangeIcons';
import { getModelIcon } from './ModelIcons'; import { getModelIcon } from './ModelIcons';
import { Bot, Brain, Landmark, BarChart3, Trash2, Plus, Users, AlertTriangle } from 'lucide-react';
// 获取友好的AI模型名称 // 获取友好的AI模型名称
function getModelDisplayName(modelId: string): string { function getModelDisplayName(modelId: string): string {
@@ -360,11 +361,11 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
{/* Header */} {/* Header */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{ <div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)', background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)' boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
}}> }}>
🤖 <Bot className="w-6 h-6" style={{ color: '#000' }} />
</div> </div>
<div> <div>
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}> <h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
@@ -385,38 +386,41 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<div className="flex gap-3"> <div className="flex gap-3">
<button <button
onClick={handleAddModel} onClick={handleAddModel}
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105" className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
style={{ style={{
background: '#2B3139', background: '#2B3139',
color: '#EAECEF', color: '#EAECEF',
border: '1px solid #474D57' border: '1px solid #474D57'
}} }}
> >
{t('aiModels', language)} <Plus className="w-4 h-4" />
{t('aiModels', language)}
</button> </button>
<button <button
onClick={handleAddExchange} onClick={handleAddExchange}
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105" className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-2"
style={{ style={{
background: '#2B3139', background: '#2B3139',
color: '#EAECEF', color: '#EAECEF',
border: '1px solid #474D57' border: '1px solid #474D57'
}} }}
> >
{t('exchanges', language)} <Plus className="w-4 h-4" />
{t('exchanges', language)}
</button> </button>
<button <button
onClick={() => setShowCreateModal(true)} onClick={() => setShowCreateModal(true)}
disabled={configuredModels.length === 0 || configuredExchanges.length === 0} 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" 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"
style={{ style={{
background: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#F0B90B' : '#2B3139', background: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#F0B90B' : '#2B3139',
color: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#000' : '#848E9C' color: (configuredModels.length > 0 && configuredExchanges.length > 0) ? '#000' : '#848E9C'
}} }}
> >
{t('createTrader', language)} <Plus className="w-4 h-4" />
{t('createTrader', language)}
</button> </button>
</div> </div>
</div> </div>
@@ -425,8 +429,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* AI Models */} {/* AI Models */}
<div className="binance-card p-4"> <div className="binance-card p-4">
<h3 className="text-lg font-semibold mb-3" style={{ color: '#EAECEF' }}> <h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
🧠 {t('aiModels', language)} <Brain className="w-5 h-5" style={{ color: '#60a5fa' }} />
{t('aiModels', language)}
</h3> </h3>
<div className="space-y-3"> <div className="space-y-3">
{configuredModels.map(model => { {configuredModels.map(model => {
@@ -465,7 +470,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
})} })}
{configuredModels.length === 0 && ( {configuredModels.length === 0 && (
<div className="text-center py-8" style={{ color: '#848E9C' }}> <div className="text-center py-8" style={{ color: '#848E9C' }}>
<div className="text-2xl mb-2">🧠</div> <Brain className="w-12 h-12 mx-auto mb-2 opacity-50" />
<div className="text-sm">AI模型</div> <div className="text-sm">AI模型</div>
</div> </div>
)} )}
@@ -474,8 +479,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
{/* Exchanges */} {/* Exchanges */}
<div className="binance-card p-4"> <div className="binance-card p-4">
<h3 className="text-lg font-semibold mb-3" style={{ color: '#EAECEF' }}> <h3 className="text-lg font-semibold mb-3 flex items-center gap-2" style={{ color: '#EAECEF' }}>
🏦 {t('exchanges', language)} <Landmark className="w-5 h-5" style={{ color: '#F0B90B' }} />
{t('exchanges', language)}
</h3> </h3>
<div className="space-y-3"> <div className="space-y-3">
{configuredExchanges.map(exchange => { {configuredExchanges.map(exchange => {
@@ -506,7 +512,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
})} })}
{configuredExchanges.length === 0 && ( {configuredExchanges.length === 0 && (
<div className="text-center py-8" style={{ color: '#848E9C' }}> <div className="text-center py-8" style={{ color: '#848E9C' }}>
<div className="text-2xl mb-2">🏦</div> <Landmark className="w-12 h-12 mx-auto mb-2 opacity-50" />
<div className="text-sm"></div> <div className="text-sm"></div>
</div> </div>
)} )}
@@ -518,7 +524,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<div className="binance-card p-6"> <div className="binance-card p-6">
<div className="flex items-center justify-between mb-5"> <div className="flex items-center justify-between mb-5">
<h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}> <h2 className="text-xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
👥 {t('currentTraders', language)} <Users className="w-6 h-6" style={{ color: '#F0B90B' }} />
{t('currentTraders', language)}
</h2> </h2>
</div> </div>
@@ -529,12 +536,12 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
className="flex items-center justify-between p-4 rounded transition-all hover:translate-y-[-1px]" className="flex items-center justify-between p-4 rounded transition-all hover:translate-y-[-1px]"
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}> style={{ background: '#0B0E11', border: '1px solid #2B3139' }}>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full flex items-center justify-center text-xl" <div className="w-12 h-12 rounded-full flex items-center justify-center"
style={{ style={{
background: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc', background: trader.ai_model.includes('deepseek') ? '#60a5fa' : '#c084fc',
color: '#fff' color: '#fff'
}}> }}>
🤖 <Bot className="w-6 h-6" />
</div> </div>
<div> <div>
<div className="font-bold text-lg" style={{ color: '#EAECEF' }}> <div className="font-bold text-lg" style={{ color: '#EAECEF' }}>
@@ -566,10 +573,11 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => onTraderSelect?.(trader.trader_id)} onClick={() => onTraderSelect?.(trader.trader_id)}
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105" className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 flex items-center gap-1"
style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }} style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }}
> >
📊 <BarChart3 className="w-4 h-4" />
</button> </button>
<button <button
@@ -588,7 +596,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105" className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }} style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
> >
🗑 <Trash2 className="w-4 h-4" />
</button> </button>
</div> </div>
</div> </div>
@@ -597,7 +605,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
</div> </div>
) : ( ) : (
<div className="text-center py-16" style={{ color: '#848E9C' }}> <div className="text-center py-16" style={{ color: '#848E9C' }}>
<div className="text-6xl mb-4 opacity-50">🤖</div> <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-lg font-semibold mb-2">{t('noTraders', language)}</div>
<div className="text-sm mb-4">{t('createFirstTrader', language)}</div> <div className="text-sm mb-4">{t('createFirstTrader', language)}</div>
{(configuredModels.length === 0 || configuredExchanges.length === 0) && ( {(configuredModels.length === 0 || configuredExchanges.length === 0) && (
@@ -855,11 +863,12 @@ function CreateTraderModal({
style={{ accentColor: '#F6465D' }} style={{ accentColor: '#F6465D' }}
/> />
<div> <div>
<div className="text-sm font-semibold" style={{ color: '#F6465D' }}> <div className="text-sm font-semibold flex items-center gap-2" style={{ color: '#F6465D' }}>
<AlertTriangle className="w-4 h-4" />
</div> </div>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}> <div className="text-xs mt-1" style={{ color: '#848E9C' }}>
使使 使使
使 使
</div> </div>
</div> </div>
@@ -950,7 +959,7 @@ function ModelConfigModal({
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }} style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
title="删除配置" title="删除配置"
> >
🗑 <Trash2 className="w-4 h-4" />
</button> </button>
)} )}
</div> </div>
@@ -1122,7 +1131,7 @@ function ExchangeConfigModal({
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }} style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
title="删除配置" title="删除配置"
> >
🗑 <Trash2 className="w-4 h-4" />
</button> </button>
)} )}
</div> </div>

View File

@@ -14,6 +14,7 @@ import useSWR from 'swr';
import { api } from '../lib/api'; import { api } from '../lib/api';
import type { CompetitionTraderData } from '../types'; import type { CompetitionTraderData } from '../types';
import { getTraderColor } from '../utils/traderColors'; import { getTraderColor } from '../utils/traderColors';
import { BarChart3 } from 'lucide-react';
interface ComparisonChartProps { interface ComparisonChartProps {
traders: CompetitionTraderData[]; traders: CompetitionTraderData[];
@@ -133,7 +134,9 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
if (combinedData.length === 0) { if (combinedData.length === 0) {
return ( return (
<div className="text-center py-16" style={{ color: '#848E9C' }}> <div className="text-center py-16" style={{ color: '#848E9C' }}>
<div className="text-6xl mb-4 opacity-50">📊</div> <div className="mb-4 flex justify-center opacity-50">
<BarChart3 className="w-16 h-16" />
</div>
<div className="text-lg font-semibold mb-2"></div> <div className="text-lg font-semibold mb-2"></div>
<div className="text-sm">线</div> <div className="text-sm">线</div>
</div> </div>

View File

@@ -3,6 +3,7 @@ import { api } from '../lib/api';
import type { CompetitionData } from '../types'; import type { CompetitionData } from '../types';
import { ComparisonChart } from './ComparisonChart'; import { ComparisonChart } from './ComparisonChart';
import { getTraderColor } from '../utils/traderColors'; import { getTraderColor } from '../utils/traderColors';
import { Trophy, Medal, Circle, CircleDot } from 'lucide-react';
export function CompetitionPage() { export function CompetitionPage() {
const { data: competition } = useSWR<CompetitionData>( const { data: competition } = useSWR<CompetitionData>(
@@ -51,11 +52,11 @@ export function CompetitionPage() {
{/* Competition Header - 精简版 */} {/* Competition Header - 精简版 */}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-xl flex items-center justify-center text-2xl" style={{ <div className="w-12 h-12 rounded-xl flex items-center justify-center" style={{
background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)', background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)',
boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)' boxShadow: '0 4px 14px rgba(240, 185, 11, 0.4)'
}}> }}>
🏆 <Trophy className="w-7 h-7" style={{ color: '#000' }} />
</div> </div>
<div> <div>
<h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}> <h1 className="text-2xl font-bold flex items-center gap-2" style={{ color: '#EAECEF' }}>
@@ -121,8 +122,12 @@ export function CompetitionPage() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Rank & Name */} {/* Rank & Name */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="text-2xl w-6"> <div className="w-8 h-8 rounded-full flex items-center justify-center" style={{
{index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'} background: index === 0 ? 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)' :
index === 1 ? 'linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%)' :
'linear-gradient(135deg, #CD7F32 0%, #8B4513 100%)'
}}>
<Medal className="w-5 h-5" style={{ color: '#000' }} />
</div> </div>
<div> <div>
<div className="font-bold text-sm" style={{ color: '#EAECEF' }}>{trader.trader_name}</div> <div className="font-bold text-sm" style={{ color: '#EAECEF' }}>{trader.trader_name}</div>
@@ -171,13 +176,17 @@ export function CompetitionPage() {
{/* Status */} {/* Status */}
<div> <div>
<div <div
className="px-2 py-1 rounded text-xs font-bold" className="px-2 py-1 rounded text-xs font-bold flex items-center justify-center"
style={trader.is_running style={trader.is_running
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' } ? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' } : { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
} }
> >
{trader.is_running ? '●' : '○'} {trader.is_running ? (
<CircleDot className="w-3 h-3" />
) : (
<Circle className="w-3 h-3" />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -13,6 +13,7 @@ import useSWR from 'swr';
import { api } from '../lib/api'; import { api } from '../lib/api';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations'; import { t } from '../i18n/translations';
import { AlertTriangle, BarChart3, DollarSign, Percent, TrendingUp as ArrowUp, TrendingDown as ArrowDown } from 'lucide-react'
interface EquityPoint { interface EquityPoint {
timestamp: string; timestamp: string;
@@ -52,16 +53,26 @@ export function EquityChart({ traderId }: EquityChartProps) {
if (error) { if (error) {
return ( return (
<div className="binance-card p-6"> <div className='binance-card p-6'>
<div className="flex items-center gap-3 p-4 rounded" style={{ background: 'rgba(246, 70, 93, 0.1)', border: '1px solid rgba(246, 70, 93, 0.2)' }}> <div
<div className="text-2xl"></div> className='flex items-center gap-3 p-4 rounded'
style={{
background: 'rgba(246, 70, 93, 0.1)',
border: '1px solid rgba(246, 70, 93, 0.2)',
}}
>
<AlertTriangle className='w-6 h-6' style={{ color: '#F6465D' }} />
<div> <div>
<div className="font-semibold" style={{ color: '#F6465D' }}>{t('loadingError', language)}</div> <div className='font-semibold' style={{ color: '#F6465D' }}>
<div className="text-sm" style={{ color: '#848E9C' }}>{error.message}</div> {t('loadingError', language)}
</div>
<div className='text-sm' style={{ color: '#848E9C' }}>
{error.message}
</div>
</div> </div>
</div> </div>
</div> </div>
); )
} }
// 过滤掉无效数据total_equity为0或小于1的数据点API失败导致 // 过滤掉无效数据total_equity为0或小于1的数据点API失败导致
@@ -69,15 +80,21 @@ export function EquityChart({ traderId }: EquityChartProps) {
if (!validHistory || validHistory.length === 0) { if (!validHistory || validHistory.length === 0) {
return ( return (
<div className="binance-card p-6"> <div className='binance-card p-6'>
<h3 className="text-lg font-semibold mb-6" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3> <h3 className='text-lg font-semibold mb-6' style={{ color: '#EAECEF' }}>
<div className="text-center py-16" style={{ color: '#848E9C' }}> {t('accountEquityCurve', language)}
<div className="text-6xl mb-4 opacity-50">📊</div> </h3>
<div className="text-lg font-semibold mb-2">{t('noHistoricalData', language)}</div> <div className='text-center py-16' style={{ color: '#848E9C' }}>
<div className="text-sm">{t('dataWillAppear', language)}</div> <div className='mb-4 flex justify-center opacity-50'>
<BarChart3 className='w-16 h-16' />
</div>
<div className='text-lg font-semibold mb-2'>
{t('noHistoricalData', language)}
</div>
<div className='text-sm'>{t('dataWillAppear', language)}</div>
</div> </div>
</div> </div>
); )
} }
// 限制显示最近的数据点(性能优化) // 限制显示最近的数据点(性能优化)
@@ -161,142 +178,238 @@ export function EquityChart({ traderId }: EquityChartProps) {
}; };
return ( return (
<div className="binance-card p-3 sm:p-5 animate-fade-in"> <div className='binance-card p-3 sm:p-5 animate-fade-in'>
{/* Header */} {/* Header */}
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4"> <div className='flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between mb-4'>
<div className="flex-1"> <div className='flex-1'>
<h3 className="text-base sm:text-lg font-bold mb-2" style={{ color: '#EAECEF' }}>{t('accountEquityCurve', language)}</h3> <h3
<div className="flex flex-col sm:flex-row sm:items-baseline gap-2 sm:gap-4"> className='text-base sm:text-lg font-bold mb-2'
<span className="text-2xl sm:text-3xl font-bold mono" style={{ color: '#EAECEF' }}> style={{ color: '#EAECEF' }}
>
{t('accountEquityCurve', language)}
</h3>
<div className='flex flex-col sm:flex-row sm:items-baseline gap-2 sm:gap-4'>
<span
className='text-2xl sm:text-3xl font-bold mono'
style={{ color: '#EAECEF' }}
>
{account?.total_equity.toFixed(2) || '0.00'} {account?.total_equity.toFixed(2) || '0.00'}
<span className="text-base sm:text-lg ml-1" style={{ color: '#848E9C' }}>USDT</span>
</span>
<div className="flex items-center gap-2 flex-wrap">
<span <span
className="text-sm sm:text-lg font-bold mono px-2 sm:px-3 py-1 rounded" className='text-base sm:text-lg ml-1'
style={{ color: '#848E9C' }}
>
USDT
</span>
</span>
<div className='flex items-center gap-2 flex-wrap'>
<span
className='text-sm sm:text-lg font-bold mono px-2 sm:px-3 py-1 rounded flex items-center gap-1'
style={{ style={{
color: isProfit ? '#0ECB81' : '#F6465D', color: isProfit ? '#0ECB81' : '#F6465D',
background: isProfit ? 'rgba(14, 203, 129, 0.1)' : 'rgba(246, 70, 93, 0.1)', background: isProfit
border: `1px solid ${isProfit ? 'rgba(14, 203, 129, 0.2)' : 'rgba(246, 70, 93, 0.2)'}` ? 'rgba(14, 203, 129, 0.1)'
: 'rgba(246, 70, 93, 0.1)',
border: `1px solid ${
isProfit
? 'rgba(14, 203, 129, 0.2)'
: 'rgba(246, 70, 93, 0.2)'
}`,
}} }}
> >
{isProfit ? '▲' : '▼'} {isProfit ? '+' : ''} {isProfit ? <ArrowUp className="w-4 h-4" /> : <ArrowDown className="w-4 h-4" />}
{isProfit ? '+' : ''}
{currentValue.raw_pnl_pct}% {currentValue.raw_pnl_pct}%
</span> </span>
<span className="text-xs sm:text-sm mono" style={{ color: '#848E9C' }}> <span
({isProfit ? '+' : ''}{currentValue.raw_pnl.toFixed(2)} USDT) className='text-xs sm:text-sm mono'
style={{ color: '#848E9C' }}
>
({isProfit ? '+' : ''}
{currentValue.raw_pnl.toFixed(2)} USDT)
</span> </span>
</div> </div>
</div> </div>
</div> </div>
{/* Display Mode Toggle */} {/* Display Mode Toggle */}
<div className="flex gap-0.5 sm:gap-1 rounded p-0.5 sm:p-1 self-start sm:self-auto" style={{ background: '#0B0E11', border: '1px solid #2B3139' }}> <div
className='flex gap-0.5 sm:gap-1 rounded p-0.5 sm:p-1 self-start sm:self-auto'
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
>
<button <button
onClick={() => setDisplayMode('dollar')} onClick={() => setDisplayMode('dollar')}
className="px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all" className='px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all flex items-center gap-1'
style={displayMode === 'dollar' style={
? { background: '#F0B90B', color: '#000', boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)' } displayMode === 'dollar'
: { background: 'transparent', color: '#848E9C' } ? {
background: '#F0B90B',
color: '#000',
boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)',
}
: { background: 'transparent', color: '#848E9C' }
} }
> >
💵 USDT <DollarSign className='w-4 h-4' /> USDT
</button> </button>
<button <button
onClick={() => setDisplayMode('percent')} onClick={() => setDisplayMode('percent')}
className="px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all" className='px-3 sm:px-4 py-1.5 sm:py-2 rounded text-xs sm:text-sm font-bold transition-all flex items-center gap-1'
style={displayMode === 'percent' style={
? { background: '#F0B90B', color: '#000', boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)' } displayMode === 'percent'
: { background: 'transparent', color: '#848E9C' } ? {
background: '#F0B90B',
color: '#000',
boxShadow: '0 2px 8px rgba(240, 185, 11, 0.4)',
}
: { background: 'transparent', color: '#848E9C' }
} }
> >
📊 % <Percent className='w-4 h-4' />
</button> </button>
</div> </div>
</div> </div>
{/* Chart */} {/* Chart */}
<div className="my-2" style={{ borderRadius: '8px', overflow: 'hidden' }}> <div className='my-2' style={{ borderRadius: '8px', overflow: 'hidden' }}>
<ResponsiveContainer width="100%" height={280}> <ResponsiveContainer width='100%' height={280}>
<LineChart data={chartData} margin={{ top: 10, right: 20, left: 5, bottom: 30 }}> <LineChart
<defs> data={chartData}
<linearGradient id="colorGradient" x1="0" y1="0" x2="0" y2="1"> margin={{ top: 10, right: 20, left: 5, bottom: 30 }}
<stop offset="5%" stopColor="#F0B90B" stopOpacity={0.8} /> >
<stop offset="95%" stopColor="#FCD535" stopOpacity={0.2} /> <defs>
</linearGradient> <linearGradient id='colorGradient' x1='0' y1='0' x2='0' y2='1'>
</defs> <stop offset='5%' stopColor='#F0B90B' stopOpacity={0.8} />
<CartesianGrid strokeDasharray="3 3" stroke="#2B3139" /> <stop offset='95%' stopColor='#FCD535' stopOpacity={0.2} />
<XAxis </linearGradient>
dataKey="time" </defs>
stroke="#5E6673" <CartesianGrid strokeDasharray='3 3' stroke='#2B3139' />
tick={{ fill: '#848E9C', fontSize: 11 }} <XAxis
tickLine={{ stroke: '#2B3139' }} dataKey='time'
interval={Math.floor(chartData.length / 10)} stroke='#5E6673'
angle={-15} tick={{ fill: '#848E9C', fontSize: 11 }}
textAnchor="end" tickLine={{ stroke: '#2B3139' }}
height={60} interval={Math.floor(chartData.length / 10)}
/> angle={-15}
<YAxis textAnchor='end'
stroke="#5E6673" height={60}
tick={{ fill: '#848E9C', fontSize: 12 }} />
tickLine={{ stroke: '#2B3139' }} <YAxis
domain={calculateYDomain()} stroke='#5E6673'
tickFormatter={(value) => tick={{ fill: '#848E9C', fontSize: 12 }}
displayMode === 'dollar' ? `$${value.toFixed(0)}` : `${value}%` tickLine={{ stroke: '#2B3139' }}
} domain={calculateYDomain()}
/> tickFormatter={(value) =>
<Tooltip content={<CustomTooltip />} /> displayMode === 'dollar' ? `$${value.toFixed(0)}` : `${value}%`
<ReferenceLine }
y={displayMode === 'dollar' ? initialBalance : 0} />
stroke="#474D57" <Tooltip content={<CustomTooltip />} />
strokeDasharray="3 3" <ReferenceLine
label={{ y={displayMode === 'dollar' ? initialBalance : 0}
value: displayMode === 'dollar' ? t('initialBalance', language).split(' ')[0] : '0%', stroke='#474D57'
fill: '#848E9C', strokeDasharray='3 3'
fontSize: 12, label={{
}} value:
/> displayMode === 'dollar'
<Line ? t('initialBalance', language).split(' ')[0]
type="natural" : '0%',
dataKey="value" fill: '#848E9C',
stroke="url(#colorGradient)" fontSize: 12,
strokeWidth={3} }}
dot={chartData.length > 50 ? false : { fill: '#F0B90B', r: 3 }} />
activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }} <Line
connectNulls={true} type='natural'
/> dataKey='value'
</LineChart> stroke='url(#colorGradient)'
</ResponsiveContainer> strokeWidth={3}
dot={chartData.length > 50 ? false : { fill: '#F0B90B', r: 3 }}
activeDot={{
r: 6,
fill: '#FCD535',
stroke: '#F0B90B',
strokeWidth: 2,
}}
connectNulls={true}
/>
</LineChart>
</ResponsiveContainer>
</div> </div>
{/* Footer Stats */} {/* Footer Stats */}
<div className="mt-3 grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3 pt-3" style={{ borderTop: '1px solid #2B3139' }}> <div
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}> className='mt-3 grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3 pt-3'
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('initialBalance', language)}</div> style={{ borderTop: '1px solid #2B3139' }}
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}> >
<div
className='p-2 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('initialBalance', language)}
</div>
<div
className='text-xs sm:text-sm font-bold mono'
style={{ color: '#EAECEF' }}
>
{initialBalance.toFixed(2)} USDT {initialBalance.toFixed(2)} USDT
</div> </div>
</div> </div>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}> <div
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('currentEquity', language)}</div> className='p-2 rounded transition-all hover:bg-opacity-50'
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}> style={{ background: 'rgba(240, 185, 11, 0.05)' }}
>
<div
className='text-xs mb-1 uppercase tracking-wider'
style={{ color: '#848E9C' }}
>
{t('currentEquity', language)}
</div>
<div
className='text-xs sm:text-sm font-bold mono'
style={{ color: '#EAECEF' }}
>
{currentValue.raw_equity.toFixed(2)} USDT {currentValue.raw_equity.toFixed(2)} USDT
</div> </div>
</div> </div>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}> <div
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('historicalCycles', language)}</div> className='p-2 rounded transition-all hover:bg-opacity-50'
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}>{validHistory.length} {t('cycles', language)}</div> style={{ background: 'rgba(240, 185, 11, 0.05)' }}
>
<div
className='text-xs mb-1 uppercase tracking-wider'
style={{ color: '#848E9C' }}
>
{t('historicalCycles', language)}
</div>
<div
className='text-xs sm:text-sm font-bold mono'
style={{ color: '#EAECEF' }}
>
{validHistory.length} {t('cycles', language)}
</div>
</div> </div>
<div className="p-2 rounded transition-all hover:bg-opacity-50" style={{ background: 'rgba(240, 185, 11, 0.05)' }}> <div
<div className="text-xs mb-1 uppercase tracking-wider" style={{ color: '#848E9C' }}>{t('displayRange', language)}</div> className='p-2 rounded transition-all hover:bg-opacity-50'
<div className="text-xs sm:text-sm font-bold mono" style={{ color: '#EAECEF' }}> 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-xs sm:text-sm font-bold mono'
style={{ color: '#EAECEF' }}
>
{validHistory.length > MAX_DISPLAY_POINTS {validHistory.length > MAX_DISPLAY_POINTS
? `${t('recent', language)} ${MAX_DISPLAY_POINTS}` ? `${t('recent', language)} ${MAX_DISPLAY_POINTS}`
: t('allData', language) : t('allData', language)}
}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); )
} }

View File

@@ -14,9 +14,8 @@ export function Header({ simple = false }: HeaderProps) {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{/* Left - Logo and Title */} {/* Left - Logo and Title */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full flex items-center justify-center text-xl" <div className="w-8 h-8 flex items-center justify-center">
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}> <img src="/images/logo.png" alt="NoFx Logo" className="w-full h-full object-contain" />
</div> </div>
<div> <div>
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}> <h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>

View File

@@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations'; import { t } from '../i18n/translations';
import { Header } from './Header'; import { Header } from './Header';
import { Smartphone } from 'lucide-react';
export function LoginPage() { export function LoginPage() {
const { language } = useLanguage(); const { language } = useLanguage();
@@ -57,9 +58,8 @@ export function LoginPage() {
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center text-3xl" <div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}> <img src="/images/logo.png" alt="NoFx Logo" className="w-full h-full object-contain" />
</div> </div>
<h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}> <h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
{t('loginTitle', language)} {t('loginTitle', language)}
@@ -121,7 +121,9 @@ export function LoginPage() {
) : ( ) : (
<form onSubmit={handleOTPVerify} className="space-y-4"> <form onSubmit={handleOTPVerify} className="space-y-4">
<div className="text-center mb-4"> <div className="text-center mb-4">
<div className="text-4xl mb-2">📱</div> <div className="mb-2 flex justify-center">
<Smartphone className="w-10 h-10" style={{ color: '#F0B90B' }} />
</div>
<p className="text-sm" style={{ color: '#848E9C' }}> <p className="text-sm" style={{ color: '#848E9C' }}>
{t('scanQRCodeInstructions', language)}<br /> {t('scanQRCodeInstructions', language)}<br />
{t('enterOTPCode', language)} {t('enterOTPCode', language)}

View File

@@ -2,6 +2,7 @@ import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext'; import { useAuth } from '../contexts/AuthContext';
import { useLanguage } from '../contexts/LanguageContext'; import { useLanguage } from '../contexts/LanguageContext';
import { t } from '../i18n/translations'; import { t } from '../i18n/translations';
import { Smartphone, Lock } from 'lucide-react';
export function RegisterPage() { export function RegisterPage() {
const { language } = useLanguage(); const { language } = useLanguage();
@@ -75,9 +76,8 @@ export function RegisterPage() {
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="w-16 h-16 rounded-full mx-auto mb-4 flex items-center justify-center text-3xl" <div className="w-16 h-16 mx-auto mb-4 flex items-center justify-center">
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}> <img src="/images/logo.png" alt="NoFx Logo" className="w-full h-full object-contain" />
</div> </div>
<h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}> <h1 className="text-2xl font-bold" style={{ color: '#EAECEF' }}>
{t('appTitle', language)} {t('appTitle', language)}
@@ -158,7 +158,9 @@ export function RegisterPage() {
{step === 'setup-otp' && ( {step === 'setup-otp' && (
<div className="space-y-4"> <div className="space-y-4">
<div className="text-center"> <div className="text-center">
<div className="text-4xl mb-2">📱</div> <div className="mb-2 flex justify-center">
<Smartphone className="w-10 h-10" style={{ color: '#F0B90B' }} />
</div>
<h3 className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}> <h3 className="text-lg font-semibold mb-2" style={{ color: '#EAECEF' }}>
{t('setupTwoFactor', language)} {t('setupTwoFactor', language)}
</h3> </h3>
@@ -236,7 +238,9 @@ export function RegisterPage() {
{step === 'verify-otp' && ( {step === 'verify-otp' && (
<form onSubmit={handleOTPVerify} className="space-y-4"> <form onSubmit={handleOTPVerify} className="space-y-4">
<div className="text-center mb-4"> <div className="text-center mb-4">
<div className="text-4xl mb-2">🔐</div> <div className="mb-2 flex justify-center">
<Lock className="w-10 h-10" style={{ color: '#F0B90B' }} />
</div>
<p className="text-sm" style={{ color: '#848E9C' }}> <p className="text-sm" style={{ color: '#848E9C' }}>
{t('enterOTPCode', language)}<br /> {t('enterOTPCode', language)}<br />
{t('completeRegistrationSubtitle', language)} {t('completeRegistrationSubtitle', language)}