feat(leader): Leader 管理展示跟单/回测数量并支持跳转筛选

- 后端:LeaderDto 增加 backtestCount,LeaderService 统计回测数并返回
- Leader 列表:展示跟单数、回测数,点击跳转跟单配置/回测页并带 leaderId 筛选
- 跟单配置页:从 URL 读取 leaderId 并应用筛选,Leader 下拉改用 LeaderSelect
- 回测页:支持 Leader 筛选,页面加载时请求 Leader 列表,筛选与详情统一显示规则
- LeaderSelect:支持 style,回测/跟单筛选统一使用
- 多语言:补充 leaderList 相关 key
- 移动端:Leader 管理卡片内按钮增加内边距 padding 8px 16px

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
WrBug
2026-02-09 02:03:17 +08:00
co-authored by Cursor
parent c1829fe21b
commit 5487c7c862
10 changed files with 173 additions and 40 deletions
+39 -15
View File
@@ -1,4 +1,5 @@
import { useState, useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
import { Table, Card, Button, Select, Tag, Space, Modal, message, Row, Col, Form, Input, InputNumber, Switch, Statistic, Descriptions } from 'antd'
import { useTranslation } from 'react-i18next'
import { PlusOutlined, ReloadOutlined, DeleteOutlined, StopOutlined, EyeOutlined, RedoOutlined, CopyOutlined, SyncOutlined } from '@ant-design/icons'
@@ -13,6 +14,7 @@ import LeaderSelect from '../components/LeaderSelect'
const BacktestList: React.FC = () => {
const { t } = useTranslation()
const [searchParams] = useSearchParams()
const isMobile = useMediaQuery({ maxWidth: 768 })
const [loading, setLoading] = useState(false)
const [tasks, setTasks] = useState<BacktestTaskDto[]>([])
@@ -20,7 +22,14 @@ const BacktestList: React.FC = () => {
const [page, setPage] = useState(1)
const [size] = useState(10)
const [statusFilter, setStatusFilter] = useState<string | undefined>()
const [leaderIdFilter] = useState<number | undefined>()
const [leaderIdFilter, setLeaderIdFilter] = useState<number | undefined>(() => {
const leaderIdParam = searchParams.get('leaderId')
if (leaderIdParam) {
const id = parseInt(leaderIdParam, 10)
return isNaN(id) ? undefined : id
}
return undefined
})
const [sortBy, setSortBy] = useState<'profitAmount' | 'profitRate' | 'createdAt'>('createdAt')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
@@ -80,6 +89,15 @@ const BacktestList: React.FC = () => {
}
}
// 从 URL 读取 leaderId 并应用筛选(如从 Leader 管理页跳转过来)
useEffect(() => {
const leaderIdParam = searchParams.get('leaderId')
if (leaderIdParam) {
const id = parseInt(leaderIdParam, 10)
setLeaderIdFilter(isNaN(id) ? undefined : id)
}
}, [searchParams])
useEffect(() => {
fetchTasks()
}, [page, statusFilter, leaderIdFilter, sortBy, sortOrder])
@@ -200,22 +218,20 @@ const BacktestList: React.FC = () => {
})
}
// 获取 Leader 列表
// 获取 Leader 列表(页面加载时请求,供筛选和创建任务使用)
useEffect(() => {
if (createModalVisible) {
const fetchLeaders = async () => {
try {
const response = await apiService.leaders.list({})
if (response.data.code === 0 && response.data.data) {
setLeaders(response.data.data.list || [])
}
} catch (error) {
console.error('Failed to fetch leaders:', error)
const fetchLeaders = async () => {
try {
const response = await apiService.leaders.list({})
if (response.data.code === 0 && response.data.data) {
setLeaders(response.data.data.list || [])
}
} catch (error) {
console.error('Failed to fetch leaders:', error)
}
fetchLeaders()
}
}, [createModalVisible])
fetchLeaders()
}, [])
// 打开创建 modal
const handleCreate = () => {
@@ -487,7 +503,7 @@ const BacktestList: React.FC = () => {
dataIndex: 'leaderName',
key: 'leaderName',
width: isMobile ? 100 : 150,
render: (_: any, record: BacktestTaskDto) => record.leaderName || record.leaderAddress?.substring(0, 8) + '...' || '-'
render: (_: any, record: BacktestTaskDto) => record.leaderName || `Leader ${record.leaderId}`
},
{
title: t('backtest.initialBalance'),
@@ -656,6 +672,14 @@ const BacktestList: React.FC = () => {
<Row justify="space-between" align="middle" gutter={[16, 16]}>
<Col xs={24} sm={24} md={12} lg={16}>
<Space size="middle" direction={isMobile ? 'vertical' : 'horizontal'} style={{ width: isMobile ? '100%' : 'auto' }}>
<LeaderSelect
style={{ width: isMobile ? '100%' : 180 }}
placeholder={t('backtest.leader')}
allowClear
value={leaderIdFilter}
onChange={(value) => setLeaderIdFilter(value)}
leaders={leaders}
/>
<Select
style={{ width: isMobile ? '100%' : 150 }}
placeholder={t('backtest.status')}
@@ -1068,7 +1092,7 @@ const BacktestList: React.FC = () => {
<Descriptions column={isMobile ? 1 : 2} bordered size="small">
<Descriptions.Item label={t('backtest.taskName')}>{detailTask.taskName}</Descriptions.Item>
<Descriptions.Item label={t('backtest.leader')}>
{detailTask.leaderName || detailTask.leaderAddress}
{detailTask.leaderName || `Leader ${detailTask.leaderId}`}
</Descriptions.Item>
<Descriptions.Item label={t('backtest.initialBalance')}>
{formatUSDC(detailTask.initialBalance)} USDC