feat: 在创建跟单配置时显示Leader资产信息
- 选择Leader后自动获取并显示资产信息(总资产、可用余额、仓位资产) - 在AddModal和EditModal中都实现该功能 - 使用Card和Statistic组件美观展示 - 添加中英文多语言支持 - 使用formatUSDC格式化显示金额
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -840,7 +840,13 @@
|
||||
"noAccounts": "暂无账户",
|
||||
"importAccount": "导入账户",
|
||||
"noLeaders": "暂无 Leader",
|
||||
"addLeader": "添加 Leader"
|
||||
"addLeader": "添加 Leader",
|
||||
"leaderAssetInfo": "Leader 资产信息",
|
||||
"loadingAssetInfo": "加载资产信息中...",
|
||||
"totalAsset": "总资产",
|
||||
"availableBalance": "可用余额",
|
||||
"positionAsset": "仓位资产",
|
||||
"fetchAssetInfoFailed": "获取资产信息失败"
|
||||
},
|
||||
"copyTradingEdit": {
|
||||
"title": "编辑跟单配置",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user