372 lines
19 KiB
PHP
372 lines
19 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(24, 26, 32, 1); border: 1px solid #2b2f36; border-radius: 4px; overflow: hidden; }
|
|
.trade-nav-item { cursor: pointer; padding: 10px 15px; border-bottom: 2px solid transparent; color: #848e9c; font-size: 14px; }
|
|
.trade-nav-item.active { border-bottom-color: #f0b90b; color: #f0b90b; font-weight: bold; }
|
|
.coin-row { transition: background 0.2s; border-bottom: 1px solid #1e2329; }
|
|
.coin-row:hover { background: #2b3139; cursor: pointer; }
|
|
.coin-row.active { background: #1e2329; border-left: 3px solid #f0b90b; }
|
|
.price-up { color: #0ecb81 !important; }
|
|
.price-down { color: #f6465d !important; }
|
|
#order-book table td { padding: 3px 8px; font-size: 12px; }
|
|
.smaller { font-size: 11px; }
|
|
::-webkit-scrollbar { width: 4px; }
|
|
::-webkit-scrollbar-thumb { background: #3b4149; border-radius: 10px; }
|
|
</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" style="max-height: 900px; display: flex; flex-direction: column;">
|
|
<div class="p-2 border-bottom border-secondary bg-dark">
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-transparent border-secondary text-secondary"><i class="bi bi-search"></i></span>
|
|
<input type="text" id="coin-search" class="form-control bg-transparent text-white border-secondary" placeholder="搜索币种" onkeyup="filterCoins()">
|
|
</div>
|
|
</div>
|
|
<div id="left-coin-list" style="overflow-y: auto; flex-grow: 1;">
|
|
<!-- 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 bg-dark">
|
|
<div class="d-flex align-items-center me-4">
|
|
<span class="fw-bold fs-5 text-white" id="current-symbol-title"><?php echo $symbol; ?></span>
|
|
</div>
|
|
<div class="me-4 border-end border-secondary pe-4">
|
|
<div class="fw-bold fs-5" id="header-price">--</div>
|
|
<div class="smaller text-secondary">≈ $<span id="header-price-usd">--</span></div>
|
|
</div>
|
|
<div class="me-4">
|
|
<div class="smaller text-secondary mb-1">24h 涨跌</div>
|
|
<div class="fw-bold" id="header-change">--</div>
|
|
</div>
|
|
<div class="me-4 d-none d-md-block">
|
|
<div class="smaller text-secondary mb-1">24h 最高</div>
|
|
<div class="fw-bold smaller" id="header-high">--</div>
|
|
</div>
|
|
<div class="me-4 d-none d-md-block">
|
|
<div class="smaller text-secondary mb-1">24h 最低</div>
|
|
<div class="fw-bold smaller" id="header-low">--</div>
|
|
</div>
|
|
<div class="ms-auto d-flex gap-1 bg-black p-1 rounded">
|
|
<a href="?type=SPOT&symbol=<?php echo $symbol; ?>" class="btn btn-sm <?php echo $trade_type=='SPOT'?'btn-warning':'text-secondary'; ?> fw-bold px-3">现货</a>
|
|
<a href="?type=CONTRACT&symbol=<?php echo $symbol; ?>" class="btn btn-sm <?php echo $trade_type=='CONTRACT'?'btn-warning':'text-secondary'; ?> fw-bold px-3">合约</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- TradingView -->
|
|
<div class="glass-card mb-1" style="height: 480px;">
|
|
<div id="tradingview_widget" style="height: 100%;"></div>
|
|
</div>
|
|
|
|
<!-- Trading Form -->
|
|
<div class="glass-card p-3">
|
|
<div class="row g-4">
|
|
<div class="col-md-6 border-end border-secondary">
|
|
<div class="d-flex justify-content-between mb-3 align-items-center">
|
|
<span class="text-success fw-bold fs-5">买入 / 做多</span>
|
|
<span class="text-secondary smaller">可用: <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 d-flex align-items-center gap-2">
|
|
<label class="smaller text-secondary" style="min-width: 40px;">杠杆</label>
|
|
<select id="leverage" class="form-select form-select-sm bg-dark text-white border-secondary w-auto" style="min-width: 100px;">
|
|
<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" style="width: 60px;">价格</span>
|
|
<input type="text" class="form-control bg-dark text-warning border-secondary fw-bold" value="市场最优价" disabled>
|
|
</div>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 60px;"><?php echo $trade_type=='CONTRACT'?'手数':'数量'; ?></span>
|
|
<input type="number" id="buy-amount" class="form-control bg-dark text-white border-secondary" placeholder="0.00">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary"><?php echo $trade_type=='CONTRACT'?'张':$base_symbol; ?></span>
|
|
</div>
|
|
|
|
<button class="btn btn-success w-100 fw-bold py-2 shadow-sm" onclick="submitOrder('BUY')">立即买入 (做多)</button>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="d-flex justify-content-between mb-3 align-items-center">
|
|
<span class="text-danger fw-bold fs-5">卖出 / 做空</span>
|
|
<span class="text-secondary smaller">市价委托</span>
|
|
</div>
|
|
|
|
<?php if ($trade_type === 'CONTRACT'): ?>
|
|
<div class="mb-3"><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" style="width: 60px;">价格</span>
|
|
<input type="text" class="form-control bg-dark text-warning border-secondary fw-bold" value="市场最优价" disabled>
|
|
</div>
|
|
|
|
<div class="input-group input-group-sm mb-3">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary" style="width: 60px;"><?php echo $trade_type=='CONTRACT'?'手数':'数量'; ?></span>
|
|
<input type="number" id="sell-amount" class="form-control bg-dark text-white border-secondary" placeholder="0.00">
|
|
<span class="input-group-text bg-dark text-secondary border-secondary"><?php echo $trade_type=='CONTRACT'?'张':$base_symbol; ?></span>
|
|
</div>
|
|
|
|
<button class="btn btn-danger w-100 fw-bold py-2 shadow-sm" 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-0" style="min-height: 250px;">
|
|
<div class="table-responsive">
|
|
<table class="table table-dark table-hover mb-0 align-middle" id="position-table" style="font-size: 13px;">
|
|
<thead>
|
|
<tr class="text-secondary" style="background: #1e2329;">
|
|
<th class="ps-3">交易对</th>
|
|
<th>方向</th>
|
|
<th>杠杆</th>
|
|
<th>持有数量</th>
|
|
<th>开仓价格</th>
|
|
<th>标记价格</th>
|
|
<th>盈亏 (USDT)</th>
|
|
<th class="text-end pe-3">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Order Book -->
|
|
<div class="col-lg-3">
|
|
<div class="glass-card h-100" style="display: flex; flex-direction: column;">
|
|
<div class="p-2 border-bottom border-secondary bg-dark small fw-bold">实时订单簿</div>
|
|
<div id="order-book" style="flex-grow: 1;">
|
|
<div class="px-2 py-2 d-flex justify-content-between text-secondary smaller">
|
|
<span>价格 (USDT)</span>
|
|
<span>数量 (<?php echo $base_symbol; ?>)</span>
|
|
</div>
|
|
<div id="asks-container" class="mb-1">
|
|
<table class="w-100">
|
|
<tbody id="asks-list"></tbody>
|
|
</table>
|
|
</div>
|
|
<div class="py-3 text-center border-top border-bottom border-secondary my-1 bg-dark">
|
|
<span id="book-price" class="fs-4 fw-bold">--</span>
|
|
<div class="smaller text-secondary" id="book-price-usd">≈ $--</div>
|
|
</div>
|
|
<div id="bids-container">
|
|
<table class="w-100">
|
|
<tbody id="bids-list"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="p-2 border-top border-secondary bg-dark">
|
|
<div class="d-flex justify-content-between smaller">
|
|
<span class="text-secondary">最新成交</span>
|
|
<span class="text-success">实时</span>
|
|
</div>
|
|
</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;
|
|
let allCoins = [];
|
|
|
|
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"
|
|
});
|
|
|
|
function formatPrice(p) {
|
|
p = parseFloat(p);
|
|
if (p < 0.0001) return p.toFixed(8);
|
|
if (p < 0.01) return p.toFixed(6);
|
|
if (p < 1) return p.toFixed(4);
|
|
return p.toFixed(2);
|
|
}
|
|
|
|
async function updateMarket() {
|
|
try {
|
|
const r = await fetch('api.php?action=market_data');
|
|
const coins = await r.json();
|
|
allCoins = coins;
|
|
renderCoins();
|
|
|
|
const c = coins.find(i => i.symbol === symbol);
|
|
if (c) {
|
|
currentPrice = parseFloat(c.price);
|
|
const pFormatted = formatPrice(currentPrice);
|
|
const changeClass = c.change >= 0 ? 'price-up' : 'price-down';
|
|
|
|
document.getElementById('header-price').textContent = pFormatted;
|
|
document.getElementById('header-price').className = 'fw-bold fs-5 ' + changeClass;
|
|
document.getElementById('header-price-usd').textContent = pFormatted;
|
|
|
|
document.getElementById('header-change').textContent = (c.change >= 0 ? '+' : '') + c.change + '%';
|
|
document.getElementById('header-change').className = 'fw-bold ' + changeClass;
|
|
|
|
document.getElementById('header-high').textContent = formatPrice(c.high || currentPrice * 1.02);
|
|
document.getElementById('header-low').textContent = formatPrice(c.low || currentPrice * 0.98);
|
|
|
|
const bookPrice = document.getElementById('book-price');
|
|
bookPrice.textContent = pFormatted;
|
|
bookPrice.className = 'fs-4 fw-bold ' + changeClass;
|
|
document.getElementById('book-price-usd').textContent = '≈ $' + pFormatted;
|
|
|
|
renderOrderBook(currentPrice);
|
|
}
|
|
} catch(e) { console.error(e); }
|
|
}
|
|
|
|
function renderCoins() {
|
|
const search = document.getElementById('coin-search').value.toLowerCase();
|
|
let listHtml = '';
|
|
allCoins.forEach(c => {
|
|
if (search && !c.symbol.toLowerCase().includes(search)) return;
|
|
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?'active':''}" onclick="location.href='?type=${tradeType}&symbol=${c.symbol}'">
|
|
<div class="d-flex align-items-center">
|
|
<img src="${c.icon_url}" width="20" height="20" class="me-2 rounded-circle">
|
|
<div>
|
|
<div class="fw-bold smaller text-white">${c.symbol.replace('USDT','')}</div>
|
|
<div class="text-secondary smaller" style="font-size:9px">USDT</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-end">
|
|
<div class="fw-bold smaller ${changeClass}">${formatPrice(c.price)}</div>
|
|
<div class="${changeClass}" style="font-size:10px">${(c.change >= 0 ? '+' : '') + c.change}%</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
document.getElementById('left-coin-list').innerHTML = listHtml;
|
|
}
|
|
|
|
function filterCoins() { renderCoins(); }
|
|
|
|
function renderOrderBook(price) {
|
|
if (!price) return;
|
|
let asks = '', bids = '';
|
|
// Asks (Red) - from high to low
|
|
for(let i=10; i>0; i--) {
|
|
const p = price * (1 + i * 0.0001);
|
|
const amt = (Math.random() * 2).toFixed(3);
|
|
asks += `<tr><td class="text-danger">${formatPrice(p)}</td><td class="text-end text-secondary">${amt}</td></tr>`;
|
|
}
|
|
// Bids (Green) - from high to low
|
|
for(let i=1; i<=10; i++) {
|
|
const p = price * (1 - i * 0.0001);
|
|
const amt = (Math.random() * 2).toFixed(3);
|
|
bids += `<tr><td class="text-success">${formatPrice(p)}</td><td class="text-end text-secondary">${amt}</td></tr>`;
|
|
}
|
|
document.getElementById('asks-list').innerHTML = asks;
|
|
document.getElementById('bids-list').innerHTML = bids;
|
|
}
|
|
|
|
async function updatePositions() {
|
|
try {
|
|
const r = await fetch('api.php?action=positions');
|
|
const pos = await r.json();
|
|
let html = '';
|
|
if (pos.length === 0) {
|
|
html = '<tr><td colspan="8" class="text-center py-5 text-secondary">暂无持有仓位</td></tr>';
|
|
} else {
|
|
pos.forEach(p => {
|
|
const pnlClass = p.pnl >= 0 ? 'text-success' : 'text-danger';
|
|
html += `
|
|
<tr>
|
|
<td class="ps-3 fw-bold">${p.symbol}</td>
|
|
<td><span class="badge ${p.side==='LONG'?'bg-success':'bg-danger'}">${p.side}</span></td>
|
|
<td><span class="text-warning">${p.leverage}x</span></td>
|
|
<td>${p.lots} 张</td>
|
|
<td>${formatPrice(p.entry_price)}</td>
|
|
<td class="text-warning">${formatPrice(p.current_price)}</td>
|
|
<td class="${pnlClass} fw-bold">${parseFloat(p.pnl).toFixed(2)} USDT</td>
|
|
<td class="text-end pe-3"><button class="btn btn-sm btn-outline-danger py-0" onclick="closePosition(${p.id})">平仓</button></td>
|
|
</tr>
|
|
`;
|
|
});
|
|
}
|
|
document.querySelector('#position-table tbody').innerHTML = html;
|
|
} catch(e) {}
|
|
}
|
|
|
|
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 || amount <= 0) { 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'; ?>
|