'use client' import { useEffect, useState, useCallback } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { Input } from '@/components/ui/input' import { Separator } from '@/components/ui/separator' import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend, } from 'recharts' import { Wallet, TrendingUp, TrendingDown, Plus, ArrowUpRight, ArrowDownRight, Bitcoin, Building2, Globe2, RefreshCw, DollarSign, Search, Check, Download, Upload, Trash2, Edit, } from 'lucide-react' import { fetchAccounts, fetchTransactions, fetchPositions, fetchSecurities, createTransaction, deleteTransaction, updateTransaction, formatCurrency, formatPercent, marketLabels, transactionTypeLabels } from '@/lib/api' import { Account, Position, Transaction, MarketType, TransactionType, Security } from '@/types' import { toast } from 'sonner' import { exportTransactionsToCSV, exportPositionsToCSV, downloadCSV, TRANSACTION_IMPORT_TEMPLATE, parseImportCSV, validateImportTransaction, ImportTransaction } from '@/lib/import-export' // 市场图标映射 const marketIcons: Record = { US: , CN: , HK: , CRYPTO: , } // 市场颜色配置 const marketColors: Record = { US: '#3b82f6', CN: '#ef4444', HK: '#f97316', CRYPTO: '#eab308', } // 持仓分析数据结构 interface PositionAnalytics { symbol: string name: string marketType: string accountName: string quantity: number avgCost: number currentPrice: number change: number changePercent: number costBasis: number marketValue: number marketValueUSD: number pnl: number pnlPercent: number pnlUSD: number currency: string isCrypto: boolean } // 分析汇总数据结构 interface AnalyticsSummary { totalCostBasis: number totalMarketValue: number totalPnL: number totalPnLPercent: number positionCount: number } // 货币转换函数(以 USD 为基准) const EXCHANGE_RATES: Record = { USD: 1, CNY: 7.24, // 1 USD = 7.24 CNY HKD: 7.78, // 1 USD = 7.78 HKD } function convertCurrency(amount: number, from: string, to: string): number { if (from === to) return amount // 先转换为 USD,再转换为目标货币 const usdAmount = amount / EXCHANGE_RATES[from] return usdAmount * EXCHANGE_RATES[to] } // 主页面组件 export default function Dashboard() { // 状态定义 const [accounts, setAccounts] = useState([]) const [transactions, setTransactions] = useState([]) const [positions, setPositions] = useState([]) const [securities, setSecurities] = useState([]) const [analytics, setAnalytics] = useState<{ prices: Record; positions: PositionAnalytics[]; summary: AnalyticsSummary } | null>(null) const [loading, setLoading] = useState(true) const [selectedAccountId, setSelectedAccountId] = useState('') const [showTxDialog, setShowTxDialog] = useState(false) const [showConfirmDialog, setShowConfirmDialog] = useState(false) const [showImportDialog, setShowImportDialog] = useState(false) const [showDeleteDialog, setShowDeleteDialog] = useState(false) const [positionToDelete, setPositionToDelete] = useState(null) const [symbolSearch, setSymbolSearch] = useState('') const [filteredSecurities, setFilteredSecurities] = useState([]) const [importFile, setImportFile] = useState(null) const [importData, setImportData] = useState([]) // 显示货币状态(默认 CNY) const [displayCurrency, setDisplayCurrency] = useState<'CNY' | 'USD' | 'HKD'>('CNY') // 交易记录删除状态 const [transactionToDelete, setTransactionToDelete] = useState(null) const [showDeleteTxDialog, setShowDeleteTxDialog] = useState(false) // 交易记录编辑状态 const [transactionToEdit, setTransactionToEdit] = useState(null) const [showEditTxDialog, setShowEditTxDialog] = useState(false) const [editTxForm, setEditTxForm] = useState({ type: 'BUY' as TransactionType, symbol: '', quantity: '', price: '', amount: '', fee: '0', currency: 'USD', notes: '', executedAt: new Date().toISOString().slice(0, 16), }) // 交易表单状态 const [txForm, setTxForm] = useState({ type: 'BUY' as TransactionType, symbol: '', quantity: '', price: '', amount: '', // 仅用于出金/入金 fee: '0', currency: 'USD', notes: '', executedAt: new Date().toISOString().slice(0, 16), }) // 获取当前选中的账户对象 const selectedAccount = accounts.find(a => a.id === selectedAccountId) // 加载数据 const loadData = useCallback(async () => { try { setLoading(true) // 并行加载所有数据 const [accountsData, transactionsData, positionsData, securitiesData, analyticsData] = await Promise.all([ fetchAccounts(), fetchTransactions({ limit: 50 }), fetchPositions(), fetchSecurities(), fetch('/api/dashboard/analytics').then(r => r.json()).catch(() => null), ]) setAccounts(accountsData) setTransactions(transactionsData.data || []) setPositions(positionsData) setSecurities(securitiesData) setAnalytics(analyticsData) // 默认选中第一个账户 if (accountsData.length > 0 && !selectedAccountId) { setSelectedAccountId(accountsData[0].id) } } catch (error) { toast.error('加载数据失败') } finally { setLoading(false) } }, [selectedAccountId]) useEffect(() => { loadData() }, [loadData]) // 搜索证券(根据输入实时过滤) useEffect(() => { if (symbolSearch.length >= 1) { const filtered = securities.filter(s => s.symbol.toLowerCase().includes(symbolSearch.toLowerCase()) || s.name.toLowerCase().includes(symbolSearch.toLowerCase()) ).slice(0, 8) setFilteredSecurities(filtered) } else { setFilteredSecurities([]) } }, [symbolSearch, securities]) // 获取证券显示文本(代码+名称) const getSecurityDisplayText = (symbol: string) => { if (!symbol) return '' const sec = securities.find(s => s.symbol === symbol) return sec ? `${sec.symbol} ${sec.name}` : symbol } // 选择证券后自动填充价格和币种 const handleSelectSecurity = (symbol: string) => { const price = analytics?.prices[symbol]?.price || 0 const sec = securities.find(s => s.symbol === symbol) setTxForm(prev => ({ ...prev, symbol, price: price > 0 ? price.toString() : prev.price, currency: sec?.currency || prev.currency, })) setSymbolSearch('') setFilteredSecurities([]) } // 当数量或价格变化时,自动计算成交总额 const calculatedAmount = txForm.quantity && txForm.price ? (parseFloat(txForm.quantity) * parseFloat(txForm.price)).toFixed(2) : '' // 提交交易记录 const handleSubmitTx = async () => { try { if (!selectedAccountId) { toast.error('请先选择账户') return } const amount = txForm.quantity && txForm.price ? (parseFloat(txForm.quantity) * parseFloat(txForm.price)).toFixed(2) : txForm.amount await createTransaction({ accountId: selectedAccountId, type: txForm.type, symbol: txForm.symbol || undefined, quantity: txForm.quantity ? parseFloat(txForm.quantity) : undefined, price: txForm.price ? parseFloat(txForm.price) : undefined, amount: parseFloat(amount || '0'), fee: parseFloat(txForm.fee) || 0, currency: txForm.currency, notes: txForm.notes || undefined, executedAt: txForm.executedAt, }) toast.success('交易记录成功') setShowTxDialog(false) setShowConfirmDialog(false) resetTxForm() loadData() } catch (error) { toast.error('创建交易失败') } } // 删除持仓 const handleDeletePosition = async () => { if (!positionToDelete) return try { // 通过卖出全部来删除持仓 const position = positions.find(p => p.symbol === positionToDelete.symbol) if (!position) { toast.error('未找到持仓记录') return } await createTransaction({ accountId: selectedAccountId || position.accountId, type: 'SELL', symbol: positionToDelete.symbol, quantity: parseFloat(positionToDelete.quantity.toString()), price: parseFloat(positionToDelete.currentPrice.toString()), amount: parseFloat((positionToDelete.quantity * positionToDelete.currentPrice).toFixed(2)), fee: 0, currency: positionToDelete.currency, notes: '删除持仓', executedAt: new Date().toISOString(), }) toast.success('持仓已删除') setShowDeleteDialog(false) setPositionToDelete(null) loadData() } catch (error) { toast.error('删除持仓失败') } } // 打开删除确认对话框 const openDeleteDialog = (pos: PositionAnalytics) => { setPositionToDelete(pos) setShowDeleteDialog(true) } // 打开删除交易记录对话框 const openDeleteTxDialog = (tx: Transaction) => { setTransactionToDelete(tx) setShowDeleteTxDialog(true) } // 删除交易记录 const handleDeleteTransaction = async () => { if (!transactionToDelete) return try { await deleteTransaction(transactionToDelete.id) toast.success('交易记录已删除') setShowDeleteTxDialog(false) setTransactionToDelete(null) loadData() } catch (error) { toast.error('删除交易记录失败') } } // 打开编辑交易记录对话框 const openEditTxDialog = (tx: Transaction) => { setTransactionToEdit(tx) setEditTxForm({ type: tx.type, symbol: tx.symbol || '', quantity: tx.quantity ? parseFloat(tx.quantity.toString()).toString() : '', price: tx.price ? parseFloat(tx.price.toString()).toString() : '', amount: parseFloat(tx.amount.toString()).toString(), fee: parseFloat(tx.fee.toString()).toString(), currency: tx.currency, notes: tx.notes || '', executedAt: new Date(tx.executedAt).toISOString().slice(0, 16), }) setShowEditTxDialog(true) } // 提交编辑交易记录 const handleEditTransaction = async () => { if (!transactionToEdit) return try { const amount = editTxForm.quantity && editTxForm.price ? (parseFloat(editTxForm.quantity) * parseFloat(editTxForm.price)).toFixed(2) : editTxForm.amount await updateTransaction(transactionToEdit.id, { type: editTxForm.type, symbol: editTxForm.symbol || undefined, quantity: editTxForm.quantity ? parseFloat(editTxForm.quantity) : undefined, price: editTxForm.price ? parseFloat(editTxForm.price) : undefined, amount: parseFloat(amount || '0'), fee: parseFloat(editTxForm.fee) || 0, currency: editTxForm.currency, notes: editTxForm.notes || undefined, executedAt: editTxForm.executedAt, }) toast.success('交易记录已更新') setShowEditTxDialog(false) setTransactionToEdit(null) loadData() } catch (error) { toast.error('更新交易记录失败') } } // 重置交易表单 const resetTxForm = () => { setTxForm({ type: 'BUY', symbol: '', quantity: '', price: '', amount: '', fee: '0', currency: selectedAccount?.baseCurrency || 'USD', notes: '', executedAt: new Date().toISOString().slice(0, 16), }) } // 打开交易对话框 const openTxDialog = () => { resetTxForm() setShowTxDialog(true) } // 获取确认对话框显示信息 const getConfirmInfo = () => { const typeLabel = transactionTypeLabels[txForm.type] const symbolLabel = txForm.symbol ? `${txForm.symbol} ` : '' const qtyLabel = txForm.quantity ? `${txForm.quantity}股 ` : '' const priceLabel = txForm.price ? `@ ${formatCurrency(parseFloat(txForm.price), txForm.currency)}` : '' const total = txForm.quantity && txForm.price ? (parseFloat(txForm.quantity) * parseFloat(txForm.price)).toFixed(2) : txForm.amount return { account: selectedAccount?.name || '未选择', market: selectedAccount ? marketLabels[selectedAccount.marketType] : '', action: `${typeLabel} ${symbolLabel}${qtyLabel}${priceLabel}`, total: total ? `合计 ${formatCurrency(parseFloat(total), txForm.currency)}` : '', fee: txForm.fee && parseFloat(txForm.fee) > 0 ? `手续费: ${formatCurrency(parseFloat(txForm.fee), txForm.currency)}` : '', time: new Date(txForm.executedAt).toLocaleString('zh-CN'), } } // 导出交易记录为 CSV const handleExportTransactions = () => { const csv = exportTransactionsToCSV(transactions) const date = new Date().toISOString().slice(0, 10) downloadCSV(csv, `transactions_${date}.csv`) toast.success('交易记录已导出') } // 导出持仓记录为 CSV const handleExportPositions = () => { const csv = exportPositionsToCSV(positions.map(p => ({ ...p, name: analytics?.positions?.find(ap => ap.symbol === p.symbol)?.name || p.symbol, currentPrice: analytics?.prices[p.symbol]?.price || parseFloat(p.averageCost), value: parseFloat(p.quantity) * (analytics?.prices[p.symbol]?.price || parseFloat(p.averageCost)), valueInUSD: parseFloat(p.quantity) * (analytics?.prices[p.symbol]?.price || parseFloat(p.averageCost)), pnl: 0, pnlPercent: 0, }))) const date = new Date().toISOString().slice(0, 10) downloadCSV(csv, `positions_${date}.csv`) toast.success('持仓记录已导出') } // 下载导入模板 CSV const handleDownloadTemplate = () => { downloadCSV(TRANSACTION_IMPORT_TEMPLATE, 'transaction_import_template.csv') } // 处理文件导入 const handleFileImport = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return setImportFile(file) const reader = new FileReader() reader.onload = (event) => { const content = event.target?.result as string const { headers, rows } = parseImportCSV(content) // 验证每一行数据 const validated = rows.map(row => validateImportTransaction(row, headers)) setImportData(validated) } reader.readAsText(file) } // 执行批量导入 const handleExecuteImport = async () => { if (!selectedAccountId) { toast.error('请先选择导入目标账户') return } const validTxs = importData.filter(tx => tx.errors.length === 0) if (validTxs.length === 0) { toast.error('没有有效的交易记录') return } try { const response = await fetch('/api/import/transactions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transactions: validTxs.map(tx => ({ type: tx.type, symbol: tx.symbol || null, quantity: tx.quantity || null, price: tx.price || null, amount: tx.amount, fee: tx.fee || '0', currency: tx.currency, notes: tx.notes || null, executedAt: tx.executedAt, })), accountId: selectedAccountId, }), }) const result = await response.json() toast.success(`导入完成: ${result.success} 成功, ${result.failed} 失败`) setShowImportDialog(false) setImportData([]) setImportFile(null) loadData() } catch (error) { toast.error('导入失败') } } // 市场分布数据(用于饼图) const marketDistribution = analytics?.summary ? [ { name: '美股', value: analytics.summary.totalMarketValue * 0.6, color: marketColors.US }, { name: 'A股', value: analytics.summary.totalMarketValue * 0.2, color: marketColors.CN }, { name: '港股', value: analytics.summary.totalMarketValue * 0.15, color: marketColors.HK }, { name: '加密', value: analytics.summary.totalMarketValue * 0.05, color: marketColors.CRYPTO }, ].filter(item => item.value > 0) : [] // 加载中状态 if (loading) { return (
) } return (
{/* 顶部导航栏 */}

投资持仓管理

{/* 显示货币选择 */} {/* 账户选择下拉框 */} {/* 导入按钮 */} {/* 导出按钮 */} {/* 记录交易按钮 */}
{/* 资产概览卡片 */}
{/* 总资产卡片 */} 总资产 ({displayCurrency})
{formatCurrency(convertCurrency(analytics?.summary?.totalMarketValue || 0, 'USD', displayCurrency), displayCurrency)}

成本 {formatCurrency(convertCurrency(analytics?.summary?.totalCostBasis || 0, 'USD', displayCurrency), displayCurrency)}

{/* 浮动盈亏卡片 */} 浮动盈亏
{formatCurrency(convertCurrency(analytics?.summary?.totalPnL || 0, 'USD', displayCurrency), displayCurrency)}

= 0 ? 'text-emerald-200' : 'text-red-200'}`}> {(analytics?.summary?.totalPnL || 0) >= 0 ? : } {formatPercent(analytics?.summary?.totalPnLPercent || 0)}

{/* 持仓市值卡片 */} 持仓市值
{formatCurrency(convertCurrency(analytics?.summary?.totalMarketValue || 0, 'USD', displayCurrency), displayCurrency)}

{analytics?.summary?.positionCount || 0} 个持仓

{/* 账户数量卡片 */} 账户数量
{accounts.length}

分布在 {accounts.length} 个市场

{/* 图表区域 */}
{/* 市场分布饼图 */} 资产配置 {marketDistribution.length > 0 ? ( {marketDistribution.map((entry, index) => ( ))} formatCurrency(value as number)} contentStyle={{ backgroundColor: 'var(--card)', border: '1px solid var(--border)' }} /> ) : (
暂无数据
)}
{/* 持仓分析卡片 */} 持仓分析
{analytics?.positions?.slice(0, 4).map((pos) => (
{marketIcons[pos.marketType as MarketType]} {pos.symbol} {pos.currency}
{formatCurrency(pos.marketValue, pos.currency)}
= 0 ? 'text-green-500' : 'text-red-500'}`}> {pos.pnl >= 0 ? : } {formatCurrency(Math.abs(pos.pnl), pos.currency)} ({formatPercent(pos.pnlPercent)})
))}
{/* 标签页区域:持仓明细、交易流水、分析 */} 持仓明细 交易流水 分析 {/* 持仓明细标签页 */} 证券 市场 数量 成本价 当前价 市值 盈亏 {analytics?.positions && analytics.positions.length > 0 ? analytics.positions?.map((pos) => (
{pos.symbol}
{pos.name}
{marketIcons[pos.marketType as MarketType]} {marketLabels[pos.marketType as MarketType]} {pos.quantity.toFixed(pos.isCrypto ? 6 : 0)} {formatCurrency(pos.avgCost, pos.currency)}
{formatCurrency(pos.currentPrice, pos.currency)} {pos.change !== 0 && ( = 0 ? 'text-green-500' : 'text-red-500'}`}> {pos.change >= 0 ? '+' : ''}{pos.changePercent.toFixed(2)}% )}
{formatCurrency(pos.marketValue, pos.currency)} = 0 ? 'text-green-500' : 'text-red-500'}`}>
{pos.pnl >= 0 ? : } {formatCurrency(Math.abs(pos.pnl))} ({formatPercent(pos.pnlPercent)})
)) : ( 暂无持仓记录 )}
{/* 交易流水标签页 */} 交易流水 时间 类型 证券 数量 价格 金额 账户 {transactions.length > 0 ? transactions.map((tx) => ( {new Date(tx.executedAt).toLocaleDateString('zh-CN')} {transactionTypeLabels[tx.type]} {tx.symbol || '-'} {tx.quantity ? parseFloat(tx.quantity).toFixed(4) : '-'} {tx.price ? formatCurrency(parseFloat(tx.price), tx.currency) : '-'} {formatCurrency(parseFloat(tx.amount), tx.currency)}
{marketIcons[tx.account?.marketType || 'US']} {tx.account?.name}
)) : ( 暂无交易记录 )}
{/* 分析标签页 */}
{/* 资产分布条形图 */} 资产分布
{analytics?.positions?.map((pos) => { const percent = analytics?.summary?.totalMarketValue && analytics.summary.totalMarketValue > 0 ? (pos.marketValueUSD / analytics.summary.totalMarketValue) * 100 : 0 return (
{marketIcons[pos.marketType as MarketType]} {pos.symbol} {pos.currency}
{percent.toFixed(1)}% {formatCurrency(pos.marketValue, pos.currency)}
) })}
{/* 盈亏排行榜 */} 盈亏排行
{analytics?.positions ?.sort((a, b) => b.pnlUSD - a.pnlUSD) .map((pos, index) => (
#{index + 1}
{pos.symbol}
{pos.name}
= 0 ? 'text-green-500' : 'text-red-500'}`}>
{pos.pnl >= 0 ? : } {formatCurrency(Math.abs(pos.pnl), pos.currency)}
{formatPercent(pos.pnlPercent)}
))}
{/* 记录交易对话框 */} 记录交易
{/* 账户选择 */}
{/* 交易类型选择 */}
{/* 证券代码搜索(买入/卖出/分红时显示) */} {['BUY', 'SELL', 'DIVIDEND'].includes(txForm.type) && (
{ setSymbolSearch(e.target.value) setTxForm({ ...txForm, symbol: e.target.value.toUpperCase() }) }} /> {/* 搜索结果下拉列表 */} {filteredSecurities.length > 0 && (
{filteredSecurities.map((sec) => ( ))}
)}
)} {/* 数量和价格输入(买入/卖出/分红时显示) */} {['BUY', 'SELL', 'DIVIDEND'].includes(txForm.type) && (
setTxForm({ ...txForm, quantity: e.target.value })} />
setTxForm({ ...txForm, price: e.target.value })} />
)} {/* 成交总额(自动计算) */} {['BUY', 'SELL', 'DIVIDEND'].includes(txForm.type) && (
{calculatedAmount ? formatCurrency(parseFloat(calculatedAmount), txForm.currency) : '—'} {txForm.quantity && txForm.price && ( ({txForm.quantity} × {formatCurrency(parseFloat(txForm.price), txForm.currency)}) )}
)} {/* 入金/出金金额输入 */} {['DEPOSIT', 'WITHDRAW'].includes(txForm.type) && (
setTxForm({ ...txForm, amount: e.target.value })} />
)} {/* 手续费输入 */}
setTxForm({ ...txForm, fee: e.target.value })} />
{/* 交易时间 */}
setTxForm({ ...txForm, executedAt: e.target.value })} />
{/* 备注 */}
setTxForm({ ...txForm, notes: e.target.value })} />
{/* 提交按钮 */}
{/* 交易确认对话框 */} 确认交易信息
{(() => { const info = getConfirmInfo() return ( <>
账户: {info.account} {info.market}
{info.action}
成交总额 {info.total}
{info.fee && (
{info.fee}
)}
交易时间: {info.time}
) })()}
{/* 编辑交易记录对话框 */} 编辑交易记录
{/* 交易类型选择 */}
{/* 交易时间 */}
setEditTxForm({ ...editTxForm, executedAt: e.target.value })} />
{/* 证券代码(买入/卖出/分红时显示) */} {['BUY', 'SELL', 'DIVIDEND'].includes(editTxForm.type) && (
setEditTxForm({ ...editTxForm, symbol: e.target.value.toUpperCase() })} />
)} {/* 数量和价格输入(买入/卖出/分红时显示) */} {['BUY', 'SELL', 'DIVIDEND'].includes(editTxForm.type) && (
setEditTxForm({ ...editTxForm, quantity: e.target.value })} />
setEditTxForm({ ...editTxForm, price: e.target.value })} />
)} {/* 金额输入(入金/出金时显示) */} {['DEPOSIT', 'WITHDRAW'].includes(editTxForm.type) && (
setEditTxForm({ ...editTxForm, amount: e.target.value })} />
)} {/* 手续费输入 */}
setEditTxForm({ ...editTxForm, fee: e.target.value })} />
{/* 备注 */}
setEditTxForm({ ...editTxForm, notes: e.target.value })} />
{/* 提交按钮 */}
{/* 删除交易记录确认对话框 */} 确认删除交易记录 {transactionToDelete && (
类型 {transactionTypeLabels[transactionToDelete.type]}
{transactionToDelete.symbol && (
证券 {transactionToDelete.symbol}
)}
金额 {formatCurrency(parseFloat(transactionToDelete.amount), transactionToDelete.currency)}
时间 {new Date(transactionToDelete.executedAt).toLocaleString('zh-CN')}
确定要删除此交易记录吗?此操作将回滚相关账户余额和持仓变化
)}
{/* 删除持仓确认对话框 */} 确认删除持仓 {positionToDelete && (
{positionToDelete.symbol}
{positionToDelete.name}
数量 {positionToDelete.quantity.toFixed(positionToDelete.isCrypto ? 6 : 0)}
当前价 {formatCurrency(positionToDelete.currentPrice, positionToDelete.currency)}
市值 {formatCurrency(positionToDelete.marketValue, positionToDelete.currency)}
确定要删除此持仓吗?这将自动卖出全部持仓
)}
{/* 导入对话框 */} 导入交易记录
{/* 下载模板按钮 */}
先下载模板,填写后导入
{/* 文件选择 */}
{/* 导入预览表格 */} {importFile && (
导入预览 ({importData.length} 条)
状态 时间 类型 证券 金额 {importData.slice(0, 10).map((tx, index) => ( {tx.errors.length > 0 ? ( 错误 {tx.errors.length} ) : ( 有效 )} {tx.executedAt} {tx.type} {tx.symbol || '-'} {tx.amount} ))}
{importData.length > 10 && (
还有 {importData.length - 10} 条记录...
)}
{importData.some(tx => tx.errors.length > 0) && (
部分记录存在错误,将被跳过
)}
)}
) }