feat: 添加从回测任务创建跟单配置功能
- 在回测任务列表页和详情页添加'创建跟单'按钮 - 实现从回测任务配置自动填充到跟单配置表单 - 复用 AddModal 组件,支持 preFilledConfig 参数预填充 - 修复配置字段从 BacktestConfigDto 获取的问题 - 列表页点击按钮时先获取任务详情以获取配置信息 - 添加调试日志便于问题排查 - 更新多语言文案:'一键创建跟单配置' -> '创建跟单'
This commit is contained in:
@@ -1279,6 +1279,8 @@
|
|||||||
"retryConfirm": "Are you sure you want to retry this backtest task? It will continue from the breakpoint and preserve processed trades.",
|
"retryConfirm": "Are you sure you want to retry this backtest task? It will continue from the breakpoint and preserve processed trades.",
|
||||||
"deleteConfirm": "Are you sure you want to delete this backtest task?",
|
"deleteConfirm": "Are you sure you want to delete this backtest task?",
|
||||||
"stopConfirm": "Are you sure you want to stop this backtest task?",
|
"stopConfirm": "Are you sure you want to stop this backtest task?",
|
||||||
|
"createCopyTrading": "Create Copy Trading",
|
||||||
|
"createCopyTradingSuccess": "Copy trading config created successfully",
|
||||||
"noTasks": "No backtest tasks",
|
"noTasks": "No backtest tasks",
|
||||||
"noTrades": "No trade records",
|
"noTrades": "No trade records",
|
||||||
"fetchTasksFailed": "Failed to fetch task list",
|
"fetchTasksFailed": "Failed to fetch task list",
|
||||||
|
|||||||
@@ -1279,6 +1279,8 @@
|
|||||||
"retryConfirm": "确定重新运行此回测任务吗?将从断点继续执行,保留已处理的交易记录。",
|
"retryConfirm": "确定重新运行此回测任务吗?将从断点继续执行,保留已处理的交易记录。",
|
||||||
"deleteConfirm": "确定删除此回测任务吗?",
|
"deleteConfirm": "确定删除此回测任务吗?",
|
||||||
"stopConfirm": "确定停止此回测任务吗?",
|
"stopConfirm": "确定停止此回测任务吗?",
|
||||||
|
"createCopyTrading": "创建跟单",
|
||||||
|
"createCopyTradingSuccess": "跟单配置创建成功",
|
||||||
"noTasks": "暂无回测任务",
|
"noTasks": "暂无回测任务",
|
||||||
"noTrades": "暂无交易记录",
|
"noTrades": "暂无交易记录",
|
||||||
"fetchTasksFailed": "获取任务列表失败",
|
"fetchTasksFailed": "获取任务列表失败",
|
||||||
|
|||||||
@@ -1279,6 +1279,8 @@
|
|||||||
"retryConfirm": "確定重新運行此回測任務嗎?將從斷點繼續執行,保留已處理的交易記錄。",
|
"retryConfirm": "確定重新運行此回測任務嗎?將從斷點繼續執行,保留已處理的交易記錄。",
|
||||||
"deleteConfirm": "確定刪除此回測任務嗎?",
|
"deleteConfirm": "確定刪除此回測任務嗎?",
|
||||||
"stopConfirm": "確定停止此回測任務嗎?",
|
"stopConfirm": "確定停止此回測任務嗎?",
|
||||||
|
"createCopyTrading": "創建跟單",
|
||||||
|
"createCopyTradingSuccess": "跟單配置創建成功",
|
||||||
"noTasks": "暫無回測任務",
|
"noTasks": "暫無回測任務",
|
||||||
"noTrades": "暫無交易記錄",
|
"noTrades": "暫無交易記錄",
|
||||||
"fetchTasksFailed": "獲取任務列表失敗",
|
"fetchTasksFailed": "獲取任務列表失敗",
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useParams, useNavigate } from 'react-router-dom'
|
import { useParams, useNavigate } from 'react-router-dom'
|
||||||
import { Card, Descriptions, Button, Tag, Space, Table, message, Row, Col, Statistic, Spin } from 'antd'
|
import { Card, Descriptions, Button, Tag, Space, Table, message, Row, Col, Statistic, Spin } from 'antd'
|
||||||
import { ArrowLeftOutlined, ReloadOutlined, DeleteOutlined, StopOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, ReloadOutlined, DeleteOutlined, StopOutlined, CopyOutlined } from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { formatUSDC } from '../utils'
|
import { formatUSDC } from '../utils'
|
||||||
import { backtestService } from '../services/api'
|
import { backtestService } from '../services/api'
|
||||||
import type { BacktestTaskDto, BacktestConfigDto, BacktestStatisticsDto, BacktestTradeDto } from '../types/backtest'
|
import type { BacktestTaskDto, BacktestConfigDto, BacktestStatisticsDto, BacktestTradeDto } from '../types/backtest'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
import BacktestChart from './BacktestChart'
|
import BacktestChart from './BacktestChart'
|
||||||
|
import AddCopyTradingModal from './CopyTradingOrders/AddModal'
|
||||||
|
|
||||||
const BacktestDetail: React.FC = () => {
|
const BacktestDetail: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -16,7 +17,7 @@ const BacktestDetail: React.FC = () => {
|
|||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [task, setTask] = useState<BacktestTaskDto | null>(null)
|
const [task, setTask] = useState<BacktestTaskDto | null>(null)
|
||||||
const [, setConfig] = useState<BacktestConfigDto | null>(null)
|
const [config, setConfig] = useState<BacktestConfigDto | null>(null)
|
||||||
const [statistics, setStatistics] = useState<BacktestStatisticsDto | null>(null)
|
const [statistics, setStatistics] = useState<BacktestStatisticsDto | null>(null)
|
||||||
const [trades, setTrades] = useState<BacktestTradeDto[]>([])
|
const [trades, setTrades] = useState<BacktestTradeDto[]>([])
|
||||||
const [allTrades, setAllTrades] = useState<BacktestTradeDto[]>([]) // 用于图表显示的所有交易数据
|
const [allTrades, setAllTrades] = useState<BacktestTradeDto[]>([]) // 用于图表显示的所有交易数据
|
||||||
@@ -26,6 +27,10 @@ const BacktestDetail: React.FC = () => {
|
|||||||
const [tradesSize] = useState(20)
|
const [tradesSize] = useState(20)
|
||||||
const [polling, setPolling] = useState<NodeJS.Timeout | null>(null)
|
const [polling, setPolling] = useState<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
|
// 创建跟单配置 Modal
|
||||||
|
const [addCopyTradingModalVisible, setAddCopyTradingModalVisible] = useState(false)
|
||||||
|
const [preFilledConfig, setPreFilledConfig] = useState<any>(null)
|
||||||
|
|
||||||
// 获取回测任务详情
|
// 获取回测任务详情
|
||||||
const fetchTaskDetail = async () => {
|
const fetchTaskDetail = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -173,6 +178,36 @@ const BacktestDetail: React.FC = () => {
|
|||||||
fetchTrades(tradesPage)
|
fetchTrades(tradesPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一键创建跟单配置
|
||||||
|
const handleCreateCopyTrading = () => {
|
||||||
|
console.log('[BacktestDetail] handleCreateCopyTrading called, task:', task, 'config:', config)
|
||||||
|
if (!task || !config) {
|
||||||
|
console.log('[BacktestDetail] No task or config available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预填充回测任务的配置参数(从 config 中获取)
|
||||||
|
const preFilled = {
|
||||||
|
leaderId: task.leaderId,
|
||||||
|
copyMode: config.copyMode,
|
||||||
|
copyRatio: config.copyMode === 'RATIO' ? parseFloat(config.copyRatio) * 100 : undefined,
|
||||||
|
fixedAmount: config.copyMode === 'FIXED' ? config.fixedAmount : undefined,
|
||||||
|
maxOrderSize: parseFloat(config.maxOrderSize),
|
||||||
|
minOrderSize: parseFloat(config.minOrderSize),
|
||||||
|
maxDailyLoss: parseFloat(config.maxDailyLoss),
|
||||||
|
maxDailyOrders: config.maxDailyOrders,
|
||||||
|
supportSell: config.supportSell,
|
||||||
|
keywordFilterMode: config.keywordFilterMode || 'DISABLED',
|
||||||
|
keywords: config.keywords || [],
|
||||||
|
configName: `回测任务-${task.taskName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[BacktestDetail] Generated preFilled config:', preFilled)
|
||||||
|
console.log('[BacktestDetail] Setting preFilledConfig and opening modal')
|
||||||
|
setPreFilledConfig(preFilled)
|
||||||
|
setAddCopyTradingModalVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
// 状态标签颜色
|
// 状态标签颜色
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -285,6 +320,11 @@ const BacktestDetail: React.FC = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
<Space>
|
<Space>
|
||||||
|
{task.status === 'COMPLETED' && (
|
||||||
|
<Button type="primary" icon={<CopyOutlined />} onClick={handleCreateCopyTrading} size={isMobile ? 'middle' : 'large'}>
|
||||||
|
{t('backtest.createCopyTrading')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{(task.status === 'RUNNING' || task.status === 'PENDING') && (
|
{(task.status === 'RUNNING' || task.status === 'PENDING') && (
|
||||||
<Button danger icon={<StopOutlined />} onClick={handleStop} size={isMobile ? 'middle' : 'large'}>
|
<Button danger icon={<StopOutlined />} onClick={handleStop} size={isMobile ? 'middle' : 'large'}>
|
||||||
{t('backtest.stop')}
|
{t('backtest.stop')}
|
||||||
@@ -463,6 +503,21 @@ const BacktestDetail: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* 创建跟单配置 Modal */}
|
||||||
|
<AddCopyTradingModal
|
||||||
|
open={addCopyTradingModalVisible}
|
||||||
|
onClose={() => {
|
||||||
|
setAddCopyTradingModalVisible(false)
|
||||||
|
setPreFilledConfig(null)
|
||||||
|
}}
|
||||||
|
onSuccess={() => {
|
||||||
|
message.success(t('backtest.createCopyTradingSuccess'))
|
||||||
|
setAddCopyTradingModalVisible(false)
|
||||||
|
setPreFilledConfig(null)
|
||||||
|
}}
|
||||||
|
preFilledConfig={preFilledConfig}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import { useState, useEffect } from 'react'
|
|||||||
import { Table, Card, Button, Select, Tag, Space, Modal, message, Row, Col, Form, Input, InputNumber, Switch } from 'antd'
|
import { Table, Card, Button, Select, Tag, Space, Modal, message, Row, Col, Form, Input, InputNumber, Switch } from 'antd'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { PlusOutlined, ReloadOutlined, DeleteOutlined, StopOutlined, EyeOutlined, RedoOutlined } from '@ant-design/icons'
|
import { PlusOutlined, ReloadOutlined, DeleteOutlined, StopOutlined, EyeOutlined, RedoOutlined, CopyOutlined } from '@ant-design/icons'
|
||||||
import { formatUSDC } from '../utils'
|
import { formatUSDC } from '../utils'
|
||||||
import { backtestService, apiService } from '../services/api'
|
import { backtestService, apiService } from '../services/api'
|
||||||
import type { BacktestTaskDto, BacktestListRequest, BacktestCreateRequest } from '../types/backtest'
|
import type { BacktestTaskDto, BacktestListRequest, BacktestCreateRequest } from '../types/backtest'
|
||||||
import type { Leader } from '../types'
|
import type { Leader } from '../types'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import AddCopyTradingModal from './CopyTradingOrders/AddModal'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
@@ -32,6 +33,10 @@ const BacktestList: React.FC = () => {
|
|||||||
const [leaders, setLeaders] = useState<Leader[]>([])
|
const [leaders, setLeaders] = useState<Leader[]>([])
|
||||||
const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO')
|
const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO')
|
||||||
|
|
||||||
|
// 创建跟单配置 Modal
|
||||||
|
const [addCopyTradingModalVisible, setAddCopyTradingModalVisible] = useState(false)
|
||||||
|
const [preFilledConfig, setPreFilledConfig] = useState<any>(null)
|
||||||
|
|
||||||
// 获取回测任务列表
|
// 获取回测任务列表
|
||||||
const fetchTasks = async () => {
|
const fetchTasks = async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -217,6 +222,53 @@ const BacktestList: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 一键创建跟单配置
|
||||||
|
const handleCreateCopyTrading = async (task: BacktestTaskDto) => {
|
||||||
|
console.log('[BacktestList] handleCreateCopyTrading called, task:', task)
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 先获取任务详情以获取配置信息
|
||||||
|
const response = await backtestService.detail({ id: task.id })
|
||||||
|
if (response.data.code === 0 && response.data.data) {
|
||||||
|
const taskDetail = response.data.data.task
|
||||||
|
const taskConfig = response.data.data.config
|
||||||
|
|
||||||
|
console.log('[BacktestList] Fetched task detail, config:', taskConfig)
|
||||||
|
|
||||||
|
if (!taskConfig) {
|
||||||
|
message.error(t('backtest.fetchTaskDetailFailed') || '获取任务配置失败')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预填充回测任务的配置参数(从 config 中获取)
|
||||||
|
const preFilled = {
|
||||||
|
leaderId: taskDetail.leaderId,
|
||||||
|
copyMode: taskConfig.copyMode,
|
||||||
|
copyRatio: taskConfig.copyMode === 'RATIO' ? parseFloat(taskConfig.copyRatio) * 100 : undefined,
|
||||||
|
fixedAmount: taskConfig.copyMode === 'FIXED' ? taskConfig.fixedAmount : undefined,
|
||||||
|
maxOrderSize: parseFloat(taskConfig.maxOrderSize),
|
||||||
|
minOrderSize: parseFloat(taskConfig.minOrderSize),
|
||||||
|
maxDailyLoss: parseFloat(taskConfig.maxDailyLoss),
|
||||||
|
maxDailyOrders: taskConfig.maxDailyOrders,
|
||||||
|
supportSell: taskConfig.supportSell,
|
||||||
|
keywordFilterMode: taskConfig.keywordFilterMode || 'DISABLED',
|
||||||
|
keywords: taskConfig.keywords || [],
|
||||||
|
configName: `回测任务-${taskDetail.taskName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[BacktestList] Generated preFilled config:', preFilled)
|
||||||
|
console.log('[BacktestList] Setting preFilledConfig and opening modal')
|
||||||
|
setPreFilledConfig(preFilled)
|
||||||
|
setAddCopyTradingModalVisible(true)
|
||||||
|
} else {
|
||||||
|
message.error(response.data.msg || t('backtest.fetchTaskDetailFailed') || '获取任务详情失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[BacktestList] Failed to fetch task detail:', error)
|
||||||
|
message.error(t('backtest.fetchTaskDetailFailed') || '获取任务详情失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 状态标签颜色
|
// 状态标签颜色
|
||||||
const getStatusColor = (status: string) => {
|
const getStatusColor = (status: string) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -357,6 +409,16 @@ const BacktestList: React.FC = () => {
|
|||||||
>
|
>
|
||||||
{t('common.viewDetail')}
|
{t('common.viewDetail')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{record.status === 'COMPLETED' && (
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
icon={<CopyOutlined />}
|
||||||
|
onClick={() => handleCreateCopyTrading(record)}
|
||||||
|
>
|
||||||
|
{t('backtest.createCopyTrading')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{record.status === 'RUNNING' && (
|
{record.status === 'RUNNING' && (
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
@@ -712,6 +774,21 @@ const BacktestList: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{/* 创建跟单配置 Modal */}
|
||||||
|
<AddCopyTradingModal
|
||||||
|
open={addCopyTradingModalVisible}
|
||||||
|
onClose={() => {
|
||||||
|
setAddCopyTradingModalVisible(false)
|
||||||
|
setPreFilledConfig(null)
|
||||||
|
}}
|
||||||
|
onSuccess={() => {
|
||||||
|
message.success(t('backtest.createCopyTradingSuccess'))
|
||||||
|
setAddCopyTradingModalVisible(false)
|
||||||
|
setPreFilledConfig(null)
|
||||||
|
}}
|
||||||
|
preFilledConfig={preFilledConfig}
|
||||||
|
/>
|
||||||
</div >
|
</div >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,27 @@ interface AddModalProps {
|
|||||||
open: boolean
|
open: boolean
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
onSuccess?: () => void
|
onSuccess?: () => void
|
||||||
|
preFilledConfig?: {
|
||||||
|
leaderId?: number
|
||||||
|
copyMode?: 'RATIO' | 'FIXED'
|
||||||
|
copyRatio?: number
|
||||||
|
fixedAmount?: string
|
||||||
|
maxOrderSize?: number
|
||||||
|
minOrderSize?: number
|
||||||
|
maxDailyLoss?: number
|
||||||
|
maxDailyOrders?: number
|
||||||
|
supportSell?: boolean
|
||||||
|
keywordFilterMode?: string
|
||||||
|
keywords?: string[]
|
||||||
|
configName?: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddModal: React.FC<AddModalProps> = ({
|
const AddModal: React.FC<AddModalProps> = ({
|
||||||
open,
|
open,
|
||||||
onClose,
|
onClose,
|
||||||
onSuccess
|
onSuccess,
|
||||||
|
preFilledConfig
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
@@ -63,26 +78,113 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
})
|
})
|
||||||
return `跟单配置-${dateStr}-${timeStr}`
|
return `跟单配置-${dateStr}-${timeStr}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取 Leader 资产信息
|
||||||
|
const fetchLeaderAssetInfo = async (leaderId: number) => {
|
||||||
|
if (!leaderId) return
|
||||||
|
|
||||||
|
setLoadingAssetInfo(true)
|
||||||
|
setLeaderAssetInfo(null)
|
||||||
|
try {
|
||||||
|
const response = await apiService.leaders.balance({ leaderId })
|
||||||
|
if (response.data.code === 0 && response.data.data) {
|
||||||
|
const balance = response.data.data
|
||||||
|
setLeaderAssetInfo({
|
||||||
|
total: balance.totalBalance || '0',
|
||||||
|
available: balance.availableBalance || '0',
|
||||||
|
position: balance.positionBalance || '0'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
message.error(response.data.msg || t('copyTradingAdd.fetchAssetInfoFailed') || '获取资产信息失败')
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('获取 Leader 资产失败:', error)
|
||||||
|
message.error(error.message || t('copyTradingAdd.fetchAssetInfoFailed') || '获取资产信息失败')
|
||||||
|
} finally {
|
||||||
|
setLoadingAssetInfo(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充预配置数据到表单(复用模板填充逻辑)
|
||||||
|
const fillPreFilledConfig = (config: typeof preFilledConfig) => {
|
||||||
|
console.log('[AddModal] fillPreFilledConfig called with config:', config)
|
||||||
|
if (!config) {
|
||||||
|
console.log('[AddModal] fillPreFilledConfig: config is null/undefined')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const formValues = {
|
||||||
|
configName: config.configName || generateDefaultConfigName(),
|
||||||
|
leaderId: config.leaderId,
|
||||||
|
copyMode: config.copyMode || 'RATIO',
|
||||||
|
copyRatio: config.copyRatio,
|
||||||
|
fixedAmount: config.fixedAmount,
|
||||||
|
maxOrderSize: config.maxOrderSize,
|
||||||
|
minOrderSize: config.minOrderSize,
|
||||||
|
maxDailyLoss: config.maxDailyLoss,
|
||||||
|
maxDailyOrders: config.maxDailyOrders,
|
||||||
|
supportSell: config.supportSell,
|
||||||
|
keywordFilterMode: config.keywordFilterMode || 'DISABLED'
|
||||||
|
}
|
||||||
|
console.log('[AddModal] fillPreFilledConfig: setting form values:', formValues)
|
||||||
|
|
||||||
|
form.setFieldsValue(formValues)
|
||||||
|
setCopyMode(config.copyMode || 'RATIO')
|
||||||
|
setKeywords(config.keywords || [])
|
||||||
|
|
||||||
|
console.log('[AddModal] fillPreFilledConfig: form values set, copyMode:', config.copyMode, 'keywords:', config.keywords)
|
||||||
|
|
||||||
|
// 自动获取 Leader 资产信息
|
||||||
|
if (config.leaderId) {
|
||||||
|
console.log('[AddModal] fillPreFilledConfig: fetching leader asset info for leaderId:', config.leaderId)
|
||||||
|
fetchLeaderAssetInfo(config.leaderId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 Modal 打开/关闭
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
console.log('[AddModal] useEffect triggered, open:', open, 'preFilledConfig:', preFilledConfig)
|
||||||
if (open) {
|
if (open) {
|
||||||
|
console.log('[AddModal] Modal opened, fetching accounts, leaders, templates')
|
||||||
fetchAccounts()
|
fetchAccounts()
|
||||||
fetchLeaders()
|
fetchLeaders()
|
||||||
fetchTemplates()
|
fetchTemplates()
|
||||||
|
|
||||||
// 生成默认配置名
|
// 如果有预填充配置,填充表单(延迟执行确保数据已加载)
|
||||||
const defaultConfigName = generateDefaultConfigName()
|
if (preFilledConfig) {
|
||||||
form.setFieldsValue({ configName: defaultConfigName })
|
console.log('[AddModal] preFilledConfig exists, will fill form after 100ms')
|
||||||
|
// 使用 setTimeout 确保在下一个事件循环执行,此时 Modal 已完全打开
|
||||||
// 重置关键字列表
|
setTimeout(() => {
|
||||||
setKeywords([])
|
console.log('[AddModal] setTimeout callback executed, calling fillPreFilledConfig')
|
||||||
|
fillPreFilledConfig(preFilledConfig)
|
||||||
|
}, 100)
|
||||||
|
} else {
|
||||||
|
console.log('[AddModal] No preFilledConfig, using default values')
|
||||||
|
// 没有预填充配置时,生成默认配置名
|
||||||
|
const defaultConfigName = generateDefaultConfigName()
|
||||||
|
form.setFieldsValue({
|
||||||
|
configName: defaultConfigName,
|
||||||
|
copyMode: 'RATIO',
|
||||||
|
copyRatio: 100,
|
||||||
|
maxOrderSize: 1000,
|
||||||
|
minOrderSize: 1,
|
||||||
|
maxDailyLoss: 10000,
|
||||||
|
maxDailyOrders: 100,
|
||||||
|
supportSell: true,
|
||||||
|
keywordFilterMode: 'DISABLED'
|
||||||
|
})
|
||||||
|
setCopyMode('RATIO')
|
||||||
|
setKeywords([])
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log('[AddModal] Modal closed, resetting form')
|
||||||
// 关闭时重置表单
|
// 关闭时重置表单
|
||||||
form.resetFields()
|
form.resetFields()
|
||||||
setKeywords([])
|
setKeywords([])
|
||||||
setCopyMode('RATIO')
|
setCopyMode('RATIO')
|
||||||
|
setLeaderAssetInfo(null)
|
||||||
}
|
}
|
||||||
}, [open])
|
}, [open, preFilledConfig])
|
||||||
|
|
||||||
const fetchLeaders = async () => {
|
const fetchLeaders = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -133,32 +235,6 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
setCopyMode(mode)
|
setCopyMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 Leader 资产信息
|
|
||||||
const fetchLeaderAssetInfo = async (leaderId: number) => {
|
|
||||||
if (!leaderId) return
|
|
||||||
|
|
||||||
setLoadingAssetInfo(true)
|
|
||||||
setLeaderAssetInfo(null)
|
|
||||||
try {
|
|
||||||
const response = await apiService.leaders.balance({ leaderId })
|
|
||||||
if (response.data.code === 0 && response.data.data) {
|
|
||||||
const balance = response.data.data
|
|
||||||
setLeaderAssetInfo({
|
|
||||||
total: balance.totalBalance || '0',
|
|
||||||
available: balance.availableBalance || '0',
|
|
||||||
position: balance.positionBalance || '0'
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
message.error(response.data.msg || t('copyTradingAdd.fetchAssetInfoFailed') || '获取资产信息失败')
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('获取 Leader 资产失败:', error)
|
|
||||||
message.error(error.message || t('copyTradingAdd.fetchAssetInfoFailed') || '获取资产信息失败')
|
|
||||||
} finally {
|
|
||||||
setLoadingAssetInfo(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理导入账户成功
|
// 处理导入账户成功
|
||||||
const handleAccountImportSuccess = async (accountId: number) => {
|
const handleAccountImportSuccess = async (accountId: number) => {
|
||||||
message.success(t('accountImport.importSuccess'))
|
message.success(t('accountImport.importSuccess'))
|
||||||
|
|||||||
Reference in New Issue
Block a user