feat: enhance copy trading logic and UI

- fix: correct copyRatio scaling (divide by 100)
- fix: enforce minimum order size with round-up logic
- feat: customize Telegram notification icons
- feat: optimize frontend order list (active loading, refresh buttons)
- refactor: optimize OnChain WebSocket connection management
This commit is contained in:
WrBug
2026-01-04 20:36:41 +08:00
parent 2bb8cbc564
commit 3e667b70fd
15 changed files with 630 additions and 594 deletions
+1 -1
View File
@@ -1026,7 +1026,7 @@
"remainingQuantity": "剩余",
"sellStatus": "卖出状态",
"status": "状态",
"statusFilled": "已完成",
"statusFilled": "未成交",
"statusPartiallySold": "部分成交",
"statusFullySold": "全部成交",
"realizedPnl": "已实现盈亏",
@@ -8,11 +8,14 @@ import type { BuyOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } fr
const { Option } = Select
import { ReloadOutlined } from '@ant-design/icons'
interface BuyOrdersTabProps {
copyTradingId: string
active?: boolean
}
const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId, active = false }) => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [loading, setLoading] = useState(false)
@@ -25,16 +28,16 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
side?: string
status?: string
}>({})
useEffect(() => {
if (copyTradingId) {
if (copyTradingId && active) {
fetchOrders()
}
}, [copyTradingId, page, limit, filters])
}, [copyTradingId, active, page, limit, filters])
const fetchOrders = async () => {
if (!copyTradingId) return
setLoading(true)
try {
const request: OrderTrackingRequest = {
@@ -44,7 +47,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
limit,
...filters
}
const response = await apiService.orderTracking.list(request)
if (response.data.code === 0 && response.data.data) {
const data = response.data.data as OrderTrackingListResponse
@@ -57,17 +60,17 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
setLoading(false)
}
}
const getStatusTag = (status: string) => {
const statusMap: Record<string, { color: string; text: string }> = {
filled: { color: 'processing', text: t('copyTradingOrders.statusFilled') || '已完成' },
filled: { color: 'processing', text: t('copyTradingOrders.statusFilled') || '未成交' },
partially_matched: { color: 'warning', text: t('copyTradingOrders.statusPartiallySold') || '部分成交' },
fully_matched: { color: 'success', text: t('copyTradingOrders.statusFullySold') || '全部成交' }
}
const config = statusMap[status] || { color: 'default', text: status }
return <Tag color={config.color}>{config.text}</Tag>
}
const columns = [
{
title: t('copyTradingOrders.orderId') || '订单ID',
@@ -76,7 +79,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -90,7 +93,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -104,7 +107,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -184,7 +187,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 120 : 160,
render: (timestamp: number) => (
<span style={{ fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? new Date(timestamp).toLocaleDateString('zh-CN')
: new Date(timestamp).toLocaleString('zh-CN')
}
@@ -192,7 +195,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
)
}
]
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
@@ -203,7 +206,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
value={filters.marketId}
onChange={(e) => setFilters({ ...filters, marketId: e.target.value || undefined })}
/>
<Select
placeholder={t('copyTradingOrders.filterSide') || '筛选方向'}
allowClear
@@ -213,10 +216,8 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
>
<Option value="0">YES</Option>
<Option value="1">NO</Option>
<Option value="YES">YES</Option>
<Option value="NO">NO</Option>
</Select>
<Select
placeholder={t('copyTradingOrders.filterStatus') || '筛选状态'}
allowClear
@@ -224,14 +225,14 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
value={filters.status}
onChange={(value) => setFilters({ ...filters, status: value || undefined })}
>
<Option value="filled">{t('copyTradingOrders.statusFilled') || '已完成'}</Option>
<Option value="filled">{t('copyTradingOrders.statusFilled') || '未成交'}</Option>
<Option value="partially_matched">{t('copyTradingOrders.statusPartiallySold') || '部分成交'}</Option>
<Option value="fully_matched">{t('copyTradingOrders.statusFullySold') || '全部成交'}</Option>
</Select>
<Button onClick={fetchOrders}>{t('common.search') || '查询'}</Button>
<Button type="primary" onClick={fetchOrders} icon={<ReloadOutlined />}>{t('common.refresh') || '刷新'}</Button>
</div>
{isMobile ? (
<div>
{loading ? (
@@ -255,7 +256,7 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
})
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
return (
<Card
key={order.orderId}
@@ -267,9 +268,9 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
bodyStyle={{ padding: '16px' }}
>
<div style={{ marginBottom: '12px' }}>
<div style={{
fontSize: '14px',
fontWeight: 'bold',
<div style={{
fontSize: '14px',
fontWeight: 'bold',
marginBottom: '8px',
fontFamily: 'monospace'
}}>
@@ -280,9 +281,9 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
{getStatusTag(order.status)}
</div>
</div>
<Divider style={{ margin: '12px 0' }} />
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.buyInfo') || '买入信息'}</div>
<div style={{ fontSize: '14px', fontWeight: '500' }}>
@@ -292,28 +293,28 @@ const BuyOrdersTab: React.FC<BuyOrdersTabProps> = ({ copyTradingId }) => {
{t('copyTradingOrders.amount') || '金额'}: {formatUSDC(amount)} USDC
</div>
</div>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.matchInfo') || '匹配信息'}</div>
<div style={{ fontSize: '13px', color: '#333' }}>
{t('copyTradingOrders.matched') || '已匹配'}: {formatUSDC(order.matchedQuantity)} | {t('copyTradingOrders.remaining') || '剩余'}: {formatUSDC(order.remainingQuantity)}
</div>
</div>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.leaderTradeId') || 'Leader 交易ID'}</div>
<div style={{ fontSize: '12px', color: '#999', fontFamily: 'monospace' }}>
{order.leaderTradeId.slice(0, 8)}...{order.leaderTradeId.slice(-6)}
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.marketId') || '市场ID'}</div>
<div style={{ fontSize: '12px', color: '#999', fontFamily: 'monospace' }}>
{order.marketId.slice(0, 8)}...{order.marketId.slice(-6)}
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#999' }}>
{t('copyTradingOrders.createdAt') || '创建时间'}: {formattedDate}
@@ -6,11 +6,14 @@ import { useMediaQuery } from 'react-responsive'
import { useTranslation } from 'react-i18next'
import type { MatchedOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../../types'
import { ReloadOutlined } from '@ant-design/icons'
interface MatchedOrdersTabProps {
copyTradingId: string
active?: boolean
}
const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) => {
const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId, active = false }) => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [loading, setLoading] = useState(false)
@@ -22,16 +25,16 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
sellOrderId?: string
buyOrderId?: string
}>({})
useEffect(() => {
if (copyTradingId) {
if (copyTradingId && active) {
fetchOrders()
}
}, [copyTradingId, page, limit, filters])
}, [copyTradingId, active, page, limit, filters])
const fetchOrders = async () => {
if (!copyTradingId) return
setLoading(true)
try {
const request: OrderTrackingRequest = {
@@ -41,7 +44,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
limit,
...filters
}
const response = await apiService.orderTracking.list(request)
if (response.data.code === 0 && response.data.data) {
const data = response.data.data as OrderTrackingListResponse
@@ -54,13 +57,13 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
setLoading(false)
}
}
const getPnlColor = (value: string): string => {
const num = parseFloat(value)
if (isNaN(num)) return '#666'
return num >= 0 ? '#3f8600' : '#cf1322'
}
const columns = [
{
title: t('copyTradingOrders.sellOrderId') || '卖出订单ID',
@@ -69,7 +72,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -83,7 +86,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -123,8 +126,8 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
key: 'realizedPnl',
width: isMobile ? 100 : 120,
render: (value: string) => (
<span style={{
color: getPnlColor(value),
<span style={{
color: getPnlColor(value),
fontWeight: 500,
fontSize: isMobile ? 12 : 14
}}>
@@ -139,7 +142,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
width: isMobile ? 120 : 160,
render: (timestamp: number) => (
<span style={{ fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? new Date(timestamp).toLocaleDateString('zh-CN')
: new Date(timestamp).toLocaleString('zh-CN')
}
@@ -147,7 +150,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
)
}
]
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
@@ -158,7 +161,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
value={filters.sellOrderId}
onChange={(e) => setFilters({ ...filters, sellOrderId: e.target.value || undefined })}
/>
<Input
placeholder={t('copyTradingOrders.filterBuyOrderId') || '筛选买入订单ID'}
allowClear
@@ -166,10 +169,10 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
value={filters.buyOrderId}
onChange={(e) => setFilters({ ...filters, buyOrderId: e.target.value || undefined })}
/>
<Button onClick={fetchOrders}>{t('common.search') || '查询'}</Button>
<Button type="primary" onClick={fetchOrders} icon={<ReloadOutlined />}>{t('common.search') || '查询'}</Button>
</div>
{isMobile ? (
<div>
{loading ? (
@@ -191,7 +194,7 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
hour: '2-digit',
minute: '2-digit'
})
return (
<Card
key={`${order.sellOrderId}-${order.buyOrderId}-${order.matchedAt}`}
@@ -204,8 +207,8 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.sellOrderId') || '卖出订单ID'}</div>
<div style={{
fontSize: '13px',
<div style={{
fontSize: '13px',
fontWeight: '500',
fontFamily: 'monospace',
marginBottom: '8px'
@@ -213,42 +216,42 @@ const MatchedOrdersTab: React.FC<MatchedOrdersTabProps> = ({ copyTradingId }) =>
{order.sellOrderId.slice(0, 8)}...{order.sellOrderId.slice(-6)}
</div>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.buyOrderId') || '买入订单ID'}</div>
<div style={{
fontSize: '13px',
<div style={{
fontSize: '13px',
fontWeight: '500',
fontFamily: 'monospace'
}}>
{order.buyOrderId.slice(0, 8)}...{order.buyOrderId.slice(-6)}
</div>
</div>
<Divider style={{ margin: '12px 0' }} />
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.matchedQuantity') || '匹配数量'}</div>
<div style={{ fontSize: '14px', fontWeight: '500' }}>
{formatUSDC(order.matchedQuantity)}
</div>
</div>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.priceInfo') || '价格信息'}</div>
<div style={{ fontSize: '13px', color: '#333' }}>
{t('copyTradingOrders.buy') || '买入'}: {formatUSDC(order.buyPrice)} | {t('copyTradingOrders.sell') || '卖出'}: {formatUSDC(order.sellPrice)}
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.realizedPnl') || '盈亏'}</div>
<div style={{
fontSize: '16px',
<div style={{
fontSize: '16px',
fontWeight: 'bold',
color: getPnlColor(order.realizedPnl)
}}>
{formatUSDC(order.realizedPnl)} USDC
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#999' }}>
{t('copyTradingOrders.matchedAt') || '匹配时间'}: {formattedDate}
@@ -8,11 +8,14 @@ import type { SellOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } f
const { Option } = Select
import { ReloadOutlined } from '@ant-design/icons'
interface SellOrdersTabProps {
copyTradingId: string
active?: boolean
}
const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId, active = false }) => {
const { t } = useTranslation()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [loading, setLoading] = useState(false)
@@ -24,16 +27,16 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
marketId?: string
side?: string
}>({})
useEffect(() => {
if (copyTradingId) {
if (copyTradingId && active) {
fetchOrders()
}
}, [copyTradingId, page, limit, filters])
}, [copyTradingId, active, page, limit, filters])
const fetchOrders = async () => {
if (!copyTradingId) return
setLoading(true)
try {
const request: OrderTrackingRequest = {
@@ -43,7 +46,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
limit,
...filters
}
const response = await apiService.orderTracking.list(request)
if (response.data.code === 0 && response.data.data) {
const data = response.data.data as OrderTrackingListResponse
@@ -56,13 +59,13 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
setLoading(false)
}
}
const getPnlColor = (value: string): string => {
const num = parseFloat(value)
if (isNaN(num)) return '#666'
return num >= 0 ? '#3f8600' : '#cf1322'
}
const columns = [
{
title: t('copyTradingOrders.orderId') || '订单ID',
@@ -71,7 +74,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -85,7 +88,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -99,7 +102,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 100 : 150,
render: (text: string) => (
<span style={{ fontFamily: 'monospace', fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? `${text.slice(0, 6)}...${text.slice(-4)}`
: `${text.slice(0, 8)}...${text.slice(-6)}`
}
@@ -153,8 +156,8 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
key: 'realizedPnl',
width: isMobile ? 100 : 120,
render: (value: string) => (
<span style={{
color: getPnlColor(value),
<span style={{
color: getPnlColor(value),
fontWeight: 500,
fontSize: isMobile ? 12 : 14
}}>
@@ -169,7 +172,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
width: isMobile ? 120 : 160,
render: (timestamp: number) => (
<span style={{ fontSize: isMobile ? 11 : 12 }}>
{isMobile
{isMobile
? new Date(timestamp).toLocaleDateString('zh-CN')
: new Date(timestamp).toLocaleString('zh-CN')
}
@@ -177,7 +180,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
)
}
]
return (
<div>
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
@@ -188,7 +191,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
value={filters.marketId}
onChange={(e) => setFilters({ ...filters, marketId: e.target.value || undefined })}
/>
<Select
placeholder={t('copyTradingOrders.filterSide') || '筛选方向'}
allowClear
@@ -201,10 +204,10 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
<Option value="YES">YES</Option>
<Option value="NO">NO</Option>
</Select>
<Button onClick={fetchOrders}>{t('common.search') || '查询'}</Button>
<Button type="primary" onClick={fetchOrders} icon={<ReloadOutlined />}>{t('common.refresh') || '刷新'}</Button>
</div>
{isMobile ? (
<div>
{loading ? (
@@ -228,7 +231,7 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
})
const amount = (parseFloat(order.quantity) * parseFloat(order.price)).toString()
const displaySide = order.side === '0' ? 'YES' : order.side === '1' ? 'NO' : order.side
return (
<Card
key={order.orderId}
@@ -240,9 +243,9 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
bodyStyle={{ padding: '16px' }}
>
<div style={{ marginBottom: '12px' }}>
<div style={{
fontSize: '14px',
fontWeight: 'bold',
<div style={{
fontSize: '14px',
fontWeight: 'bold',
marginBottom: '8px',
fontFamily: 'monospace'
}}>
@@ -252,9 +255,9 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
<Tag>{displaySide}</Tag>
</div>
</div>
<Divider style={{ margin: '12px 0' }} />
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.sellInfo') || '卖出信息'}</div>
<div style={{ fontSize: '14px', fontWeight: '500' }}>
@@ -264,32 +267,32 @@ const SellOrdersTab: React.FC<SellOrdersTabProps> = ({ copyTradingId }) => {
{t('copyTradingOrders.amount') || '金额'}: {formatUSDC(amount)} USDC
</div>
</div>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.realizedPnl') || '已实现盈亏'}</div>
<div style={{
fontSize: '16px',
<div style={{
fontSize: '16px',
fontWeight: 'bold',
color: getPnlColor(order.realizedPnl)
}}>
{formatUSDC(order.realizedPnl)} USDC
</div>
</div>
<div style={{ marginBottom: '12px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.leaderTradeId') || 'Leader 交易ID'}</div>
<div style={{ fontSize: '12px', color: '#999', fontFamily: 'monospace' }}>
{order.leaderTradeId.slice(0, 8)}...{order.leaderTradeId.slice(-6)}
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>{t('copyTradingOrders.marketId') || '市场ID'}</div>
<div style={{ fontSize: '12px', color: '#999', fontFamily: 'monospace' }}>
{order.marketId.slice(0, 8)}...{order.marketId.slice(-6)}
</div>
</div>
<div style={{ marginBottom: '16px' }}>
<div style={{ fontSize: '12px', color: '#999' }}>
{t('copyTradingOrders.createdAt') || '创建时间'}: {formattedDate}
@@ -22,13 +22,13 @@ const CopyTradingOrdersModal: React.FC<CopyTradingOrdersModalProps> = ({
}) => {
const { t } = useTranslation()
const [activeTab, setActiveTab] = useState<TabType>(defaultTab)
useEffect(() => {
if (open) {
setActiveTab(defaultTab)
}
}, [open, defaultTab])
return (
<Modal
title={t('copyTradingOrders.title') || '订单列表'}
@@ -38,25 +38,26 @@ const CopyTradingOrdersModal: React.FC<CopyTradingOrdersModalProps> = ({
width="90%"
style={{ top: 20 }}
bodyStyle={{ padding: '24px', maxHeight: 'calc(100vh - 100px)', overflow: 'auto' }}
destroyOnClose
>
<Tabs
activeKey={activeTab}
<Tabs
activeKey={activeTab}
onChange={(key) => setActiveTab(key as TabType)}
items={[
{
key: 'buy',
label: t('copyTradingOrders.buyOrders') || '买入订单',
children: <BuyOrdersTab copyTradingId={copyTradingId} />
children: <BuyOrdersTab copyTradingId={copyTradingId} active={activeTab === 'buy'} />
},
{
key: 'sell',
label: t('copyTradingOrders.sellOrders') || '卖出订单',
children: <SellOrdersTab copyTradingId={copyTradingId} />
children: <SellOrdersTab copyTradingId={copyTradingId} active={activeTab === 'sell'} />
},
{
key: 'matched',
label: t('copyTradingOrders.matchedOrders') || '匹配关系',
children: <MatchedOrdersTab copyTradingId={copyTradingId} />
children: <MatchedOrdersTab copyTradingId={copyTradingId} active={activeTab === 'matched'} />
}
]}
/>