306 lines
22 KiB
PHP
306 lines
22 KiB
PHP
<?php
|
|
include 'header.php';
|
|
require_once 'db/config.php';
|
|
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
?>
|
|
|
|
<style>
|
|
/* Layout Fixes */
|
|
@media (max-width: 991px) {
|
|
.navbar, footer { display: none !important; }
|
|
body { padding-bottom: 65px !important; margin: 0 !important; max-width: 100% !important; }
|
|
}
|
|
|
|
.trading-container {
|
|
display: flex;
|
|
background: #0b0e11;
|
|
min-height: calc(100vh - 64px);
|
|
max-width: 100%;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.trading-container { min-height: 100vh; flex-direction: column; }
|
|
}
|
|
|
|
.left-col { width: 280px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
|
.center-col { flex: 1; min-width: 0; background: #0b0e11; display: flex; flex-direction: column; overflow: hidden; }
|
|
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-left: 1px solid #2b3139; }
|
|
|
|
/* Pairs List */
|
|
#pairs-list { flex: 1; overflow-y: auto; }
|
|
.pair-item { display: flex; align-items: center; padding: 12px 15px; cursor: pointer; transition: all 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
|
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
|
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; }
|
|
|
|
/* Chart Area */
|
|
.chart-header { padding: 12px 20px; display: flex; align-items: center; gap: 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
|
.chart-box { height: 400px; background: #0b0e11; position: relative; }
|
|
@media (max-width: 991px) { .chart-box { height: 350px; } }
|
|
|
|
/* Order Placement */
|
|
.order-box { padding: 20px; display: grid; grid-template-columns: 1fr 1fr; gap: 30px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
|
@media (max-width: 991px) { .order-box { grid-template-columns: 1fr; gap: 20px; padding: 15px; } }
|
|
|
|
.order-type-tabs { display: flex; gap: 20px; margin-bottom: 15px; }
|
|
.order-type-tab { font-size: 13px; color: #848e9c; cursor: pointer; padding-bottom: 5px; border-bottom: 2px solid transparent; }
|
|
.order-type-tab.active { color: white; border-bottom-color: var(--primary-color); font-weight: bold; }
|
|
|
|
.input-group { background: #2b3139; border-radius: 4px; padding: 10px 12px; display: flex; align-items: center; margin-bottom: 10px; border: 1px solid transparent; }
|
|
.input-group:focus-within { border-color: var(--primary-color); }
|
|
.input-group input { background: transparent; border: none; color: white; flex: 1; text-align: right; outline: none; font-size: 14px; width: 100%; }
|
|
.input-group .label { color: #848e9c; font-size: 12px; }
|
|
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 12px; }
|
|
|
|
/* Slider Style */
|
|
.slider-container { margin: 15px 0 25px; padding: 0 5px; }
|
|
.slider-labels { display: flex; justify-content: space-between; margin-top: 8px; font-size: 10px; color: #848e9c; }
|
|
input[type=range] { -webkit-appearance: none; width: 100%; height: 4px; background: #2b3139; border-radius: 2px; outline: none; }
|
|
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; background: var(--primary-color); border-radius: 50%; cursor: pointer; border: 3px solid #161a1e; }
|
|
|
|
.trade-btn { width: 100%; padding: 14px; border-radius: 4px; font-weight: bold; font-size: 15px; border: none; cursor: pointer; margin-top: 10px; }
|
|
.btn-buy { background: #0ecb81; color: white; }
|
|
.btn-sell { background: #f6465d; color: white; }
|
|
|
|
/* Records */
|
|
.record-tabs { display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 0 20px; }
|
|
.record-tab { padding: 15px 0; margin-right: 30px; font-size: 13px; color: #848e9c; cursor: pointer; position: relative; }
|
|
.record-tab.active { color: white; border-bottom: 2px solid var(--primary-color); font-weight: bold; }
|
|
.records-content { min-height: 300px; background: #161a1e; }
|
|
@media (max-width: 991px) { .records-content { padding-bottom: 80px; } }
|
|
|
|
/* Mobile Headers */
|
|
.m-trading-header { display: none; background: #161a1e; padding: 12px 15px; border-bottom: 1px solid #2b3139; }
|
|
.m-currency-bar { display: none; overflow-x: auto; background: #161a1e; padding: 10px; gap: 10px; border-bottom: 1px solid #2b3139; scrollbar-width: none; }
|
|
.m-nav-tabs { display: none; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
|
@media (max-width: 991px) { .m-trading-header, .m-currency-bar, .m-nav-tabs { display: flex; } .m-trading-header { flex-direction: column; } }
|
|
.m-coin-item { flex-shrink: 0; background: #2b3139; padding: 6px 12px; border-radius: 4px; display: flex; align-items: center; gap: 6px; border: 1px solid transparent; }
|
|
.m-coin-item.active { border-color: var(--primary-color); background: rgba(0, 82, 255, 0.1); }
|
|
.m-coin-item img { width: 16px; height: 16px; border-radius: 50%; }
|
|
.m-nav-tab { flex:1; text-align:center; padding:12px; font-size:14px; color:#848e9c; border-bottom:2px solid transparent; }
|
|
.m-nav-tab.active { color:white; border-bottom-color:var(--primary-color); font-weight:bold; }
|
|
</style>
|
|
|
|
<!-- Mobile Trading Header -->
|
|
<div class="m-trading-header">
|
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
<div style="display: flex; align-items: center; gap: 10px;" onclick="toggleSidebar()">
|
|
<i class="fas fa-bars" style="font-size: 18px;"></i>
|
|
<div style="font-size: 18px; font-weight: 800; display: flex; align-items: center; gap: 6px;">
|
|
<img id="m-curr-icon" src="" style="width:24px; height:24px; border-radius:50%">
|
|
<span id="m-curr-pair">BTC/USDT</span>
|
|
<i class="fas fa-chevron-down" style="font-size: 10px; opacity: 0.5;"></i>
|
|
</div>
|
|
</div>
|
|
<div style="text-align: right">
|
|
<div id="m-curr-price" style="font-size: 22px; font-weight: 800; color: #0ecb81; line-height:1">--</div>
|
|
<div id="m-curr-change" style="font-size: 12px; font-weight: 600; margin-top:4px">--</div>
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 15px; margin-top: 10px; font-size: 11px; color: #848e9c;">
|
|
<span id="m-24h-high">H: --</span><span id="m-24h-low">L: --</span><span id="m-24h-vol">V: --</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="m-currency-bar" id="m-coin-list"></div>
|
|
|
|
<div class="m-nav-tabs">
|
|
<div class="m-nav-tab active" onclick="switchMobileView('trade')"><?php echo __('nav_trade'); ?></div>
|
|
<div class="m-nav-tab" onclick="switchMobileView('chart')"><?php echo __('chart'); ?></div>
|
|
<div class="m-nav-tab" onclick="switchMobileView('orders')"><?php echo __('orders'); ?></div>
|
|
</div>
|
|
|
|
<div class="trading-container">
|
|
<div class="left-col d-none d-lg-flex">
|
|
<div class="market-header" style="padding:15px; font-weight:bold; border-bottom:1px solid #2b3139"><?php echo __('nav_market'); ?></div>
|
|
<div id="pairs-list"></div>
|
|
</div>
|
|
|
|
<div class="center-col m-view active" id="view-trade">
|
|
<div class="chart-header d-none d-lg-flex">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:32px; height:32px;">
|
|
<span id="curr-pair" style="font-weight: 800; font-size: 20px;">BTC/USDT</span>
|
|
</div>
|
|
<div style="flex:1"></div>
|
|
<div style="text-align: right">
|
|
<div id="curr-price" style="font-size: 24px; font-weight: 800; color: #0ecb81;">--</div>
|
|
<div id="curr-change" style="font-size: 13px; font-weight: 600;">--</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-box d-none d-lg-block">
|
|
<div id="tv_chart_container_desk" style="height: 100%;"></div>
|
|
</div>
|
|
|
|
<div class="order-box">
|
|
<!-- Buy -->
|
|
<div>
|
|
<div class="order-type-tabs">
|
|
<div class="order-type-tab active" onclick="switchOrderType('buy', 'limit')"><?php echo __('limit_order'); ?></div>
|
|
<div class="order-type-tab" onclick="switchOrderType('buy', 'market')"><?php echo __('market_order'); ?></div>
|
|
</div>
|
|
<div class="input-group">
|
|
<span class="label"><?php echo __('price'); ?></span>
|
|
<input type="number" id="buy-price" placeholder="0.00">
|
|
<span class="unit">USDT</span>
|
|
</div>
|
|
<div class="input-group">
|
|
<span class="label"><?php echo __('amount'); ?></span>
|
|
<input type="number" id="buy-amount" placeholder="0.00">
|
|
<span class="unit coin-name">BTC</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" min="0" max="100" value="0" step="1" oninput="setSlider('buy', this.value)">
|
|
<div class="slider-labels"><span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span></div>
|
|
</div>
|
|
<div style="font-size:12px; color:#848e9c; margin-bottom:10px; display:flex; justify-content:space-between">
|
|
<span>Available</span><span id="avail-usdt">0.00 USDT</span>
|
|
</div>
|
|
<button onclick="placeOrder('buy')" class="trade-btn btn-buy"><?php echo __('buy'); ?> <span class="coin-name">BTC</span></button>
|
|
</div>
|
|
<!-- Sell -->
|
|
<div>
|
|
<div class="order-type-tabs">
|
|
<div class="order-type-tab active" onclick="switchOrderType('sell', 'limit')"><?php echo __('limit_order'); ?></div>
|
|
<div class="order-type-tab" onclick="switchOrderType('sell', 'market')"><?php echo __('market_order'); ?></div>
|
|
</div>
|
|
<div class="input-group">
|
|
<span class="label"><?php echo __('price'); ?></span>
|
|
<input type="number" id="sell-price" placeholder="0.00">
|
|
<span class="unit">USDT</span>
|
|
</div>
|
|
<div class="input-group">
|
|
<span class="label"><?php echo __('amount'); ?></span>
|
|
<input type="number" id="sell-amount" placeholder="0.00">
|
|
<span class="unit coin-name">BTC</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" min="0" max="100" value="0" step="1" oninput="setSlider('sell', this.value)">
|
|
<div class="slider-labels"><span>0%</span><span>25%</span><span>50%</span><span>75%</span><span>100%</span></div>
|
|
</div>
|
|
<div style="font-size:12px; color:#848e9c; margin-bottom:10px; display:flex; justify-content:space-between">
|
|
<span>Available</span><span id="avail-coin">0.00 BTC</span>
|
|
</div>
|
|
<button onclick="placeOrder('sell')" class="trade-btn btn-sell"><?php echo __('sell'); ?> <span class="coin-name">BTC</span></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-tabs">
|
|
<div class="record-tab active" onclick="switchRecords('open')"><?php echo __('current_orders'); ?></div>
|
|
<div class="record-tab" onclick="switchRecords('history')"><?php echo __('history_orders'); ?></div>
|
|
<div class="record-tab" onclick="switchRecords('trades')"><?php echo __('settled'); ?></div>
|
|
</div>
|
|
<div id="records-list" class="records-content"></div>
|
|
</div>
|
|
|
|
<div class="center-col m-view" id="view-chart" style="display:none">
|
|
<div class="chart-box"><div id="tv_chart_container_mob" style="height: 100%;"></div></div>
|
|
</div>
|
|
<div class="center-col m-view" id="view-orders" style="display:none">
|
|
<div class="record-tabs">
|
|
<div class="record-tab active" onclick="switchRecords('open')"><?php echo __('current_orders'); ?></div>
|
|
<div class="record-tab" onclick="switchRecords('history')"><?php echo __('history_orders'); ?></div>
|
|
<div class="record-tab" onclick="switchRecords('trades')"><?php echo __('settled'); ?></div>
|
|
</div>
|
|
<div id="m-records-list" class="records-content"></div>
|
|
</div>
|
|
|
|
<div class="right-col d-none d-lg-flex">
|
|
<div class="ob-header"><span>Price</span><span>Amount</span></div>
|
|
<div id="asks-list" style="flex: 1; display: flex; flex-direction: column-reverse; overflow: hidden;"></div>
|
|
<div id="mid-price">--</div>
|
|
<div id="bids-list" style="flex: 1; overflow: hidden;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
|
<script>
|
|
let currentPair = 'BTCUSDT'; let currentPrice = 0; let orderTypes = {buy:'limit', sell:'limit'}; let currentStatus = 'open';
|
|
const pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'SHIBUSDT', 'TRXUSDT', 'AVAXUSDT', 'LINKUSDT', 'BCHUSDT', 'UNIUSDT', 'ETCUSDT', 'NEARUSDT', 'FILUSDT', 'ALGOUSDT', 'FTMUSDT', 'SANDUSDT', 'MANAUSDT', 'AXSUSDT', 'ATOMUSDT', 'HBARUSDT', 'ICPUSDT', 'VETUSDT'];
|
|
const marketData = {}; let balances = {usdt:0, coin:0};
|
|
|
|
function getIcon(s) { return `https://cdn.jsdelivr.net/gh/atomiclabs/cryptocurrency-icons@1a72df150d6d8481e2345e691456c6c7d1e84a22/32/color/${s.replace('USDT', '').toLowerCase()}.png`; }
|
|
function initChart(symbol, container) { new TradingView.widget({ "width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol, "interval": "15", "theme": "dark", "style": "1", "locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": container, "backgroundColor": "#0b0e11", "hide_side_toolbar": true }); }
|
|
|
|
const ws = new WebSocket('wss://stream.binance.com:9443/ws/' + pairs.map(p => p.toLowerCase() + '@ticker').join('/'));
|
|
ws.onmessage = (e) => {
|
|
const d = JSON.parse(e.data);
|
|
if (d.s === currentPair) { currentPrice = parseFloat(d.c); updatePriceUI(d); if(orderTypes.buy==='market')document.getElementById('buy-price').value=currentPrice; if(orderTypes.sell==='market')document.getElementById('sell-price').value=currentPrice; renderOrderBook(); }
|
|
marketData[d.s] = d; renderPairs();
|
|
};
|
|
|
|
function updatePriceUI(d) {
|
|
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
|
document.getElementById('curr-price').innerText = currentPrice.toLocaleString();
|
|
document.getElementById('m-curr-price').innerText = currentPrice.toLocaleString();
|
|
document.getElementById('curr-price').style.color = color;
|
|
document.getElementById('m-curr-price').style.color = color;
|
|
document.getElementById('curr-change').innerText = (d.P>=0?'+':'')+d.P+'%';
|
|
document.getElementById('m-curr-change').innerText = (d.P>=0?'+':'')+d.P+'%';
|
|
document.getElementById('m-24h-high').innerText = 'H: ' + parseFloat(d.h).toLocaleString();
|
|
document.getElementById('m-24h-low').innerText = 'L: ' + parseFloat(d.l).toLocaleString();
|
|
document.getElementById('m-24h-vol').innerText = 'V: ' + (parseFloat(d.v)/1000).toFixed(1) + 'K';
|
|
document.getElementById('mid-price').innerText = currentPrice.toLocaleString();
|
|
document.getElementById('mid-price').style.color = color;
|
|
}
|
|
|
|
function renderPairs() {
|
|
const list = document.getElementById('pairs-list'); const mBar = document.getElementById('m-coin-list');
|
|
let lH = ''; let mH = '';
|
|
pairs.forEach(p => {
|
|
const d = marketData[p]||{c:0,P:0}; const icon = getIcon(p); const active = p === currentPair; const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
|
lH += `<div class="pair-item ${active?'active':''}" onclick="switchPair('${p}')"><img src="${icon}" class="coin-icon"> <div style="flex:1"><div style="font-weight:700; font-size:13px">${p.replace('USDT','')}/USDT</div></div> <div style="text-align:right"><div style="font-size:13px; font-weight:bold">${parseFloat(d.c).toLocaleString()}</div><div style="font-size:11px; color:${color}">${(d.P>=0?'+':'')+d.P}%</div></div></div>`;
|
|
mH += `<div class="m-coin-item ${active?'active':''}" onclick="switchPair('${p}')"><img src="${icon}"><span>${p.replace('USDT','')}</span></div>`;
|
|
});
|
|
if(list) list.innerHTML = lH; if(mBar) mBar.innerHTML = mH;
|
|
}
|
|
|
|
function switchPair(p) {
|
|
currentPair = p; const icon = getIcon(p);
|
|
document.getElementById('curr-pair').innerText = p.replace('USDT','')+'/USDT'; document.getElementById('m-curr-pair').innerText = p.replace('USDT','')+'/USDT';
|
|
document.getElementById('curr-icon').src = icon; document.getElementById('m-curr-icon').src = icon;
|
|
document.querySelectorAll('.coin-name').forEach(el => el.innerText = p.replace('USDT',''));
|
|
initChart(p, "tv_chart_container_desk"); initChart(p, "tv_chart_container_mob"); updateBalance();
|
|
}
|
|
|
|
function switchOrderType(side, type) { orderTypes[side] = type; event.target.parentElement.querySelectorAll('.order-type-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); document.getElementById(side+'-price').disabled = (type==='market'); if(type==='market') document.getElementById(side+'-price').value = currentPrice; }
|
|
function setSlider(side, val) { const pct = val / 100; if(side==='buy') { const p = parseFloat(document.getElementById('buy-price').value)||currentPrice; if(p>0) document.getElementById('buy-amount').value = (balances.usdt*pct/p).toFixed(6); } else { document.getElementById('sell-amount').value = (balances.coin*pct).toFixed(6); } }
|
|
|
|
async function placeOrder(side) {
|
|
const p = parseFloat(document.getElementById(side+'-price').value); const a = parseFloat(document.getElementById(side+'-amount').value);
|
|
if(!a || a<=0) return alert('Invalid amount');
|
|
const resp = await fetch('api/place_order.php', { method:'POST', body:JSON.stringify({ symbol:currentPair, type:'spot', side:side, order_type:orderTypes[side], price:p, amount:a, total:p*a }) });
|
|
const res = await resp.json();
|
|
if(res.success) { alert('Order placed'); updateBalance(); fetchOrders(); } else alert(res.error);
|
|
}
|
|
|
|
function renderOrderBook() {
|
|
const asks = document.getElementById('asks-list'); const bids = document.getElementById('bids-list');
|
|
let aH = ''; let bH = '';
|
|
for(let i=10; i>0; i--) { const p = currentPrice * (1+i*0.0001); const amt = Math.random()*2; aH += `<div class="ob-row" onclick="setPrice(${p})"><div class="ob-bar" style="width:${amt*20}%; background:#f6465d"></div><span style="color:#f6465d">${p.toFixed(2)}</span><span>${amt.toFixed(4)}</span></div>`; }
|
|
for(let i=1; i<=10; i++) { const p = currentPrice * (1-i*0.0001); const amt = Math.random()*2; bH += `<div class="ob-row" onclick="setPrice(${p})"><div class="ob-bar" style="width:${amt*20}%; background:#0ecb81"></div><span style="color:#0ecb81">${p.toFixed(2)}</span><span>${amt.toFixed(4)}</span></div>`; }
|
|
if(asks) asks.innerHTML = aH; if(bids) bids.innerHTML = bH;
|
|
}
|
|
|
|
function setPrice(p) { if(orderTypes.buy==='limit')document.getElementById('buy-price').value=p.toFixed(2); if(orderTypes.sell==='limit')document.getElementById('sell-price').value=p.toFixed(2); }
|
|
async function updateBalance() {
|
|
const resp = await fetch('api/get_assets.php'); const res = await resp.json();
|
|
if(res.success) { const usdt=res.data.find(a=>a.symbol==='USDT'); const coinSym=currentPair.replace('USDT',''); const coin=res.data.find(a=>a.symbol===coinSym); balances.usdt=usdt?parseFloat(usdt.amount):0; balances.coin=coin?parseFloat(coin.amount):0; document.getElementById('avail-usdt').innerText=balances.usdt.toFixed(2)+' USDT'; document.getElementById('avail-coin').innerText=balances.coin.toFixed(6)+' '+coinSym; }
|
|
}
|
|
|
|
async function fetchOrders() {
|
|
const resp = await fetch(`api/get_orders.php?type=spot&status=${currentStatus}`); const res = await resp.json();
|
|
const l = document.getElementById('records-list'); const mL = document.getElementById('m-records-list');
|
|
let h = res.success && res.data.length > 0 ? res.data.map(o => `<div style="padding:15px; border-bottom:1px solid #2b3139; font-size:12px"><div style="display:flex; justify-content:space-between; align-items:center"><span style="font-weight:bold; font-size:14px">${o.symbol} <span style="color:${o.side==='buy'?'#0ecb81':'#f6465d'}">${o.side.toUpperCase()}</span></span><span style="color:#848e9c">${o.created_at}</span></div><div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px; margin-top:10px; color:#848e9c"><div>Price<br><span style="color:white; font-weight:bold">${parseFloat(o.price).toFixed(2)}</span></div><div>Amount<br><span style="color:white; font-weight:bold">${parseFloat(o.amount).toFixed(4)}</span></div><div>Status<br><span style="color:white; font-weight:bold">${o.status}</span></div></div></div>`).join('') : '<div style="padding:40px; text-align:center; color:#848e9c">No records</div>';
|
|
if(l) l.innerHTML = h; if(mL) mL.innerHTML = h;
|
|
}
|
|
|
|
function switchRecords(s) { currentStatus = s; document.querySelectorAll('.record-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); fetchOrders(); }
|
|
function switchMobileView(view) { document.querySelectorAll('.m-nav-tab').forEach(n => n.classList.remove('active')); event.target.classList.add('active'); document.querySelectorAll('.m-view').forEach(v => v.style.display = 'none'); document.getElementById('view-' + view).style.display = 'block'; if(view === 'orders') fetchOrders(); }
|
|
|
|
switchPair(currentPair); updateBalance(); fetchOrders();
|
|
</script>
|
|
<?php include 'footer.php'; ?>
|