Autosave: 20260213-130144
This commit is contained in:
parent
451acfd26a
commit
18dcb64063
BIN
assets/pasted-20260213-122445-3e8fb5b0.png
Normal file
BIN
assets/pasted-20260213-122445-3e8fb5b0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
assets/pasted-20260213-124109-d325d10e.png
Normal file
BIN
assets/pasted-20260213-124109-d325d10e.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
assets/pasted-20260213-124455-68677edd.png
Normal file
BIN
assets/pasted-20260213-124455-68677edd.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
assets/pasted-20260213-124521-96536323.png
Normal file
BIN
assets/pasted-20260213-124521-96536323.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
assets/pasted-20260213-124811-f79e651e.png
Normal file
BIN
assets/pasted-20260213-124811-f79e651e.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
375
futures.php
375
futures.php
@ -45,12 +45,10 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
.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; }
|
||||
@ -64,7 +62,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
.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: 320px; height: 320px; } }
|
||||
@media (max-width: 991px) { .chart-box { min-height: 350px; height: 350px; } }
|
||||
|
||||
.order-box { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
.futures-config { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
@ -92,22 +90,18 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
.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; }
|
||||
|
||||
/* Adjusted for 20 rows of asks and bids */
|
||||
#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; }
|
||||
@ -146,7 +140,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>" oninput="filterPairs()">
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
@ -155,7 +149,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<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;">BTC/USDT Perpetual</span>
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/-- Perpetual</span>
|
||||
</div>
|
||||
|
||||
<div class="stats-item">
|
||||
@ -181,8 +175,8 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
<div class="order-box">
|
||||
<div class="futures-config">
|
||||
<div class="config-btn" id="margin-mode" onclick="toggleMarginMode()"><?php echo __('cross'); ?></div>
|
||||
<div class="config-btn" id="leverage-val" onclick="toggleLeverageModal()">20x</div>
|
||||
<div class="config-btn" id="margin-mode"><?php echo __('cross'); ?></div>
|
||||
<div class="config-btn" id="leverage-val">20x</div>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="label"><?php echo __('price'); ?></span>
|
||||
@ -192,10 +186,10 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<div class="input-group">
|
||||
<span class="label"><?php echo __('amount'); ?></span>
|
||||
<input type="number" id="futures-amount" placeholder="0.00">
|
||||
<span class="unit coin-name">BTC</span>
|
||||
<span class="unit coin-name">--</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input type="range" min="0" max="100" value="0" step="1" oninput="setSlider(this.value)">
|
||||
<input type="range" id="futures-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="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
|
||||
@ -206,134 +200,351 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<span><?php echo __('available'); ?></span><span id="futures-avail" style="color:white; font-weight:600">0.00 USDT</span>
|
||||
</div>
|
||||
<div class="trade-actions">
|
||||
<button onclick="placeOrder('buy')" class="trade-btn btn-long"><?php echo __('buy_long'); ?></button>
|
||||
<button onclick="placeOrder('sell')" class="trade-btn btn-short"><?php echo __('sell_short'); ?></button>
|
||||
<button id="btn-buy-long" class="trade-btn btn-long"><?php echo __('buy_long'); ?></button>
|
||||
<button id="btn-sell-short" class="trade-btn btn-short"><?php echo __('sell_short'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="record-tabs">
|
||||
<div class="record-tab active" onclick="switchRecords('positions')"><?php echo __('positions'); ?></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 class="record-tab active" data-status="positions"><?php echo __('positions'); ?></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'); ?></span><span><?php echo __('amount'); ?></span></div>
|
||||
<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">--</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>
|
||||
|
||||
<div class="modal-overlay" id="leverage-modal">
|
||||
<div class="modal-content">
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:20px;"><span style="font-weight:bold; font-size:16px;"><?php echo __('adjust_leverage'); ?></span><i class="fas fa-times" onclick="toggleLeverageModal()" style="cursor:pointer; color:#848e9c"></i></div>
|
||||
<div style="display:flex; justify-content:space-between; margin-bottom:20px;"><span style="font-weight:bold; font-size:16px;"><?php echo __('adjust_leverage'); ?></span><i class="fas fa-times" id="close-leverage-modal" style="cursor:pointer; color:#848e9c"></i></div>
|
||||
<div style="text-align:center; font-size:32px; font-weight:800; color:var(--primary-color); margin-bottom:10px;" id="slider-val">20x</div>
|
||||
<div class="slider-container">
|
||||
<input type="range" id="leverage-range" min="20" max="100" value="20" oninput="updateSliderVal(this.value)">
|
||||
<input type="range" id="leverage-range" min="20" max="100" value="20">
|
||||
<div style="display:flex; justify-content:space-between; font-size:12px; color:#848e9c; margin-top:10px;"><span>20x</span><span>100x</span></div>
|
||||
</div>
|
||||
<button class="trade-btn btn-long" style="width:100%;" onclick="confirmLeverage()"><?php echo __('confirm'); ?></button>
|
||||
<button class="trade-btn btn-long" style="width:100%;" id="confirm-leverage-btn"><?php echo __('confirm'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script>
|
||||
let currentPair = 'BTCUSDT'; let currentPrice = 0; let currentLeverage = 20; let marginMode = 'cross'; let currentStatus = 'positions'; let searchQuery = '';
|
||||
TradingView.onready(function() {
|
||||
let currentPair = 'BTCUSDT';
|
||||
let currentPrice = 0;
|
||||
let currentLeverage = 20;
|
||||
let marginMode = 'cross';
|
||||
let currentStatus = 'positions';
|
||||
let searchQuery = '';
|
||||
let ws;
|
||||
let balances = {usdt: 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 = {}; let balances = {usdt: 0};
|
||||
const marketData = {};
|
||||
|
||||
function getIcon(s) {
|
||||
const symbol = s.replace('USDT', '').toLowerCase();
|
||||
return `https://assets.coincap.io/assets/icons/${symbol}@2x.png`;
|
||||
}
|
||||
|
||||
function initChart(symbol) { 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 }); }
|
||||
initChart(currentPair);
|
||||
function initChart(symbol) {
|
||||
const container = document.getElementById('tv_chart_container');
|
||||
if (container) container.innerHTML = '';
|
||||
new TradingView.widget({
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"symbol": "BINANCE:" + symbol + 'PERP', // Futures pairs often have PERP
|
||||
"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
|
||||
});
|
||||
}
|
||||
|
||||
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); renderOrderBook(); }
|
||||
marketData[d.s] = d; renderPairs();
|
||||
};
|
||||
function connectWebSocket() {
|
||||
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
||||
return;
|
||||
}
|
||||
ws = new WebSocket('wss://fstream.binance.com/stream?streams=' + pairs.map(p => p.toLowerCase() + '@ticker').join('/') + '/' + pairs.map(p => p.toLowerCase() + '@depth20@100ms').join('/'));
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
const { stream, data } = JSON.parse(e.data);
|
||||
const symbol = stream.split('@')[0].toUpperCase();
|
||||
|
||||
if (stream.includes('@ticker')) {
|
||||
marketData[symbol] = data;
|
||||
if (symbol === currentPair) {
|
||||
currentPrice = parseFloat(data.c);
|
||||
updatePriceUI(data);
|
||||
}
|
||||
renderPairs();
|
||||
} else if (symbol === currentPair) {
|
||||
renderOrderBook(data.b, data.a);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
function updatePriceUI(d) {
|
||||
const color = d.P>=0 ? '#0ecb81' : '#f6465d';
|
||||
document.getElementById('curr-price').innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
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?'+':'')+d.P+'%';
|
||||
document.getElementById('curr-change').innerText = (d.p >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%';
|
||||
document.getElementById('curr-change').style.color = color;
|
||||
const hHigh = document.getElementById('h-high'); if(hHigh) hHigh.innerText = parseFloat(d.h).toLocaleString();
|
||||
const hLow = document.getElementById('h-low'); if(hLow) hLow.innerText = parseFloat(d.l).toLocaleString();
|
||||
const hVol = document.getElementById('h-vol'); if(hVol) hVol.innerText = (parseFloat(d.v)/1000).toFixed(1) + 'K';
|
||||
const mid = document.getElementById('mid-price'); if(mid) { mid.innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2}); mid.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');
|
||||
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';
|
||||
const disp = p.replace('USDT','') + '/USDT';
|
||||
if (searchQuery === '' || disp.includes(searchQuery)) {
|
||||
lH += `<div class="pair-item ${active?'active':''}" onclick="switchPair('${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">${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}" onerror="this.src='https://ui-avatars.com/api/?name=${p.replace('USDT','')}&background=2b3139&color=fff'"><span>${p.replace('USDT','')}</span></div>`;
|
||||
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));
|
||||
});
|
||||
if(list) list.innerHTML = lH; if(mBar) mBar.innerHTML = mH;
|
||||
}
|
||||
|
||||
function filterPairs() { searchQuery = document.getElementById('pair-search').value.toUpperCase(); renderPairs(); }
|
||||
function filterPairs(e) {
|
||||
searchQuery = e.target.value.toUpperCase();
|
||||
renderPairs();
|
||||
}
|
||||
|
||||
function switchPair(p) {
|
||||
currentPair = p; const icon = getIcon(p);
|
||||
document.getElementById('curr-pair').innerText = p.replace('USDT','')+'/USDT';
|
||||
currentPair = p;
|
||||
const coinName = p.replace('USDT', '');
|
||||
const icon = getIcon(p);
|
||||
document.getElementById('curr-pair').innerText = coinName + '/USDT Perpetual';
|
||||
const iconEl = document.getElementById('curr-icon');
|
||||
iconEl.src = icon;
|
||||
iconEl.onerror = function() { this.src = `https://ui-avatars.com/api/?name=${p.replace('USDT','')}&background=2b3139&color=fff`; };
|
||||
document.querySelectorAll('.coin-name').forEach(el => el.innerText = p.replace('USDT',''));
|
||||
initChart(p); updateBalance();
|
||||
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, P: 0, h: 0, l: 0, v: 0};
|
||||
updatePriceUI(d);
|
||||
renderOrderBook([], []);
|
||||
}
|
||||
|
||||
function toggleMarginMode() { marginMode = marginMode === 'cross' ? 'isolated' : 'cross'; document.getElementById('margin-mode').innerText = marginMode === 'cross' ? '<?php echo __('cross'); ?>' : '<?php echo __('isolated'); ?>'; }
|
||||
function toggleLeverageModal() { const modal = document.getElementById('leverage-modal'); modal.style.display = modal.style.display === 'flex' ? 'none' : 'flex'; }
|
||||
function updateSliderVal(v) { document.getElementById('slider-val').innerText = v + 'x'; }
|
||||
function confirmLeverage() { currentLeverage = document.getElementById('leverage-range').value; document.getElementById('leverage-val').innerText = currentLeverage + 'x'; toggleLeverageModal(); }
|
||||
function setSlider(val) { const pct = val / 100; const p = parseFloat(document.getElementById('futures-price').value) || currentPrice; if(p>0) document.getElementById('futures-amount').value = (balances.usdt * pct * currentLeverage / p).toFixed(4); }
|
||||
function toggleMarginMode() {
|
||||
marginMode = marginMode === 'cross' ? 'isolated' : 'cross';
|
||||
document.getElementById('margin-mode').innerText = marginMode === 'cross' ? '<?php echo __("cross"); ?>' : '<?php echo __("isolated"); ?>';
|
||||
}
|
||||
|
||||
function toggleLeverageModal() {
|
||||
const modal = document.getElementById('leverage-modal');
|
||||
modal.style.display = modal.style.display === 'flex' ? 'none' : 'flex';
|
||||
}
|
||||
|
||||
function updateSliderVal(v) {
|
||||
document.getElementById('slider-val').innerText = v + 'x';
|
||||
}
|
||||
|
||||
function confirmLeverage() {
|
||||
currentLeverage = document.getElementById('leverage-range').value;
|
||||
document.getElementById('leverage-val').innerText = currentLeverage + 'x';
|
||||
toggleLeverageModal();
|
||||
}
|
||||
|
||||
function setSlider(value) {
|
||||
const pct = value / 100;
|
||||
const price = parseFloat(document.getElementById('futures-price').value) || currentPrice;
|
||||
if (price > 0) {
|
||||
document.getElementById('futures-amount').value = (balances.usdt * pct * currentLeverage / price).toFixed(4);
|
||||
}
|
||||
}
|
||||
|
||||
async function placeOrder(side) {
|
||||
const p = parseFloat(document.getElementById('futures-price').value) || currentPrice; const a = parseFloat(document.getElementById('futures-amount').value);
|
||||
if(!a || a<=0) return alert('<?php echo __('amount'); ?> Error');
|
||||
const resp = await fetch('api/place_order.php', { method:'POST', body:JSON.stringify({ symbol:currentPair, type:'futures', side:side, order_type:'market', price:p, amount:a, total:p*a, leverage:currentLeverage, tp_price:document.getElementById('futures-tp').value, sl_price:document.getElementById('futures-sl').value }) });
|
||||
const res = await resp.json();
|
||||
if(res.success) { alert('<?php echo __('order_placed'); ?>'); updateBalance(); fetchOrders(); } else alert(res.error);
|
||||
const price = parseFloat(document.getElementById('futures-price').value) || currentPrice;
|
||||
const amount = parseFloat(document.getElementById('futures-amount').value);
|
||||
if (!amount || amount <= 0) return alert('Amount 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: 'futures', side: side, order_type: 'market',
|
||||
price: price, amount: amount, total: price * amount, leverage: currentLeverage,
|
||||
tp_price: document.getElementById('futures-tp').value, sl_price: document.getElementById('futures-sl').value
|
||||
})
|
||||
});
|
||||
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() {
|
||||
const asks = document.getElementById('asks-list'); const bids = document.getElementById('bids-list');
|
||||
let aH = ''; let bH = '';
|
||||
const rowCount = 20;
|
||||
for(let i=rowCount; i>0; i--) { const p = currentPrice * (1+i*0.0001); const amt = Math.random()*2; aH += `<div class="ob-row"><div class="ob-bar" style="width:${amt*30}%; background:#f6465d"></div><span style="color:#f6465d">${p.toFixed(2)}</span><span>${amt.toFixed(4)}</span></div>`; }
|
||||
for(let i=1; i<=rowCount; i++) { const p = currentPrice * (1-i*0.0001); const amt = Math.random()*2; bH += `<div class="ob-row"><div class="ob-bar" style="width:${amt*30}%; 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 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"><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"><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;
|
||||
}
|
||||
|
||||
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'); balances.usdt=usdt?parseFloat(usdt.amount):0; document.getElementById('futures-avail').innerText=balances.usdt.toFixed(2)+' USDT'; }
|
||||
<?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');
|
||||
balances.usdt = usdt ? parseFloat(usdt.amount) : 0;
|
||||
document.getElementById('futures-avail').innerText = balances.usdt.toFixed(2) + ' USDT';
|
||||
} else {
|
||||
console.error('Failed to fetch balance:', res.error);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Network error fetching balance:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchOrders() {
|
||||
const resp = await fetch(`api/get_orders.php?type=futures&status=${currentStatus}`); const res = await resp.json();
|
||||
const l = document.getElementById('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="color:${o.side==='buy'?'#0ecb81':'#f6465d'}; font-weight:bold; font-size:14px">${o.side==='buy'?'Long':'Short'} ${o.symbol} ${o.leverage}x</span><span style="color:#848e9c">${o.created_at}</span></div><div style="display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px; color:#848e9c; margin-top:10px"><div><?php echo __('size'); ?><br><span style="color:white; font-weight:bold">${o.amount}</span></div><div><?php echo __('price'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.price).toFixed(2)}</span></div><div>TP/SL<br><span style="color:white; font-weight:bold">${o.tp_price||'--'}/${o.sl_price||'--'}</span></div></div></div>`).join('') : '<div style="padding:40px; text-align:center; color:#848e9c"><?php echo __('no_records'); ?></div>';
|
||||
if(l) l.innerHTML = h;
|
||||
<?php if (!$user_id) return; ?>
|
||||
try {
|
||||
const resp = await fetch(`api/get_orders.php?type=futures&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="color:${o.side === 'buy' ? '#0ecb81' : '#f6465d'}; font-weight:bold; font-size:14px">${o.side === 'buy' ? 'Long' : 'Short'} ${o.symbol} ${o.leverage}x</span>
|
||||
<span style="color:#848e9c">${o.created_at}</span>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns:1fr 1fr 1fr 1fr; gap:10px; color:#848e9c; margin-top:10px">
|
||||
<div><?php echo __('size'); ?><br><span style="color:white; font-weight:bold">${o.amount}</span></div>
|
||||
<div><?php echo __('price'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.price).toFixed(2)}</span></div>
|
||||
<div><?php echo __('margin'); ?><br><span style="color:white; font-weight:bold">${(o.total / o.leverage).toFixed(2)}</span></div>
|
||||
<div>TP/SL<br><span style="color:white; font-weight:bold">${o.tp_price || '--'}/${o.sl_price || '--'}</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(s) { currentStatus = s; document.querySelectorAll('.record-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); fetchOrders(); }
|
||||
switchPair(currentPair); updateBalance(); fetchOrders();
|
||||
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('margin-mode').addEventListener('click', toggleMarginMode);
|
||||
document.getElementById('leverage-val').addEventListener('click', toggleLeverageModal);
|
||||
document.getElementById('close-leverage-modal').addEventListener('click', toggleLeverageModal);
|
||||
document.getElementById('leverage-range').addEventListener('input', (e) => updateSliderVal(e.target.value));
|
||||
document.getElementById('confirm-leverage-btn').addEventListener('click', confirmLeverage);
|
||||
document.getElementById('futures-slider').addEventListener('input', (e) => setSlider(e.target.value));
|
||||
document.getElementById('btn-buy-long').addEventListener('click', () => placeOrder('buy'));
|
||||
document.getElementById('btn-sell-short').addEventListener('click', () => placeOrder('sell'));
|
||||
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'; ?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
@ -202,6 +202,17 @@ $translations = [
|
||||
'days' => 'Days',
|
||||
'coming_soon' => 'Coming Soon',
|
||||
'next_project' => 'Next Project',
|
||||
// New keys for error messages
|
||||
'error_placing_order' => 'Error placing order',
|
||||
'network_error' => 'Network Error',
|
||||
'could_not_connect_server' => 'Could not connect to server',
|
||||
'error_fetching_orders' => 'Error fetching orders',
|
||||
'error_fetching_balance' => 'Error fetching balance',
|
||||
'error_no_live_price' => 'No live price data available yet. Please wait.',
|
||||
'websocket_connecting' => 'Connecting to market data...',
|
||||
'websocket_connected' => 'Connected to market data.',
|
||||
'websocket_error' => 'Market data connection error!',
|
||||
'websocket_disconnected' => 'Market data disconnected. Reconnecting...',
|
||||
],
|
||||
'zh' => [
|
||||
'site_name' => 'NovaEx',
|
||||
@ -371,9 +382,9 @@ $translations = [
|
||||
'home_slide2_title' => '100倍杠杆合约交易',
|
||||
'home_slide2_desc' => '通过我们的专业永续合约最大限度地提高您的资金效率。',
|
||||
'home_slide3_title' => '安全数字资产质押',
|
||||
'home_slide3_desc' => '通过我们的高收益质押池赚取被动收入。',
|
||||
'home_slide3_desc' => 'Earn passive income on your idle assets with our high-yield staking pools.',
|
||||
'home_download_title' => '随时随地,随心交易',
|
||||
'home_download_desc' => '通过 NovaEx 移动应用保持与市场的连接。在掌中体验专业的交易功能。',
|
||||
'home_download_desc' => 'Stay connected to the markets with the NovaEx mobile app. Experience professional trading features in the palm of your hand.',
|
||||
'fast_secure' => '快速且安全',
|
||||
'fast_secure_desc' => '为您的所有数据提供军用级加密。',
|
||||
'real_time' => '实时行情',
|
||||
@ -401,6 +412,17 @@ $translations = [
|
||||
'days' => '天',
|
||||
'coming_soon' => '即将到来',
|
||||
'next_project' => '下一个项目',
|
||||
// New keys for error messages
|
||||
'error_placing_order' => '下单失败',
|
||||
'network_error' => '网络错误',
|
||||
'could_not_connect_server' => '无法连接到服务器',
|
||||
'error_fetching_orders' => '获取订单失败',
|
||||
'error_fetching_balance' => '获取余额失败',
|
||||
'error_no_live_price' => '暂无实时价格数据,请稍候。',
|
||||
'websocket_connecting' => '正在连接市场数据...',
|
||||
'websocket_connected' => '市场数据已连接。',
|
||||
'websocket_error' => '市场数据连接错误!',
|
||||
'websocket_disconnected' => '市场数据已断开。正在重新连接...',
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
404
options.php
404
options.php
@ -1,8 +1,15 @@
|
||||
<?php
|
||||
session_start(); // Ensure session is started
|
||||
include 'header.php';
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/i18n.php'; // Include i18n for localization
|
||||
|
||||
$user_id = $_SESSION['user_id'] ?? null;
|
||||
if (!$user_id) {
|
||||
// Redirect to login page if not logged in
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
?>
|
||||
|
||||
<style>
|
||||
@ -73,7 +80,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.records-content { min-height: 200px; padding-bottom: 20px; height: auto; }
|
||||
.chart-box { min-height: 320px; height: 320px; }
|
||||
.chart-box { min-height: 350px; height: 350px; }
|
||||
}
|
||||
|
||||
.order-panel { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
||||
@ -104,7 +111,6 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
.ob-bg-sell { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(246, 70, 93, 0.1); z-index: 0; }
|
||||
.ob-bg-buy { position: absolute; right: 0; top: 0; bottom: 0; background: rgba(14, 203, 129, 0.1); z-index: 0; }
|
||||
|
||||
/* Adjusted for 20 rows of asks and bids */
|
||||
#ob-asks { height: 400px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden; }
|
||||
#ob-bids { height: 400px; display: flex; flex-direction: column; justify-content: flex-start; overflow: hidden; }
|
||||
|
||||
@ -158,6 +164,19 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
.profit-estimate { font-size: 14px; font-weight: 700; margin-top: 4px; }
|
||||
|
||||
.modal-footer-note { font-size: 11px; color: #5e6673; text-align: center; line-height: 1.4; }
|
||||
|
||||
#websocket-status {
|
||||
padding: 8px 15px;
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
background-color: #333;
|
||||
text-align: center;
|
||||
display: none; /* Hidden by default */
|
||||
}
|
||||
#websocket-status.connected { background-color: #0ecb81; }
|
||||
#websocket-status.disconnected { background-color: #f6465d; }
|
||||
#websocket-status.connecting { background-color: #ffc107; }
|
||||
|
||||
</style>
|
||||
|
||||
<div class="m-trade-nav">
|
||||
@ -177,17 +196,18 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>" oninput="filterPairs()">
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
|
||||
<div class="center-col">
|
||||
<div id="websocket-status"></div>
|
||||
<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;">
|
||||
<div>
|
||||
<div style="font-weight: 800; font-size: 16px; line-height: 1.2;"><span id="curr-pair">BTC/USDT</span></div>
|
||||
<div style="font-weight: 800; font-size: 16px; line-height: 1.2;"><span id="curr-pair">--/--</span></div>
|
||||
<div style="font-size: 10px; color: var(--primary-color); font-weight: 600;"><?php echo __('nav_options'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
@ -239,16 +259,16 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
</div>
|
||||
|
||||
<div style="display:flex; gap:8px;">
|
||||
<button class="trade-btn btn-up" onclick="placeOptionOrder('up')"><?php echo __('buy_up'); ?></button>
|
||||
<button class="trade-btn btn-down" onclick="placeOptionOrder('down')"><?php echo __('buy_down'); ?></button>
|
||||
<button class="trade-btn btn-up" id="btn-buy-up"><?php echo __('buy_up'); ?></button>
|
||||
<button class="trade-btn btn-down" id="btn-buy-down"><?php echo __('buy_down'); ?></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="records-section">
|
||||
<div class="record-tabs">
|
||||
<div class="record-tab active" onclick="switchRecords('pending')"><?php echo __('in_progress'); ?></div>
|
||||
<div class="record-tab" onclick="switchRecords('completed')"><?php echo __('settled'); ?></div>
|
||||
<div class="record-tab active" data-status="pending"><?php echo __('in_progress'); ?></div>
|
||||
<div class="record-tab" data-status="completed"><?php echo __('settled'); ?></div>
|
||||
</div>
|
||||
<div class="records-content" id="records-list"></div>
|
||||
</div>
|
||||
@ -304,7 +324,18 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
||||
<script>
|
||||
let currentPair = 'BTCUSDT'; let currentPrice = 0; let currentDuration = 60; let currentRate = 0.08; let currentMin = 100; let currentStatus = 'pending'; let searchQuery = '';
|
||||
TradingView.onready(function() {
|
||||
let currentPair = 'BTCUSDT';
|
||||
let currentPrice = 0;
|
||||
let currentDuration = 60;
|
||||
let currentRate = 0.08;
|
||||
let currentMin = 100;
|
||||
let currentStatus = 'pending';
|
||||
let searchQuery = '';
|
||||
let ws;
|
||||
let modalInterval = null;
|
||||
let modalActiveRate = 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 = {};
|
||||
|
||||
@ -313,100 +344,185 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
return `https://assets.coincap.io/assets/icons/${symbol}@2x.png`;
|
||||
}
|
||||
|
||||
function initChart(symbol) { new TradingView.widget({ "width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol, "interval": "1", "theme": "dark", "style": "1", "locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>", "container_id": "tv_chart_container", "backgroundColor": "#0b0e11", "hide_side_toolbar": true }); }
|
||||
initChart(currentPair);
|
||||
function initChart(symbol) {
|
||||
const container = document.getElementById('tv_chart_container');
|
||||
if (container) container.innerHTML = ''; // Clear previous chart
|
||||
new TradingView.widget({
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"symbol": "BINANCE:" + symbol,
|
||||
"interval": "1",
|
||||
"theme": "dark",
|
||||
"style": "1",
|
||||
"locale": "<?php echo $lang == 'zh' ? 'zh_CN' : 'en'; ?>",
|
||||
"container_id": "tv_chart_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); updateOrderBook(); updateModalLive(); }
|
||||
marketData[d.s] = d; renderPairs();
|
||||
};
|
||||
function connectWebSocket() {
|
||||
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const websocketStatusEl = document.getElementById('websocket-status');
|
||||
websocketStatusEl.style.display = 'block';
|
||||
websocketStatusEl.className = 'connecting';
|
||||
websocketStatusEl.innerText = '<?php echo __('websocket_connecting'); ?>';
|
||||
|
||||
ws = new WebSocket('wss://stream.binance.com:9443/ws/' + pairs.map(p => p.toLowerCase() + '@ticker').join('/'));
|
||||
|
||||
ws.onopen = () => {
|
||||
console.log('WebSocket Connected');
|
||||
websocketStatusEl.className = 'connected';
|
||||
websocketStatusEl.innerText = '<?php echo __('websocket_connected'); ?>';
|
||||
setTimeout(() => websocketStatusEl.style.display = 'none', 3000);
|
||||
};
|
||||
|
||||
ws.onmessage = (e) => {
|
||||
const d = JSON.parse(e.data);
|
||||
if (!d || !d.s) return;
|
||||
marketData[d.s] = d;
|
||||
if (d.s === currentPair) {
|
||||
currentPrice = parseFloat(d.c);
|
||||
updatePriceUI(d);
|
||||
updateOrderBook();
|
||||
updateModalLive();
|
||||
}
|
||||
renderPairs();
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket Error:', error);
|
||||
websocketStatusEl.className = 'disconnected';
|
||||
websocketStatusEl.innerText = '<?php echo __('websocket_error'); ?>';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket Connection Closed');
|
||||
websocketStatusEl.className = 'disconnected';
|
||||
websocketStatusEl.innerText = '<?php echo __('websocket_disconnected'); ?>';
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
function updatePriceUI(d) {
|
||||
const pStr = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
if (!d) return;
|
||||
let pStr = parseFloat(d.c).toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
|
||||
document.getElementById('curr-price').innerText = pStr;
|
||||
document.getElementById('ob-mid-price').innerText = pStr;
|
||||
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
|
||||
document.getElementById('curr-price').style.color = color;
|
||||
document.getElementById('ob-mid-price').style.color = color;
|
||||
document.getElementById('curr-change').innerText = (d.P >= 0 ? '+' : '') + d.P + '%';
|
||||
document.getElementById('curr-change').innerText = (d.P >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%';
|
||||
document.getElementById('curr-change').style.color = color;
|
||||
const hHigh = document.getElementById('h-high'); if (hHigh) hHigh.innerText = parseFloat(d.h).toLocaleString();
|
||||
const hLow = document.getElementById('h-low'); if (hLow) hLow.innerText = parseFloat(d.l).toLocaleString();
|
||||
const hVol = document.getElementById('h-vol'); if (hVol) hVol.innerText = (parseFloat(d.v)/1000).toFixed(1) + 'K';
|
||||
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';
|
||||
}
|
||||
|
||||
function updateOrderBook() {
|
||||
const asks = document.getElementById('ob-asks'); const bids = document.getElementById('ob-bids');
|
||||
let aHtml = ''; let bHtml = ''; const rowCount = 20;
|
||||
const asks = document.getElementById('ob-asks');
|
||||
const bids = document.getElementById('ob-bids');
|
||||
if (!asks || !bids) return;
|
||||
let aHtml = '';
|
||||
let bHtml = '';
|
||||
const rowCount = 20;
|
||||
const effectiveCurrentPrice = currentPrice !== 0 ? currentPrice : 30000;
|
||||
|
||||
for (let i = 0; i < rowCount; i++) {
|
||||
const aPrice = currentPrice * (1 + (rowCount - i) * 0.0001);
|
||||
const aPrice = effectiveCurrentPrice * (1 + (rowCount - i) * 0.0001);
|
||||
const aAmt = (Math.random() * 2).toFixed(4);
|
||||
aHtml += `<div class="ob-row"><div class="ob-bg-sell" style="width:${Math.random()*80}%"></div><span class="ob-price" style="color:#f6465d">${aPrice.toFixed(2)}</span><span class="ob-amount">${aAmt}</span><span class="ob-total">${(aPrice*aAmt).toFixed(2)}</span></div>`;
|
||||
const bPrice = currentPrice * (1 - (i + 1) * 0.0001);
|
||||
const bPrice = effectiveCurrentPrice * (1 - (i + 1) * 0.0001);
|
||||
const bAmt = (Math.random() * 2).toFixed(4);
|
||||
bHtml += `<div class="ob-row"><div class="ob-bg-buy" style="width:${Math.random()*80}%"></div><span class="ob-price" style="color:#0ecb81">${bPrice.toFixed(2)}</span><span class="ob-amount">${bAmt}</span><span class="ob-total">${(bPrice*bAmt).toFixed(2)}</span></div>`;
|
||||
}
|
||||
asks.innerHTML = aHtml; bids.innerHTML = bHtml;
|
||||
asks.innerHTML = aHtml;
|
||||
bids.innerHTML = bHtml;
|
||||
}
|
||||
|
||||
function renderPairs() {
|
||||
const list = document.getElementById('pairs-list'); const mBar = document.getElementById('m-coin-list');
|
||||
let lHtml = ''; let mHtml = '';
|
||||
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';
|
||||
const list = document.getElementById('pairs-list');
|
||||
const mBar = document.getElementById('m-coin-list');
|
||||
if (!list || !mBar) return;
|
||||
let lHtml = '';
|
||||
let mHtml = '';
|
||||
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';
|
||||
if (searchQuery === '' || disp.includes(searchQuery)) {
|
||||
lHtml += `<div class="pair-item ${active ? 'active' : ''}" onclick="switchPair('${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">${parseFloat(d.c).toLocaleString()}</div><div style="font-size:11px; color:${color}">${(d.P >= 0 ? '+' : '') + d.P}%</div></div></div>`;
|
||||
}
|
||||
mHtml += `<div class="m-coin-item ${active ? 'active' : ''}" onclick="switchPair('${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>`;
|
||||
|
||||
lHtml += `<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>`;
|
||||
mHtml += `<div class="m-coin-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'"><span>${p.replace('USDT', '')}</span></div>`;
|
||||
});
|
||||
list.innerHTML = lHtml;
|
||||
mBar.innerHTML = mHtml;
|
||||
|
||||
document.querySelectorAll('.pair-item, .m-coin-item').forEach(item => {
|
||||
item.addEventListener('click', () => switchPair(item.dataset.pair));
|
||||
});
|
||||
if(list) list.innerHTML = lHtml; if(mBar) mBar.innerHTML = mHtml;
|
||||
}
|
||||
|
||||
function filterPairs() { searchQuery = document.getElementById('pair-search').value.toUpperCase(); renderPairs(); }
|
||||
function filterPairs(e) {
|
||||
searchQuery = e.target.value.toUpperCase();
|
||||
renderPairs();
|
||||
}
|
||||
|
||||
function switchPair(p) {
|
||||
currentPair = p; const icon = getIcon(p);
|
||||
currentPair = p;
|
||||
const icon = getIcon(p);
|
||||
document.getElementById('curr-pair').innerText = p.replace('USDT', '') + '/USDT';
|
||||
const iconEl = document.getElementById('curr-icon');
|
||||
iconEl.src = icon;
|
||||
iconEl.onerror = function() { this.src = `https://ui-avatars.com/api/?name=${p.replace('USDT','')}&background=2b3139&color=fff`; };
|
||||
initChart(p); renderPairs();
|
||||
iconEl.onerror = function() { this.src = `https://ui-avatars.com/api/?name=${p.replace('USDT','')}
|
||||
&background=2b3139&color=fff`; };
|
||||
initChart(p);
|
||||
renderPairs();
|
||||
const d = marketData[p] || {c:0, P:0, h:0, l:0, v:0};
|
||||
updatePriceUI(d);
|
||||
}
|
||||
|
||||
document.querySelectorAll('.time-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
currentDuration = parseInt(this.dataset.duration); currentRate = parseInt(this.dataset.rate) / 100; currentMin = parseInt(this.dataset.min);
|
||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
document.getElementById('option-amount').placeholder = `<?php echo __('min_order'); ?> ${currentMin}`;
|
||||
updatePotentialProfit();
|
||||
});
|
||||
});
|
||||
|
||||
function updatePotentialProfit() {
|
||||
const amt = parseFloat(document.getElementById('option-amount').value) || 0;
|
||||
const profit = (amt * currentRate).toFixed(2);
|
||||
document.getElementById('potential-profit').innerText = '+' + profit;
|
||||
}
|
||||
|
||||
document.getElementById('option-amount').addEventListener('input', updatePotentialProfit);
|
||||
|
||||
async function placeOptionOrder(dir) {
|
||||
const amt = parseFloat(document.getElementById('option-amount').value);
|
||||
if (!amt || amt < currentMin) return alert('<?php echo __('min_amount'); ?>: ' + currentMin + ' USDT');
|
||||
const resp = await fetch('api/place_option_order.php', { method: 'POST', body: JSON.stringify({ symbol: currentPair, amount: amt, direction: dir, duration: currentDuration, opening_price: currentPrice }) });
|
||||
const res = await resp.json();
|
||||
if (res.success) {
|
||||
showCountdownModal(dir, amt, currentPrice, currentDuration, currentRate);
|
||||
document.getElementById('option-amount').value = ''; updateBalance(); fetchOrders();
|
||||
} else alert(res.error);
|
||||
}
|
||||
if (currentPrice === 0) {
|
||||
alert('<?php echo __('error_no_live_price'); ?>');
|
||||
return;
|
||||
}
|
||||
|
||||
let modalInterval = null;
|
||||
let modalActiveRate = 0;
|
||||
try {
|
||||
const resp = await fetch('api/place_option_order.php', { method: 'POST', body: JSON.stringify({ symbol: currentPair, amount: amt, direction: dir, duration: currentDuration, opening_price: currentPrice }) });
|
||||
const res = await resp.json();
|
||||
if (res.success) {
|
||||
showCountdownModal(dir, amt, currentPrice, currentDuration, currentRate);
|
||||
document.getElementById('option-amount').value = '';
|
||||
updateBalance();
|
||||
fetchOrders();
|
||||
} else {
|
||||
alert('<?php echo __('error_placing_order'); ?>: ' + (res.error || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('<?php echo __('network_error'); ?>: <?php echo __('could_not_connect_server'); ?>');
|
||||
}
|
||||
}
|
||||
|
||||
function showCountdownModal(dir, amount, openPrice, duration, rate) {
|
||||
const modal = document.getElementById('order-modal');
|
||||
const timer = document.getElementById('modal-timer');
|
||||
@ -446,63 +562,141 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
function updateModalLive() {
|
||||
const modal = document.getElementById('order-modal');
|
||||
if(modal.style.display !== 'flex') return;
|
||||
|
||||
const priceEl = document.getElementById('modal-price-live');
|
||||
const profitEl = document.getElementById('modal-profit-estimate');
|
||||
const openPrice = parseFloat(document.getElementById('modal-open-price').innerText.replace(/,/g, ''));
|
||||
const dir = (document.getElementById('modal-dir').innerText.includes('UP') || document.getElementById('modal-dir').innerText.includes('涨')) ? 'up' : 'down';
|
||||
const amount = parseFloat(document.getElementById('modal-amount').innerText);
|
||||
|
||||
if(modal.style.display === 'flex') {
|
||||
const openPrice = parseFloat(document.getElementById('modal-open-price').innerText.replace(/,/g, ''));
|
||||
const dir = document.getElementById('modal-dir').innerText.includes('UP') || document.getElementById('modal-dir').innerText.includes('涨') ? 'up' : 'down';
|
||||
const amount = parseFloat(document.getElementById('modal-amount').innerText);
|
||||
|
||||
const isProfit = dir === 'up' ? currentPrice >= openPrice : currentPrice <= openPrice;
|
||||
const color = isProfit ? '#0ecb81' : '#f6465d';
|
||||
|
||||
priceEl.innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
priceEl.style.color = color;
|
||||
|
||||
const profitVal = isProfit ? (amount * modalActiveRate).toFixed(2) : '-' + amount.toFixed(2);
|
||||
profitEl.innerText = '<?php echo __('estimated_profit'); ?>: ' + (isProfit ? '+' : '') + profitVal + ' USDT';
|
||||
profitEl.style.color = color;
|
||||
}
|
||||
const isProfit = dir === 'up' ? currentPrice >= openPrice : currentPrice <= openPrice;
|
||||
const color = isProfit ? '#0ecb81' : '#f6465d';
|
||||
|
||||
priceEl.innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
priceEl.style.color = color;
|
||||
|
||||
const profitVal = isProfit ? (amount * modalActiveRate).toFixed(2) : '-' + amount.toFixed(2);
|
||||
profitEl.innerText = '<?php echo __('estimated_profit'); ?>: ' + (isProfit ? '+' : '') + profitVal + ' USDT';
|
||||
profitEl.style.color = color;
|
||||
}
|
||||
|
||||
function switchRecords(s) { currentStatus = s; document.querySelectorAll('.record-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); fetchOrders(); }
|
||||
function switchRecords(s) {
|
||||
currentStatus = s;
|
||||
document.querySelectorAll('.record-tab').forEach(t => t.classList.remove('active'));
|
||||
document.querySelector(`.record-tab[data-status="${s}"]`).classList.add('active');
|
||||
fetchOrders();
|
||||
}
|
||||
|
||||
async function fetchOrders() {
|
||||
const resp = await fetch('api/get_option_orders.php?status=' + currentStatus); const res = await resp.json();
|
||||
const list = document.getElementById('records-list');
|
||||
if (res.success && res.data.length > 0) {
|
||||
list.innerHTML = res.data.map(o => `
|
||||
<div style="padding:15px; border-bottom:1px solid #2b3139; font-size:13px">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px">
|
||||
<div><span style="color:${o.direction==='up'?'#0ecb81':'#f6465d'}; font-weight:800; font-size:14px">${o.direction==='up'?'▲':'▼'} ${o.symbol}</span> <span style="font-size:11px; color:#848e9c; margin-left:8px">${o.created_at}</span></div>
|
||||
<div style="font-weight:800; color:${parseFloat(o.profit)>=0?'#0ecb81':'#f6465d'}; font-size:15px">${currentStatus==='pending'?'--':(parseFloat(o.profit)>=0?'+':'')+parseFloat(o.profit).toFixed(2)}</div>
|
||||
try {
|
||||
const resp = await fetch('api/get_option_orders.php?status=' + currentStatus);
|
||||
const res = await resp.json();
|
||||
const list = document.getElementById('records-list');
|
||||
if (!list) return;
|
||||
if (res.success && res.data.length > 0) {
|
||||
list.innerHTML = res.data.map(o => `
|
||||
<div style="padding:15px; border-bottom:1px solid #2b3139; font-size:13px">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px">
|
||||
<div><span style="color:${o.direction==='up'?'#0ecb81':'#f6465d'}; font-weight:800; font-size:14px">${o.direction==='up'?'▲':'▼'} ${o.symbol}</span> <span style="font-size:11px; color:#848e9c; margin-left:8px">${o.created_at}</span></div>
|
||||
<div style="font-weight:800; color:${parseFloat(o.profit)>=0?'#0ecb81':'#f6465d'}; font-size:15px">${currentStatus==='pending'?'--':(parseFloat(o.profit)>=0?'+':'')+parseFloat(o.profit).toFixed(2)}</div>
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:10px; color:#848e9c; font-size:11px">
|
||||
<div><?php echo __('amount'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.amount).toFixed(2)}</span></div>
|
||||
<div><?php echo __('opening_price'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.opening_price).toLocaleString()}</span></div>
|
||||
<div>${currentStatus==='pending'?'<?php echo __('time'); ?>':'<?php echo __('closing_price'); ?>'}<br><span style="color:white; font-weight:bold">${currentStatus==='pending'?o.duration+'s':parseFloat(o.closing_price).toLocaleString()}</span></div>
|
||||
</div>
|
||||
${currentStatus==='pending'?`<div class="progress-bar-container"><div class="progress-bar-fill" id="pb-${o.id}" data-start="${new Date(o.created_at).getTime()}" data-duration="${o.duration}"></div></div>`:''}
|
||||
</div>
|
||||
<div style="display:grid; grid-template-columns:repeat(3, 1fr); gap:10px; color:#848e9c; font-size:11px">
|
||||
<div><?php echo __('amount'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.amount).toFixed(2)}</span></div>
|
||||
<div><?php echo __('opening_price'); ?><br><span style="color:white; font-weight:bold">${parseFloat(o.opening_price).toLocaleString()}</span></div>
|
||||
<div>${currentStatus==='pending'?'<?php echo __('time'); ?>':'<?php echo __('closing_price'); ?>'}<br><span style="color:white; font-weight:bold">${currentStatus==='pending'?o.duration+'s':parseFloat(o.closing_price).toLocaleString()}</span></div>
|
||||
</div>
|
||||
${currentStatus==='pending'?`<div class="progress-bar-container"><div class="progress-bar-fill" id="pb-${o.id}" data-start="${new Date(o.created_at).getTime()}" data-duration="${o.duration}"></div></div>`:''}
|
||||
</div>
|
||||
`).join('');
|
||||
if (currentStatus === 'pending') updateProgressBars();
|
||||
} else list.innerHTML = `<div style="padding:60px 20px; text-align:center; color:#848e9c"><i class="fas fa-file-invoice" style="font-size:40px; margin-bottom:15px; display:block; opacity:0.3"></i><?php echo __('no_records'); ?></div>`;
|
||||
`).join('');
|
||||
if (currentStatus === 'pending') updateProgressBars();
|
||||
} else if (res.success && res.data.length === 0) {
|
||||
list.innerHTML = `<div style="padding:60px 20px; text-align:center; color:#848e9c"><i class="fas fa-file-invoice" style="font-size:40px; margin-bottom:15px; display:block; opacity:0.3"></i><?php echo __('no_records'); ?></div>`;
|
||||
} else {
|
||||
list.innerHTML = `<div style="padding:60px 20px; text-align:center; color:#f6465d;"><i class="fas fa-exclamation-triangle" style="font-size:40px; margin-bottom:15px; display:block; opacity:0.6"></i><?php echo __('error_fetching_orders'); ?>: ${res.error || 'Unknown error'}</div>`;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Network error fetching orders:', e);
|
||||
const list = document.getElementById('records-list');
|
||||
if(list) list.innerHTML = `<div style="padding:60px 20px; text-align:center; color:#f6465d;"><i class="fas fa-exclamation-triangle" style="font-size:40px; margin-bottom:15px; display:block; opacity:0.6"></i><?php echo __('network_error'); ?>: <?php echo __('could_not_connect_server'); ?></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgressBars() {
|
||||
const bars = document.querySelectorAll('.progress-bar-fill'); const now = Date.now();
|
||||
bars.forEach(bar => { const start = parseInt(bar.dataset.start); const dur = parseInt(bar.dataset.duration) * 1000; const elapsed = now - start; bar.style.width = Math.min(100, (elapsed / dur) * 100) + '%'; });
|
||||
if (currentStatus === 'pending' && bars.length > 0) setTimeout(updateProgressBars, 1000);
|
||||
const bars = document.querySelectorAll('.progress-bar-fill');
|
||||
if (bars.length === 0) return;
|
||||
const now = Date.now();
|
||||
bars.forEach(bar => {
|
||||
const start = parseInt(bar.dataset.start);
|
||||
const dur = parseInt(bar.dataset.duration) * 1000;
|
||||
const elapsed = now - start;
|
||||
bar.style.width = Math.min(100, (elapsed / dur) * 100) + '%';
|
||||
});
|
||||
if (currentStatus === 'pending') setTimeout(updateProgressBars, 1000);
|
||||
}
|
||||
|
||||
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 bal = usdt ? parseFloat(usdt.amount).toFixed(2) : '0.00';
|
||||
document.getElementById('usdt-balance').innerText = bal;
|
||||
try {
|
||||
const resp = await fetch('api/get_assets.php');
|
||||
const res = await resp.json();
|
||||
const balanceEl = document.getElementById('usdt-balance');
|
||||
if (!balanceEl) return;
|
||||
if (res.success) {
|
||||
const usdt = res.data.find(a => a.symbol === 'USDT');
|
||||
balanceEl.innerText = usdt ? parseFloat(usdt.amount).toFixed(2) : '0.00';
|
||||
} else {
|
||||
balanceEl.innerText = '--';
|
||||
alert('<?php echo __('error_fetching_balance'); ?>: ' + (res.error || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
const balanceEl = document.getElementById('usdt-balance');
|
||||
if(balanceEl) balanceEl.innerText = '--';
|
||||
alert('<?php echo __('network_error'); ?>: <?php echo __('could_not_connect_server'); ?>');
|
||||
}
|
||||
}
|
||||
setInterval(fetchOrders, 3000); updateBalance(); switchPair(currentPair);
|
||||
|
||||
// ===== Main Execution =====
|
||||
|
||||
// 1. Add Event Listeners
|
||||
document.querySelectorAll('.time-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
currentDuration = parseInt(this.dataset.duration);
|
||||
currentRate = parseInt(this.dataset.rate) / 100;
|
||||
currentMin = parseInt(this.dataset.min);
|
||||
document.getElementById('option-amount').placeholder = `<?php echo __('min_order'); ?> ${currentMin}`;
|
||||
updatePotentialProfit();
|
||||
});
|
||||
});
|
||||
document.getElementById('option-amount').addEventListener('input', updatePotentialProfit);
|
||||
document.getElementById('pair-search').addEventListener('input', filterPairs);
|
||||
document.getElementById('btn-buy-up').addEventListener('click', () => placeOptionOrder('up'));
|
||||
document.getElementById('btn-buy-down').addEventListener('click', () => placeOptionOrder('down'));
|
||||
document.querySelectorAll('.record-tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => switchRecords(tab.dataset.status));
|
||||
});
|
||||
|
||||
// 2. Initial Render (pre-data)
|
||||
switchPair(currentPair);
|
||||
updateOrderBook();
|
||||
|
||||
// 3. Fetch Initial Data
|
||||
updateBalance();
|
||||
fetchOrders();
|
||||
|
||||
// 4. Initialize Chart & WebSocket
|
||||
initChart(currentPair);
|
||||
connectWebSocket();
|
||||
|
||||
// 5. Setup Intervals
|
||||
setInterval(fetchOrders, 3000);
|
||||
setInterval(updateBalance, 5000);
|
||||
setInterval(() => {
|
||||
if (!ws || ws.readyState === WebSocket.CLOSED) {
|
||||
connectWebSocket();
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
</script>
|
||||
<?php include 'footer.php'; ?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
396
spot.php
396
spot.php
@ -64,7 +64,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
|
||||
.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: 320px; height: 320px; } }
|
||||
@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; }
|
||||
@ -97,7 +97,6 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
.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; }
|
||||
|
||||
/* Adjusted for 20 rows of asks and bids */
|
||||
#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; }
|
||||
|
||||
@ -142,7 +141,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>" oninput="filterPairs()">
|
||||
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
||||
</div>
|
||||
<div id="pairs-list"></div>
|
||||
</div>
|
||||
@ -151,7 +150,7 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<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;">BTC/USDT</span>
|
||||
<span id="curr-pair" style="font-weight: 800; font-size: 16px;">--/--</span>
|
||||
</div>
|
||||
|
||||
<div class="stats-item">
|
||||
@ -178,9 +177,9 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<div class="order-box">
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 25px;">
|
||||
<div>
|
||||
<div class="order-type-tabs">
|
||||
<div class="order-type-tab active" onclick="switchOrderType('buy', 'limit')"><?php echo __('limit'); ?></div>
|
||||
<div class="order-type-tab" onclick="switchOrderType('buy', 'market')"><?php echo __('market'); ?></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>
|
||||
@ -190,21 +189,21 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<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>
|
||||
<span class="unit coin-name">--</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input type="range" min="0" max="100" value="0" step="1" oninput="setSlider('buy', this.value)">
|
||||
<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 onclick="placeOrder('buy')" class="trade-btn btn-buy"><?php echo __('buy'); ?> <span class="coin-name">BTC</span></button>
|
||||
<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">
|
||||
<div class="order-type-tab active" onclick="switchOrderType('sell', 'limit')"><?php echo __('limit'); ?></div>
|
||||
<div class="order-type-tab" onclick="switchOrderType('sell', 'market')"><?php echo __('market'); ?></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>
|
||||
@ -214,129 +213,364 @@ $user_id = $_SESSION['user_id'] ?? null;
|
||||
<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>
|
||||
<span class="unit coin-name">--</span>
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<input type="range" min="0" max="100" value="0" step="1" oninput="setSlider('sell', this.value)">
|
||||
<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 BTC</span>
|
||||
<span><?php echo __('available'); ?></span><span id="avail-coin">0.00 --</span>
|
||||
</div>
|
||||
<button onclick="placeOrder('sell')" class="trade-btn btn-sell"><?php echo __('sell'); ?> <span class="coin-name">BTC</span></button>
|
||||
<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" 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 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'); ?>(BTC)</span></div>
|
||||
<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">--</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>
|
||||
let currentPair = 'BTCUSDT'; let currentPrice = 0; let orderTypes = {buy:'limit', sell:'limit'}; let currentStatus = 'open'; let searchQuery = '';
|
||||
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};
|
||||
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};
|
||||
|
||||
function getIcon(s) {
|
||||
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) { 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 }); }
|
||||
initChart(currentPair);
|
||||
|
||||
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 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';
|
||||
document.getElementById('curr-price').innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2});
|
||||
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?'+':'')+d.P+'%';
|
||||
document.getElementById('curr-change').innerText = (d.P >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%';
|
||||
document.getElementById('curr-change').style.color = color;
|
||||
const hHigh = document.getElementById('h-high'); if(hHigh) hHigh.innerText = parseFloat(d.h).toLocaleString();
|
||||
const hLow = document.getElementById('h-low'); if(hLow) hLow.innerText = parseFloat(d.l).toLocaleString();
|
||||
const hVol = document.getElementById('h-vol'); if(hVol) hVol.innerText = (parseFloat(d.v)/1000).toFixed(1) + 'K';
|
||||
const mid = document.getElementById('mid-price'); if(mid) { mid.innerText = currentPrice.toLocaleString(undefined, {minimumFractionDigits: 2}); mid.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');
|
||||
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';
|
||||
const disp = p.replace('USDT','') + '/USDT';
|
||||
if (searchQuery === '' || disp.includes(searchQuery)) {
|
||||
lH += `<div class="pair-item ${active?'active':''}" onclick="switchPair('${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">${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}" onerror="this.src='https://ui-avatars.com/api/?name=${p.replace('USDT','')}&background=2b3139&color=fff'"><span>${p.replace('USDT','')}</span></div>`;
|
||||
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));
|
||||
});
|
||||
if(list) list.innerHTML = lH; if(mBar) mBar.innerHTML = mH;
|
||||
}
|
||||
|
||||
function filterPairs() { searchQuery = document.getElementById('pair-search').value.toUpperCase(); renderPairs(); }
|
||||
function filterPairs(e) {
|
||||
searchQuery = e.target.value.toUpperCase();
|
||||
renderPairs();
|
||||
}
|
||||
|
||||
function switchPair(p) {
|
||||
currentPair = p; const icon = getIcon(p);
|
||||
document.getElementById('curr-pair').innerText = p.replace('USDT','')+'/USDT';
|
||||
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=${p.replace('USDT','')}&background=2b3139&color=fff`; };
|
||||
document.querySelectorAll('.coin-name').forEach(el => el.innerText = p.replace('USDT',''));
|
||||
initChart(p); updateBalance();
|
||||
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; 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); } }
|
||||
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 p = parseFloat(document.getElementById(side+'-price').value); const a = parseFloat(document.getElementById(side+'-amount').value);
|
||||
if(!a || a<=0) return alert('<?php echo __('amount'); ?> Error');
|
||||
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('<?php echo __('order_placed'); ?>'); updateBalance(); fetchOrders(); } else alert(res.error);
|
||||
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() {
|
||||
const asks = document.getElementById('asks-list'); const bids = document.getElementById('bids-list');
|
||||
let aH = ''; let bH = '';
|
||||
const rowCount = 20;
|
||||
for(let i=rowCount; 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*30}%; background:#f6465d"></div><span style="color:#f6465d">${p.toFixed(2)}</span><span>${amt.toFixed(4)}</span></div>`; }
|
||||
for(let i=1; i<=rowCount; 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*30}%; 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 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);
|
||||
}
|
||||
|
||||
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; }
|
||||
<?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() {
|
||||
const resp = await fetch(`api/get_orders.php?type=spot&status=${currentStatus}`); const res = await resp.json();
|
||||
const l = document.getElementById('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><?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 __('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"><?php echo __('no_records'); ?></div>';
|
||||
if(l) l.innerHTML = h;
|
||||
<?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(s) { currentStatus = s; document.querySelectorAll('.record-tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); fetchOrders(); }
|
||||
switchPair(currentPair); updateBalance(); fetchOrders();
|
||||
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'; ?>
|
||||
<?php include 'footer.php'; ?>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user