38350-vm/futures.php
2026-02-12 08:00:55 +00:00

327 lines
19 KiB
PHP

<?php
session_start();
include 'header.php';
require_once 'db/config.php';
$user_id = $_SESSION['user_id'] ?? null;
$balance = 0;
if ($user_id) {
$stmt = db()->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
$balance = $user['balance'] ?? 0;
}
?>
<style>
* { box-sizing: border-box; }
:root {
--bg-color: #0b0e11;
--panel-bg: #161a1e;
--border-color: #2b3139;
--text-primary: #EAECEF;
--text-secondary: #848e9c;
--accent-color: #4facfe;
--up-color: #00c087;
--down-color: #f6465d;
--input-bg: #1e2329;
}
body { background-color: var(--bg-color); color: var(--text-primary); font-family: 'PingFang SC', sans-serif; margin: 0; overflow-y: auto !important; }
.trading-layout { display: flex; gap: 1px; background: var(--border-color); min-height: calc(100vh - 64px); }
.panel { background: var(--panel-bg); display: flex; flex-direction: column; }
/* Market Panel */
.market-panel { width: 280px; flex-shrink: 0; border-right: 1px solid var(--border-color); }
#pairs-list { height: 600px; overflow-y: auto; }
.pair-item { display: flex; justify-content: space-between; padding: 10px 12px; cursor: pointer; border-bottom: 1px solid rgba(255,255,255,0.02); }
.pair-item.active { background: rgba(79, 172, 254, 0.1); }
/* Center Panel */
.center-panel { flex: 1; background: var(--bg-color); display: flex; flex-direction: column; }
.info-bar { height: 60px; display: flex; align-items: center; padding: 0 15px; gap: 15px; border-bottom: 1px solid var(--border-color); background: var(--panel-bg); flex-wrap: wrap; }
.chart-container { height: 420px; background: var(--bg-color); border-bottom: 1px solid var(--border-color); }
.order-form-panel { padding: 20px; background: var(--panel-bg); border-bottom: 1px solid var(--border-color); }
.order-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; }
.input-row { background: var(--input-bg); border: 1px solid var(--border-color); border-radius: 4px; display: flex; align-items: center; margin-bottom: 10px; padding: 8px 12px; }
.input-row input { flex: 1; background: transparent; border: none; color: white; text-align: right; outline: none; font-size: 14px; }
.btn-trade { padding: 12px; border: none; border-radius: 6px; font-weight: bold; font-size: 15px; cursor: pointer; color: white; }
/* Responsive */
@media (max-width: 1200px) {
.market-panel { display: none; }
}
@media (max-width: 992px) {
.trading-layout { flex-direction: column; }
.order-book-panel { display: none; }
.chart-container { height: 350px; }
.order-form-grid { grid-template-columns: 1fr; gap: 10px; }
.info-bar { height: auto; padding: 10px 15px; }
}
</style>
<div class="trading-layout">
<!-- Left Panel -->
<div class="panel market-panel">
<div style="padding: 12px; border-bottom: 1px solid var(--border-color);">
<input type="text" id="market-search" placeholder="搜索合约" style="width: 100%; background: var(--input-bg); border: 1px solid var(--border-color); color: white; padding: 8px 12px; border-radius: 6px; font-size: 13px;">
</div>
<div id="pairs-list"></div>
</div>
<!-- Center Panel -->
<div class="panel center-panel">
<div class="info-bar">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="current-logo" src="https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/btc.png" width="28" height="28" onerror="this.src='https://cdn-icons-png.flaticon.com/512/2585/2585274.png'">
<div>
<div id="current-pair-display" style="font-size: 15px; font-weight: bold;">BTC/USDT 永续</div>
<div id="leverage-display" style="font-size: 11px; color: var(--accent-color); cursor: pointer;" onclick="showLevModal()">20x</div>
</div>
</div>
<div style="display: flex; flex-direction: column;">
<span id="last-price" style="font-size: 18px; font-weight: bold; color: var(--up-color);">--</span>
<span id="price-change" style="font-size: 11px;">--</span>
</div>
<div style="margin-left: auto; display: flex; gap: 15px; font-size: 11px;" class="desktop-only">
<div style="color: var(--text-secondary);">标记 <span id="mark-price" style="color: white;">--</span></div>
<div style="color: var(--text-secondary);">24h额 <span id="vol-24h" style="color: white;">--</span></div>
</div>
</div>
<div class="chart-container">
<div id="tv_chart_container" style="height: 100%;"></div>
</div>
<div class="center-content">
<div class="order-form-panel">
<div style="display: flex; gap: 10px; margin-bottom: 15px;">
<button class="ctrl-btn active" id="margin-isolated" onclick="setMargin('isolated')" style="background: var(--input-bg); border: 1px solid var(--border-color); color: white; padding: 5px 12px; border-radius: 4px; font-size: 12px; cursor: pointer;">逐仓</button>
<button class="ctrl-btn" onclick="showLevModal()" style="background: var(--input-bg); border: 1px solid var(--border-color); color: white; padding: 5px 12px; border-radius: 4px; font-size: 12px; cursor: pointer;"><span id="leverage-val">20</span>x</button>
<div style="display: flex; gap: 15px; margin-left: 10px; align-items: center;">
<button onclick="setOrderType('limit')" id="order-type-limit" style="background: none; border: none; color: var(--text-secondary); font-size: 13px; cursor: pointer;">限价</button>
<button onclick="setOrderType('market')" id="order-type-market" style="background: none; border: none; color: var(--accent-color); font-weight: bold; font-size: 13px; cursor: pointer;">市价</button>
</div>
</div>
<div class="order-form-grid">
<div>
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;">
<span style="color: var(--text-secondary);">可用余额</span>
<span id="available-bal" style="color: white;"><?php echo number_format($balance, 2); ?> USDT</span>
</div>
<div class="input-row" id="price-row" style="display: none;">
<span style="color: var(--text-secondary); font-size: 13px; width: 40px;">价格</span>
<input type="number" id="order-price" placeholder="0.00">
</div>
<div class="input-row">
<span style="color: var(--text-secondary); font-size: 13px; width: 40px;">数量</span>
<input type="number" id="order-amount" placeholder="张数">
</div>
</div>
<div>
<div style="margin: 10px 0;">
<input type="range" min="0" max="100" value="0" id="order-slider" style="width: 100%; accent-color: var(--accent-color);" oninput="updateFromSlider(this.value)">
<div style="display: flex; justify-content: space-between; font-size: 10px; color: var(--text-secondary); margin-top: 5px;">
<span onclick="setSlider(0)">0%</span><span onclick="setSlider(25)">25%</span><span onclick="setSlider(50)">50%</span><span onclick="setSlider(75)">75%</span><span onclick="setSlider(100)">100%</span>
</div>
</div>
<div style="font-size: 13px; display: flex; justify-content: space-between; margin-top: 15px;">
<span style="color: var(--text-secondary);">预计保证金</span>
<span><span id="order-cost">0.00</span> USDT</span>
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 20px;">
<button class="btn-trade" style="background: var(--up-color);" onclick="placeOrder('buy')">开多 (买入)</button>
<button class="btn-trade" style="background: var(--down-color);" onclick="placeOrder('sell')">开空 (卖出)</button>
</div>
</div>
<div style="background: var(--panel-bg);">
<div style="display: flex; border-bottom: 1px solid var(--border-color); padding: 0 15px; overflow-x: auto;">
<button class="tab-btn active" onclick="switchTab(this, 'positions')" style="background: none; border: none; color: var(--accent-color); padding: 12px 15px; font-size: 14px; border-bottom: 2px solid var(--accent-color); cursor: pointer; white-space: nowrap;">当前持仓</button>
<button class="tab-btn" onclick="switchTab(this, 'open')" style="background: none; border: none; color: var(--text-secondary); padding: 12px 15px; font-size: 14px; cursor: pointer; white-space: nowrap;">当前委托</button>
</div>
<div style="padding: 15px; overflow-x: auto;">
<table style="width: 100%; font-size: 11px; border-collapse: collapse; min-width: 600px;">
<thead id="data-thead" style="color: var(--text-secondary); text-align: left;"></thead>
<tbody id="data-tbody"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Right Panel (Order Book) -->
<div class="panel order-book-panel">
<div style="padding: 10px 15px; font-size: 12px; color: var(--text-secondary);">价格 / 数量</div>
<div id="asks-list" style="display: flex; flex-direction: column-reverse;"></div>
<div id="ob-mid-price" style="padding: 10px 0; text-align: center; font-weight: bold; border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);">--</div>
<div id="bids-list"></div>
</div>
</div>
<div id="lev-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 2000;">
<div style="background: var(--panel-bg); padding: 30px; border-radius: 12px; width: 320px; text-align: center;">
<h3 style="margin-bottom: 20px;">调整杠杆</h3>
<div id="lev-val-big" style="font-size: 36px; font-weight: bold; color: var(--accent-color); margin-bottom: 20px;">20x</div>
<input type="range" min="1" max="125" value="20" id="lev-range" style="width: 100%;" oninput="document.getElementById('lev-val-big').innerText = this.value + 'x'">
<div style="display: flex; gap: 15px; margin-top: 30px;">
<button onclick="hideLevModal()" style="flex: 1; padding: 10px; background: #2b3139; border: none; color: white; border-radius: 6px;">取消</button>
<button onclick="confirmLev()" style="flex: 1; padding: 10px; background: var(--accent-color); border: none; color: white; border-radius: 6px;">确认</button>
</div>
</div>
</div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script>
let currentPair = 'BTCUSDT';
let currentPrice = 0;
let leverage = 20;
let usdtBalance = <?php echo $balance; ?>;
let marketData = {};
let orderType = 'market';
let activeTab = 'positions';
const faceValue = 10;
const pairs = ['BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT'];
function initChart(symbol) {
new TradingView.widget({
"width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol, "interval": "15", "theme": "dark", "style": "1", "locale": "zh_CN", "container_id": "tv_chart_container", "backgroundColor": "#0b0e11", "hide_side_toolbar": true
});
}
initChart(currentPair);
let ws;
function connectWS() {
const streams = pairs.map(p => p.toLowerCase() + '@ticker').join('/');
ws = new WebSocket(`wss://fstream.binance.com/ws/${streams}`);
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
marketData[data.s] = data;
renderPairs();
if (data.s === currentPair) updateUI(data);
};
}
connectWS();
function updateUI(data) {
currentPrice = parseFloat(data.c);
document.getElementById('last-price').innerText = currentPrice.toLocaleString();
document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
document.getElementById('price-change').innerText = (data.P >= 0 ? '+' : '') + data.P + '%';
document.getElementById('mark-price').innerText = currentPrice.toLocaleString();
document.getElementById('vol-24h').innerText = parseFloat(data.q).toLocaleString();
document.getElementById('ob-mid-price').innerText = currentPrice.toLocaleString();
updateOrderBook();
}
function renderPairs() {
const list = document.getElementById('pairs-list');
if (!list) return;
let html = '';
pairs.forEach(p => {
const d = marketData[p] || {c: 0, P: 0};
html += `<div class="pair-item ${currentPair === p ? 'active' : ''}" onclick="switchPair('${p}')">
<span>${p}/USDT</span>
<span style="color: ${d.P >= 0 ? 'var(--up-color)' : 'var(--down-color)'}">${parseFloat(d.c).toLocaleString()}</span>
</div>`;
});
list.innerHTML = html;
}
function switchPair(p) {
currentPair = p;
document.getElementById('current-pair-display').innerText = p + '/USDT 永续';
initChart(p);
}
function updateOrderBook() {
const asks = document.getElementById('asks-list');
const bids = document.getElementById('bids-list');
let aH = ''; let bH = '';
for(let i=0; i<10; i++) {
aH += `<div style="display: flex; justify-content: space-between; padding: 2px 15px; font-size: 11px;"><span style="color: var(--down-color);">${(currentPrice*(1+(i+1)*0.0005)).toFixed(1)}</span><span>${(Math.random()*100).toFixed(0)}</span></div>`;
bH += `<div style="display: flex; justify-content: space-between; padding: 2px 15px; font-size: 11px;"><span style="color: var(--up-color);">${(currentPrice*(1-(i+1)*0.0005)).toFixed(1)}</span><span>${(Math.random()*100).toFixed(0)}</span></div>`;
}
asks.innerHTML = aH; bids.innerHTML = bH;
}
function setSlider(val) { document.getElementById('order-slider').value = val; updateFromSlider(val); }
function updateFromSlider(val) {
const cost = usdtBalance * (val / 100);
document.getElementById('order-amount').value = Math.floor((cost * leverage) / faceValue);
document.getElementById('order-cost').innerText = cost.toFixed(2);
}
function showLevModal() { document.getElementById('lev-modal').style.display = 'flex'; }
function hideLevModal() { document.getElementById('lev-modal').style.display = 'none'; }
function confirmLev() {
leverage = document.getElementById('lev-range').value;
document.getElementById('leverage-display').innerText = leverage + 'x';
document.getElementById('leverage-val').innerText = leverage;
hideLevModal();
}
function setOrderType(type) {
orderType = type;
document.getElementById('order-type-limit').style.color = type === 'limit' ? 'var(--accent-color)' : 'var(--text-secondary)';
document.getElementById('order-type-market').style.color = type === 'market' ? 'var(--accent-color)' : 'var(--text-secondary)';
document.getElementById('price-row').style.display = type === 'limit' ? 'flex' : 'none';
}
async function placeOrder(side) {
const amount = parseFloat(document.getElementById('order-amount').value);
if (!amount) return alert('数量错误');
const price = orderType === 'limit' ? parseFloat(document.getElementById('order-price').value) : currentPrice;
const resp = await fetch('api/place_order.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
symbol: currentPair, type: 'futures', side: side, order_type: orderType,
price: price, amount: amount, leverage: leverage, total: amount * faceValue
})
});
const res = await resp.json();
if (res.success) { alert('成功'); fetchOrders(); } else { alert(res.error); }
}
async function fetchOrders() {
const resp = await fetch(`api/get_orders.php?type=futures&status=${activeTab}`);
const res = await resp.json();
const tbody = document.getElementById('data-tbody');
const thead = document.getElementById('data-thead');
if (activeTab === 'positions') {
thead.innerHTML = `<tr><th style="padding: 10px 5px;">合约</th><th style="padding: 10px 5px;">仓位</th><th style="padding: 10px 5px;">开仓价</th><th style="padding: 10px 5px;">盈亏</th><th style="padding: 10px 5px; text-align: right;">操作</th></tr>`;
} else {
thead.innerHTML = `<tr><th style="padding: 10px 5px;">时间</th><th style="padding: 10px 5px;">合约</th><th style="padding: 10px 5px;">方向</th><th style="padding: 10px 5px;">价格</th><th style="padding: 10px 5px;">数量</th><th style="padding: 10px 5px; text-align: right;">操作</th></tr>`;
}
if (res.success && res.data.length > 0) {
tbody.innerHTML = res.data.map(o => {
const color = o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)';
return `<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 10px 5px;">${o.symbol}</td>
<td style="padding: 10px 5px; color: ${color};">${o.side === 'buy' ? '多' : '空'} ${o.leverage}x</td>
<td style="padding: 10px 5px;">${parseFloat(o.price).toLocaleString()}</td>
<td style="padding: 10px 5px;">--</td>
<td style="padding: 10px 5px; text-align: right;"><button onclick="closePos(${o.id})">平仓</button></td>
</tr>`;
}).join('');
} else { tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 40px;">暂无记录</td></tr>'; }
}
function switchTab(btn, tab) {
document.querySelectorAll('.tab-btn').forEach(b => { b.classList.remove('active'); b.style.color = 'var(--text-secondary)'; b.style.borderBottom = 'none'; });
btn.classList.add('active'); btn.style.color = 'var(--accent-color)'; btn.style.borderBottom = '2px solid var(--accent-color)';
activeTab = tab; fetchOrders();
}
fetchOrders(); setInterval(fetchOrders, 4000);
</script>
<?php include 'footer.php'; ?>