import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { Card, Table, Button, Space, Tag, Popconfirm, message, Typography, Spin, Modal, Descriptions, Divider, Form, Input, Alert } from 'antd' import { PlusOutlined, ReloadOutlined, EditOutlined, CopyOutlined } from '@ant-design/icons' import { useTranslation } from 'react-i18next' import { useAccountStore } from '../store/accountStore' import type { Account } from '../types' import { useMediaQuery } from 'react-responsive' import { formatUSDC } from '../utils' const { Title } = Typography const AccountList: React.FC = () => { const { t } = useTranslation() const navigate = useNavigate() const isMobile = useMediaQuery({ maxWidth: 768 }) const { accounts, loading, fetchAccounts, deleteAccount, fetchAccountBalance, fetchAccountDetail, updateAccount } = useAccountStore() const [balanceMap, setBalanceMap] = useState>({}) const [balanceLoading, setBalanceLoading] = useState>({}) const [detailModalVisible, setDetailModalVisible] = useState(false) const [detailAccount, setDetailAccount] = useState(null) const [detailBalance, setDetailBalance] = useState<{ total: string; available: string; position: string; positions: any[] } | null>(null) const [detailBalanceLoading, setDetailBalanceLoading] = useState(false) const [editModalVisible, setEditModalVisible] = useState(false) const [editAccount, setEditAccount] = useState(null) const [editForm] = Form.useForm() const [editLoading, setEditLoading] = useState(false) useEffect(() => { fetchAccounts() }, [fetchAccounts]) // 加载所有账户的余额 useEffect(() => { const loadBalances = async () => { for (const account of accounts) { if (!balanceMap[account.id] && !balanceLoading[account.id]) { setBalanceLoading(prev => ({ ...prev, [account.id]: true })) try { const balanceData = await fetchAccountBalance(account.id) setBalanceMap(prev => ({ ...prev, [account.id]: { total: balanceData.totalBalance || '0', available: balanceData.availableBalance || '0', position: balanceData.positionBalance || '0' } })) } catch (error) { console.error(`获取账户 ${account.id} 余额失败:`, error) setBalanceMap(prev => ({ ...prev, [account.id]: { total: '-', available: '-', position: '-' } })) } finally { setBalanceLoading(prev => ({ ...prev, [account.id]: false })) } } } } if (accounts.length > 0) { loadBalances() } }, [accounts]) const handleDelete = async (account: Account) => { try { await deleteAccount(account.id) message.success(t('accountList.deleteSuccess')) } catch (error: any) { message.error(error.message || t('accountList.deleteFailed')) } } const handleCopy = (text: string) => { navigator.clipboard.writeText(text).then(() => { message.success(t('accountList.copySuccess')) }).catch(() => { message.error(t('accountList.copyFailed')) }) } const handleShowDetail = async (account: Account) => { try { setDetailModalVisible(true) setDetailAccount(account) setDetailBalance(null) setDetailBalanceLoading(false) // 加载详情和余额 try { const accountDetail = await fetchAccountDetail(account.id) setDetailAccount(accountDetail) // 加载余额 setDetailBalanceLoading(true) try { const balanceData = await fetchAccountBalance(account.id) setDetailBalance({ total: balanceData.totalBalance || '0', available: balanceData.availableBalance || '0', position: balanceData.positionBalance || '0', positions: balanceData.positions || [] }) } catch (error) { console.error('获取余额失败:', error) setDetailBalance(null) } finally { setDetailBalanceLoading(false) } } catch (error: any) { console.error('获取账户详情失败:', error) message.error(error.message || t('accountList.getDetailFailed')) setDetailModalVisible(false) setDetailAccount(null) } } catch (error: any) { console.error('打开详情失败:', error) message.error(t('accountList.openDetailFailed')) setDetailModalVisible(false) setDetailAccount(null) } } const handleRefreshDetailBalance = async () => { if (!detailAccount) return setDetailBalanceLoading(true) try { const balanceData = await fetchAccountBalance(detailAccount.id) setDetailBalance({ total: balanceData.totalBalance || '0', available: balanceData.availableBalance || '0', position: balanceData.positionBalance || '0', positions: balanceData.positions || [] }) message.success(t('accountList.refreshBalanceSuccess')) } catch (error: any) { message.error(error.message || t('accountList.refreshBalanceFailed')) } finally { setDetailBalanceLoading(false) } } const handleShowEdit = async (account: Account) => { try { setEditModalVisible(true) setEditAccount(account) // 加载账户详情并设置表单初始值 const accountDetail = await fetchAccountDetail(account.id) setEditAccount(accountDetail) editForm.setFieldsValue({ accountName: accountDetail.accountName || '', apiKey: '', // 不显示实际值,留空表示不修改 apiSecret: '', // 不显示实际值,留空表示不修改 apiPassphrase: '' // 不显示实际值,留空表示不修改 }) } catch (error: any) { console.error('打开编辑失败:', error) message.error(error.message || t('accountList.getDetailFailedForEdit')) setEditModalVisible(false) setEditAccount(null) } } const handleEditSubmit = async (values: any) => { if (!editAccount) return setEditLoading(true) try { // 构建更新请求,空字符串转换为 undefined(不修改) const updateData: any = { accountId: editAccount.id, accountName: values.accountName || undefined } // 只有非空字符串才更新 API 凭证 if (values.apiKey && values.apiKey.trim()) { updateData.apiKey = values.apiKey.trim() } if (values.apiSecret && values.apiSecret.trim()) { updateData.apiSecret = values.apiSecret.trim() } if (values.apiPassphrase && values.apiPassphrase.trim()) { updateData.apiPassphrase = values.apiPassphrase.trim() } await updateAccount(updateData) message.success(t('accountList.updateSuccess')) setEditModalVisible(false) setEditAccount(null) editForm.resetFields() // 刷新账户列表 await fetchAccounts() // 如果详情 Modal 打开着,也刷新详情 if (detailModalVisible && detailAccount && detailAccount.id === editAccount.id) { const accountDetail = await fetchAccountDetail(editAccount.id) setDetailAccount(accountDetail) } } catch (error: any) { message.error(error.message || t('accountList.updateFailed')) } finally { setEditLoading(false) } } const columns = [ { title: t('accountList.accountName'), dataIndex: 'accountName', key: 'accountName', render: (text: string, record: Account) => text || `${t('accountList.accountName')} ${record.id}` }, { title: t('accountList.walletAddress'), dataIndex: 'walletAddress', key: 'walletAddress', render: (text: string) => ( {text} handleDelete(record)} okText={t('accountList.deleteConfirmOk')} cancelText={t('common.cancel')} okButtonProps={{ danger: true }} > ) } ] const mobileColumns = [ { title: t('accountList.accountName'), key: 'info', render: (_: any, record: Account) => { const allConfigured = record.apiKeyConfigured && record.apiSecretConfigured && record.apiPassphraseConfigured const partialConfigured = record.apiKeyConfigured || record.apiSecretConfigured || record.apiPassphraseConfigured return (
{record.accountName || `${t('accountList.accountName')} ${record.id}`}
{t('accountList.walletAddress')}: {record.walletAddress}
{t('accountList.proxyAddress')}: {record.proxyAddress}
{allConfigured ? t('accountList.fullConfig') : partialConfigured ? t('accountList.partialConfig') : t('accountList.notConfigured')}
{t('accountList.totalBalance')}: {balanceLoading[record.id] ? ( ) : balanceMap[record.id]?.total && balanceMap[record.id].total !== '-' ? ( `${formatUSDC(balanceMap[record.id].total)} USDC` ) : ( '-' )}
{balanceMap[record.id] && balanceMap[record.id].available !== '-' && (
{t('accountList.available')}: {formatUSDC(balanceMap[record.id].available)} USDC | {t('accountList.position')}: {formatUSDC(balanceMap[record.id].position)} USDC
)} {(record.activeOrders !== undefined && record.activeOrders !== null) && (
{t('accountList.activeOrders')}: 0 ? 'orange' : 'default'} style={{ margin: 0 }}>{record.activeOrders}
)}
) } }, { title: t('accountList.action'), key: 'action', width: 100, render: (_: any, record: Account) => ( handleDelete(record)} okText={t('accountList.deleteConfirmOk')} cancelText={t('common.cancel')} okButtonProps={{ danger: true }} > ) } ] return (
{t('accountList.title')}
{isMobile ? ( ) : (
)} {/* 账户详情 Modal */} { setDetailModalVisible(false) setDetailAccount(null) setDetailBalance(null) }} footer={[ , , ]} width={isMobile ? '95%' : 800} style={{ top: isMobile ? 20 : 50 }} destroyOnClose maskClosable closable > {detailAccount ? (
{detailAccount.id} {detailAccount.accountName || '-'} {detailAccount.walletAddress || '-'}
) : (
{t('accountList.loading')}
)}
{/* 编辑账户 Modal */} { setEditModalVisible(false) setEditAccount(null) editForm.resetFields() }} footer={null} width={isMobile ? '95%' : 600} style={{ top: isMobile ? 20 : 50 }} destroyOnClose maskClosable closable > {editAccount ? (
) : (
{t('accountList.loading')}
)}
) } export default AccountList