diff --git a/web/package-lock.json b/web/package-lock.json index c7990faa..32e3c01b 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "clsx": "^2.1.1", "date-fns": "^4.1.0", + "lucide-react": "^0.552.0", "react": "^18.3.1", "react-dom": "^18.3.1", "recharts": "^2.15.2", @@ -2156,6 +2157,15 @@ "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": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", diff --git a/web/package.json b/web/package.json index a126d761..dfe57495 100644 --- a/web/package.json +++ b/web/package.json @@ -8,22 +8,23 @@ "preview": "vite preview" }, "dependencies": { + "clsx": "^2.1.1", + "date-fns": "^4.1.0", + "lucide-react": "^0.552.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "zustand": "^5.0.2", - "swr": "^2.2.5", "recharts": "^2.15.2", - "date-fns": "^4.1.0", - "clsx": "^2.1.1" + "swr": "^2.2.5", + "zustand": "^5.0.2" }, "devDependencies": { "@types/react": "^18.3.17", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", - "typescript": "^5.8.3", - "vite": "^6.0.7", - "tailwindcss": "^3.4.17", + "autoprefixer": "^10.4.20", "postcss": "^8.4.49", - "autoprefixer": "^10.4.20" + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^6.0.7" } } diff --git a/web/public/images/logo.png b/web/public/images/logo.png new file mode 100644 index 00000000..28ec8c71 Binary files /dev/null and b/web/public/images/logo.png differ diff --git a/web/src/App.tsx b/web/src/App.tsx index ddc6d028..7c84ba9d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -11,6 +11,7 @@ import { LanguageProvider, useLanguage } from './contexts/LanguageContext'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import { t, type Language } from './i18n/translations'; 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 { SystemStatus, AccountInfo, @@ -175,10 +176,7 @@ function App() { return (
-
- ⚡ -
+ NoFx Logo

加载中...

@@ -201,9 +199,7 @@ function App() {
{/* Left - Logo and Title */}
-
- ⚡ -
+ NoFx Logo

{t('appTitle', language)} @@ -264,7 +260,8 @@ function App() { {/* Admin Mode Indicator */} {systemConfig?.admin_mode && (
- ⚡ 管理员模式 + + 管理员模式
)} @@ -429,9 +426,9 @@ function TraderDetailsPage({

- - 🤖 - +
+ +
{selectedTrader.trader_name}

@@ -470,8 +467,9 @@ function TraderDetailsPage({ {/* Debug Info */} {account && (
-
- 🔄 Last Update: {lastUpdate} | Total Equity: {account?.total_equity?.toFixed(2) || '0.00'} | +
+ + 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'}{' '} ({account?.total_pnl_pct?.toFixed(2) || '0.00'}%)
@@ -517,7 +515,8 @@ function TraderDetailsPage({

- 📈 {t('currentPositions', language)} + + {t('currentPositions', language)}

{positions && positions.length > 0 && (
@@ -581,7 +580,9 @@ function TraderDetailsPage({
) : (
-
📊
+
+ +
{t('noPositions', language)}
{t('noActivePositions', language)}
@@ -594,11 +595,11 @@ function TraderDetailsPage({
{/* 标题 */}
-
- 🧠 +

{t('recentDecisions', language)}

@@ -618,7 +619,9 @@ function TraderDetailsPage({ )) ) : (
-
🧠
+
+ +
{t('noDecisionsYet', language)}
{t('aiDecisionsWillAppear', language)}
@@ -657,10 +660,11 @@ function StatCard({ {change !== undefined && (
- {positive ? '▲' : '▼'} {positive ? '+' : ''} + {positive ? : } + {positive ? '+' : ''} {change.toFixed(2)}%
@@ -704,7 +708,8 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua className="flex items-center gap-2 text-sm transition-colors" style={{ color: '#60a5fa' }} > - 📥 {t('inputPrompt', language)} + + {t('inputPrompt', language)} {showInputPrompt ? t('collapse', language) : t('expand', language)} {showInputPrompt && ( @@ -723,7 +728,8 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua className="flex items-center gap-2 text-sm transition-colors" style={{ color: '#F0B90B' }} > - 📤 {t('aiThinking', language)} + + {t('aiThinking', language)} {showCoT ? t('collapse', language) : t('expand', language)} {showCoT && ( @@ -753,9 +759,11 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua {action.price > 0 && ( @{action.price.toFixed(4)} )} - - {action.success ? '✓' : '✗'} - + {action.success ? ( + + ) : ( + + )} {action.error && {action.error}}
))} @@ -789,8 +797,9 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua {/* Error Message */} {decision.error_message && ( -
- ❌ {decision.error_message} +
+ + {decision.error_message}
)}
diff --git a/web/src/components/AILearning.tsx b/web/src/components/AILearning.tsx index 20da61d2..f44baf27 100644 --- a/web/src/components/AILearning.tsx +++ b/web/src/components/AILearning.tsx @@ -2,6 +2,7 @@ import useSWR from 'swr'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; import { api } from '../lib/api'; +import { Brain, BarChart3, TrendingUp, TrendingDown, Sparkles, Coins, Trophy, ScrollText, Lightbulb } from 'lucide-react'; interface TradeOutcome { symbol: string; @@ -72,7 +73,9 @@ export default function AILearning({ traderId }: AILearningProps) { if (!performance) { return (
-
📊 {t('loading', language)}
+
+ {t('loading', language)} +
); } @@ -81,7 +84,7 @@ export default function AILearning({ traderId }: AILearningProps) { return (
- 🧠 +

{t('aiLearning', language)}

@@ -109,12 +112,12 @@ export default function AILearning({ traderId }: AILearningProps) { filter: 'blur(60px)' }} />
-
- 🧠 +

{performance.total_trades}

-
📊 Trades
+
+ Trades +
@@ -199,7 +204,9 @@ export default function AILearning({ traderId }: AILearningProps) {
+{(performance.avg_win || 0).toFixed(2)}
-
📈 USDT Average
+
+ USDT Average +
@@ -220,7 +227,9 @@ export default function AILearning({ traderId }: AILearningProps) {
{(performance.avg_loss || 0).toFixed(2)}
-
📉 USDT Average
+
+ USDT Average +
@@ -239,11 +248,11 @@ export default function AILearning({ traderId }: AILearningProps) { }} />
-
- 🧬 +
夏普比率
@@ -307,11 +316,11 @@ export default function AILearning({ traderId }: AILearningProps) { }} />
-
- 💰 +
@@ -373,7 +382,7 @@ export default function AILearning({ traderId }: AILearningProps) { boxShadow: '0 4px 16px rgba(16, 185, 129, 0.1)' }}>
- 🏆 + {t('bestPerformer', language)}
@@ -395,7 +404,7 @@ export default function AILearning({ traderId }: AILearningProps) { boxShadow: '0 4px 16px rgba(248, 113, 113, 0.1)' }}>
- 📉 + {t('worstPerformer', language)}
@@ -428,7 +437,7 @@ export default function AILearning({ traderId }: AILearningProps) { backdropFilter: 'blur(10px)' }}>

- 📊 {t('symbolPerformance', language)} + {t('symbolPerformance', language)}

@@ -488,7 +497,7 @@ export default function AILearning({ traderId }: AILearningProps) { backdropFilter: 'blur(10px)' }}>
- 📜 +

{t('tradeHistory', language)}

@@ -631,7 +640,9 @@ export default function AILearning({ traderId }: AILearningProps) { }) ) : (

-
📜
+
+ +
{t('noCompletedTrades', language)}
)} @@ -646,11 +657,11 @@ export default function AILearning({ traderId }: AILearningProps) { boxShadow: '0 4px 16px rgba(240, 185, 11, 0.1)' }}>
-
- 💡 +

{t('howAILearns', language)}

diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index d76d45f7..25763608 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -6,6 +6,7 @@ import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; import { getExchangeIcon } from './ExchangeIcons'; import { getModelIcon } from './ModelIcons'; +import { Bot, Brain, Landmark, BarChart3, Trash2, Plus, Users, AlertTriangle } from 'lucide-react'; // 获取友好的AI模型名称 function getModelDisplayName(modelId: string): string { @@ -360,11 +361,11 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { {/* Header */}
-
- 🤖 +

@@ -385,38 +386,41 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
- + - +

@@ -425,8 +429,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
{/* AI Models */}
-

- 🧠 {t('aiModels', language)} +

+ + {t('aiModels', language)}

{configuredModels.map(model => { @@ -465,7 +470,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { })} {configuredModels.length === 0 && (
-
🧠
+
暂无已配置的AI模型
)} @@ -474,8 +479,9 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { {/* Exchanges */}
-

- 🏦 {t('exchanges', language)} +

+ + {t('exchanges', language)}

{configuredExchanges.map(exchange => { @@ -506,7 +512,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { })} {configuredExchanges.length === 0 && (
-
🏦
+
暂无已配置的交易所
)} @@ -518,23 +524,24 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {

- 👥 {t('currentTraders', language)} + + {t('currentTraders', language)}

{traders && traders.length > 0 ? (
{traders.map(trader => ( -
-
- 🤖 +
@@ -566,29 +573,30 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
- + - +
@@ -597,7 +605,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
) : (
-
🤖
+
{t('noTraders', language)}
{t('createFirstTrader', language)}
{(configuredModels.length === 0 || configuredExchanges.length === 0) && ( @@ -855,11 +863,12 @@ function CreateTraderModal({ style={{ accentColor: '#F6465D' }} />
-
+
+ 覆盖基础交易策略
- ⚠️ 警告:勾选后将完全使用您的自定义策略,不再使用系统默认的风控和交易逻辑。 + 警告:勾选后将完全使用您的自定义策略,不再使用系统默认的风控和交易逻辑。 这可能导致交易风险增加。仅在您完全理解交易逻辑时使用此选项。
@@ -950,7 +959,7 @@ function ModelConfigModal({ style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }} title="删除配置" > - 🗑️ + )}
@@ -1122,7 +1131,7 @@ function ExchangeConfigModal({ style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }} title="删除配置" > - 🗑️ + )}
diff --git a/web/src/components/ComparisonChart.tsx b/web/src/components/ComparisonChart.tsx index b333c04f..b0dec3f0 100644 --- a/web/src/components/ComparisonChart.tsx +++ b/web/src/components/ComparisonChart.tsx @@ -14,6 +14,7 @@ import useSWR from 'swr'; import { api } from '../lib/api'; import type { CompetitionTraderData } from '../types'; import { getTraderColor } from '../utils/traderColors'; +import { BarChart3 } from 'lucide-react'; interface ComparisonChartProps { traders: CompetitionTraderData[]; @@ -133,7 +134,9 @@ export function ComparisonChart({ traders }: ComparisonChartProps) { if (combinedData.length === 0) { return (
-
📊
+
+ +
暂无历史数据
运行几个周期后将显示对比曲线
diff --git a/web/src/components/CompetitionPage.tsx b/web/src/components/CompetitionPage.tsx index 2a638b83..59e9be88 100644 --- a/web/src/components/CompetitionPage.tsx +++ b/web/src/components/CompetitionPage.tsx @@ -3,6 +3,7 @@ import { api } from '../lib/api'; import type { CompetitionData } from '../types'; import { ComparisonChart } from './ComparisonChart'; import { getTraderColor } from '../utils/traderColors'; +import { Trophy, Medal, Circle, CircleDot } from 'lucide-react'; export function CompetitionPage() { const { data: competition } = useSWR( @@ -51,11 +52,11 @@ export function CompetitionPage() { {/* Competition Header - 精简版 */}
-
- 🏆 +

@@ -121,8 +122,12 @@ export function CompetitionPage() {
{/* Rank & Name */}
-
- {index === 0 ? '🥇' : index === 1 ? '🥈' : '🥉'} +
+
{trader.trader_name}
@@ -171,13 +176,17 @@ export function CompetitionPage() { {/* Status */}
- {trader.is_running ? '●' : '○'} + {trader.is_running ? ( + + ) : ( + + )}
diff --git a/web/src/components/EquityChart.tsx b/web/src/components/EquityChart.tsx index b8b846cf..bfa89617 100644 --- a/web/src/components/EquityChart.tsx +++ b/web/src/components/EquityChart.tsx @@ -13,6 +13,7 @@ import useSWR from 'swr'; import { api } from '../lib/api'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; +import { AlertTriangle, BarChart3, DollarSign, Percent, TrendingUp as ArrowUp, TrendingDown as ArrowDown } from 'lucide-react' interface EquityPoint { timestamp: string; @@ -52,16 +53,26 @@ export function EquityChart({ traderId }: EquityChartProps) { if (error) { return ( -
-
-
⚠️
+
+
+
-
{t('loadingError', language)}
-
{error.message}
+
+ {t('loadingError', language)} +
+
+ {error.message} +
- ); + ) } // 过滤掉无效数据:total_equity为0或小于1的数据点(API失败导致) @@ -69,15 +80,21 @@ export function EquityChart({ traderId }: EquityChartProps) { if (!validHistory || validHistory.length === 0) { return ( -
-

{t('accountEquityCurve', language)}

-
-
📊
-
{t('noHistoricalData', language)}
-
{t('dataWillAppear', language)}
+
+

+ {t('accountEquityCurve', language)} +

+
+
+ +
+
+ {t('noHistoricalData', language)} +
+
{t('dataWillAppear', language)}
- ); + ) } // 限制显示最近的数据点(性能优化) @@ -161,142 +178,238 @@ export function EquityChart({ traderId }: EquityChartProps) { }; return ( -
+
{/* Header */} -
-
-

{t('accountEquityCurve', language)}

-
- +
+
+

+ {t('accountEquityCurve', language)} +

+
+ {account?.total_equity.toFixed(2) || '0.00'} - USDT - -
+ USDT + + +
+ - {isProfit ? '▲' : '▼'} {isProfit ? '+' : ''} + {isProfit ? : } + {isProfit ? '+' : ''} {currentValue.raw_pnl_pct}% - - ({isProfit ? '+' : ''}{currentValue.raw_pnl.toFixed(2)} USDT) + + ({isProfit ? '+' : ''} + {currentValue.raw_pnl.toFixed(2)} USDT)
{/* Display Mode Toggle */} -
+
{/* Chart */} -
- - - - - - - - - - - - displayMode === 'dollar' ? `$${value.toFixed(0)}` : `${value}%` - } - /> - } /> - - 50 ? false : { fill: '#F0B90B', r: 3 }} - activeDot={{ r: 6, fill: '#FCD535', stroke: '#F0B90B', strokeWidth: 2 }} - connectNulls={true} - /> - - +
+ + + + + + + + + + + + displayMode === 'dollar' ? `$${value.toFixed(0)}` : `${value}%` + } + /> + } /> + + 50 ? false : { fill: '#F0B90B', r: 3 }} + activeDot={{ + r: 6, + fill: '#FCD535', + stroke: '#F0B90B', + strokeWidth: 2, + }} + connectNulls={true} + /> + +
{/* Footer Stats */} -
-
-
{t('initialBalance', language)}
-
+
+
+
+ {t('initialBalance', language)} +
+
{initialBalance.toFixed(2)} USDT
-
-
{t('currentEquity', language)}
-
+
+
+ {t('currentEquity', language)} +
+
{currentValue.raw_equity.toFixed(2)} USDT
-
-
{t('historicalCycles', language)}
-
{validHistory.length} {t('cycles', language)}
+
+
+ {t('historicalCycles', language)} +
+
+ {validHistory.length} {t('cycles', language)} +
-
-
{t('displayRange', language)}
-
+
+
+ {t('displayRange', language)} +
+
{validHistory.length > MAX_DISPLAY_POINTS ? `${t('recent', language)} ${MAX_DISPLAY_POINTS}` - : t('allData', language) - } + : t('allData', language)}
- ); + ) } diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx index 3fb1c693..af141d85 100644 --- a/web/src/components/Header.tsx +++ b/web/src/components/Header.tsx @@ -14,9 +14,8 @@ export function Header({ simple = false }: HeaderProps) {
{/* Left - Logo and Title */}
-
- ⚡ +
+ NoFx Logo

diff --git a/web/src/components/LoginPage.tsx b/web/src/components/LoginPage.tsx index bf4dd9e3..8923ebe2 100644 --- a/web/src/components/LoginPage.tsx +++ b/web/src/components/LoginPage.tsx @@ -3,6 +3,7 @@ import { useAuth } from '../contexts/AuthContext'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; import { Header } from './Header'; +import { Smartphone } from 'lucide-react'; export function LoginPage() { const { language } = useLanguage(); @@ -57,9 +58,8 @@ export function LoginPage() {
{/* Logo */}
-
- ⚡ +
+ NoFx Logo

{t('loginTitle', language)} @@ -121,7 +121,9 @@ export function LoginPage() { ) : (
-
📱
+
+ +

{t('scanQRCodeInstructions', language)}
{t('enterOTPCode', language)} diff --git a/web/src/components/RegisterPage.tsx b/web/src/components/RegisterPage.tsx index 7d3d2284..68139e42 100644 --- a/web/src/components/RegisterPage.tsx +++ b/web/src/components/RegisterPage.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { useAuth } from '../contexts/AuthContext'; import { useLanguage } from '../contexts/LanguageContext'; import { t } from '../i18n/translations'; +import { Smartphone, Lock } from 'lucide-react'; export function RegisterPage() { const { language } = useLanguage(); @@ -75,9 +76,8 @@ export function RegisterPage() {

{/* Logo */}
-
- ⚡ +
+ NoFx Logo

{t('appTitle', language)} @@ -158,7 +158,9 @@ export function RegisterPage() { {step === 'setup-otp' && (
-
📱
+
+ +

{t('setupTwoFactor', language)}

@@ -236,7 +238,9 @@ export function RegisterPage() { {step === 'verify-otp' && (
-
🔐
+
+ +

{t('enterOTPCode', language)}
{t('completeRegistrationSubtitle', language)}