feat: 在创建跟单配置时显示Leader资产信息
- 选择Leader后自动获取并显示资产信息(总资产、可用余额、仓位资产) - 在AddModal和EditModal中都实现该功能 - 使用Card和Statistic组件美观展示 - 添加中英文多语言支持 - 使用formatUSDC格式化显示金额
This commit is contained in:
@@ -840,7 +840,13 @@
|
|||||||
"noAccounts": "No accounts",
|
"noAccounts": "No accounts",
|
||||||
"importAccount": "Import Account",
|
"importAccount": "Import Account",
|
||||||
"noLeaders": "No Leaders",
|
"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": {
|
"copyTradingEdit": {
|
||||||
"title": "Edit Copy Trading Config",
|
"title": "Edit Copy Trading Config",
|
||||||
|
|||||||
@@ -840,7 +840,13 @@
|
|||||||
"noAccounts": "暂无账户",
|
"noAccounts": "暂无账户",
|
||||||
"importAccount": "导入账户",
|
"importAccount": "导入账户",
|
||||||
"noLeaders": "暂无 Leader",
|
"noLeaders": "暂无 Leader",
|
||||||
"addLeader": "添加 Leader"
|
"addLeader": "添加 Leader",
|
||||||
|
"leaderAssetInfo": "Leader 资产信息",
|
||||||
|
"loadingAssetInfo": "加载资产信息中...",
|
||||||
|
"totalAsset": "总资产",
|
||||||
|
"availableBalance": "可用余额",
|
||||||
|
"positionAsset": "仓位资产",
|
||||||
|
"fetchAssetInfoFailed": "获取资产信息失败"
|
||||||
},
|
},
|
||||||
"copyTradingEdit": {
|
"copyTradingEdit": {
|
||||||
"title": "编辑跟单配置",
|
"title": "编辑跟单配置",
|
||||||
|
|||||||
@@ -837,10 +837,16 @@
|
|||||||
"fetchLeaderFailed": "獲取 Leader 列表失敗",
|
"fetchLeaderFailed": "獲取 Leader 列表失敗",
|
||||||
"fetchTemplateFailed": "獲取模板列表失敗",
|
"fetchTemplateFailed": "獲取模板列表失敗",
|
||||||
"templateName": "模板名稱",
|
"templateName": "模板名稱",
|
||||||
"noAccounts": "暫無賬戶",
|
"noAccounts": "暫無帳戶",
|
||||||
"importAccount": "導入賬戶",
|
"importAccount": "導入帳戶",
|
||||||
"noLeaders": "暫無 Leader",
|
"noLeaders": "暫無 Leader",
|
||||||
"addLeader": "添加 Leader"
|
"addLeader": "添加 Leader",
|
||||||
|
"leaderAssetInfo": "Leader 資產資訊",
|
||||||
|
"loadingAssetInfo": "加載資產資訊中...",
|
||||||
|
"totalAsset": "總資產",
|
||||||
|
"availableBalance": "可用餘額",
|
||||||
|
"positionAsset": "倉位資產",
|
||||||
|
"fetchAssetInfoFailed": "獲取資產資訊失敗"
|
||||||
},
|
},
|
||||||
"copyTradingEdit": {
|
"copyTradingEdit": {
|
||||||
"title": "編輯跟單配置",
|
"title": "編輯跟單配置",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react'
|
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 { SaveOutlined, FileTextOutlined, PlusOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../../services/api'
|
import { apiService } from '../../services/api'
|
||||||
import { useAccountStore } from '../../store/accountStore'
|
import { useAccountStore } from '../../store/accountStore'
|
||||||
@@ -36,6 +36,8 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
const keywordInputRef = useRef<InputRef>(null)
|
const keywordInputRef = useRef<InputRef>(null)
|
||||||
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
||||||
const [maxMarketEndDateUnit, setMaxMarketEndDateUnit] = useState<'HOUR' | 'DAY'>('HOUR')
|
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相关状态
|
// 导入账户modal相关状态
|
||||||
const [accountImportModalVisible, setAccountImportModalVisible] = useState(false)
|
const [accountImportModalVisible, setAccountImportModalVisible] = useState(false)
|
||||||
@@ -132,6 +134,32 @@ 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'))
|
||||||
@@ -382,6 +410,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
onChange={(value) => fetchLeaderAssetInfo(value)}
|
||||||
>
|
>
|
||||||
{leaders.map(leader => (
|
{leaders.map(leader => (
|
||||||
<Option key={leader.id} value={leader.id}>
|
<Option key={leader.id} value={leader.id}>
|
||||||
@@ -391,6 +420,61 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</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>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react'
|
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 { SaveOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../../services/api'
|
import { apiService } from '../../services/api'
|
||||||
import type { CopyTrading, CopyTradingUpdateRequest } from '../../types'
|
import type { CopyTrading, CopyTradingUpdateRequest } from '../../types'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { formatUSDC } from '../../utils'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
@@ -26,11 +27,13 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
const [fetching, setFetching] = useState(true)
|
const [fetching, setFetching] = useState(true)
|
||||||
const [copyTrading, setCopyTrading] = useState<CopyTrading | null>(null)
|
const [copyTrading, setCopyTrading] = useState<CopyTrading | null>(null)
|
||||||
const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO')
|
const [copyMode, setCopyMode] = useState<'RATIO' | 'FIXED'>('RATIO')
|
||||||
const [originalEnabled, setOriginalEnabled] = useState<boolean>(true)
|
const [originalEnabled, setOriginalEnabled] = useState<boolean>(true)
|
||||||
const [keywords, setKeywords] = useState<string[]>([])
|
const [keywords, setKeywords] = useState<string[]>([])
|
||||||
const keywordInputRef = useRef<InputRef>(null)
|
const keywordInputRef = useRef<InputRef>(null)
|
||||||
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
const [maxMarketEndDateValue, setMaxMarketEndDateValue] = useState<number | undefined>()
|
||||||
const [maxMarketEndDateUnit, setMaxMarketEndDateUnit] = useState<'HOUR' | 'DAY'>('HOUR')
|
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(() => {
|
useEffect(() => {
|
||||||
if (open && copyTradingId) {
|
if (open && copyTradingId) {
|
||||||
@@ -96,6 +99,9 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
})
|
})
|
||||||
// 设置关键字列表
|
// 设置关键字列表
|
||||||
setKeywords(found.keywords || [])
|
setKeywords(found.keywords || [])
|
||||||
|
|
||||||
|
// 获取 Leader 资产信息
|
||||||
|
fetchLeaderAssetInfo(found.leaderId)
|
||||||
} else {
|
} else {
|
||||||
message.error(t('copyTradingEdit.fetchFailed') || '跟单配置不存在')
|
message.error(t('copyTradingEdit.fetchFailed') || '跟单配置不存在')
|
||||||
onClose()
|
onClose()
|
||||||
@@ -116,6 +122,30 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
setCopyMode(mode)
|
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>) => {
|
const handleAddKeyword = (e?: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
let inputValue = ''
|
let inputValue = ''
|
||||||
@@ -301,6 +331,59 @@ const EditModal: React.FC<EditModalProps> = ({
|
|||||||
</Select>
|
</Select>
|
||||||
</Form.Item>
|
</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>
|
<Divider>{t('copyTradingEdit.basicConfig') || '基础配置'}</Divider>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
Reference in New Issue
Block a user