feat: 在创建跟单配置时显示Leader资产信息
- 选择Leader后自动获取并显示资产信息(总资产、可用余额、仓位资产) - 在AddModal和EditModal中都实现该功能 - 使用Card和Statistic组件美观展示 - 添加中英文多语言支持 - 使用formatUSDC格式化显示金额
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState, useRef } from 'react'
|
||||
import { Modal, Form, Button, Switch, message, Space, Radio, InputNumber, Table, Select, Divider, Input, Tag, InputRef } from 'antd'
|
||||
import { Modal, Form, Button, Switch, message, Space, Radio, InputNumber, Table, Select, Divider, Input, Tag, InputRef, Card, Row, Col, Statistic } from 'antd'
|
||||
import { SaveOutlined, FileTextOutlined, PlusOutlined } from '@ant-design/icons'
|
||||
import { apiService } from '../../services/api'
|
||||
import { useAccountStore } from '../../store/accountStore'
|
||||
@@ -36,6 +36,8 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
const keywordInputRef = useRef<InputRef>(null)
|
||||
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
||||
const [maxMarketEndDateUnit, setMaxMarketEndDateUnit] = useState<'HOUR' | 'DAY'>('HOUR')
|
||||
const [leaderAssetInfo, setLeaderAssetInfo] = useState<{ total: string; available: string; position: string } | null>(null)
|
||||
const [loadingAssetInfo, setLoadingAssetInfo] = useState(false)
|
||||
|
||||
// 导入账户modal相关状态
|
||||
const [accountImportModalVisible, setAccountImportModalVisible] = useState(false)
|
||||
@@ -132,6 +134,32 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
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) => {
|
||||
message.success(t('accountImport.importSuccess'))
|
||||
@@ -382,6 +410,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
onChange={(value) => fetchLeaderAssetInfo(value)}
|
||||
>
|
||||
{leaders.map(leader => (
|
||||
<Option key={leader.id} value={leader.id}>
|
||||
@@ -391,6 +420,61 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* Leader 资产信息 */}
|
||||
{leaderAssetInfo && (
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<span>{t('copyTradingAdd.leaderAssetInfo') || 'Leader 资产信息'}</span>
|
||||
</Space>
|
||||
}
|
||||
size="small"
|
||||
style={{ marginBottom: '16px', backgroundColor: '#f5f5f5', border: '1px solid #d9d9d9' }}
|
||||
>
|
||||
{loadingAssetInfo ? (
|
||||
<div style={{ textAlign: 'center', padding: '24px' }}>
|
||||
<Spin />
|
||||
<div style={{ marginTop: '8px', color: '#999' }}>
|
||||
{t('copyTradingAdd.loadingAssetInfo') || '加载资产信息中...'}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.totalAsset') || '总资产'}
|
||||
value={parseFloat(leaderAssetInfo.total)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#52c41a', fontWeight: 'bold', fontSize: '16px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.availableBalance') || '可用余额'}
|
||||
value={parseFloat(leaderAssetInfo.available)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#1890ff', fontSize: '14px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.positionAsset') || '仓位资产'}
|
||||
value={parseFloat(leaderAssetInfo.position)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#722ed1', fontSize: '14px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* 模板填充按钮 */}
|
||||
<Form.Item>
|
||||
<Button
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useEffect, useState, useRef } from 'react'
|
||||
import { Modal, Form, Button, message, Radio, InputNumber, Divider, Spin, Select, Input, Space, Switch, Tag, InputRef } from 'antd'
|
||||
import { Modal, Form, Button, message, Radio, InputNumber, Divider, Spin, Select, Input, Space, Switch, Tag, InputRef, Card, Row, Col, Statistic } from 'antd'
|
||||
import { SaveOutlined } from '@ant-design/icons'
|
||||
import { apiService } from '../../services/api'
|
||||
import type { CopyTrading, CopyTradingUpdateRequest } from '../../types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { formatUSDC } from '../../utils'
|
||||
|
||||
const { Option } = Select
|
||||
|
||||
@@ -26,11 +27,13 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
const [fetching, setFetching] = useState(true)
|
||||
const [copyTrading, setCopyTrading] = useState<CopyTrading | null>(null)
|
||||
const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO')
|
||||
const [originalEnabled, setOriginalEnabled] = useState<boolean>(true)
|
||||
const [keywords, setKeywords] = useState<string[]>([])
|
||||
const keywordInputRef = useRef<InputRef>(null)
|
||||
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
||||
const [maxMarketEndDateUnit, setMaxMarketEndDateUnit] = useState<'HOUR' | 'DAY'>('HOUR')
|
||||
const [originalEnabled, setOriginalEnabled] = useState<boolean>(true)
|
||||
const [keywords, setKeywords] = useState<string[]>([])
|
||||
const keywordInputRef = useRef<InputRef>(null)
|
||||
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
||||
const [maxMarketEndDateUnit, setMaxMarketEndDateUnit] = useState<'HOUR' | 'DAY'>('HOUR')
|
||||
const [leaderAssetInfo, setLeaderAssetInfo] = useState<{ total: string; available: string; position: string } | null>(null)
|
||||
const [loadingAssetInfo, setLoadingAssetInfo] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (open && copyTradingId) {
|
||||
@@ -96,6 +99,9 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
})
|
||||
// 设置关键字列表
|
||||
setKeywords(found.keywords || [])
|
||||
|
||||
// 获取 Leader 资产信息
|
||||
fetchLeaderAssetInfo(found.leaderId)
|
||||
} else {
|
||||
message.error(t('copyTradingEdit.fetchFailed') || '跟单配置不存在')
|
||||
onClose()
|
||||
@@ -116,6 +122,30 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
setCopyMode(mode)
|
||||
}
|
||||
|
||||
// 获取 Leader 资产信息
|
||||
const fetchLeaderAssetInfo = async (leaderId: number) => {
|
||||
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 handleAddKeyword = (e?: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
let inputValue = ''
|
||||
@@ -301,6 +331,59 @@ const EditModal: React.FC<EditModalProps> = ({
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* Leader 资产信息 */}
|
||||
<Card
|
||||
title={
|
||||
<Space>
|
||||
<span>{t('copyTradingAdd.leaderAssetInfo') || 'Leader 资产信息'}</span>
|
||||
</Space>
|
||||
}
|
||||
size="small"
|
||||
style={{ marginBottom: '16px', backgroundColor: '#f5f5f5', border: '1px solid #d9d9d9' }}
|
||||
>
|
||||
{loadingAssetInfo ? (
|
||||
<div style={{ textAlign: 'center', padding: '24px' }}>
|
||||
<Spin />
|
||||
<div style={{ marginTop: '8px', color: '#999' }}>
|
||||
{t('copyTradingAdd.loadingAssetInfo') || '加载资产信息中...'}
|
||||
</div>
|
||||
</div>
|
||||
) : leaderAssetInfo ? (
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.totalAsset') || '总资产'}
|
||||
value={parseFloat(leaderAssetInfo.total)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#52c41a', fontWeight: 'bold', fontSize: '16px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.availableBalance') || '可用余额'}
|
||||
value={parseFloat(leaderAssetInfo.available)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#1890ff', fontSize: '14px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title={t('copyTradingAdd.positionAsset') || '仓位资产'}
|
||||
value={parseFloat(leaderAssetInfo.position)}
|
||||
precision={4}
|
||||
valueStyle={{ color: '#722ed1', fontSize: '14px' }}
|
||||
suffix="USDC"
|
||||
formatter={(value) => formatUSDC(value?.toString() || '0')}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
</Card>
|
||||
|
||||
<Divider>{t('copyTradingEdit.basicConfig') || '基础配置'}</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
Reference in New Issue
Block a user