245 lines
7.2 KiB
JavaScript
245 lines
7.2 KiB
JavaScript
import React, { useState, useEffect } from 'react'
|
||
import { api } from '../services/api'
|
||
import './TradeList.css'
|
||
|
||
const TradeList = () => {
|
||
const [trades, setTrades] = useState([])
|
||
const [stats, setStats] = useState(null)
|
||
const [loading, setLoading] = useState(true)
|
||
|
||
// 筛选状态
|
||
const [period, setPeriod] = useState(null) // '1d', '7d', '30d', null
|
||
const [startDate, setStartDate] = useState('')
|
||
const [endDate, setEndDate] = useState('')
|
||
const [symbol, setSymbol] = useState('')
|
||
const [status, setStatus] = useState('')
|
||
const [useCustomDate, setUseCustomDate] = useState(false)
|
||
|
||
useEffect(() => {
|
||
loadData()
|
||
}, [])
|
||
|
||
const loadData = async () => {
|
||
setLoading(true)
|
||
try {
|
||
const params = {
|
||
limit: 100
|
||
}
|
||
|
||
// 如果使用快速时间段筛选
|
||
if (!useCustomDate && period) {
|
||
params.period = period
|
||
} else if (useCustomDate) {
|
||
// 使用自定义日期
|
||
if (startDate) params.start_date = startDate
|
||
if (endDate) params.end_date = endDate
|
||
}
|
||
|
||
if (symbol) params.symbol = symbol
|
||
if (status) params.status = status
|
||
|
||
const [tradesData, statsData] = await Promise.all([
|
||
api.getTrades(params),
|
||
api.getTradeStats(params)
|
||
])
|
||
setTrades(tradesData.trades || [])
|
||
setStats(statsData)
|
||
} catch (error) {
|
||
console.error('Failed to load trades:', error)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handlePeriodChange = (newPeriod) => {
|
||
setPeriod(newPeriod)
|
||
setUseCustomDate(false)
|
||
setStartDate('')
|
||
setEndDate('')
|
||
}
|
||
|
||
const handleCustomDateToggle = () => {
|
||
setUseCustomDate(!useCustomDate)
|
||
if (!useCustomDate) {
|
||
setPeriod(null)
|
||
}
|
||
}
|
||
|
||
const handleReset = () => {
|
||
setPeriod(null)
|
||
setStartDate('')
|
||
setEndDate('')
|
||
setSymbol('')
|
||
setStatus('')
|
||
setUseCustomDate(false)
|
||
}
|
||
|
||
if (loading) return <div className="loading">加载中...</div>
|
||
|
||
return (
|
||
<div className="trade-list">
|
||
<h2>交易记录</h2>
|
||
|
||
{/* 筛选面板 */}
|
||
<div className="filter-panel">
|
||
<div className="filter-section">
|
||
<label>快速筛选:</label>
|
||
<div className="period-buttons">
|
||
<button
|
||
className={period === '1d' ? 'active' : ''}
|
||
onClick={() => handlePeriodChange('1d')}
|
||
>
|
||
最近1天
|
||
</button>
|
||
<button
|
||
className={period === '7d' ? 'active' : ''}
|
||
onClick={() => handlePeriodChange('7d')}
|
||
>
|
||
最近7天
|
||
</button>
|
||
<button
|
||
className={period === '30d' ? 'active' : ''}
|
||
onClick={() => handlePeriodChange('30d')}
|
||
>
|
||
最近30天
|
||
</button>
|
||
<button
|
||
className={period === null && !useCustomDate ? 'active' : ''}
|
||
onClick={() => handlePeriodChange(null)}
|
||
>
|
||
全部
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="filter-section">
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
checked={useCustomDate}
|
||
onChange={handleCustomDateToggle}
|
||
/>
|
||
自定义时间段
|
||
</label>
|
||
{useCustomDate && (
|
||
<div className="date-inputs">
|
||
<input
|
||
type="date"
|
||
value={startDate}
|
||
onChange={(e) => setStartDate(e.target.value)}
|
||
placeholder="开始日期"
|
||
/>
|
||
<span>至</span>
|
||
<input
|
||
type="date"
|
||
value={endDate}
|
||
onChange={(e) => setEndDate(e.target.value)}
|
||
placeholder="结束日期"
|
||
min={startDate}
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="filter-section">
|
||
<label>交易对:</label>
|
||
<input
|
||
type="text"
|
||
value={symbol}
|
||
onChange={(e) => setSymbol(e.target.value)}
|
||
placeholder="如: BTCUSDT"
|
||
style={{ width: '150px' }}
|
||
/>
|
||
</div>
|
||
|
||
<div className="filter-section">
|
||
<label>状态:</label>
|
||
<select
|
||
value={status}
|
||
onChange={(e) => setStatus(e.target.value)}
|
||
style={{ width: '120px' }}
|
||
>
|
||
<option value="">全部</option>
|
||
<option value="open">持仓中</option>
|
||
<option value="closed">已平仓</option>
|
||
<option value="cancelled">已取消</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="filter-actions">
|
||
<button className="btn-primary" onClick={loadData}>
|
||
查询
|
||
</button>
|
||
<button className="btn-secondary" onClick={handleReset}>
|
||
重置
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{stats && (
|
||
<div className="stats-summary">
|
||
<div className="stat-card">
|
||
<div className="stat-label">总交易数</div>
|
||
<div className="stat-value">{stats.total_trades}</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-label">胜率</div>
|
||
<div className="stat-value">{stats.win_rate.toFixed(2)}%</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-label">总盈亏</div>
|
||
<div className={`stat-value ${stats.total_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||
{stats.total_pnl.toFixed(2)} USDT
|
||
</div>
|
||
</div>
|
||
<div className="stat-card">
|
||
<div className="stat-label">平均盈亏</div>
|
||
<div className={`stat-value ${stats.avg_pnl >= 0 ? 'positive' : 'negative'}`}>
|
||
{stats.avg_pnl.toFixed(2)} USDT
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{trades.length === 0 ? (
|
||
<div className="no-data">暂无交易记录</div>
|
||
) : (
|
||
<table className="trades-table">
|
||
<thead>
|
||
<tr>
|
||
<th>交易对</th>
|
||
<th>方向</th>
|
||
<th>数量</th>
|
||
<th>入场价</th>
|
||
<th>出场价</th>
|
||
<th>盈亏</th>
|
||
<th>状态</th>
|
||
<th>时间</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{trades.map(trade => (
|
||
<tr key={trade.id}>
|
||
<td>{trade.symbol}</td>
|
||
<td className={trade.side === 'BUY' ? 'buy' : 'sell'}>{trade.side}</td>
|
||
<td>{parseFloat(trade.quantity).toFixed(4)}</td>
|
||
<td>{parseFloat(trade.entry_price).toFixed(4)}</td>
|
||
<td>{trade.exit_price ? parseFloat(trade.exit_price).toFixed(4) : '-'}</td>
|
||
<td className={parseFloat(trade.pnl) >= 0 ? 'positive' : 'negative'}>
|
||
{parseFloat(trade.pnl).toFixed(2)} USDT
|
||
</td>
|
||
<td>
|
||
<span className={`status ${trade.status}`}>{trade.status}</span>
|
||
</td>
|
||
<td>{new Date(trade.entry_time).toLocaleString('zh-CN')}</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default TradeList
|