577 lines
29 KiB
PHP
577 lines
29 KiB
PHP
<?php
|
|
include 'header.php';
|
|
require_once 'db/config.php';
|
|
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
?>
|
|
|
|
<style>
|
|
/* Global layout enforcement */
|
|
html, body {
|
|
background: #0b0e11;
|
|
min-width: 1280px;
|
|
max-width: 1920px;
|
|
margin: 0 auto;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
html, body {
|
|
min-width: 100%;
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
.navbar, footer { display: none !important; }
|
|
body { padding-bottom: 0 !important; }
|
|
}
|
|
|
|
.trading-container {
|
|
display: flex;
|
|
background: #0b0e11;
|
|
min-height: calc(100vh - 64px);
|
|
width: 100%;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.trading-container { min-height: calc(100vh - 50px); flex-direction: column; }
|
|
}
|
|
|
|
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
|
.center-col { flex: 1; min-width: 600px; background: #0b0e11; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
|
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
|
|
|
@media (max-width: 991px) {
|
|
.left-col, .right-col { display: none !important; }
|
|
.center-col { min-width: 100%; }
|
|
}
|
|
|
|
/* Category Tabs above Search */
|
|
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
|
|
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 11px; color: #848e9c; cursor: pointer; transition: all 0.2s; white-space: nowrap; border: 1px solid transparent; }
|
|
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
|
|
|
/* Search Box */
|
|
.search-box { padding: 5px 15px 10px; position: relative; }
|
|
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
|
|
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
|
|
.search-box input:focus { border-color: var(--primary-color); }
|
|
|
|
#pairs-list { flex: 1; overflow-y: auto; }
|
|
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: all 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
|
.pair-item:hover { background: #1e2329; }
|
|
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
|
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; flex-shrink: 0; background: #2b3139; }
|
|
|
|
.chart-header { padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; flex-wrap: nowrap; gap: 20px; height: 50px; }
|
|
.chart-box { flex: 1; min-height: 350px; height: 350px; background: #0b0e11; border-bottom: 1px solid #2b3139; }
|
|
@media (max-width: 991px) { .chart-box { min-height: 350px; height: 350px; } }
|
|
|
|
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
|
.order-type-tabs { display: flex; gap: 20px; margin-bottom: 10px; }
|
|
.order-type-tab { font-size: 13px; color: #848e9c; cursor: pointer; padding-bottom: 4px; 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: 6px 12px; display: flex; align-items: center; margin-bottom: 6px; 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: left; outline: none; font-size: 13px; width: 100%; }
|
|
.input-group .label { color: #848e9c; font-size: 11px; margin-right: 10px; }
|
|
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
|
|
|
|
.slider-container { margin: 8px 0 15px; padding: 0 5px; }
|
|
.slider-labels { display: flex; justify-content: space-between; margin-top: 4px; font-size: 10px; color: #848e9c; }
|
|
input[type=range] { -webkit-appearance: none; width: 100%; height: 3px; background: #2b3139; border-radius: 2px; outline: none; }
|
|
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; background: var(--primary-color); border-radius: 50%; cursor: pointer; border: 2px solid #161a1e; }
|
|
|
|
.trade-btn { width: 100%; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; margin-top: 5px; }
|
|
.btn-buy { background: #0ecb81; color: white; }
|
|
.btn-sell { background: #f6465d; color: white; }
|
|
|
|
.record-tabs { display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 0 20px; }
|
|
.record-tab { padding: 10px 0; margin-right: 25px; 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: 200px; background: #161a1e; overflow-y: auto; }
|
|
|
|
/* Order Book Styles */
|
|
.ob-header { padding: 10px 15px; font-size: 11px; color: #848e9c; display: flex; justify-content: space-between; border-bottom: 1px solid #2b3139; background: #161a1e; }
|
|
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; cursor: pointer; height: 20px; align-items: center; }
|
|
.ob-bar { position: absolute; right: 0; top: 0; bottom: 0; opacity: 0.1; z-index: 0; }
|
|
#mid-price { padding: 8px 15px; font-size: 16px; font-weight: 800; text-align: center; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; background: #161a1e; }
|
|
|
|
#asks-list { height: 400px; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end; }
|
|
#bids-list { height: 400px; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start; }
|
|
|
|
/* Stats Item */
|
|
.stats-item { display: flex; flex-direction: column; justify-content: center; }
|
|
.stats-label { font-size: 10px; color: #848e9c; margin-bottom: 1px; white-space: nowrap; }
|
|
.stats-value { font-size: 12px; font-weight: 600; color: white; white-space: nowrap; }
|
|
|
|
/* Mobile Nav */
|
|
.m-trade-nav { display: none; background: #161a1e; border-bottom: 1px solid #2b3139; position: sticky; top: 0; z-index: 100; }
|
|
.m-trade-nav a { flex: 1; text-align: center; padding: 14px; font-size: 14px; color: #848e9c; text-decoration: none; border-bottom: 2px solid transparent; }
|
|
.m-trade-nav a.active { color: var(--primary-color); border-bottom-color: var(--primary-color); font-weight: bold; }
|
|
|
|
.m-currency-bar { display: none; overflow-x: auto; background: #161a1e; padding: 10px; gap: 10px; border-bottom: 1px solid #2b3139; scrollbar-width: none; }
|
|
.m-currency-bar::-webkit-scrollbar { display: none; }
|
|
.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%; }
|
|
|
|
@media (max-width: 991px) {
|
|
.m-trade-nav { display: flex; }
|
|
.m-currency-bar { display: flex; }
|
|
.chart-header { gap: 10px; padding: 10px 15px; overflow-x: auto; scrollbar-width: none; }
|
|
.chart-header::-webkit-scrollbar { display: none; }
|
|
}
|
|
</style>
|
|
|
|
<div class="m-trade-nav">
|
|
<a href="spot.php" class="active"><?php echo __('nav_spot'); ?></a>
|
|
<a href="futures.php"><?php echo __('nav_futures'); ?></a>
|
|
<a href="options.php"><?php echo __('nav_options'); ?></a>
|
|
</div>
|
|
|
|
<div class="m-currency-bar" id="m-coin-list"></div>
|
|
|
|
<div class="trading-container">
|
|
<div class="left-col d-none d-lg-flex">
|
|
<div class="category-tabs">
|
|
<div class="category-tab" onclick="location.href='options.php'"><?php echo $lang == 'zh' ? '秒合约' : 'Options'; ?></div>
|
|
<div class="category-tab active" onclick="location.href='spot.php'"><?php echo $lang == 'zh' ? '现货交易' : 'Spot'; ?></div>
|
|
<div class="category-tab" onclick="location.href='futures.php'"><?php echo $lang == 'zh' ? '合约交易' : 'Futures'; ?></div>
|
|
</div>
|
|
<div class="search-box">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
|
</div>
|
|
<div id="pairs-list"></div>
|
|
</div>
|
|
|
|
<div class="center-col">
|
|
<div class="chart-header">
|
|
<div style="display: flex; align-items: center; gap: 10px; padding-right: 15px; border-right: 1px solid #2b3139;">
|
|
<img id="curr-icon" src="" class="coin-icon" style="margin:0; width:28px; height:28px;">
|
|
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
|
|
</div>
|
|
|
|
<div class="stats-item">
|
|
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
|
|
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
|
|
</div>
|
|
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_high'); ?></span>
|
|
<span class="stats-value" id="h-high">--</span>
|
|
</div>
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_low'); ?></span>
|
|
<span class="stats-value" id="h-low">--</span>
|
|
</div>
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_vol'); ?></span>
|
|
<span class="stats-value" id="h-vol">--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-box"><div id="tv_chart_container" style="height: 100%;"></div></div>
|
|
|
|
<div class="order-box">
|
|
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 25px;">
|
|
<div>
|
|
<div class="order-type-tabs" data-side="buy">
|
|
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
|
|
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></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">--</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" id="buy-slider" min="0" max="100" value="0" step="1">
|
|
<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:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between">
|
|
<span><?php echo __('available'); ?></span><span id="avail-usdt">0.00 USDT</span>
|
|
</div>
|
|
<button id="btn-buy" class="trade-btn btn-buy"><?php echo __('buy'); ?> <span class="coin-name">--</span></button>
|
|
</div>
|
|
<div>
|
|
<div class="order-type-tabs" data-side="sell">
|
|
<div class="order-type-tab active" data-type="limit"><?php echo __('limit'); ?></div>
|
|
<div class="order-type-tab" data-type="market"><?php echo __('market'); ?></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">--</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" id="sell-slider" min="0" max="100" value="0" step="1">
|
|
<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:11px; color:#848e9c; margin-bottom:8px; display:flex; justify-content:space-between">
|
|
<span><?php echo __('available'); ?></span><span id="avail-coin">0.00 --</span>
|
|
</div>
|
|
<button id="btn-sell" class="trade-btn btn-sell"><?php echo __('sell'); ?> <span class="coin-name">--</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="record-tabs">
|
|
<div class="record-tab active" data-status="open"><?php echo __('current_orders'); ?></div>
|
|
<div class="record-tab" data-status="history"><?php echo __('history_orders'); ?></div>
|
|
<div class="record-tab" data-status="trades"><?php echo __('settled'); ?></div>
|
|
</div>
|
|
<div id="records-list" class="records-content"></div>
|
|
</div>
|
|
|
|
<div class="right-col d-none d-lg-flex">
|
|
<div class="ob-header">
|
|
<span><?php echo __('price'); ?>(USDT)</span>
|
|
<span><?php echo __('amount'); ?>(<span class="coin-name">--</span>)</span>
|
|
<span><?php echo __('total'); ?></span>
|
|
</div>
|
|
<div id="asks-list" style="height: 400px; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-end;"></div>
|
|
<div id="mid-price" style="color: white;">--</div>
|
|
<div id="bids-list" style="height: 400px; display: flex; flex-direction: column; overflow: hidden; justify-content: flex-start;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
|
<script>
|
|
TradingView.onready(function() {
|
|
let currentPair = 'BTCUSDT';
|
|
let currentPrice = 0;
|
|
let orderTypes = {buy: 'limit', sell: 'limit'};
|
|
let currentStatus = 'open';
|
|
let searchQuery = '';
|
|
let ws;
|
|
let balances = {usdt: 0, coin: 0};
|
|
|
|
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 = {};
|
|
|
|
function getIcon(s) {
|
|
const symbol = s.replace('USDT', '').toLowerCase();
|
|
return `https://assets.coincap.io/assets/icons/${symbol}@2x.png`;
|
|
}
|
|
|
|
function initChart(symbol) {
|
|
const container = document.getElementById('tv_chart_container');
|
|
if (container) container.innerHTML = '';
|
|
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": "tv_chart_container",
|
|
"backgroundColor": "#0b0e11",
|
|
"hide_side_toolbar": true
|
|
});
|
|
}
|
|
|
|
function connectWebSocket() {
|
|
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
return;
|
|
}
|
|
ws = new WebSocket('wss://stream.binance.com:9443/ws/' + pairs.map(p => p.toLowerCase() + '@ticker').concat(pairs.map(p => p.toLowerCase() + '@depth20@100ms')).join('/'));
|
|
|
|
ws.onmessage = (e) => {
|
|
const data = JSON.parse(e.data);
|
|
if (data.e === 'ticker') {
|
|
marketData[data.s] = data;
|
|
if (data.s === currentPair) {
|
|
currentPrice = parseFloat(data.c);
|
|
updatePriceUI(data);
|
|
if (orderTypes.buy === 'market') document.getElementById('buy-price').value = currentPrice.toFixed(2);
|
|
if (orderTypes.sell === 'market') document.getElementById('sell-price').value = currentPrice.toFixed(2);
|
|
}
|
|
renderPairs();
|
|
} else if (data.s === currentPair) {
|
|
renderOrderBook(data.b, data.a);
|
|
}
|
|
};
|
|
|
|
ws.onclose = () => {
|
|
setTimeout(connectWebSocket, 5000);
|
|
};
|
|
}
|
|
|
|
function updatePriceUI(d) {
|
|
if (!d) return;
|
|
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
|
const priceStr = parseFloat(d.c).toLocaleString(undefined, {minimumFractionDigits: 2});
|
|
document.getElementById('curr-price').innerText = priceStr;
|
|
document.getElementById('curr-price').style.color = color;
|
|
document.getElementById('curr-change').innerText = (d.P >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%';
|
|
document.getElementById('curr-change').style.color = color;
|
|
document.getElementById('h-high').innerText = parseFloat(d.h).toLocaleString();
|
|
document.getElementById('h-low').innerText = parseFloat(d.l).toLocaleString();
|
|
document.getElementById('h-vol').innerText = (parseFloat(d.v) / 1000).toFixed(1) + 'K';
|
|
const midPriceEl = document.getElementById('mid-price');
|
|
if (midPriceEl) {
|
|
midPriceEl.innerText = priceStr;
|
|
midPriceEl.style.color = color;
|
|
}
|
|
}
|
|
|
|
function renderPairs() {
|
|
const list = document.getElementById('pairs-list');
|
|
const mBar = document.getElementById('m-coin-list');
|
|
if (!list || !mBar) return;
|
|
|
|
let lH = '';
|
|
let mH = '';
|
|
const filteredPairs = pairs.filter(p => p.toUpperCase().includes(searchQuery));
|
|
|
|
filteredPairs.forEach(p => {
|
|
const d = marketData[p] || {};
|
|
const icon = getIcon(p);
|
|
const active = p === currentPair;
|
|
const color = (d.P && parseFloat(d.P) >= 0) ? '#0ecb81' : '#f6465d';
|
|
const price = d.c ? parseFloat(d.c).toLocaleString(undefined, {minimumFractionDigits: 2}) : '--';
|
|
const change = d.P ? ((parseFloat(d.P) >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%') : '--';
|
|
const disp = p.replace('USDT', '') + '/USDT';
|
|
|
|
lH += `<div class="pair-item ${active ? 'active' : ''}" data-pair="${p}"><img src="${icon}" class="coin-icon" onerror="this.src='https://ui-avatars.com/api/?name=${p.replace('USDT', '')}&background=2b3139&color=fff'"> <div style="flex:1"><div style="font-weight:700; font-size:13px">${disp}</div></div> <div style="text-align:right"><div style="font-size:13px; font-weight:bold">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div></div>`;
|
|
mH += `<div class="m-coin-item ${active ? 'active' : ''}" data-pair="${p}"><img src="${icon}" onerror="this.src='https://ui-avatars.com/api/?name=${p.replace('USDT', '')}&background=2b3139&color=fff'"><span>${p.replace('USDT', '')}</span></div>`;
|
|
});
|
|
|
|
list.innerHTML = lH;
|
|
mBar.innerHTML = mH;
|
|
|
|
document.querySelectorAll('.pair-item, .m-coin-item').forEach(item => {
|
|
item.addEventListener('click', () => switchPair(item.dataset.pair));
|
|
});
|
|
}
|
|
|
|
function filterPairs(e) {
|
|
searchQuery = e.target.value.toUpperCase();
|
|
renderPairs();
|
|
}
|
|
|
|
function switchPair(p) {
|
|
currentPair = p;
|
|
const coinName = p.replace('USDT', '');
|
|
const icon = getIcon(p);
|
|
|
|
document.getElementById('curr-pair').innerText = coinName + '/USDT';
|
|
const iconEl = document.getElementById('curr-icon');
|
|
iconEl.src = icon;
|
|
iconEl.onerror = function() { this.src = `https://ui-avatars.com/api/?name=${coinName}&background=2b3139&color=fff`; };
|
|
|
|
document.querySelectorAll('.coin-name').forEach(el => el.innerText = coinName);
|
|
|
|
initChart(p);
|
|
updateBalance();
|
|
renderPairs();
|
|
|
|
const d = marketData[p] || {c: 0, P: 0, h: 0, l: 0, v: 0};
|
|
updatePriceUI(d);
|
|
renderOrderBook([], []);
|
|
}
|
|
|
|
function switchOrderType(side, type) {
|
|
orderTypes[side] = type;
|
|
document.querySelector(`.order-type-tabs[data-side="${side}"] .order-type-tab.active`).classList.remove('active');
|
|
document.querySelector(`.order-type-tabs[data-side="${side}"] .order-type-tab[data-type="${type}"]`).classList.add('active');
|
|
document.getElementById(`${side}-price`).disabled = (type === 'market');
|
|
if (type === 'market' && currentPrice > 0) {
|
|
document.getElementById(`${side}-price`).value = currentPrice.toFixed(2);
|
|
}
|
|
}
|
|
|
|
function setSlider(side, value) {
|
|
const pct = value / 100;
|
|
if (side === 'buy') {
|
|
const price = parseFloat(document.getElementById('buy-price').value) || currentPrice;
|
|
if (price > 0) {
|
|
document.getElementById('buy-amount').value = (balances.usdt * pct / price).toFixed(6);
|
|
}
|
|
} else {
|
|
document.getElementById('sell-amount').value = (balances.coin * pct).toFixed(6);
|
|
}
|
|
}
|
|
|
|
async function placeOrder(side) {
|
|
const price = parseFloat(document.getElementById(`${side}-price`).value);
|
|
const amount = parseFloat(document.getElementById(`${side}-amount`).value);
|
|
if (!amount || amount <= 0) return alert('Amount error');
|
|
if (orderTypes[side] === 'limit' && (!price || price <= 0)) return alert('Price error');
|
|
|
|
<?php if (!$user_id): ?>
|
|
alert('<?php echo __('login_to_trade'); ?>');
|
|
window.location.href = 'login.php';
|
|
return;
|
|
<?php endif; ?>
|
|
|
|
try {
|
|
const resp = await fetch('api/place_order.php', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
symbol: currentPair,
|
|
type: 'spot',
|
|
side: side,
|
|
order_type: orderTypes[side],
|
|
price: price,
|
|
amount: amount,
|
|
total: price * amount
|
|
})
|
|
});
|
|
const res = await resp.json();
|
|
if (res.success) {
|
|
alert('<?php echo __('order_placed'); ?>');
|
|
updateBalance();
|
|
fetchOrders();
|
|
} else {
|
|
alert(res.error || 'An unknown error occurred.');
|
|
}
|
|
} catch (e) {
|
|
alert('<?php echo __('network_error'); ?>');
|
|
}
|
|
}
|
|
|
|
function renderOrderBook(bids, asks) {
|
|
const asksList = document.getElementById('asks-list');
|
|
const bidsList = document.getElementById('bids-list');
|
|
if (!asksList || !bidsList) return;
|
|
let asksHtml = '';
|
|
let bidsHtml = '';
|
|
|
|
asks.slice(0, 20).reverse().forEach(([price, quantity]) => {
|
|
const p = parseFloat(price);
|
|
const q = parseFloat(quantity);
|
|
asksHtml += `<div class="ob-row" onclick="setPrice(${p})"><div class="ob-bar" style="width:${q * 2}%; background:#f6465d"></div><span style="color:#f6465d">${p.toFixed(2)}</span><span>${q.toFixed(4)}</span><span>${(p * q).toFixed(2)}</span></div>`;
|
|
});
|
|
|
|
bids.slice(0, 20).forEach(([price, quantity]) => {
|
|
const p = parseFloat(price);
|
|
const q = parseFloat(quantity);
|
|
bidsHtml += `<div class="ob-row" onclick="setPrice(${p})"><div class="ob-bar" style="width:${q * 2}%; background:#0ecb81"></div><span style="color:#0ecb81">${p.toFixed(2)}</span><span>${q.toFixed(4)}</span><span>${(p * q).toFixed(2)}</span></div>`;
|
|
});
|
|
|
|
asksList.innerHTML = asksHtml;
|
|
bidsList.innerHTML = bidsHtml;
|
|
}
|
|
|
|
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() {
|
|
<?php if (!$user_id) return; ?>
|
|
try {
|
|
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;
|
|
} else {
|
|
console.error('Failed to fetch balance:', res.error);
|
|
}
|
|
} catch(e) {
|
|
console.error('Network error fetching balance:', e);
|
|
}
|
|
}
|
|
|
|
async function fetchOrders() {
|
|
<?php if (!$user_id) return; ?>
|
|
try {
|
|
const resp = await fetch(`api/get_orders.php?type=spot&status=${currentStatus}`);
|
|
const res = await resp.json();
|
|
const listEl = document.getElementById('records-list');
|
|
if (!listEl) return;
|
|
|
|
let html = '';
|
|
if (res.success && res.data.length > 0) {
|
|
html = 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:repeat(4, 1fr); gap:10px; margin-top:10px; color:#848e9c">
|
|
<div><?php echo __('price'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.price).toFixed(2)}</span></div>
|
|
<div><?php echo __('amount'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.amount).toFixed(4)}</span></div>
|
|
<div><?php echo __('total'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.total).toFixed(2)}</span></div>
|
|
<div><?php echo __('status'); ?><br><span style="color:white; font-weight:bold">${o.status}</span></div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
} else {
|
|
html = '<div style="padding:40px; text-align:center; color:#848e9c"><?php echo __('no_records'); ?></div>';
|
|
}
|
|
listEl.innerHTML = html;
|
|
} catch (e) {
|
|
console.error('Error fetching orders:', e);
|
|
}
|
|
}
|
|
|
|
function switchRecords(status) {
|
|
currentStatus = status;
|
|
document.querySelector('.record-tabs .record-tab.active').classList.remove('active');
|
|
document.querySelector(`.record-tabs .record-tab[data-status="${status}"]`).classList.add('active');
|
|
fetchOrders();
|
|
}
|
|
|
|
// ===== Main Execution =====
|
|
|
|
// 1. Add Event Listeners
|
|
document.getElementById('pair-search').addEventListener('input', filterPairs);
|
|
document.getElementById('btn-buy').addEventListener('click', () => placeOrder('buy'));
|
|
document.getElementById('btn-sell').addEventListener('click', () => placeOrder('sell'));
|
|
document.getElementById('buy-slider').addEventListener('input', (e) => setSlider('buy', e.target.value));
|
|
document.getElementById('sell-slider').addEventListener('input', (e) => setSlider('sell', e.target.value));
|
|
document.querySelectorAll('.order-type-tabs[data-side="buy"] .order-type-tab').forEach(tab => {
|
|
tab.addEventListener('click', () => switchOrderType('buy', tab.dataset.type));
|
|
});
|
|
document.querySelectorAll('.order-type-tabs[data-side="sell"] .order-type-tab').forEach(tab => {
|
|
tab.addEventListener('click', () => switchOrderType('sell', tab.dataset.type));
|
|
});
|
|
document.querySelectorAll('.record-tabs .record-tab').forEach(tab => {
|
|
tab.addEventListener('click', () => switchRecords(tab.dataset.status));
|
|
});
|
|
|
|
// 2. Initial Render
|
|
switchPair(currentPair);
|
|
renderOrderBook([], []);
|
|
|
|
// 3. Fetch Initial Data
|
|
updateBalance();
|
|
fetchOrders();
|
|
|
|
// 4. Initialize Chart & WebSocket
|
|
initChart(currentPair);
|
|
connectWebSocket();
|
|
|
|
// 5. Setup Intervals
|
|
setInterval(updateBalance, 5000);
|
|
setInterval(fetchOrders, 10000);
|
|
setInterval(() => {
|
|
if (!ws || ws.readyState === WebSocket.CLOSED) {
|
|
connectWebSocket();
|
|
}
|
|
}, 10000);
|
|
});
|
|
</script>
|
|
<?php include 'footer.php'; ?>
|