import { useEffect, useState } from 'react' 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' import AccountImportForm from '../components/AccountImportForm' import AccountSetupStatusBlock from '../components/AccountSetupStatusBlock' const { Title } = Typography const AccountList: React.FC = () => { const { t } = useTranslation() 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) const [accountImportModalVisible, setAccountImportModalVisible] = useState(false) const [accountImportForm] = Form.useForm() useEffect(() => { fetchAccounts() }, [fetchAccounts]) const handleAccountImportSuccess = async () => { message.success(t('accountImport.importSuccess')) setAccountImportModalVisible(false) accountImportForm.resetFields() 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) => { if (!text) { message.warning(t('accountList.copyFailed') || '复制失败:地址为空') return } if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(text).then(() => { message.success({ content: t('accountList.copySuccess') || '已复制到剪贴板', duration: 2 }) }).catch((err) => { console.error('复制失败:', err) // 降级方案:使用传统方法 fallbackCopyTextToClipboard(text) }) } else { // 降级方案:使用传统方法 fallbackCopyTextToClipboard(text) } } const fallbackCopyTextToClipboard = (text: string) => { const textArea = document.createElement('textarea') textArea.value = text textArea.style.position = 'fixed' textArea.style.left = '-999999px' textArea.style.top = '-999999px' document.body.appendChild(textArea) textArea.focus() textArea.select() try { const successful = document.execCommand('copy') if (successful) { message.success({ content: t('accountList.copySuccess') || '已复制到剪贴板', duration: 2 }) } else { message.error(t('accountList.copyFailed') || '复制失败') } } catch (err) { console.error('复制失败:', err) message.error(t('accountList.copyFailed') || '复制失败') } finally { document.body.removeChild(textArea) } } 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 || '' }) } 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 { // 构建更新请求,只支持编辑账户名称 const updateData: any = { accountId: editAccount.id, accountName: values.accountName || undefined } 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) => { const formatted = text ? `${text.slice(0, 6)}...${text.slice(-4)}` : '-' return ( {formatted} 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) => { return (
{record.accountName || `${t('accountList.accountName')} ${record.id}`}
{t('accountList.walletAddress')}: {record.walletAddress ? `${record.walletAddress.slice(0, 6)}...${record.walletAddress.slice(-4)}` : '-'}
{t('accountList.proxyAddress')}: {record.proxyAddress ? `${record.proxyAddress.slice(0, 6)}...${record.proxyAddress.slice(-4)}` : '-'}
{record.walletType && (
{t('accountList.walletType')}:{' '} {record.walletType.toLowerCase() === 'magic' ? 'Magic' : 'Safe'}
)}
{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
)}
) } }, { 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')}
)}
{/* 导入账户 Modal */} { setAccountImportModalVisible(false) accountImportForm.resetFields() }} footer={null} width={isMobile ? '95%' : 640} style={{ top: isMobile ? 20 : 50 }} bodyStyle={{ padding: isMobile ? '16px 20px' : '24px 28px', maxHeight: 'calc(100vh - 140px)', overflow: 'auto' }} destroyOnClose maskClosable closable > { setAccountImportModalVisible(false) accountImportForm.resetFields() }} /> ) } export default AccountList