import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { Card, Table, Button, Space, Tag, Popconfirm, message, List, Empty, Spin, Divider, Typography, Modal, Descriptions, Statistic, Row, Col } from 'antd' import { PlusOutlined, EditOutlined, DeleteOutlined, GlobalOutlined, EyeOutlined, ReloadOutlined, WalletOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import { apiService } from '../services/api' import type { Leader, LeaderBalanceResponse } from '../types' import { useMediaQuery } from 'react-responsive' import { formatUSDC } from '../utils' const { Text } = Typography const LeaderList: React.FC = () => { const { t, i18n } = useTranslation() const navigate = useNavigate() const isMobile = useMediaQuery({ maxWidth: 768 }) const [leaders, setLeaders] = useState([]) const [loading, setLoading] = useState(false) const [balanceMap, setBalanceMap] = useState>({}) const [balanceLoading, setBalanceLoading] = useState>({}) // 详情 Modal const [detailModalVisible, setDetailModalVisible] = useState(false) const [detailLeader, setDetailLeader] = useState(null) const [detailBalance, setDetailBalance] = useState(null) const [detailBalanceLoading, setDetailBalanceLoading] = useState(false) useEffect(() => { fetchLeaders() }, []) const fetchLeaders = async () => { setLoading(true) try { const response = await apiService.leaders.list() if (response.data.code === 0 && response.data.data) { setLeaders(response.data.data.list || []) } else { message.error(response.data.msg || t('leaderList.fetchFailed')) } } catch (error: any) { message.error(error.message || t('leaderList.fetchFailed')) } finally { setLoading(false) } } // 加载所有 Leader 的余额 useEffect(() => { const loadBalances = async () => { for (const leader of leaders) { if (!balanceMap[leader.id] && !balanceLoading[leader.id]) { setBalanceLoading(prev => ({ ...prev, [leader.id]: true })) try { const balanceData = await apiService.leaders.balance({ leaderId: leader.id }) if (balanceData.data.code === 0 && balanceData.data.data) { setBalanceMap(prev => ({ ...prev, [leader.id]: { total: balanceData.data.data.totalBalance || '0', available: balanceData.data.data.availableBalance || '0', position: balanceData.data.data.positionBalance || '0' } })) } } catch (error) { console.error(`获取 Leader ${leader.id} 余额失败:`, error) setBalanceMap(prev => ({ ...prev, [leader.id]: { total: '-', available: '-', position: '-' } })) } finally { setBalanceLoading(prev => ({ ...prev, [leader.id]: false })) } } } } if (leaders.length > 0) { loadBalances() } }, [leaders]) const handleDelete = async (leaderId: number) => { try { const response = await apiService.leaders.delete({ leaderId }) if (response.data.code === 0) { message.success(t('leaderList.deleteSuccess')) fetchLeaders() } else { message.error(response.data.msg || t('leaderList.deleteFailed')) } } catch (error: any) { message.error(error.message || t('leaderList.deleteFailed')) } } const handleShowDetail = async (leader: Leader) => { try { setDetailModalVisible(true) setDetailLeader(leader) setDetailBalance(null) setDetailBalanceLoading(false) // 加载详情和余额 try { const leaderDetail = await apiService.leaders.detail({ leaderId: leader.id }) if (leaderDetail.data.code === 0 && leaderDetail.data.data) { setDetailLeader(leaderDetail.data.data) } // 加载余额 setDetailBalanceLoading(true) try { const balanceData = await apiService.leaders.balance({ leaderId: leader.id }) if (balanceData.data.code === 0 && balanceData.data.data) { setDetailBalance(balanceData.data.data) } } catch (error) { console.error('获取余额失败:', error) setDetailBalance(null) } finally { setDetailBalanceLoading(false) } } catch (error: any) { console.error('获取 Leader 详情失败:', error) message.error(error.message || t('leaderList.fetchFailed')) setDetailModalVisible(false) setDetailLeader(null) } } catch (error: any) { console.error('打开详情失败:', error) message.error(error.message || t('leaderList.openDetailFailed')) setDetailModalVisible(false) setDetailLeader(null) } } const handleRefreshDetailBalance = async () => { if (!detailLeader) return setDetailBalanceLoading(true) try { const balanceData = await apiService.leaders.balance({ leaderId: detailLeader.id }) if (balanceData.data.code === 0 && balanceData.data.data) { setDetailBalance(balanceData.data.data) message.success(t('leaderDetail.refresh')) } } catch (error: any) { message.error(error.message || t('leaderDetail.fetchBalanceFailed')) } finally { setDetailBalanceLoading(false) } } const formatTimestamp = (timestamp: number) => { const date = new Date(timestamp) return date.toLocaleString(i18n.language || 'zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) } const getPositionColumns = () => { return [ { title: t('leaderDetail.market'), dataIndex: 'title', key: 'title', render: (title: string) => { if (!title) return - const displayText = isMobile && title.length > 20 ? `${title.slice(0, 20)}...` : title return {displayText} } }, { title: t('leaderDetail.side'), dataIndex: 'side', key: 'side', render: (side: string) => { const color = side === 'YES' ? 'green' : 'red' return {side} } }, { title: t('leaderDetail.quantity'), dataIndex: 'quantity', key: 'quantity', render: (quantity: string) => formatUSDC(quantity) }, { title: t('leaderDetail.avgPrice'), dataIndex: 'avgPrice', key: 'avgPrice', render: (price: string) => formatUSDC(price) }, { title: t('leaderDetail.currentValue'), dataIndex: 'currentValue', key: 'currentValue', render: (value: string) => formatUSDC(value) }, { title: t('leaderDetail.pnl'), dataIndex: 'pnl', key: 'pnl', render: (pnl: string | undefined) => { if (!pnl || pnl === '0') { return - } else { const numPnl = parseFloat(pnl) const color = numPnl > 0 ? '#52c41a' : '#ff4d4f' return {formatUSDC(pnl)} } } } ] } const columns = [ { title: t('leaderList.leaderName'), dataIndex: 'leaderName', key: 'leaderName', width: 150, render: (text: string, record: Leader) => ( {text || `Leader ${record.id}`} {record.leaderAddress} ) }, { title: t('leaderList.remark'), dataIndex: 'remark', key: 'remark', width: 200, ellipsis: true, render: (remark: string | undefined) => { if (!remark) return - return {remark} } }, { title: t('leaderDetail.availableBalance'), key: 'balance', width: 150, render: (_: any, record: Leader) => { const balance = balanceMap[record.id] if (!balance) return const displayText = balance.available === '-' ? '-' : `${formatUSDC(balance.available)} USDC` return {displayText} } }, { title: t('leaderList.copyTradingCount'), dataIndex: 'copyTradingCount', key: 'copyTradingCount', width: 100, render: (count: number) => {count} }, { title: t('common.actions'), key: 'action', width: isMobile ? 180 : 250, fixed: 'right' as const, render: (_: any, record: Leader) => ( {record.website && ( )} 0 ? t('leaderList.deleteConfirmDesc', { count: record.copyTradingCount }) : undefined} onConfirm={() => handleDelete(record.id)} okText={t('common.confirm')} cancelText={t('common.cancel')} > ) } ] return (

{t('leaderList.title')}

{isMobile ? (
{loading ? (
) : leaders.length === 0 ? ( ) : ( { const balance = balanceMap[leader.id] return (
{leader.leaderName || `Leader ${leader.id}`}
{leader.leaderAddress}
{balance && (
{t('leaderDetail.availableBalance')}: {balance.available === '-' ? '-' : `${formatUSDC(balance.available)} USDC`}
{t('leaderDetail.positionBalance')}: {formatUSDC(balance.position)}
)}
{leader.copyTradingCount} {t('leaderList.copyTradingCount')}
{leader.remark && (
{t('leaderList.remark')}: {leader.remark}
)}
{leader.website && ( )} 0 ? t('leaderList.deleteConfirmDesc', { count: leader.copyTradingCount }) : undefined} onConfirm={() => handleDelete(leader.id)} okText={t('common.confirm')} cancelText={t('common.cancel')} >
) }} /> )}
) : ( `共 ${total} 条` }} size="large" style={{ fontSize: '14px' }} /> )} {/* 详情 Modal */} {t('leaderDetail.title')} } open={detailModalVisible} onCancel={() => setDetailModalVisible(false)} footer={[ ]} width={isMobile ? '95%' : 1000} style={{ top: 20 }} > {!detailLeader ? (
) : ( <> {/* 基本信息 */} {t('leaderDetail.basicInfo')} } bordered column={isMobile ? 1 : 2} size={isMobile ? 'small' : 'default'} > {detailLeader.leaderName || `Leader ${detailLeader.id}`} {detailLeader.leaderAddress} {detailLeader.copyTradingCount || 0} {detailLeader.remark || -} {formatTimestamp(detailLeader.updatedAt)} {detailLeader.website ? ( ) : -} {/* 余额信息 */}
{t('leaderDetail.balanceInfo')}
{detailBalanceLoading && !detailBalance ? (
) : detailBalance ? ( <>
formatUSDC(value?.toString() || '0')} /> formatUSDC(value?.toString() || '0')} /> formatUSDC(value?.toString() || '0')} /> {/* 持仓列表 */}
{t('leaderDetail.positions')} {detailBalance.positions?.length || 0}
{detailBalance.positions && detailBalance.positions.length > 0 ? (
`${record.title}-${record.side}-${index}`} pagination={{ pageSize: 10, showSizeChanger: !isMobile }} scroll={{ x: isMobile ? 800 : 'auto' }} size={isMobile ? 'small' : 'middle'} /> ) : ( )} ) : ( )} )} ) } export default LeaderList