mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-07 04:50:57 +08:00
refactor: optimize codebase encoding
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { t } from '../i18n/translations'
|
||||
|
||||
export function DataPage() {
|
||||
const { language } = useLanguage()
|
||||
@@ -7,7 +8,7 @@ export function DataPage() {
|
||||
<div className="w-full h-[calc(100vh-64px)]">
|
||||
<iframe
|
||||
src="https://nofxos.ai/dashboard"
|
||||
title={language === 'zh' ? '数据中心' : 'Data Center'}
|
||||
title={t('dataCenter', language)}
|
||||
className="w-full h-full border-0"
|
||||
allow="fullscreen"
|
||||
/>
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
import { useLanguage } from '../contexts/LanguageContext'
|
||||
import { useAuth } from '../contexts/AuthContext'
|
||||
import { toast } from 'sonner'
|
||||
import { t } from '../i18n/translations'
|
||||
import { DeepVoidBackground } from '../components/common/DeepVoidBackground'
|
||||
|
||||
interface PublicStrategy {
|
||||
@@ -106,88 +107,7 @@ export function StrategyMarketPage() {
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('all')
|
||||
const [copiedId, setCopiedId] = useState<string | null>(null)
|
||||
|
||||
const texts = {
|
||||
zh: {
|
||||
title: '策略市场',
|
||||
subtitle: 'STRATEGY MARKETPLACE',
|
||||
description: '发现、学习并复用社区精英交易员的策略配置',
|
||||
search: '搜索参数...',
|
||||
all: '全部协议',
|
||||
popular: '热门配置',
|
||||
recent: '最新提交',
|
||||
myStrategies: '我的库',
|
||||
noStrategies: '无信号',
|
||||
noStrategiesDesc: '当前频段未检测到策略信号',
|
||||
author: 'OPERATOR',
|
||||
createdAt: 'TIMESTAMP',
|
||||
viewConfig: 'DECRYPT CONFIG',
|
||||
hideConfig: 'ENCRYPT',
|
||||
copyConfig: 'CLONE CONFIG',
|
||||
copied: 'COPIED',
|
||||
configHidden: 'ENCRYPTED',
|
||||
configHiddenDesc: '配置参数已加密',
|
||||
indicators: 'INDICATORS',
|
||||
maxPositions: 'POS_LIMIT',
|
||||
maxLeverage: 'LEV_MAX',
|
||||
shareYours: 'UPLOAD_STRATEGY',
|
||||
makePublic: 'PUBLISH',
|
||||
loading: 'INITIALIZING...'
|
||||
},
|
||||
en: {
|
||||
title: 'STRATEGY MARKET',
|
||||
subtitle: 'GLOBAL STRATEGY DATABASE',
|
||||
description: 'Discover, analyze, and clone high-performance trading algorithms',
|
||||
search: 'SEARCH PARAMETERS...',
|
||||
all: 'ALL PROTOCOLS',
|
||||
popular: 'TRENDING',
|
||||
recent: 'LATEST',
|
||||
myStrategies: 'MY LIBRARY',
|
||||
noStrategies: 'NO SIGNAL',
|
||||
noStrategiesDesc: 'No strategic signals detected in this frequency',
|
||||
author: 'OPERATOR',
|
||||
createdAt: 'TIMESTAMP',
|
||||
viewConfig: 'DECRYPT CONFIG',
|
||||
hideConfig: 'ENCRYPT',
|
||||
copyConfig: 'CLONE CONFIG',
|
||||
copied: 'COPIED',
|
||||
configHidden: 'ENCRYPTED',
|
||||
configHiddenDesc: 'Configuration parameters encrypted',
|
||||
indicators: 'INDICATORS',
|
||||
maxPositions: 'POS_LIMIT',
|
||||
maxLeverage: 'LEV_MAX',
|
||||
shareYours: 'UPLOAD_STRATEGY',
|
||||
makePublic: 'PUBLISH',
|
||||
loading: 'INITIALIZING...'
|
||||
},
|
||||
id: {
|
||||
title: 'PASAR STRATEGI',
|
||||
subtitle: 'DATABASE STRATEGI GLOBAL',
|
||||
description: 'Temukan, analisis, dan kloning algoritma trading berperforma tinggi',
|
||||
search: 'CARI PARAMETER...',
|
||||
all: 'SEMUA PROTOKOL',
|
||||
popular: 'TREN',
|
||||
recent: 'TERBARU',
|
||||
myStrategies: 'PERPUSTAKAAN SAYA',
|
||||
noStrategies: 'TIDAK ADA SINYAL',
|
||||
noStrategiesDesc: 'Tidak ada sinyal strategis terdeteksi pada frekuensi ini',
|
||||
author: 'OPERATOR',
|
||||
createdAt: 'TIMESTAMP',
|
||||
viewConfig: 'DEKRIPSI CONFIG',
|
||||
hideConfig: 'ENKRIPSI',
|
||||
copyConfig: 'KLON CONFIG',
|
||||
copied: 'DISALIN',
|
||||
configHidden: 'TERENKRIPSI',
|
||||
configHiddenDesc: 'Parameter konfigurasi terenkripsi',
|
||||
indicators: 'INDIKATOR',
|
||||
maxPositions: 'BATAS_POS',
|
||||
maxLeverage: 'LEV_MAKS',
|
||||
shareYours: 'UNGGAH_STRATEGI',
|
||||
makePublic: 'PUBLIKASI',
|
||||
loading: 'MENGINISIALISASI...'
|
||||
}
|
||||
}
|
||||
|
||||
const t = texts[language]
|
||||
const tr = (key: string) => t(`strategyMarket.${key}`, language)
|
||||
|
||||
// Fetch public strategies
|
||||
const { data: strategies, isLoading } = useSWR<PublicStrategy[]>(
|
||||
@@ -218,7 +138,7 @@ export function StrategyMarketPage() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(JSON.stringify(strategy.config, null, 2))
|
||||
setCopiedId(strategy.id)
|
||||
toast.success(t.copied)
|
||||
toast.success(tr('copied'))
|
||||
setTimeout(() => setCopiedId(null), 2000)
|
||||
} catch (err) {
|
||||
console.error('Failed to copy:', err)
|
||||
@@ -271,16 +191,16 @@ export function StrategyMarketPage() {
|
||||
<Database className="w-8 h-8 text-nofx-gold relative z-10" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold tracking-tighter text-white uppercase glitch-text" data-text={t.title}>
|
||||
{t.title}
|
||||
<h1 className="text-4xl font-bold tracking-tighter text-white uppercase glitch-text" data-text={tr('title')}>
|
||||
{tr('title')}
|
||||
</h1>
|
||||
<p className="text-xs text-nofx-gold tracking-[0.3em] font-bold mt-1">
|
||||
// {t.subtitle}
|
||||
// {tr('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-zinc-500 max-w-2xl border-l-2 border-zinc-800 pl-4">
|
||||
{t.description}
|
||||
{tr('description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -295,7 +215,7 @@ export function StrategyMarketPage() {
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t.search}
|
||||
placeholder={tr('search')}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-transparent py-3 text-sm focus:outline-none placeholder-zinc-700 text-nofx-gold font-mono"
|
||||
@@ -324,7 +244,7 @@ export function StrategyMarketPage() {
|
||||
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
|
||||
/>
|
||||
)}
|
||||
<span className="relative z-10">{t[cat as keyof typeof t]}</span>
|
||||
<span className="relative z-10">{tr(cat)}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -340,7 +260,7 @@ export function StrategyMarketPage() {
|
||||
<Cpu size={24} className="text-nofx-gold/50" />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-nofx-gold text-xs tracking-widest animate-pulse">{t.loading}</p>
|
||||
<p className="text-nofx-gold text-xs tracking-widest animate-pulse">{tr('loading')}</p>
|
||||
<div className="flex gap-1">
|
||||
<div className="w-1 h-1 bg-nofx-gold rounded-full animate-bounce" style={{ animationDelay: '0s' }}></div>
|
||||
<div className="w-1 h-1 bg-nofx-gold rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
|
||||
@@ -357,9 +277,9 @@ export function StrategyMarketPage() {
|
||||
<Activity className="w-16 h-16 text-zinc-700 relative z-10" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-zinc-300 font-mono tracking-tight mb-2">
|
||||
[{t.noStrategies}]
|
||||
[{tr('noStrategies')}]
|
||||
</h3>
|
||||
<p className="text-zinc-600 text-xs tracking-wide uppercase">{t.noStrategiesDesc}</p>
|
||||
<p className="text-zinc-600 text-xs tracking-wide uppercase">{tr('noStrategiesDesc')}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -423,11 +343,11 @@ export function StrategyMarketPage() {
|
||||
{/* Meta Data */}
|
||||
<div className="grid grid-cols-2 gap-y-2 mb-6 text-[10px] font-mono text-zinc-600">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-zinc-700 uppercase">{t.author}</span>
|
||||
<span className="text-zinc-700 uppercase">{tr('author')}</span>
|
||||
<span className="text-zinc-400 group-hover:text-white transition-colors">@{strategy.author_email?.split('@')[0] || 'UNKNOWN'}</span>
|
||||
</div>
|
||||
<div className="flex flex-col text-right">
|
||||
<span className="text-zinc-700 uppercase">{t.createdAt}</span>
|
||||
<span className="text-zinc-700 uppercase">{tr('createdAt')}</span>
|
||||
<span className="text-zinc-400">{formatDate(strategy.created_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -468,7 +388,7 @@ export function StrategyMarketPage() {
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-full text-zinc-600">
|
||||
<EyeOff size={16} className="mb-1 opacity-50" />
|
||||
<span className="text-[9px] uppercase tracking-widest">{t.configHiddenDesc}</span>
|
||||
<span className="text-[9px] uppercase tracking-widest">{tr('configHiddenDesc')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -483,19 +403,19 @@ export function StrategyMarketPage() {
|
||||
{copiedId === strategy.id ? (
|
||||
<>
|
||||
<Check className="w-3 h-3 text-emerald-500" />
|
||||
<span className="text-emerald-500">{t.copied}</span>
|
||||
<span className="text-emerald-500">{tr('copied')}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Copy className="w-3 h-3 group-hover/btn:scale-110 transition-transform" />
|
||||
{t.copyConfig}
|
||||
{tr('copyConfig')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<button disabled className="w-full py-2.5 text-[10px] font-bold font-mono uppercase tracking-widest border border-zinc-800 bg-black text-zinc-700 cursor-not-allowed flex items-center justify-center gap-2">
|
||||
<Shield size={12} />
|
||||
{t.hideConfig}
|
||||
{tr('hideConfig')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -521,7 +441,7 @@ export function StrategyMarketPage() {
|
||||
<div className="relative px-8 py-4 bg-black border border-zinc-800 hover:border-nofx-gold/50 flex items-center gap-4 transition-all">
|
||||
<Hexagon className="text-nofx-gold animate-spin-slow" size={24} />
|
||||
<div className="text-left">
|
||||
<div className="text-sm font-bold text-white uppercase tracking-wider group-hover:text-nofx-gold transition-colors">{t.shareYours}</div>
|
||||
<div className="text-sm font-bold text-white uppercase tracking-wider group-hover:text-nofx-gold transition-colors">{tr('shareYours')}</div>
|
||||
<div className="text-[10px] text-zinc-500 font-mono">CONTRIBUTE TO THE GLOBAL DATABASE</div>
|
||||
</div>
|
||||
<div className="w-[1px] h-8 bg-zinc-800 mx-2"></div>
|
||||
|
||||
@@ -39,6 +39,7 @@ import { PromptSectionsEditor } from '../components/strategy/PromptSectionsEdito
|
||||
import { PublishSettingsEditor } from '../components/strategy/PublishSettingsEditor'
|
||||
import { GridConfigEditor, defaultGridConfig } from '../components/strategy/GridConfigEditor'
|
||||
import { DeepVoidBackground } from '../components/common/DeepVoidBackground'
|
||||
import { t } from '../i18n/translations'
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE || ''
|
||||
|
||||
@@ -108,7 +109,7 @@ export function StrategyStudioPage() {
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
// 后端返回的是数组,不是 { models: [] }
|
||||
// Backend returns an array, not { models: [] }
|
||||
const allModels = Array.isArray(data) ? data : (data.models || [])
|
||||
const enabledModels = allModels.filter((m: AIModel) => m.enabled)
|
||||
setAiModels(enabledModels)
|
||||
@@ -209,7 +210,7 @@ export function StrategyStudioPage() {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: language === 'zh' ? '新策略' : 'New Strategy',
|
||||
name: tr('newStrategyName'),
|
||||
description: '',
|
||||
config: defaultConfig,
|
||||
}),
|
||||
@@ -222,7 +223,7 @@ export function StrategyStudioPage() {
|
||||
const now = new Date().toISOString()
|
||||
const newStrategy = {
|
||||
id: result.id,
|
||||
name: language === 'zh' ? '新策略' : 'New Strategy',
|
||||
name: tr('newStrategyName'),
|
||||
description: '',
|
||||
is_active: false,
|
||||
is_default: false,
|
||||
@@ -246,11 +247,11 @@ export function StrategyStudioPage() {
|
||||
if (!token) return
|
||||
|
||||
const confirmed = await confirmToast(
|
||||
language === 'zh' ? '确定删除此策略?' : 'Delete this strategy?',
|
||||
tr('confirmDeleteStrategy'),
|
||||
{
|
||||
title: language === 'zh' ? '确认删除' : 'Confirm Delete',
|
||||
okText: language === 'zh' ? '删除' : 'Delete',
|
||||
cancelText: language === 'zh' ? '取消' : 'Cancel',
|
||||
title: tr('confirmDelete'),
|
||||
okText: tr('delete'),
|
||||
cancelText: tr('cancel'),
|
||||
}
|
||||
)
|
||||
if (!confirmed) return
|
||||
@@ -261,7 +262,7 @@ export function StrategyStudioPage() {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
if (!response.ok) throw new Error('Failed to delete strategy')
|
||||
notify.success(language === 'zh' ? '策略已删除' : 'Strategy deleted')
|
||||
notify.success(tr('strategyDeleted'))
|
||||
// Clear selection if deleted strategy was selected
|
||||
if (selectedStrategy?.id === id) {
|
||||
setSelectedStrategy(null)
|
||||
@@ -287,7 +288,7 @@ export function StrategyStudioPage() {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: language === 'zh' ? '策略副本' : 'Strategy Copy',
|
||||
name: tr('strategyCopy'),
|
||||
}),
|
||||
})
|
||||
if (!response.ok) throw new Error('Failed to duplicate strategy')
|
||||
@@ -330,7 +331,7 @@ export function StrategyStudioPage() {
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
notify.success(language === 'zh' ? '策略已导出' : 'Strategy exported')
|
||||
notify.success(tr('strategyExported'))
|
||||
}
|
||||
|
||||
// Import strategy from JSON file
|
||||
@@ -344,7 +345,7 @@ export function StrategyStudioPage() {
|
||||
|
||||
// Validate imported data
|
||||
if (!importData.config || !importData.name) {
|
||||
throw new Error(language === 'zh' ? '无效的策略文件' : 'Invalid strategy file')
|
||||
throw new Error(tr('invalidStrategyFile'))
|
||||
}
|
||||
|
||||
// Create new strategy with imported config
|
||||
@@ -355,14 +356,14 @@ export function StrategyStudioPage() {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: `${importData.name} (${language === 'zh' ? '导入' : 'Imported'})`,
|
||||
name: `${importData.name} (${tr('imported')})`,
|
||||
description: importData.description || '',
|
||||
config: importData.config,
|
||||
}),
|
||||
})
|
||||
if (!response.ok) throw new Error('Failed to import strategy')
|
||||
|
||||
notify.success(language === 'zh' ? '策略已导入' : 'Strategy imported')
|
||||
notify.success(tr('strategyImported'))
|
||||
await fetchStrategies()
|
||||
} catch (err) {
|
||||
const errorMsg = err instanceof Error ? err.message : 'Unknown error'
|
||||
@@ -402,7 +403,7 @@ export function StrategyStudioPage() {
|
||||
)
|
||||
if (!response.ok) throw new Error('Failed to save strategy')
|
||||
setHasChanges(false)
|
||||
notify.success(language === 'zh' ? '策略已保存' : 'Strategy saved')
|
||||
notify.success(tr('strategySaved'))
|
||||
await fetchStrategies()
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error')
|
||||
@@ -482,51 +483,7 @@ export function StrategyStudioPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const t = (key: string) => {
|
||||
const translations: Record<string, Record<string, string>> = {
|
||||
strategyStudio: { zh: '策略工作室', en: 'Strategy Studio' },
|
||||
subtitle: { zh: '可视化配置和测试交易策略', en: 'Configure and test trading strategies' },
|
||||
strategies: { zh: '策略', en: 'Strategies' },
|
||||
newStrategy: { zh: '新建', en: 'New' },
|
||||
strategyType: { zh: '策略类型', en: 'Strategy Type' },
|
||||
aiTrading: { zh: 'AI 智能交易', en: 'AI Trading' },
|
||||
aiTradingDesc: { zh: 'AI 分析市场并自主决策买卖', en: 'AI analyzes market and makes trading decisions' },
|
||||
gridTrading: { zh: 'AI 网格交易', en: 'AI Grid Trading' },
|
||||
gridTradingDesc: { zh: 'AI 控制网格策略,在震荡市场获利', en: 'AI-controlled grid strategy for ranging markets' },
|
||||
gridConfig: { zh: '网格配置', en: 'Grid Configuration' },
|
||||
coinSource: { zh: '币种来源', en: 'Coin Source' },
|
||||
indicators: { zh: '技术指标', en: 'Indicators' },
|
||||
riskControl: { zh: '风控参数', en: 'Risk Control' },
|
||||
promptSections: { zh: 'Prompt 编辑', en: 'Prompt Editor' },
|
||||
customPrompt: { zh: '附加提示', en: 'Extra Prompt' },
|
||||
save: { zh: '保存', en: 'Save' },
|
||||
saving: { zh: '保存中...', en: 'Saving...' },
|
||||
activate: { zh: '激活', en: 'Activate' },
|
||||
active: { zh: '激活中', en: 'Active' },
|
||||
default: { zh: '默认', en: 'Default' },
|
||||
promptPreview: { zh: 'Prompt 预览', en: 'Prompt Preview' },
|
||||
aiTestRun: { zh: 'AI 测试', en: 'AI Test' },
|
||||
systemPrompt: { zh: 'System Prompt', en: 'System Prompt' },
|
||||
userPrompt: { zh: 'User Prompt', en: 'User Prompt' },
|
||||
loadPrompt: { zh: '生成 Prompt', en: 'Generate Prompt' },
|
||||
refreshPrompt: { zh: '刷新', en: 'Refresh' },
|
||||
promptVariant: { zh: '风格', en: 'Style' },
|
||||
balanced: { zh: '平衡', en: 'Balanced' },
|
||||
aggressive: { zh: '激进', en: 'Aggressive' },
|
||||
conservative: { zh: '保守', en: 'Conservative' },
|
||||
selectModel: { zh: '选择 AI 模型', en: 'Select AI Model' },
|
||||
runTest: { zh: '运行 AI 测试', en: 'Run AI Test' },
|
||||
running: { zh: '运行中...', en: 'Running...' },
|
||||
aiOutput: { zh: 'AI 输出', en: 'AI Output' },
|
||||
reasoning: { zh: '思维链', en: 'Reasoning' },
|
||||
decisions: { zh: '决策', en: 'Decisions' },
|
||||
duration: { zh: '耗时', en: 'Duration' },
|
||||
noModel: { zh: '请先配置 AI 模型', en: 'Please configure AI model first' },
|
||||
testNote: { zh: '使用真实 AI 模型测试,不执行交易', en: 'Test with real AI, no trading' },
|
||||
publishSettings: { zh: '发布设置', en: 'Publish' },
|
||||
}
|
||||
return translations[key]?.[language] || key
|
||||
}
|
||||
const tr = (key: string) => t(`strategyStudio.${key}`, language)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
@@ -550,7 +507,7 @@ export function StrategyStudioPage() {
|
||||
key: 'gridConfig' as const,
|
||||
icon: Activity,
|
||||
color: '#0ECB81',
|
||||
title: t('gridConfig'),
|
||||
title: tr('gridConfig'),
|
||||
forStrategyType: 'grid_trading' as const,
|
||||
content: editingConfig?.grid_config && (
|
||||
<GridConfigEditor
|
||||
@@ -566,7 +523,7 @@ export function StrategyStudioPage() {
|
||||
key: 'coinSource' as const,
|
||||
icon: Target,
|
||||
color: '#F0B90B',
|
||||
title: t('coinSource'),
|
||||
title: tr('coinSource'),
|
||||
forStrategyType: 'ai_trading' as const,
|
||||
content: editingConfig && (
|
||||
<CoinSourceEditor
|
||||
@@ -581,7 +538,7 @@ export function StrategyStudioPage() {
|
||||
key: 'indicators' as const,
|
||||
icon: BarChart3,
|
||||
color: '#0ECB81',
|
||||
title: t('indicators'),
|
||||
title: tr('indicators'),
|
||||
forStrategyType: 'ai_trading' as const,
|
||||
content: editingConfig && (
|
||||
<IndicatorEditor
|
||||
@@ -596,7 +553,7 @@ export function StrategyStudioPage() {
|
||||
key: 'riskControl' as const,
|
||||
icon: Shield,
|
||||
color: '#F6465D',
|
||||
title: t('riskControl'),
|
||||
title: tr('riskControl'),
|
||||
forStrategyType: 'ai_trading' as const,
|
||||
content: editingConfig && (
|
||||
<RiskControlEditor
|
||||
@@ -611,7 +568,7 @@ export function StrategyStudioPage() {
|
||||
key: 'promptSections' as const,
|
||||
icon: FileText,
|
||||
color: '#a855f7',
|
||||
title: t('promptSections'),
|
||||
title: tr('promptSections'),
|
||||
forStrategyType: 'ai_trading' as const,
|
||||
content: editingConfig && (
|
||||
<PromptSectionsEditor
|
||||
@@ -626,18 +583,18 @@ export function StrategyStudioPage() {
|
||||
key: 'customPrompt' as const,
|
||||
icon: Settings,
|
||||
color: '#60a5fa',
|
||||
title: t('customPrompt'),
|
||||
title: tr('customPrompt'),
|
||||
forStrategyType: 'ai_trading' as const,
|
||||
content: editingConfig && (
|
||||
<div>
|
||||
<p className="text-xs mb-2" style={{ color: '#848E9C' }}>
|
||||
{language === 'zh' ? '附加在 System Prompt 末尾的额外提示,用于补充个性化交易风格' : 'Extra prompt appended to System Prompt for personalized trading style'}
|
||||
{tr('customPromptDesc')}
|
||||
</p>
|
||||
<textarea
|
||||
value={editingConfig.custom_prompt || ''}
|
||||
onChange={(e) => updateConfig('custom_prompt', e.target.value)}
|
||||
disabled={selectedStrategy?.is_default}
|
||||
placeholder={language === 'zh' ? '输入自定义提示词...' : 'Enter custom prompt...'}
|
||||
placeholder={tr('customPromptPlaceholder')}
|
||||
className="w-full h-32 px-3 py-2 rounded-lg resize-none font-mono text-xs"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
@@ -648,7 +605,7 @@ export function StrategyStudioPage() {
|
||||
key: 'publishSettings' as const,
|
||||
icon: Globe,
|
||||
color: '#0ECB81',
|
||||
title: t('publishSettings'),
|
||||
title: tr('publishSettings'),
|
||||
forStrategyType: 'both' as const,
|
||||
content: selectedStrategy && (
|
||||
<PublishSettingsEditor
|
||||
@@ -683,8 +640,8 @@ export function StrategyStudioPage() {
|
||||
<Sparkles className="w-5 h-5 text-black" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-lg font-bold text-nofx-text">{t('strategyStudio')}</h1>
|
||||
<p className="text-xs text-nofx-text-muted">{t('subtitle')}</p>
|
||||
<h1 className="text-lg font-bold text-nofx-text">{tr('strategyStudio')}</h1>
|
||||
<p className="text-xs text-nofx-text-muted">{tr('subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
@@ -702,10 +659,10 @@ export function StrategyStudioPage() {
|
||||
<div className="w-48 flex-shrink-0 border-r border-nofx-gold/20 overflow-y-auto bg-nofx-bg/30 backdrop-blur-sm z-10">
|
||||
<div className="p-2">
|
||||
<div className="flex items-center justify-between mb-2 px-2">
|
||||
<span className="text-xs font-medium text-nofx-text-muted">{t('strategies')}</span>
|
||||
<span className="text-xs font-medium text-nofx-text-muted">{tr('strategies')}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{/* Import button with hidden file input */}
|
||||
<label className="p-1 rounded hover:bg-white/10 transition-colors cursor-pointer text-nofx-text-muted hover:text-white" title={language === 'zh' ? '导入策略' : 'Import Strategy'}>
|
||||
<label className="p-1 rounded hover:bg-white/10 transition-colors cursor-pointer text-nofx-text-muted hover:text-white" title={tr('importStrategy')}>
|
||||
<Upload className="w-4 h-4" />
|
||||
<input
|
||||
type="file"
|
||||
@@ -717,7 +674,7 @@ export function StrategyStudioPage() {
|
||||
<button
|
||||
onClick={handleCreateStrategy}
|
||||
className="p-1 rounded hover:bg-white/10 transition-colors text-nofx-gold"
|
||||
title={language === 'zh' ? '新建策略' : 'New Strategy'}
|
||||
title={tr('newStrategyTooltip')}
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
@@ -745,7 +702,7 @@ export function StrategyStudioPage() {
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleExportStrategy(strategy) }}
|
||||
className="p-1 rounded hover:bg-white/10 text-nofx-text-muted hover:text-white"
|
||||
title={language === 'zh' ? '导出' : 'Export'}
|
||||
title={tr('export')}
|
||||
>
|
||||
<Download className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -754,14 +711,14 @@ export function StrategyStudioPage() {
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDuplicateStrategy(strategy.id) }}
|
||||
className="p-1 rounded hover:bg-white/10 text-nofx-text-muted hover:text-white"
|
||||
title={language === 'zh' ? '复制' : 'Duplicate'}
|
||||
title={tr('duplicate')}
|
||||
>
|
||||
<Copy className="w-3 h-3" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleDeleteStrategy(strategy.id) }}
|
||||
className="p-1 rounded hover:bg-nofx-danger/20 text-nofx-danger"
|
||||
title={language === 'zh' ? '删除' : 'Delete'}
|
||||
title={tr('deleteTooltip')}
|
||||
>
|
||||
<Trash2 className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -772,18 +729,18 @@ export function StrategyStudioPage() {
|
||||
<div className="flex items-center gap-1 mt-1 flex-wrap">
|
||||
{strategy.is_active && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] rounded bg-nofx-success/15 text-nofx-success">
|
||||
{t('active')}
|
||||
{tr('active')}
|
||||
</span>
|
||||
)}
|
||||
{strategy.is_default && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] rounded bg-nofx-gold/15 text-nofx-gold">
|
||||
{t('default')}
|
||||
{tr('default')}
|
||||
</span>
|
||||
)}
|
||||
{strategy.is_public && (
|
||||
<span className="px-1.5 py-0.5 text-[10px] rounded flex items-center gap-0.5 bg-blue-400/15 text-blue-400">
|
||||
<Globe className="w-2.5 h-2.5" />
|
||||
{language === 'zh' ? '公开' : 'Public'}
|
||||
{tr('public')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -818,11 +775,11 @@ export function StrategyStudioPage() {
|
||||
setHasChanges(true)
|
||||
}}
|
||||
disabled={selectedStrategy.is_default}
|
||||
placeholder={language === 'zh' ? '添加策略简介...' : 'Add strategy description...'}
|
||||
placeholder={tr('addDescription')}
|
||||
className="text-xs bg-transparent border-none outline-none w-full text-nofx-text-muted placeholder-nofx-text-muted/50 mt-1"
|
||||
/>
|
||||
{hasChanges && (
|
||||
<span className="text-xs text-nofx-gold">● {language === 'zh' ? '未保存' : 'Unsaved'}</span>
|
||||
<span className="text-xs text-nofx-gold">● {tr('unsaved')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
@@ -832,7 +789,7 @@ export function StrategyStudioPage() {
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs transition-colors bg-nofx-success/10 border border-nofx-success/30 text-nofx-success hover:bg-nofx-success/20"
|
||||
>
|
||||
<Check className="w-3 h-3" />
|
||||
{t('activate')}
|
||||
{tr('activate')}
|
||||
</button>
|
||||
)}
|
||||
{!selectedStrategy.is_default && (
|
||||
@@ -843,7 +800,7 @@ export function StrategyStudioPage() {
|
||||
${hasChanges ? 'bg-nofx-gold text-black hover:bg-yellow-500' : 'bg-nofx-bg-lighter text-nofx-text-muted cursor-not-allowed'}`}
|
||||
>
|
||||
<Save className="w-3 h-3" />
|
||||
{isSaving ? t('saving') : t('save')}
|
||||
{isSaving ? tr('saving') : tr('save')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -854,7 +811,7 @@ export function StrategyStudioPage() {
|
||||
<div className="mb-4 p-4 rounded-lg bg-nofx-bg-lighter border border-nofx-gold/20">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<Zap className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||
<span className="text-sm font-medium text-nofx-text">{t('strategyType')}</span>
|
||||
<span className="text-sm font-medium text-nofx-text">{tr('strategyType')}</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
@@ -874,9 +831,9 @@ export function StrategyStudioPage() {
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Bot className="w-4 h-4" style={{ color: '#F0B90B' }} />
|
||||
<span className="text-sm font-medium text-nofx-text">{t('aiTrading')}</span>
|
||||
<span className="text-sm font-medium text-nofx-text">{tr('aiTrading')}</span>
|
||||
</div>
|
||||
<p className="text-xs text-nofx-text-muted text-left">{t('aiTradingDesc')}</p>
|
||||
<p className="text-xs text-nofx-text-muted text-left">{tr('aiTradingDesc')}</p>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -897,9 +854,9 @@ export function StrategyStudioPage() {
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Activity className="w-4 h-4" style={{ color: '#0ECB81' }} />
|
||||
<span className="text-sm font-medium text-nofx-text">{t('gridTrading')}</span>
|
||||
<span className="text-sm font-medium text-nofx-text">{tr('gridTrading')}</span>
|
||||
</div>
|
||||
<p className="text-xs text-nofx-text-muted text-left">{t('gridTradingDesc')}</p>
|
||||
<p className="text-xs text-nofx-text-muted text-left">{tr('gridTradingDesc')}</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -940,7 +897,7 @@ export function StrategyStudioPage() {
|
||||
<div className="text-center">
|
||||
<Activity className="w-12 h-12 mx-auto mb-2 opacity-30 text-nofx-text-muted" />
|
||||
<p className="text-sm text-nofx-text-muted">
|
||||
{language === 'zh' ? '选择或创建策略' : 'Select or create a strategy'}
|
||||
{tr('selectOrCreate')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -957,7 +914,7 @@ export function StrategyStudioPage() {
|
||||
}`}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
{t('promptPreview')}
|
||||
{tr('promptPreview')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveRightTab('test')}
|
||||
@@ -965,7 +922,7 @@ export function StrategyStudioPage() {
|
||||
}`}
|
||||
>
|
||||
<Play className="w-4 h-4" />
|
||||
{t('aiTestRun')}
|
||||
{tr('aiTestRun')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -981,9 +938,9 @@ export function StrategyStudioPage() {
|
||||
onChange={(e) => setSelectedVariant(e.target.value)}
|
||||
className="px-2 py-1.5 rounded text-xs bg-nofx-bg border border-nofx-gold/20 text-nofx-text outline-none focus:border-nofx-gold"
|
||||
>
|
||||
<option value="balanced">{t('balanced')}</option>
|
||||
<option value="aggressive">{t('aggressive')}</option>
|
||||
<option value="conservative">{t('conservative')}</option>
|
||||
<option value="balanced">{tr('balanced')}</option>
|
||||
<option value="aggressive">{tr('aggressive')}</option>
|
||||
<option value="conservative">{tr('conservative')}</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={fetchPromptPreview}
|
||||
@@ -991,7 +948,7 @@ export function StrategyStudioPage() {
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition-colors disabled:opacity-50 bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
{isLoadingPrompt ? <Loader2 className="w-3 h-3 animate-spin" /> : <RefreshCw className="w-3 h-3" />}
|
||||
{promptPreview ? t('refreshPrompt') : t('loadPrompt')}
|
||||
{promptPreview ? tr('refreshPrompt') : tr('loadPrompt')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1018,7 +975,7 @@ export function StrategyStudioPage() {
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<FileText className="w-3 h-3 text-purple-500" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('systemPrompt')}</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('systemPrompt')}</span>
|
||||
</div>
|
||||
<span className="text-[10px] px-1.5 py-0.5 rounded bg-nofx-bg-lighter text-nofx-text-muted">
|
||||
{promptPreview.system_prompt.length.toLocaleString()} chars
|
||||
@@ -1035,7 +992,7 @@ export function StrategyStudioPage() {
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-nofx-text-muted">
|
||||
<Eye className="w-10 h-10 mb-2 opacity-30" />
|
||||
<p className="text-sm">{language === 'zh' ? '点击生成 Prompt 预览' : 'Click to generate prompt preview'}</p>
|
||||
<p className="text-sm">{tr('generatePromptPreview')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -1046,7 +1003,7 @@ export function StrategyStudioPage() {
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Bot className="w-4 h-4 text-green-500" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('selectModel')}</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('selectModel')}</span>
|
||||
</div>
|
||||
{aiModels.length > 0 ? (
|
||||
<select
|
||||
@@ -1062,7 +1019,7 @@ export function StrategyStudioPage() {
|
||||
</select>
|
||||
) : (
|
||||
<div className="px-3 py-2 rounded-lg text-sm bg-nofx-danger/10 text-nofx-danger">
|
||||
{t('noModel')}
|
||||
{tr('noModel')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1072,9 +1029,9 @@ export function StrategyStudioPage() {
|
||||
onChange={(e) => setSelectedVariant(e.target.value)}
|
||||
className="px-2 py-1.5 rounded text-xs bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
|
||||
>
|
||||
<option value="balanced">{t('balanced')}</option>
|
||||
<option value="aggressive">{t('aggressive')}</option>
|
||||
<option value="conservative">{t('conservative')}</option>
|
||||
<option value="balanced">{tr('balanced')}</option>
|
||||
<option value="aggressive">{tr('aggressive')}</option>
|
||||
<option value="conservative">{tr('conservative')}</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={runAiTest}
|
||||
@@ -1084,17 +1041,17 @@ export function StrategyStudioPage() {
|
||||
{isRunningAiTest ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 animate-spin" />
|
||||
{t('running')}
|
||||
{tr('running')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Send className="w-4 h-4" />
|
||||
{t('runTest')}
|
||||
{tr('runTest')}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[10px] text-nofx-text-muted">{t('testNote')}</p>
|
||||
<p className="text-[10px] text-nofx-text-muted">{tr('testNote')}</p>
|
||||
</div>
|
||||
|
||||
{/* Test Results */}
|
||||
@@ -1110,7 +1067,7 @@ export function StrategyStudioPage() {
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-3 h-3 text-nofx-text-muted" />
|
||||
<span className="text-xs text-nofx-text-muted">
|
||||
{t('duration')}: {(aiTestResult.duration_ms / 1000).toFixed(2)}s
|
||||
{tr('duration')}: {(aiTestResult.duration_ms / 1000).toFixed(2)}s
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -1120,7 +1077,7 @@ export function StrategyStudioPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<Terminal className="w-3 h-3 text-blue-400" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('userPrompt')} (Input)</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('userPrompt')} (Input)</span>
|
||||
</div>
|
||||
<pre
|
||||
className="p-2 rounded-lg text-[10px] font-mono overflow-auto bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
|
||||
@@ -1136,7 +1093,7 @@ export function StrategyStudioPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<Sparkles className="w-3 h-3 text-nofx-gold" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('reasoning')}</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('reasoning')}</span>
|
||||
</div>
|
||||
<pre
|
||||
className="p-2 rounded-lg text-[10px] font-mono overflow-auto whitespace-pre-wrap bg-nofx-bg border border-nofx-gold/30 text-nofx-text"
|
||||
@@ -1152,7 +1109,7 @@ export function StrategyStudioPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<Activity className="w-3 h-3 text-green-500" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('decisions')}</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('decisions')}</span>
|
||||
</div>
|
||||
<pre
|
||||
className="p-2 rounded-lg text-[10px] font-mono overflow-auto bg-nofx-bg border border-green-500/30 text-nofx-text"
|
||||
@@ -1168,7 +1125,7 @@ export function StrategyStudioPage() {
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5 mb-1.5">
|
||||
<FileText className="w-3 h-3 text-nofx-text-muted" />
|
||||
<span className="text-xs font-medium text-nofx-text">{t('aiOutput')} (Raw)</span>
|
||||
<span className="text-xs font-medium text-nofx-text">{tr('aiOutput')} (Raw)</span>
|
||||
</div>
|
||||
<pre
|
||||
className="p-2 rounded-lg text-[10px] font-mono overflow-auto whitespace-pre-wrap bg-nofx-bg border border-nofx-gold/20 text-nofx-text"
|
||||
@@ -1184,7 +1141,7 @@ export function StrategyStudioPage() {
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-nofx-text-muted">
|
||||
<Play className="w-10 h-10 mb-2 opacity-30" />
|
||||
<p className="text-sm">{language === 'zh' ? '点击运行 AI 测试' : 'Click to run AI test'}</p>
|
||||
<p className="text-sm">{tr('runAiTestHint')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ import type {
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
// 获取友好的AI模型名称
|
||||
// Get friendly AI model display name
|
||||
function getModelDisplayName(modelId: string): string {
|
||||
switch (modelId.toLowerCase()) {
|
||||
case 'deepseek':
|
||||
@@ -189,19 +189,17 @@ export function TraderDashboardPage({
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// 平仓操作
|
||||
// Close position handler
|
||||
const handleClosePosition = async (symbol: string, side: string) => {
|
||||
if (!selectedTraderId) return
|
||||
|
||||
const confirmMsg =
|
||||
language === 'zh'
|
||||
? `确定要平仓 ${symbol} ${side === 'LONG' ? '多仓' : '空仓'} 吗?`
|
||||
: `Are you sure you want to close ${symbol} ${side === 'LONG' ? 'LONG' : 'SHORT'} position?`
|
||||
const sideLabel = side === 'LONG' ? 'LONG' : 'SHORT'
|
||||
const confirmMsg = t('traderDashboard.confirmClosePosition', language, { symbol, side: sideLabel })
|
||||
|
||||
const confirmed = await confirmToast(confirmMsg, {
|
||||
title: language === 'zh' ? '确认平仓' : 'Confirm Close',
|
||||
okText: language === 'zh' ? '确认' : 'Confirm',
|
||||
cancelText: language === 'zh' ? '取消' : 'Cancel',
|
||||
title: t('traderDashboard.confirmClose', language),
|
||||
okText: t('traderDashboard.confirm', language),
|
||||
cancelText: t('traderDashboard.cancel', language),
|
||||
})
|
||||
|
||||
if (!confirmed) return
|
||||
@@ -209,10 +207,8 @@ export function TraderDashboardPage({
|
||||
setClosingPosition(symbol)
|
||||
try {
|
||||
await api.closePosition(selectedTraderId, symbol, side)
|
||||
notify.success(
|
||||
language === 'zh' ? '平仓成功' : 'Position closed successfully'
|
||||
)
|
||||
// 使用 SWR mutate 刷新数据而非重新加载页面
|
||||
notify.success(t('traderDashboard.positionClosed', language))
|
||||
// Use SWR mutate to refresh data instead of reloading page
|
||||
await Promise.all([
|
||||
mutate(`positions-${selectedTraderId}`),
|
||||
mutate(`account-${selectedTraderId}`),
|
||||
@@ -221,9 +217,7 @@ export function TraderDashboardPage({
|
||||
const errorMsg =
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: language === 'zh'
|
||||
? '平仓失败'
|
||||
: 'Failed to close position'
|
||||
: t('traderDashboard.closeFailed', language)
|
||||
notify.error(errorMsg)
|
||||
} finally {
|
||||
setClosingPosition(null)
|
||||
@@ -257,18 +251,16 @@ export function TraderDashboardPage({
|
||||
</svg>
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold mb-3 text-nofx-text-main">
|
||||
{language === 'zh' ? '无法连接到服务器' : 'Connection Failed'}
|
||||
{t('traderDashboard.connectionFailed', language)}
|
||||
</h2>
|
||||
<p className="text-base mb-6 text-nofx-text-muted">
|
||||
{language === 'zh'
|
||||
? '请确认后端服务已启动。'
|
||||
: 'Please check if the backend service is running.'}
|
||||
{t('traderDashboard.connectionFailedDesc', language)}
|
||||
</p>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="px-6 py-3 rounded-lg font-semibold transition-all hover:scale-105 active:scale-95 nofx-glass border border-nofx-gold/30 text-nofx-gold hover:bg-nofx-gold/10"
|
||||
>
|
||||
{language === 'zh' ? '重试' : 'Retry'}
|
||||
{t('traderDashboard.retry', language)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -414,12 +406,8 @@ export function TraderDashboardPage({
|
||||
className="p-1 rounded hover:bg-white/10 transition-colors"
|
||||
title={
|
||||
showWalletAddress
|
||||
? language === 'zh'
|
||||
? '隐藏地址'
|
||||
: 'Hide address'
|
||||
: language === 'zh'
|
||||
? '显示完整地址'
|
||||
: 'Show full address'
|
||||
? t('traderDashboard.hideAddress', language)
|
||||
: t('traderDashboard.showFullAddress', language)
|
||||
}
|
||||
>
|
||||
{showWalletAddress ? (
|
||||
@@ -432,7 +420,7 @@ export function TraderDashboardPage({
|
||||
type="button"
|
||||
onClick={handleCopyAddress}
|
||||
className="p-1 rounded hover:bg-white/10 transition-colors"
|
||||
title={language === 'zh' ? '复制地址' : 'Copy address'}
|
||||
title={t('traderDashboard.copyAddress', language)}
|
||||
>
|
||||
{copiedAddress ? (
|
||||
<Check className="w-3.5 h-3.5 text-nofx-green" />
|
||||
@@ -443,7 +431,7 @@ export function TraderDashboardPage({
|
||||
</>
|
||||
) : (
|
||||
<span className="text-xs text-nofx-text-muted">
|
||||
{language === 'zh' ? '未配置地址' : 'No address configured'}
|
||||
{t('traderDashboard.noAddressConfigured', language)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -599,14 +587,14 @@ export function TraderDashboardPage({
|
||||
<tr>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-left">{t('symbol', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-center">{t('side', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-center">{language === 'zh' ? '操作' : 'Action'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('entryPrice', language)}>{language === 'zh' ? '入场价' : 'Entry'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('markPrice', language)}>{language === 'zh' ? '标记价' : 'Mark'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right" title={t('quantity', language)}>{language === 'zh' ? '数量' : 'Qty'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('positionValue', language)}>{language === 'zh' ? '价值' : 'Value'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-center hidden md:table-cell" title={t('leverage', language)}>{language === 'zh' ? '杠杆' : 'Lev.'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right" title={t('unrealizedPnL', language)}>{language === 'zh' ? '未实现盈亏' : 'uPnL'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('liqPrice', language)}>{language === 'zh' ? '强平价' : 'Liq.'}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-center">{t('traderDashboard.action', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('entryPrice', language)}>{t('traderDashboard.entry', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('markPrice', language)}>{t('traderDashboard.mark', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right" title={t('quantity', language)}>{t('traderDashboard.qty', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('positionValue', language)}>{t('traderDashboard.value', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-center hidden md:table-cell" title={t('leverage', language)}>{t('traderDashboard.lev', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right" title={t('unrealizedPnL', language)}>{t('traderDashboard.uPnL', language)}</th>
|
||||
<th className="px-1 pb-3 font-semibold text-nofx-text-muted whitespace-nowrap text-right hidden md:table-cell" title={t('liqPrice', language)}>{t('traderDashboard.liq', language)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -644,14 +632,14 @@ export function TraderDashboardPage({
|
||||
}}
|
||||
disabled={closingPosition === pos.symbol}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 rounded text-[10px] font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed mx-auto bg-nofx-red/10 text-nofx-red border border-nofx-red/30 hover:bg-nofx-red/20"
|
||||
title={language === 'zh' ? '平仓' : 'Close Position'}
|
||||
title={t('traderDashboard.closePosition', language)}
|
||||
>
|
||||
{closingPosition === pos.symbol ? (
|
||||
<Loader2 className="w-3 h-3 animate-spin" />
|
||||
) : (
|
||||
<LogOut className="w-3 h-3" />
|
||||
)}
|
||||
{language === 'zh' ? '平仓' : 'Close'}
|
||||
{t('traderDashboard.close', language)}
|
||||
</button>
|
||||
</td>
|
||||
<td className="px-1 py-3 font-mono whitespace-nowrap text-right text-nofx-text-main hidden md:table-cell">{formatPrice(pos.entry_price)}</td>
|
||||
@@ -678,13 +666,11 @@ export function TraderDashboardPage({
|
||||
{totalPositions > 10 && (
|
||||
<div className="flex flex-wrap items-center justify-between gap-3 pt-4 mt-4 text-xs border-t border-white/5 text-nofx-text-muted">
|
||||
<span>
|
||||
{language === 'zh'
|
||||
? `显示 ${paginatedPositions.length} / ${totalPositions} 个持仓`
|
||||
: `Showing ${paginatedPositions.length} of ${totalPositions} positions`}
|
||||
{t('traderDashboard.showingPositions', language, { shown: paginatedPositions.length, total: totalPositions })}
|
||||
</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{language === 'zh' ? '每页' : 'Per page'}:</span>
|
||||
<span>{t('traderDashboard.perPage', language)}:</span>
|
||||
<select
|
||||
value={positionsPageSize}
|
||||
onChange={(e) => setPositionsPageSize(Number(e.target.value))}
|
||||
|
||||
Reference in New Issue
Block a user