feat: 在创建跟单配置时显示Leader资产信息

- 选择Leader后自动获取并显示资产信息(总资产、可用余额、仓位资产)
- 在AddModal和EditModal中都实现该功能
- 使用Card和Statistic组件美观展示
- 添加中英文多语言支持
- 使用formatUSDC格式化显示金额
This commit is contained in:
WrBug
2026-01-30 23:58:18 +08:00
parent 80976609c7
commit 390b3ee876
5 changed files with 197 additions and 12 deletions
+7 -1
View File
@@ -840,7 +840,13 @@
"noAccounts": "No accounts",
"importAccount": "Import Account",
"noLeaders": "No Leaders",
"addLeader": "Add Leader"
"addLeader": "Add Leader",
"leaderAssetInfo": "Leader Asset Info",
"loadingAssetInfo": "Loading asset info...",
"totalAsset": "Total Assets",
"availableBalance": "Available Balance",
"positionAsset": "Position Assets",
"fetchAssetInfoFailed": "Failed to fetch asset info"
},
"copyTradingEdit": {
"title": "Edit Copy Trading Config",
+7 -1
View File
@@ -840,7 +840,13 @@
"noAccounts": "暂无账户",
"importAccount": "导入账户",
"noLeaders": "暂无 Leader",
"addLeader": "添加 Leader"
"addLeader": "添加 Leader",
"leaderAssetInfo": "Leader 资产信息",
"loadingAssetInfo": "加载资产信息中...",
"totalAsset": "总资产",
"availableBalance": "可用余额",
"positionAsset": "仓位资产",
"fetchAssetInfoFailed": "获取资产信息失败"
},
"copyTradingEdit": {
"title": "编辑跟单配置",
+9 -3
View File
@@ -837,10 +837,16 @@
"fetchLeaderFailed": "獲取 Leader 列表失敗",
"fetchTemplateFailed": "獲取模板列表失敗",
"templateName": "模板名稱",
"noAccounts": "暫無戶",
"importAccount": "導入戶",
"noAccounts": "暫無戶",
"importAccount": "導入戶",
"noLeaders": "暫無 Leader",
"addLeader": "添加 Leader"
"addLeader": "添加 Leader",
"leaderAssetInfo": "Leader 資產資訊",
"loadingAssetInfo": "加載資產資訊中...",
"totalAsset": "總資產",
"availableBalance": "可用餘額",
"positionAsset": "倉位資產",
"fetchAssetInfoFailed": "獲取資產資訊失敗"
},
"copyTradingEdit": {
"title": "編輯跟單配置",
@@ -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