import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { Card, Form, Button, Switch, message, Typography, Space, Radio, InputNumber, Modal, Table, Select, Divider, Input } from 'antd' import { ArrowLeftOutlined, SaveOutlined, FileTextOutlined, PlusOutlined } from '@ant-design/icons' import { apiService } from '../services/api' import { useAccountStore } from '../store/accountStore' import type { Leader, CopyTradingTemplate, CopyTradingCreateRequest } from '../types' import { formatUSDC } from '../utils' import { useTranslation } from 'react-i18next' import { useMediaQuery } from 'react-responsive' import AccountImportForm from '../components/AccountImportForm' import LeaderAddForm from '../components/LeaderAddForm' const { Title } = Typography const { Option } = Select const CopyTradingAdd: React.FC = () => { const { t } = useTranslation() const navigate = useNavigate() const isMobile = useMediaQuery({ maxWidth: 768 }) const { accounts, fetchAccounts } = useAccountStore() const [form] = Form.useForm() const [loading, setLoading] = useState(false) const [leaders, setLeaders] = useState([]) const [templates, setTemplates] = useState([]) const [templateModalVisible, setTemplateModalVisible] = useState(false) const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO') // 导入账户modal相关状态 const [accountImportModalVisible, setAccountImportModalVisible] = useState(false) const [accountImportForm] = Form.useForm() // 添加leader modal相关状态 const [leaderAddModalVisible, setLeaderAddModalVisible] = useState(false) const [leaderAddForm] = Form.useForm() // 生成默认配置名 const generateDefaultConfigName = (): string => { const now = new Date() const dateStr = now.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-') const timeStr = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) return `跟单配置-${dateStr}-${timeStr}` } useEffect(() => { fetchAccounts() fetchLeaders() fetchTemplates() // 生成默认配置名 const defaultConfigName = generateDefaultConfigName() form.setFieldsValue({ configName: defaultConfigName }) }, []) 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: any) { message.error(error.message || t('copyTradingAdd.fetchLeaderFailed') || '获取 Leader 列表失败') } } const fetchTemplates = async () => { try { const response = await apiService.templates.list() if (response.data.code === 0 && response.data.data) { setTemplates(response.data.data.list || []) } } catch (error: any) { message.error(error.message || t('copyTradingAdd.fetchTemplateFailed') || '获取模板列表失败') } } const handleSelectTemplate = (template: CopyTradingTemplate) => { // 填充模板数据到表单(只填充模板中存在的字段) form.setFieldsValue({ copyMode: template.copyMode, copyRatio: template.copyRatio ? parseFloat(template.copyRatio) * 100 : 100, // 转换为百分比显示 fixedAmount: template.fixedAmount ? parseFloat(template.fixedAmount) : undefined, maxOrderSize: template.maxOrderSize ? parseFloat(template.maxOrderSize) : undefined, minOrderSize: template.minOrderSize ? parseFloat(template.minOrderSize) : undefined, maxDailyOrders: template.maxDailyOrders, priceTolerance: template.priceTolerance ? parseFloat(template.priceTolerance) : undefined, supportSell: template.supportSell, minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined, maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined, minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined, maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined, maxPositionValue: (template as any).maxPositionValue ? parseFloat((template as any).maxPositionValue) : undefined, maxPositionCount: (template as any).maxPositionCount }) setCopyMode(template.copyMode) setTemplateModalVisible(false) message.success(t('copyTradingAdd.templateFilled') || '模板内容已填充,您可以修改') } const handleCopyModeChange = (mode: 'RATIO' | 'FIXED') => { setCopyMode(mode) } // 处理导入账户成功 const handleAccountImportSuccess = async (accountId: number) => { message.success(t('accountImport.importSuccess')) // 刷新账户列表 await fetchAccounts() // 自动选择新添加的账户 form.setFieldsValue({ accountId }) // 关闭modal并重置表单 setAccountImportModalVisible(false) accountImportForm.resetFields() } // 处理添加leader成功 const handleLeaderAddSuccess = async (leaderId: number) => { message.success(t('leaderAdd.addSuccess') || '添加 Leader 成功') // 刷新leader列表 await fetchLeaders() // 自动选择新添加的leader form.setFieldsValue({ leaderId }) // 关闭modal并重置表单 setLeaderAddModalVisible(false) leaderAddForm.resetFields() } const handleSubmit = async (values: any) => { // 前端校验 if (values.copyMode === 'FIXED') { if (!values.fixedAmount || Number(values.fixedAmount) < 1) { message.error(t('copyTradingAdd.fixedAmountMin') || '固定金额必须 >= 1') return } } if (values.copyMode === 'RATIO' && values.minOrderSize !== undefined && values.minOrderSize !== null && Number(values.minOrderSize) < 1) { message.error(t('copyTradingAdd.minOrderSizeMin') || '最小金额必须 >= 1') return } setLoading(true) try { const request: CopyTradingCreateRequest = { accountId: values.accountId, leaderId: values.leaderId, enabled: true, // 默认启用 copyMode: values.copyMode || 'RATIO', copyRatio: values.copyMode === 'RATIO' && values.copyRatio ? (values.copyRatio / 100).toString() : undefined, fixedAmount: values.copyMode === 'FIXED' ? values.fixedAmount?.toString() : undefined, maxOrderSize: values.maxOrderSize?.toString(), minOrderSize: values.minOrderSize?.toString(), maxDailyLoss: values.maxDailyLoss?.toString(), maxDailyOrders: values.maxDailyOrders, priceTolerance: values.priceTolerance?.toString(), delaySeconds: values.delaySeconds, pollIntervalSeconds: values.pollIntervalSeconds, useWebSocket: values.useWebSocket, websocketReconnectInterval: values.websocketReconnectInterval, websocketMaxRetries: values.websocketMaxRetries, supportSell: values.supportSell !== false, minOrderDepth: values.minOrderDepth?.toString(), maxSpread: values.maxSpread?.toString(), minPrice: values.minPrice?.toString(), maxPrice: values.maxPrice?.toString(), maxPositionValue: values.maxPositionValue?.toString(), maxPositionCount: values.maxPositionCount, configName: values.configName?.trim(), pushFailedOrders: values.pushFailedOrders ?? false } const response = await apiService.copyTrading.create(request) if (response.data.code === 0) { message.success(t('copyTradingAdd.createSuccess') || '创建跟单配置成功') navigate('/copy-trading') } else { message.error(response.data.msg || t('copyTradingAdd.createFailed') || '创建跟单配置失败') } } catch (error: any) { message.error(error.message || t('copyTradingAdd.createFailed') || '创建跟单配置失败') } finally { setLoading(false) } } return (
{t('copyTradingAdd.title') || '新增跟单配置'}
{/* 基础信息 */} {/* 模板填充按钮 */} {/* 跟单金额模式 */} handleCopyModeChange(e.target.value)}> {t('copyTradingAdd.ratioMode') || '比例模式'} {t('copyTradingAdd.fixedAmountMode') || '固定金额模式'} {copyMode === 'RATIO' && ( )} {copyMode === 'FIXED' && ( { if (value !== undefined && value !== null && value !== '') { const amount = Number(value) if (isNaN(amount)) { return Promise.reject(new Error(t('copyTradingAdd.invalidNumber') || '请输入有效的数字')) } if (amount < 1) { return Promise.reject(new Error(t('copyTradingAdd.fixedAmountMin') || '固定金额必须 >= 1')) } } return Promise.resolve() } } ]} > = 1'} /> )} {copyMode === 'RATIO' && ( <> = 1'} rules={[ { validator: (_, value) => { if (value === undefined || value === null || value === '') { return Promise.resolve() } if (typeof value === 'number' && value < 1) { return Promise.reject(new Error(t('copyTradingAdd.minOrderSizeMin') || '最小金额必须 >= 1')) } return Promise.resolve() } } ]} > = 1(可选)'} /> )} {t('copyTradingAdd.priceRangeFilter') || '价格区间过滤'} - {t('copyTradingAdd.positionLimitFilter') || '最大仓位限制'} {t('copyTradingAdd.advancedSettings') || '高级设置'} {/* 跟单卖出 */} {/* 推送失败订单 */}
{/* 模板选择 Modal */} setTemplateModalVisible(false)} footer={null} width={800} > ({ onClick: () => handleSelectTemplate(record), style: { cursor: 'pointer' } })} columns={[ { title: t('copyTradingAdd.templateName') || '模板名称', dataIndex: 'templateName', key: 'templateName' }, { title: t('copyTradingAdd.copyMode') || '跟单模式', key: 'copyMode', render: (_: any, record: CopyTradingTemplate) => ( {record.copyMode === 'RATIO' ? `${t('copyTradingAdd.ratioMode') || '比例'} ${record.copyRatio}x` : `${t('copyTradingAdd.fixedAmountMode') || '固定'} ${formatUSDC(record.fixedAmount || '0')} USDC` } ) }, { title: t('copyTradingAdd.supportSell') || '跟单卖出', dataIndex: 'supportSell', key: 'supportSell', render: (supportSell: boolean) => supportSell ? (t('common.yes') || '是') : (t('common.no') || '否') } ]} /> {/* 导入账户 Modal */} { setAccountImportModalVisible(false) accountImportForm.resetFields() }} footer={null} width={isMobile ? '95%' : 600} style={{ top: isMobile ? 20 : 50 }} bodyStyle={{ padding: '24px', maxHeight: 'calc(100vh - 150px)', overflow: 'auto' }} destroyOnClose maskClosable closable > { setAccountImportModalVisible(false) accountImportForm.resetFields() }} showAlert={true} showCancelButton={true} /> {/* 添加 Leader Modal */} { setLeaderAddModalVisible(false) leaderAddForm.resetFields() }} footer={null} width={isMobile ? '95%' : 600} style={{ top: isMobile ? 20 : 50 }} bodyStyle={{ padding: '24px', maxHeight: 'calc(100vh - 150px)', overflow: 'auto' }} destroyOnClose maskClosable closable > { setLeaderAddModalVisible(false) leaderAddForm.resetFields() }} showCancelButton={true} /> ) } export default CopyTradingAdd