diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/LeaderDto.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/LeaderDto.kt index 8d91ae1..c56b8b5 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/LeaderDto.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/dto/LeaderDto.kt @@ -54,6 +54,7 @@ data class LeaderDto( val remark: String? = null, // Leader 备注(可选) val website: String? = null, // Leader 网站(可选) val copyTradingCount: Long = 0, // 跟单关系数量 + val backtestCount: Long = 0, // 回测数量 val totalOrders: Long? = null, // 总订单数(可选) val totalPnl: String? = null, // 总盈亏(可选) val createdAt: Long, diff --git a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt index 4ab057d..669c663 100644 --- a/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt +++ b/backend/src/main/kotlin/com/wrbug/polymarketbot/service/copytrading/leaders/LeaderService.kt @@ -3,6 +3,7 @@ package com.wrbug.polymarketbot.service.copytrading.leaders import com.wrbug.polymarketbot.dto.* import com.wrbug.polymarketbot.entity.Leader import com.wrbug.polymarketbot.repository.AccountRepository +import com.wrbug.polymarketbot.repository.BacktestTaskRepository import com.wrbug.polymarketbot.repository.CopyTradingRepository import com.wrbug.polymarketbot.repository.LeaderRepository import com.wrbug.polymarketbot.service.common.BlockchainService @@ -20,9 +21,10 @@ class LeaderService( private val leaderRepository: LeaderRepository, private val accountRepository: AccountRepository, private val copyTradingRepository: CopyTradingRepository, + private val backtestTaskRepository: BacktestTaskRepository, private val blockchainService: BlockchainService ) { - + private val logger = LoggerFactory.getLogger(LeaderService::class.java) /** @@ -157,7 +159,8 @@ class LeaderService( val leaderDtos = leaders.map { leader -> val copyTradingCount = copyTradingRepository.countByLeaderId(leader.id!!) - toDto(leader, copyTradingCount) + val backtestCount = backtestTaskRepository.findByLeaderId(leader.id).size.toLong() + toDto(leader, copyTradingCount, backtestCount) } Result.success( @@ -181,7 +184,8 @@ class LeaderService( ?: return Result.failure(IllegalArgumentException("Leader 不存在")) val copyTradingCount = copyTradingRepository.countByLeaderId(leaderId) - Result.success(toDto(leader, copyTradingCount)) + val backtestCount = backtestTaskRepository.findByLeaderId(leaderId).size.toLong() + Result.success(toDto(leader, copyTradingCount, backtestCount)) } catch (e: Exception) { logger.error("查询 Leader 详情失败", e) Result.failure(e) @@ -225,7 +229,7 @@ class LeaderService( /** * 转换为 DTO */ - private fun toDto(leader: Leader, copyTradingCount: Long = 0): LeaderDto { + private fun toDto(leader: Leader, copyTradingCount: Long = 0, backtestCount: Long = 0): LeaderDto { return LeaderDto( id = leader.id!!, leaderAddress = leader.leaderAddress, @@ -234,6 +238,7 @@ class LeaderService( remark = leader.remark, website = leader.website, copyTradingCount = copyTradingCount, + backtestCount = backtestCount, createdAt = leader.createdAt, updatedAt = leader.updatedAt ) diff --git a/frontend/src/components/LeaderSelect.tsx b/frontend/src/components/LeaderSelect.tsx index 099ce87..67fdaf0 100644 --- a/frontend/src/components/LeaderSelect.tsx +++ b/frontend/src/components/LeaderSelect.tsx @@ -14,6 +14,7 @@ interface LeaderSelectProps { showSearch?: boolean allowClear?: boolean notFoundContent?: React.ReactNode + style?: React.CSSProperties } const LeaderSelect: React.FC = ({ @@ -25,7 +26,8 @@ const LeaderSelect: React.FC = ({ disabled, showSearch = true, allowClear = false, - notFoundContent + notFoundContent, + style }) => { // 处理选择变化 const handleChange = (val: number | undefined) => { @@ -46,6 +48,7 @@ const LeaderSelect: React.FC = ({ showSearch={showSearch} allowClear={allowClear} notFoundContent={notFoundContent} + style={style} optionFilterProp="label" optionLabelProp="label" > diff --git a/frontend/src/locales/en/common.json b/frontend/src/locales/en/common.json index bb8f0b6..6ac9d05 100644 --- a/frontend/src/locales/en/common.json +++ b/frontend/src/locales/en/common.json @@ -434,7 +434,10 @@ "openWebsite": "Open Website", "all": "All", "copyTradingCount": "Copy Trading Count", + "backtestCount": "Backtest Count", "copyTradingRelations": "{{count}} copy trading relations", + "viewCopyTradings": "View Copy Tradings", + "viewBacktests": "View Backtests", "createdAt": "Created At", "noData": "No Leader data", "fetchFailed": "Failed to get Leader list", @@ -442,7 +445,9 @@ "deleteFailed": "Failed to delete Leader", "deleteConfirm": "Are you sure you want to delete this Leader?", "deleteConfirmDesc": "This Leader has {{count}} copy trading relations, please delete them first", - "openDetailFailed": "Failed to open detail" + "openDetailFailed": "Failed to open detail", + "noCopyTradings": "No copy trading configs", + "noBacktests": "No backtests" }, "leaderAdd": { "title": "Add Leader", diff --git a/frontend/src/locales/zh-CN/common.json b/frontend/src/locales/zh-CN/common.json index 3255910..52d8932 100644 --- a/frontend/src/locales/zh-CN/common.json +++ b/frontend/src/locales/zh-CN/common.json @@ -434,7 +434,10 @@ "openWebsite": "打开网页", "all": "全部", "copyTradingCount": "跟单数", + "backtestCount": "回测数", "copyTradingRelations": "{{count}} 个跟单关系", + "viewCopyTradings": "查看跟单配置", + "viewBacktests": "查看回测", "createdAt": "创建时间", "noData": "暂无 Leader 数据", "fetchFailed": "获取 Leader 列表失败", @@ -442,7 +445,9 @@ "deleteFailed": "删除 Leader 失败", "deleteConfirm": "确定要删除这个 Leader 吗?", "deleteConfirmDesc": "该 Leader 还有 {{count}} 个跟单关系,请先删除跟单关系", - "openDetailFailed": "打开详情失败" + "openDetailFailed": "打开详情失败", + "noCopyTradings": "暂无跟单配置", + "noBacktests": "暂无回测" }, "leaderAdd": { "title": "添加 Leader", diff --git a/frontend/src/locales/zh-TW/common.json b/frontend/src/locales/zh-TW/common.json index f36aa06..d72ac67 100644 --- a/frontend/src/locales/zh-TW/common.json +++ b/frontend/src/locales/zh-TW/common.json @@ -434,7 +434,10 @@ "openWebsite": "打開網頁", "all": "全部", "copyTradingCount": "跟單數", + "backtestCount": "回測數", "copyTradingRelations": "{{count}} 個跟單關係", + "viewCopyTradings": "查看跟單配置", + "viewBacktests": "查看回測", "createdAt": "創建時間", "noData": "暫無 Leader 數據", "fetchFailed": "獲取 Leader 列表失敗", @@ -442,7 +445,9 @@ "deleteFailed": "刪除 Leader 失敗", "deleteConfirm": "確定要刪除這個 Leader 嗎?", "deleteConfirmDesc": "該 Leader 還有 {{count}} 個跟單關係,請先刪除跟單關係", - "openDetailFailed": "打開詳情失敗" + "openDetailFailed": "打開詳情失敗", + "noCopyTradings": "暫無跟單配置", + "noBacktests": "暫無回測" }, "leaderAdd": { "title": "添加 Leader", diff --git a/frontend/src/pages/BacktestList.tsx b/frontend/src/pages/BacktestList.tsx index 302765f..01cf516 100644 --- a/frontend/src/pages/BacktestList.tsx +++ b/frontend/src/pages/BacktestList.tsx @@ -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([]) @@ -20,7 +22,14 @@ const BacktestList: React.FC = () => { const [page, setPage] = useState(1) const [size] = useState(10) const [statusFilter, setStatusFilter] = useState() - const [leaderIdFilter] = useState() + const [leaderIdFilter, setLeaderIdFilter] = useState(() => { + 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 = () => { + setLeaderIdFilter(value)} + leaders={leaders} + /> - + leaders={leaders} + />