289 lines
14 KiB
PHP
289 lines
14 KiB
PHP
<?php
|
|
include_once 'config.php';
|
|
check_auth();
|
|
|
|
$user_id = $_SESSION['user_id'];
|
|
$account = get_account($user_id);
|
|
$symbol = $_GET['symbol'] ?? 'BTCUSDT';
|
|
$trade_type = $_GET['type'] ?? 'SPOT';
|
|
$base_symbol = str_replace('USDT', '', $symbol);
|
|
|
|
include 'header.php';
|
|
?>
|
|
<style>
|
|
.glass-card { background: rgba(30, 32, 38, 0.9); border: 1px solid #2b2f36; border-radius: 4px; overflow: hidden; }
|
|
.trade-nav-item { cursor: pointer; padding: 10px 15px; border-bottom: 2px solid transparent; color: #848e9c; }
|
|
.trade-nav-item.active { border-bottom-color: #f0b90b; color: #f0b90b; }
|
|
.coin-row:hover { background: #2b2f36; cursor: pointer; }
|
|
.price-up { color: #0ecb81; }
|
|
.price-down { color: #f6465d; }
|
|
#order-book table td { padding: 2px 8px; font-size: 12px; }
|
|
</style>
|
|
|
|
<div class="container-fluid px-1 py-1" style="background-color: #0b0e11; min-height: 95vh; color: #eaecef;">
|
|
<div class="row g-1">
|
|
<!-- Market List -->
|
|
<div class="col-lg-2">
|
|
<div class="glass-card h-100">
|
|
<div class="p-2 border-bottom border-secondary">
|
|
<input type="text" id="coin-search" class="form-control form-control-sm bg-dark text-white border-secondary" placeholder="搜索">
|
|
</div>
|
|
<div id="left-coin-list" style="max-height: 800px; overflow-y: auto;">
|
|
<!-- JS populated -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chart & Trade -->
|
|
<div class="col-lg-7">
|
|
<!-- Ticker Header -->
|
|
<div class="glass-card mb-1 p-2 d-flex align-items-center">
|
|
<div class="d-flex align-items-center me-4">
|
|
<span class="fw-bold fs-5 text-white"><?php echo $symbol; ?></span>
|
|
</div>
|
|
<div class="me-4">
|
|
<div class="fw-bold fs-5" id="header-price">--</div>
|
|
<div class="small" id="header-change">--</div>
|
|
</div>
|
|
<div class="ms-auto d-flex gap-1">
|
|
<a href="?type=SPOT&symbol=<?php echo $symbol; ?>" class="btn btn-sm <?php echo $trade_type=='SPOT'?'btn-warning':'btn-outline-secondary'; ?>">现货</a>
|
|
<a href="?type=CONTRACT&symbol=<?php echo $symbol; ?>" class="btn btn-sm <?php echo $trade_type=='CONTRACT'?'btn-warning':'btn-outline-secondary'; ?>">永续</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TradingView -->
|
|
<div class="glass-card mb-1" style="height: 450px;">
|
|
<div id="tradingview_widget" style="height: 100%;"></div>
|
|
</div>
|
|
|
|
<!-- Trading Form -->
|
|
<div class="glass-card p-3">
|
|
<div class="row">
|
|
<div class="col-md-6 border-end border-secondary">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<span class="text-success fw-bold">买入 / 做多</span>
|
|
<span class="text-secondary small">可用: <span class="text-white" id="usdt-balance"><?php echo number_format($account['balance'], 2); ?></span> USDT</span>
|
|
</div>
|
|
|
|
<?php if ($trade_type === 'CONTRACT'): ?>
|
|
<div class="mb-3">
|
|
<label class="small text-secondary">杠杆</label>
|
|
<select id="leverage" class="form-select form-select-sm bg-dark text-white border-secondary">
|
|
<option value="10">10x</option>
|
|
<option value="20" selected>20x</option>
|
|
<option value="50">50x</option>
|
|
<option value="100">100x</option>
|
|
</select>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary">价格</span>
|
|
<input type="text" class="form-control bg-dark text-white border-secondary" value="市场价" disabled>
|
|
</div>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary">数量</span>
|
|
<input type="number" id="buy-amount" class="form-control bg-dark text-white border-secondary">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary"><?php echo $base_symbol; ?></span>
|
|
</div>
|
|
|
|
<button class="btn btn-success w-100 fw-bold" onclick="submitOrder('BUY')">买入 (做多)</button>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-between mb-3">
|
|
<span class="text-danger fw-bold">卖出 / 做空</span>
|
|
</div>
|
|
|
|
<?php if ($trade_type === 'CONTRACT'): ?>
|
|
<div class="mb-3"><label class="small"> </label><div style="height:31px"></div></div>
|
|
<?php endif; ?>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary">价格</span>
|
|
<input type="text" class="form-control bg-dark text-white border-secondary" value="市场价" disabled>
|
|
</div>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary">数量</span>
|
|
<input type="number" id="sell-amount" class="form-control bg-dark text-white border-secondary">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary"><?php echo $base_symbol; ?></span>
|
|
</div>
|
|
|
|
<button class="btn btn-danger w-100 fw-bold" onclick="submitOrder('SELL')">卖出 (做空)</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Positions & History -->
|
|
<div class="glass-card mt-1 p-0">
|
|
<div class="d-flex border-bottom border-secondary bg-dark">
|
|
<div class="trade-nav-item active">当前仓位</div>
|
|
<div class="trade-nav-item">历史订单</div>
|
|
</div>
|
|
<div class="p-2" style="min-height: 200px;">
|
|
<table class="table table-dark table-hover small" id="position-table">
|
|
<thead>
|
|
<tr class="text-secondary">
|
|
<th>合约</th>
|
|
<th>方向</th>
|
|
<th>杠杆</th>
|
|
<th>数量</th>
|
|
<th>开仓价</th>
|
|
<th>当前价</th>
|
|
<th>未实现盈亏</th>
|
|
<th>操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Order Book -->
|
|
<div class="col-lg-3">
|
|
<div class="glass-card h-100">
|
|
<div class="p-2 border-bottom border-secondary small fw-bold">订单簿</div>
|
|
<div id="order-book">
|
|
<table class="w-100">
|
|
<tbody id="asks-list"></tbody>
|
|
</table>
|
|
<div class="py-2 text-center border-top border-bottom border-secondary my-1">
|
|
<span id="book-price" class="fs-5 fw-bold text-success">--</span>
|
|
</div>
|
|
<table class="w-100">
|
|
<tbody id="bids-list"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
|
<script>
|
|
const symbol = '<?php echo $symbol; ?>';
|
|
const tradeType = '<?php echo $trade_type; ?>';
|
|
let currentPrice = 0;
|
|
|
|
new TradingView.widget({
|
|
"width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol,
|
|
"interval": "15", "timezone": "Etc/UTC", "theme": "dark", "style": "1",
|
|
"locale": "zh_CN", "toolbar_bg": "#f1f3f6", "enable_publishing": false,
|
|
"hide_side_toolbar": false, "allow_symbol_change": true, "container_id": "tradingview_widget"
|
|
});
|
|
|
|
async function updateMarket() {
|
|
const r = await fetch('api.php?action=market_data');
|
|
const coins = await r.json();
|
|
|
|
// Update Side List
|
|
let listHtml = '';
|
|
coins.forEach(c => {
|
|
const isTarget = c.symbol === symbol;
|
|
const changeClass = c.change >= 0 ? 'text-success' : 'text-danger';
|
|
listHtml += `
|
|
<div class="coin-row p-2 d-flex justify-content-between align-items-center ${isTarget?'bg-dark':''}" onclick="location.href='?type=${tradeType}&symbol=${c.symbol}'">
|
|
<div>
|
|
<div class="fw-bold">${c.symbol.replace('USDT','')}</div>
|
|
<div class="text-secondary smaller" style="font-size:10px">Vol --</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<div class="fw-bold ${changeClass}">${parseFloat(c.price).toFixed(c.price<1?4:2)}</div>
|
|
<div class="${changeClass}" style="font-size:10px">${c.change}%</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
if (isTarget) {
|
|
currentPrice = c.price;
|
|
document.getElementById('header-price').textContent = parseFloat(c.price).toLocaleString(undefined, {minimumFractionDigits: 2});
|
|
document.getElementById('header-price').className = 'fw-bold fs-5 ' + (c.change >= 0 ? 'text-success' : 'text-danger');
|
|
document.getElementById('header-change').textContent = (c.change >= 0 ? '+' : '') + c.change + '%';
|
|
document.getElementById('header-change').className = 'small ' + (c.change >= 0 ? 'text-success' : 'text-danger');
|
|
document.getElementById('book-price').textContent = parseFloat(c.price).toFixed(2);
|
|
}
|
|
});
|
|
document.getElementById('left-coin-list').innerHTML = listHtml;
|
|
|
|
// Mock Order Book
|
|
renderOrderBook(currentPrice);
|
|
}
|
|
|
|
function renderOrderBook(price) {
|
|
if (!price) return;
|
|
let asks = '', bids = '';
|
|
for(let i=5; i>0; i--) {
|
|
asks += `<tr><td class="text-danger">${(price * (1 + i*0.0002)).toFixed(2)}</td><td class="text-end text-secondary">${(Math.random()*2).toFixed(3)}</td></tr>`;
|
|
bids += `<tr><td class="text-success">${(price * (1 - i*0.0002)).toFixed(2)}</td><td class="text-end text-secondary">${(Math.random()*2).toFixed(3)}</td></tr>`;
|
|
}
|
|
document.getElementById('asks-list').innerHTML = asks;
|
|
document.getElementById('bids-list').innerHTML = bids;
|
|
}
|
|
|
|
async function updatePositions() {
|
|
if (tradeType !== 'CONTRACT') {
|
|
document.getElementById('position-table').parentElement.innerHTML = '<div class="text-center text-secondary py-5">现货交易暂不显示当前持仓</div>';
|
|
return;
|
|
}
|
|
const r = await fetch('api.php?action=positions');
|
|
const pos = await r.json();
|
|
let html = '';
|
|
pos.forEach(p => {
|
|
const pnlClass = p.pnl >= 0 ? 'text-success' : 'text-danger';
|
|
html += `
|
|
<tr>
|
|
<td class="fw-bold">${p.symbol}</td>
|
|
<td><span class="badge ${p.side==='LONG'?'bg-success':'bg-danger'}">${p.side}</span></td>
|
|
<td>${p.leverage}x</td>
|
|
<td>${p.lots}</td>
|
|
<td>${p.entry_price}</td>
|
|
<td>${p.current_price}</td>
|
|
<td class="${pnlClass} fw-bold">${parseFloat(p.pnl).toFixed(2)} USDT</td>
|
|
<td><button class="btn btn-sm btn-outline-warning py-0" onclick="closePosition(${p.id})">平仓</button></td>
|
|
</tr>
|
|
`;
|
|
});
|
|
document.querySelector('#position-table tbody').innerHTML = html;
|
|
}
|
|
|
|
async function closePosition(id) {
|
|
if (!confirm('确定要平掉该仓位吗?')) return;
|
|
const res = await fetch('api.php?action=close_position', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ id })
|
|
});
|
|
const json = await res.json();
|
|
alert(json.message);
|
|
updatePositions();
|
|
}
|
|
|
|
async function submitOrder(side) {
|
|
const amount = document.getElementById(side.toLowerCase() + '-amount').value;
|
|
const leverageSelect = document.getElementById('leverage');
|
|
const leverage = leverageSelect ? leverageSelect.value : 1;
|
|
|
|
if (!amount) { alert('请输入数量'); return; }
|
|
|
|
const res = await fetch('api.php?action=submit_order', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ symbol, side, trade_type: tradeType, amount, leverage })
|
|
});
|
|
const json = await res.json();
|
|
if (json.status === 'success') {
|
|
alert('下单成功');
|
|
location.reload();
|
|
} else {
|
|
alert('错误: ' + json.message);
|
|
}
|
|
}
|
|
|
|
setInterval(updateMarket, 2000);
|
|
setInterval(updatePositions, 3000);
|
|
updateMarket();
|
|
updatePositions();
|
|
</script>
|
|
<?php include 'footer.php'; ?>
|