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:
@@ -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'} />
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user