From f86749e85df81aad827bbddd3fadb5df1e9e5137 Mon Sep 17 00:00:00 2001 From: WrBug Date: Sun, 1 Mar 2026 05:57:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E4=BC=98=E5=8C=96=20Leader?= =?UTF-8?q?=20=E5=88=97=E8=A1=A8=20UI=EF=BC=8C=E4=BD=BF=E7=94=A8=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=E6=93=8D=E4=BD=9C=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移动端:Cell 样式美化,渐变背景头部,资产常驻显示 - PC 端:操作列改用图标,合并跟单/回测数到图标 Badge - 跟单/回测数量用 Badge 显示,数量为 0 时半透明禁用 Made-with: Cursor --- frontend/src/pages/LeaderList.tsx | 392 +++++++++++++++++++++--------- 1 file changed, 283 insertions(+), 109 deletions(-) diff --git a/frontend/src/pages/LeaderList.tsx b/frontend/src/pages/LeaderList.tsx index 8099e78..9151081 100644 --- a/frontend/src/pages/LeaderList.tsx +++ b/frontend/src/pages/LeaderList.tsx @@ -1,7 +1,7 @@ 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 { Card, Table, Button, Space, Tag, Popconfirm, message, List, Empty, Spin, Divider, Typography, Modal, Descriptions, Statistic, Row, Col, Tooltip, Badge } from 'antd' +import { PlusOutlined, EditOutlined, DeleteOutlined, GlobalOutlined, EyeOutlined, ReloadOutlined, WalletOutlined, CopyOutlined, LineChartOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import { apiService } from '../services/api' import type { Leader, LeaderBalanceResponse } from '../types' @@ -225,11 +225,11 @@ const LeaderList: React.FC = () => { title: t('leaderList.leaderName'), dataIndex: 'leaderName', key: 'leaderName', - width: 150, + width: 200, render: (text: string, record: Leader) => ( {text || `Leader ${record.id}`} - {record.leaderAddress} + {record.leaderAddress} ) }, @@ -237,76 +237,155 @@ const LeaderList: React.FC = () => { title: t('leaderList.remark'), dataIndex: 'remark', key: 'remark', - width: 200, + width: 180, ellipsis: true, render: (remark: string | undefined) => { if (!remark) return - - return {remark} + return {remark} } }, { title: t('leaderDetail.availableBalance'), key: 'balance', - width: 150, + width: 180, render: (_: any, record: Leader) => { const balance = balanceMap[record.id] if (!balance) return - const displayText = balance.available === '-' ? '-' : `${formatUSDC(balance.available)} USDC` - return {displayText} + return ( + + + {balance.available === '-' ? '-' : `${formatUSDC(balance.available)} USDC`} + + + {t('leaderDetail.positionBalance')}: {formatUSDC(balance.position)} + + + ) } }, - { - title: t('leaderList.copyTradingCount'), - dataIndex: 'copyTradingCount', - key: 'copyTradingCount', - width: 100, - render: (count: number, record: Leader) => ( - - ) - }, - { - title: t('leaderList.backtestCount'), - dataIndex: 'backtestCount', - key: 'backtestCount', - width: 100, - render: (count: number, record: Leader) => ( - - ) - }, { title: t('common.actions'), key: 'action', - width: isMobile ? 180 : 250, + width: 200, fixed: 'right' as const, render: (_: any, record: Leader) => ( - - + + +
handleShowDetail(record)} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '32px', + cursor: 'pointer', + borderRadius: '6px', + transition: 'background-color 0.2s' + }} + onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#f0f0f0'} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + +
+
+ {record.website && ( - + +
window.open(record.website, '_blank', 'noopener,noreferrer')} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '32px', + cursor: 'pointer', + borderRadius: '6px', + transition: 'background-color 0.2s' + }} + onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#f0f0f0'} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + +
+
)} - + + +
navigate(`/leaders/edit?id=${record.id}`)} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '32px', + cursor: 'pointer', + borderRadius: '6px', + transition: 'background-color 0.2s' + }} + onMouseEnter={(e) => e.currentTarget.style.backgroundColor = '#f0f0f0'} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + +
+
+ + +
{ + if (record.copyTradingCount > 0) { + navigate(`/copy-trading?leaderId=${record.id}`) + } + }} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '32px', + cursor: record.copyTradingCount === 0 ? 'not-allowed' : 'pointer', + borderRadius: '6px', + opacity: record.copyTradingCount === 0 ? 0.4 : 1, + transition: 'background-color 0.2s' + }} + onMouseEnter={(e) => record.copyTradingCount > 0 && (e.currentTarget.style.backgroundColor = '#f0f0f0')} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + + + +
+
+ + +
{ + if (record.backtestCount > 0) { + navigate(`/backtest?leaderId=${record.id}`) + } + }} + style={{ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: '32px', + height: '32px', + cursor: record.backtestCount === 0 ? 'not-allowed' : 'pointer', + borderRadius: '6px', + opacity: record.backtestCount === 0 ? 0.4 : 1, + transition: 'background-color 0.2s' + }} + onMouseEnter={(e) => record.backtestCount > 0 && (e.currentTarget.style.backgroundColor = '#f0f0f0')} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + + + +
+
+ 0 ? t('leaderList.deleteConfirmDesc', { count: record.copyTradingCount }) : undefined} @@ -314,9 +393,24 @@ const LeaderList: React.FC = () => { okText={t('common.confirm')} cancelText={t('common.cancel')} > - + +
e.currentTarget.style.backgroundColor = '#fff1f0'} + onMouseLeave={(e) => e.currentTarget.style.backgroundColor = 'transparent'} + > + +
+
) @@ -346,71 +440,148 @@ const LeaderList: React.FC = () => { dataSource={leaders} renderItem={(leader) => { const balance = balanceMap[leader.id] + const isLoading = balanceLoading[leader.id] return ( - -
-
- {leader.leaderName || `Leader ${leader.id}`} + + {/* 头部区域 - 名称和地址 */} +
+
+ {leader.leaderName || `Leader ${leader.id}`} + {leader.website && ( + window.open(leader.website, '_blank', 'noopener,noreferrer')} + /> + )}
-
+
{leader.leaderAddress}
- {balance && ( -
-
- {t('leaderDetail.availableBalance')}: {balance.available === '-' ? '-' : `${formatUSDC(balance.available)} USDC`} + {/* 资产区域 - 常驻显示 */} +
+
+
+
+ {t('leaderDetail.availableBalance')} +
+
+ {balance?.available && balance.available !== '-' ? `${formatUSDC(balance.available)} USDC` : '- USDC'} +
-
- {t('leaderDetail.positionBalance')}: {formatUSDC(balance.position)} +
+
+ {t('leaderDetail.positionBalance')} +
+
+ {balance?.position && balance.position !== '-' ? formatUSDC(balance.position) : '-'} +
- )} - - - -
- -
+ {/* 备注区域 */} {leader.remark && ( -
- {t('leaderList.remark')}: - {leader.remark} +
+ {t('leaderList.remark')}: + {leader.remark}
)} -
- - {leader.website && ( - - )} - + {/* 图标操作栏 */} +
+ +
handleShowDetail(leader)} + style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', cursor: 'pointer', padding: '4px 8px' }} + > + + {t('common.viewDetail')} +
+
+ + +
navigate(`/leaders/edit?id=${leader.id}`)} + style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', cursor: 'pointer', padding: '4px 8px' }} + > + + {t('common.edit')} +
+
+ + +
navigate(`/copy-trading?leaderId=${leader.id}`)} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + cursor: leader.copyTradingCount === 0 ? 'not-allowed' : 'pointer', + padding: '4px 8px', + opacity: leader.copyTradingCount === 0 ? 0.4 : 1 + }} + > + + + + {t('leaderList.viewCopyTradings')} +
+
+ + +
navigate(`/backtest?leaderId=${leader.id}`)} + style={{ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + cursor: leader.backtestCount === 0 ? 'not-allowed' : 'pointer', + padding: '4px 8px', + opacity: leader.backtestCount === 0 ? 0.4 : 1 + }} + > + + + + {t('leaderList.viewBacktests')} +
+
+ 0 ? t('leaderList.deleteConfirmDesc', { count: leader.copyTradingCount }) : undefined} @@ -418,9 +589,12 @@ const LeaderList: React.FC = () => { okText={t('common.confirm')} cancelText={t('common.cancel')} > - + +
+ + {t('common.delete')} +
+