feat: improve trading UI with interactive position table and chart tabs

- Add clickable position rows that scroll to chart and update symbol
- Add framer-motion animations to chart tab transitions
- Sync exchange selection between positions and TradingView chart
- Optimize position table layout with compact styling
- Update CSS with glass effects and premium button styles
This commit is contained in:
tinkle-community
2025-12-08 00:34:49 +08:00
parent 2334d78e4a
commit 0636ced476
5 changed files with 1204 additions and 1123 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import useSWR, { mutate } from 'swr'
import { api } from './lib/api'
import { ChartTabs } from './components/ChartTabs'
@@ -527,6 +527,9 @@ function TraderDetailsPage({
language: Language
}) {
const [closingPosition, setClosingPosition] = useState<string | null>(null)
const [selectedChartSymbol, setSelectedChartSymbol] = useState<string | undefined>(undefined)
const [chartUpdateKey, setChartUpdateKey] = useState<number>(0)
const chartSectionRef = useRef<HTMLDivElement>(null)
// 平仓操作
const handleClosePosition = async (symbol: string, side: string) => {
@@ -770,7 +773,7 @@ function TraderDetailsPage({
>
{getModelDisplayName(
selectedTrader.ai_model.split('_').pop() ||
selectedTrader.ai_model
selectedTrader.ai_model
)}
</span>
</span>
@@ -832,8 +835,17 @@ function TraderDetailsPage({
{/* 左侧:图表 + 持仓 */}
<div className="space-y-6">
{/* Chart Tabs (Equity / K-line) */}
<div className="animate-slide-in" style={{ animationDelay: '0.1s' }}>
<ChartTabs traderId={selectedTrader.trader_id} />
<div
ref={chartSectionRef}
className="chart-container animate-slide-in scroll-mt-32"
style={{ animationDelay: '0.1s' }}
>
<ChartTabs
traderId={selectedTrader.trader_id}
selectedSymbol={selectedChartSymbol}
updateKey={chartUpdateKey}
exchangeId={selectedTrader.exchange_id}
/>
</div>
{/* Current Positions */}
@@ -863,38 +875,38 @@ function TraderDetailsPage({
</div>
{positions && positions.length > 0 ? (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<table className="w-full text-xs">
<thead className="text-left border-b border-gray-800">
<tr>
<th className="pb-3 font-semibold text-gray-400">
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-left">
{t('symbol', language)}
</th>
<th className="pb-3 font-semibold text-gray-400">
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center">
{t('side', language)}
</th>
<th className="pb-3 font-semibold text-gray-400">
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center">
{language === 'zh' ? '操作' : 'Action'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('entryPrice', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('entryPrice', language)}>
{language === 'zh' ? '入场价' : 'Entry'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('markPrice', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('markPrice', language)}>
{language === 'zh' ? '标记价' : 'Mark'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('quantity', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('quantity', language)}>
{language === 'zh' ? '数量' : 'Qty'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('positionValue', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('positionValue', language)}>
{language === 'zh' ? '价值' : 'Value'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('leverage', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-center" title={t('leverage', language)}>
{language === 'zh' ? '杠杆' : 'Lev.'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('unrealizedPnL', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('unrealizedPnL', language)}>
{language === 'zh' ? '未实现盈亏' : 'uPnL'}
</th>
<th className="pb-3 font-semibold text-gray-400">
{t('liqPrice', language)}
<th className="px-1 pb-3 font-semibold text-gray-400 whitespace-nowrap text-right" title={t('liqPrice', language)}>
{language === 'zh' ? '强平价' : 'Liq.'}
</th>
</tr>
</thead>
@@ -902,24 +914,32 @@ function TraderDetailsPage({
{positions.map((pos, i) => (
<tr
key={i}
className="border-b border-gray-800 last:border-0"
className="border-b border-gray-800 last:border-0 transition-colors hover:bg-opacity-10 hover:bg-yellow-500 cursor-pointer"
onClick={() => {
setSelectedChartSymbol(pos.symbol)
setChartUpdateKey(Date.now())
// Smooth scroll to chart with ref
if (chartSectionRef.current) {
chartSectionRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}}
>
<td className="py-3 font-mono font-semibold">
<td className="px-1 py-3 font-mono font-semibold whitespace-nowrap text-left">
{pos.symbol}
</td>
<td className="py-3">
<td className="px-1 py-3 whitespace-nowrap text-center">
<span
className="px-2 py-1 rounded text-xs font-bold"
className="px-1.5 py-0.5 rounded text-[10px] font-bold"
style={
pos.side === 'long'
? {
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',
}
}
>
{t(
@@ -928,17 +948,15 @@ function TraderDetailsPage({
)}
</span>
</td>
<td className="py-3">
<td className="px-1 py-3 whitespace-nowrap text-center">
<button
type="button"
onClick={() => handleClosePosition(pos.symbol, pos.side.toUpperCase())}
disabled={closingPosition === pos.symbol}
className="flex items-center gap-1 px-2 py-1 rounded text-xs font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
style={{
background: 'rgba(246, 70, 93, 0.1)',
color: '#F6465D',
border: '1px solid rgba(246, 70, 93, 0.3)',
onClick={(e) => {
e.stopPropagation(); // Prevent row click
handleClosePosition(pos.symbol, pos.side.toUpperCase())
}}
disabled={closingPosition === pos.symbol}
className="btn-danger inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed mx-auto"
title={language === 'zh' ? '平仓' : 'Close Position'}
>
{closingPosition === pos.symbol ? (
@@ -950,36 +968,36 @@ function TraderDetailsPage({
</button>
</td>
<td
className="py-3 font-mono"
className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }}
>
{pos.entry_price.toFixed(4)}
</td>
<td
className="py-3 font-mono"
className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }}
>
{pos.mark_price.toFixed(4)}
</td>
<td
className="py-3 font-mono"
className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#EAECEF' }}
>
{pos.quantity.toFixed(4)}
</td>
<td
className="py-3 font-mono font-bold"
className="px-1 py-3 font-mono font-bold whitespace-nowrap text-right"
style={{ color: '#EAECEF' }}
>
{(pos.quantity * pos.mark_price).toFixed(2)} USDT
{(pos.quantity * pos.mark_price).toFixed(2)}
</td>
<td
className="py-3 font-mono"
className="px-1 py-3 font-mono whitespace-nowrap text-center"
style={{ color: '#F0B90B' }}
>
{pos.leverage}x
</td>
<td className="py-3 font-mono">
<td className="px-1 py-3 font-mono whitespace-nowrap text-right">
<span
style={{
color:
@@ -988,12 +1006,11 @@ function TraderDetailsPage({
}}
>
{pos.unrealized_pnl >= 0 ? '+' : ''}
{pos.unrealized_pnl.toFixed(2)} (
{pos.unrealized_pnl_pct.toFixed(2)}%)
{pos.unrealized_pnl.toFixed(2)}
</span>
</td>
<td
className="py-3 font-mono"
className="px-1 py-3 font-mono whitespace-nowrap text-right"
style={{ color: '#848E9C' }}
>
{pos.liquidation_price.toFixed(4)}