703 lines
37 KiB
PHP
703 lines
37 KiB
PHP
<?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>
|
|
/* Global layout enforcement */
|
|
html, body {
|
|
background: #0b0e11;
|
|
min-width: 1280px;
|
|
max-width: 1920px;
|
|
margin: 0 auto;
|
|
overflow-x: auto;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
html, body {
|
|
min-width: 100%;
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
.navbar, footer { display: none !important; }
|
|
body { padding-bottom: 0 !important; }
|
|
}
|
|
|
|
.trading-container {
|
|
display: flex;
|
|
background: #0b0e11;
|
|
min-height: calc(100vh - 64px);
|
|
width: 100%;
|
|
}
|
|
|
|
@media (max-width: 991px) {
|
|
.trading-container { min-height: calc(100vh - 50px); flex-direction: column; }
|
|
}
|
|
|
|
.left-col { width: 260px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
|
.center-col { flex: 1; min-width: 600px; background: #0b0e11; display: flex; flex-direction: column; border-right: 1px solid #2b3139; }
|
|
.right-col { width: 320px; flex-shrink: 0; background: #161a1e; display: flex; flex-direction: column; }
|
|
|
|
@media (max-width: 991px) {
|
|
.left-col, .right-col { display: none !important; }
|
|
.center-col { min-width: 100%; }
|
|
}
|
|
|
|
/* Category Tabs above Search */
|
|
.category-tabs { display: flex; padding: 15px 15px 5px; gap: 8px; }
|
|
.category-tab { flex: 1; text-align: center; padding: 6px 0; background: #2b3139; border-radius: 4px; font-size: 11px; color: #848e9c; cursor: pointer; transition: all 0.2s; white-space: nowrap; border: 1px solid transparent; }
|
|
.category-tab.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
|
|
|
/* Search Box */
|
|
.search-box { padding: 5px 15px 10px; position: relative; }
|
|
.search-box i { position: absolute; left: 25px; top: 50%; transform: translateY(-50%); color: #848e9c; font-size: 12px; }
|
|
.search-box input { width: 100%; background: #2b3139; border: 1px solid transparent; border-radius: 4px; padding: 8px 10px 8px 30px; color: white; font-size: 13px; outline: none; }
|
|
.search-box input:focus { border-color: var(--primary-color); }
|
|
|
|
#pairs-list { flex: 1; overflow-y: auto; }
|
|
.pair-item { display: flex; align-items: center; padding: 10px 15px; cursor: pointer; transition: all 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); }
|
|
.pair-item:hover { background: #1e2329; }
|
|
.pair-item.active { background: #1e2329; border-left: 3px solid var(--primary-color); }
|
|
.coin-icon { width: 22px; height: 22px; margin-right: 12px; border-radius: 50%; flex-shrink: 0; background: #2b3139; }
|
|
|
|
.chart-header { padding: 8px 20px; display: flex; align-items: center; background: #161a1e; border-bottom: 1px solid #2b3139; flex-wrap: nowrap; gap: 20px; height: 50px; }
|
|
.chart-box { flex: 1; min-height: 350px; height: 350px; background: #0b0e11; border-bottom: 1px solid #2b3139; }
|
|
|
|
.record-tabs { display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 0 15px; }
|
|
.record-tab { padding: 10px 0; margin-right: 25px; font-size: 13px; color: #848e9c; cursor: pointer; position: relative; }
|
|
.record-tab.active { color: white; font-weight: 600; }
|
|
.record-tab.active::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: var(--primary-color); }
|
|
.records-content { min-height: 200px; height: 220px; overflow-y: auto; background: #161a1e; }
|
|
|
|
@media (max-width: 991px) {
|
|
.records-content { min-height: 200px; padding-bottom: 20px; height: auto; }
|
|
.chart-box { min-height: 350px; height: 350px; }
|
|
}
|
|
|
|
.order-panel { padding: 12px 20px; background: #161a1e; border-bottom: 1px solid #2b3139; }
|
|
.panel-label { color: #848e9c; font-size: 11px; margin-bottom: 6px; font-weight: 500; }
|
|
.duration-grid { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 5px; scrollbar-width: none; }
|
|
.duration-grid::-webkit-scrollbar { display: none; }
|
|
.time-btn { flex: 1; min-width: 70px; background: #2b3139; border: 1px solid transparent; color: #eaecef; padding: 6px 4px; border-radius: 4px; font-size: 11px; cursor: pointer; text-align: center; display: flex; flex-direction: column; align-items: center; transition: all 0.2s; }
|
|
.time-btn.active { background: rgba(0, 82, 255, 0.1); border-color: var(--primary-color); color: var(--primary-color); font-weight: bold; }
|
|
.time-btn .rate { font-size: 9px; font-weight: bold; margin-top: 2px; }
|
|
|
|
.input-group { background: #2b3139; border-radius: 4px; padding: 6px 12px; display: flex; align-items: center; border: 1px solid transparent; transition: border-color 0.2s; }
|
|
.input-group:focus-within { border-color: var(--primary-color); }
|
|
.input-group input { background: transparent; border: none; color: white; flex: 1; text-align: left; outline: none; font-size: 13px; width: 100%; }
|
|
.input-group .unit { color: #848e9c; margin-left: 8px; font-size: 11px; }
|
|
|
|
.balance-info { display: flex; justify-content: space-between; font-size: 10px; color: #848e9c; margin-top: 4px; }
|
|
.trade-btn { flex: 1; padding: 10px; border-radius: 4px; font-weight: bold; font-size: 14px; border: none; cursor: pointer; transition: transform 0.1s; }
|
|
.trade-btn:active { transform: scale(0.98); }
|
|
.btn-up { background: #0ecb81; color: white; }
|
|
.btn-down { background: #f6465d; color: white; }
|
|
|
|
/* Order Book Styles */
|
|
.order-book-header { padding: 10px 15px; font-size: 12px; font-weight: 600; border-bottom: 1px solid #2b3139; color: white; background: #161a1e; }
|
|
.ob-row { display: flex; justify-content: space-between; padding: 3px 15px; font-size: 12px; position: relative; height: 20px; align-items: center; }
|
|
.ob-price { flex: 1; font-weight: 500; }
|
|
.ob-amount { flex: 1; text-align: right; color: #eaecef; }
|
|
.ob-total { flex: 1; text-align: right; color: #848e9c; }
|
|
.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; }
|
|
|
|
#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; }
|
|
|
|
/* Mobile Nav */
|
|
.m-trade-nav { display: none; background: #161a1e; border-bottom: 1px solid #2b3139; position: sticky; top: 0; z-index: 100; }
|
|
.m-trade-nav a { flex: 1; text-align: center; padding: 14px; font-size: 14px; color: #848e9c; text-decoration: none; border-bottom: 2px solid transparent; }
|
|
.m-trade-nav a.active { color: var(--primary-color); border-bottom-color: var(--primary-color); font-weight: bold; }
|
|
|
|
.m-currency-bar { display: none; overflow-x: auto; background: #161a1e; padding: 10px; gap: 10px; border-bottom: 1px solid #2b3139; scrollbar-width: none; }
|
|
.m-currency-bar::-webkit-scrollbar { display: none; }
|
|
.m-coin-item { flex-shrink: 0; background: #2b3139; padding: 6px 12px; border-radius: 4px; display: flex; align-items: center; gap: 6px; border: 1px solid transparent; }
|
|
.m-coin-item.active { border-color: var(--primary-color); background: rgba(0, 82, 255, 0.1); }
|
|
.m-coin-item img { width: 16px; height: 16px; border-radius: 50%; }
|
|
|
|
@media (max-width: 991px) {
|
|
.m-trade-nav { display: flex; }
|
|
.m-currency-bar { display: flex; }
|
|
.chart-header { gap: 10px; padding: 10px 15px; overflow-x: auto; scrollbar-width: none; }
|
|
.chart-header::-webkit-scrollbar { display: none; }
|
|
.stats-item { flex-shrink: 0; }
|
|
}
|
|
|
|
.progress-bar-container { width: 100%; height: 3px; background: #2b3139; border-radius: 2px; margin-top: 8px; overflow: hidden; }
|
|
.progress-bar-fill { height: 100%; background: var(--primary-color); width: 0%; transition: width 1s linear; }
|
|
|
|
.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; }
|
|
|
|
/* Countdown Modal (Beautiful Square with Circular Timer) */
|
|
.option-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); z-index: 3000; display: none; align-items: center; justify-content: center; backdrop-filter: blur(8px); }
|
|
.option-modal-content { background: #1e2329; width: 420px; height: 420px; border-radius: 24px; padding: 30px; border: 1px solid #2b3139; position: relative; overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: space-between; box-shadow: 0 20px 50px rgba(0,0,0,0.5); }
|
|
|
|
.circular-timer-box { position: relative; width: 120px; height: 120px; margin-bottom: 10px; }
|
|
.circular-timer-svg { transform: rotate(-90deg); width: 120px; height: 120px; }
|
|
.circular-timer-bg { fill: none; stroke: #2b3139; stroke-width: 8; }
|
|
.circular-timer-progress { fill: none; stroke: var(--primary-color); stroke-width: 8; stroke-linecap: round; stroke-dasharray: 351.85; stroke-dashoffset: 0; transition: stroke-dashoffset 1s linear; }
|
|
.timer-text { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 28px; font-weight: 800; color: white; font-family: 'Courier New', monospace; }
|
|
|
|
.modal-symbol { font-size: 20px; font-weight: 800; color: white; margin-bottom: 2px; }
|
|
.modal-dir { font-size: 13px; font-weight: 700; padding: 4px 16px; border-radius: 20px; margin-bottom: 15px; }
|
|
|
|
.modal-info-grid { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: 15px; background: rgba(255,255,255,0.03); padding: 15px; border-radius: 16px; margin-bottom: 15px; }
|
|
.info-item { display: flex; flex-direction: column; gap: 4px; }
|
|
.info-label { color: #848e9c; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
.info-value { color: white; font-size: 14px; font-weight: 700; }
|
|
|
|
.modal-live-box { text-align: center; width: 100%; }
|
|
.live-label { color: #848e9c; font-size: 12px; margin-bottom: 5px; }
|
|
.live-price { font-size: 24px; font-weight: 800; transition: color 0.3s; }
|
|
.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">
|
|
<a href="spot.php"><?php echo __('nav_spot'); ?></a>
|
|
<a href="futures.php"><?php echo __('nav_futures'); ?></a>
|
|
<a href="options.php" class="active"><?php echo __('nav_options'); ?></a>
|
|
</div>
|
|
|
|
<div class="m-currency-bar" id="m-coin-list"></div>
|
|
|
|
<div class="trading-container">
|
|
<div class="left-col d-none d-lg-flex">
|
|
<div class="category-tabs">
|
|
<div class="category-tab active" onclick="location.href='options.php'"><?php echo $lang == 'zh' ? '秒合约' : 'Options'; ?></div>
|
|
<div class="category-tab" onclick="location.href='spot.php'"><?php echo $lang == 'zh' ? '现货交易' : 'Spot'; ?></div>
|
|
<div class="category-tab" onclick="location.href='futures.php'"><?php echo $lang == 'zh' ? '合约交易' : 'Futures'; ?></div>
|
|
</div>
|
|
<div class="search-box">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="pair-search" placeholder="<?php echo __('search_currency'); ?>">
|
|
</div>
|
|
<div id="pairs-list"></div>
|
|
</div>
|
|
|
|
<div class="center-col">
|
|
<div 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">--/--</span></div>
|
|
<div style="font-size: 10px; color: var(--primary-color); font-weight: 600;"><?php echo __('nav_options'); ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-item">
|
|
<div id="curr-price" style="font-size: 18px; font-weight: 800; color: #0ecb81; line-height: 1.2;">--</div>
|
|
<div id="curr-change" style="font-size: 11px; font-weight: 600;">--</div>
|
|
</div>
|
|
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_high'); ?></span>
|
|
<span class="stats-value" id="h-high">--</span>
|
|
</div>
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_low'); ?></span>
|
|
<span class="stats-value" id="h-low">--</span>
|
|
</div>
|
|
<div class="stats-item">
|
|
<span class="stats-label"><?php echo __('24h_vol'); ?></span>
|
|
<span class="stats-value" id="h-vol">--</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-box"><div id="tv_chart_container" style="height: 100%;"></div></div>
|
|
|
|
<div class="order-panel">
|
|
<div style="display: grid; grid-template-columns: 1.2fr 0.8fr 1fr; gap: 15px; align-items: end;">
|
|
<div>
|
|
<div class="panel-label"><?php echo __('settlement_time'); ?></div>
|
|
<div class="duration-grid">
|
|
<div class="time-btn active" data-duration="60" data-rate="8" data-min="100">60S <span class="rate">8%</span></div>
|
|
<div class="time-btn" data-duration="90" data-rate="12" data-min="5000">90S <span class="rate">12%</span></div>
|
|
<div class="time-btn" data-duration="120" data-rate="15" data-min="30000">120S <span class="rate">15%</span></div>
|
|
<div class="time-btn" data-duration="180" data-rate="20" data-min="100000">180S <span class="rate">20%</span></div>
|
|
<div class="time-btn" data-duration="300" data-rate="32" data-min="300000">300S <span class="rate">32%</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="panel-label"><?php echo __('buy_amount'); ?></div>
|
|
<div class="input-group">
|
|
<input type="number" id="option-amount" placeholder="<?php echo __('min_order'); ?> 100">
|
|
<span class="unit">USDT</span>
|
|
</div>
|
|
<div class="balance-info">
|
|
<span><?php echo __('available'); ?>: <span id="usdt-balance" style="color:white">0.00</span></span>
|
|
<span id="potential-profit" style="color: #0ecb81; font-weight: bold;">+0.00</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="display:flex; gap:8px;">
|
|
<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" 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>
|
|
</div>
|
|
|
|
<div class="right-col d-none d-lg-flex">
|
|
<div class="order-book-header"><?php echo __('order_book'); ?></div>
|
|
<div style="padding: 10px 0; display: flex; flex-direction: column; flex: 1;">
|
|
<div id="ob-asks" style="height: 400px; display: flex; flex-direction: column; justify-content: flex-end; overflow: hidden;"></div>
|
|
<div style="padding: 10px; border-top: 1px solid #2b3139; border-bottom: 1px solid #2b3139; margin: 5px 0; text-align: center; background: #161a1e;">
|
|
<span id="ob-mid-price" style="font-size: 16px; font-weight: 800; color: #0ecb81;">--</span>
|
|
</div>
|
|
<div id="ob-bids" style="height: 400px; display: flex; flex-direction: column; justify-content: flex-start; overflow: hidden;"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="option-modal" id="order-modal">
|
|
<div class="option-modal-content">
|
|
<div class="modal-symbol" id="modal-symbol">BTC/USDT</div>
|
|
<div class="modal-dir" id="modal-dir">BUY UP</div>
|
|
|
|
<div class="circular-timer-box">
|
|
<svg class="circular-timer-svg">
|
|
<circle class="circular-timer-bg" cx="60" cy="60" r="56"></circle>
|
|
<circle class="circular-timer-progress" id="circular-progress" cx="60" cy="60" r="56"></circle>
|
|
</svg>
|
|
<div class="timer-text" id="modal-timer">00</div>
|
|
</div>
|
|
|
|
<div class="modal-live-box">
|
|
<div class="live-label"><?php echo __('current_price'); ?></div>
|
|
<div class="live-price" id="modal-price-live">--</div>
|
|
<div class="profit-estimate" id="modal-profit-estimate">--</div>
|
|
</div>
|
|
|
|
<div class="modal-info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label"><?php echo __('opening_price'); ?></div>
|
|
<div class="info-value" id="modal-open-price">--</div>
|
|
</div>
|
|
<div class="info-item" style="text-align: right;">
|
|
<div class="info-label"><?php echo __('buy_amount'); ?></div>
|
|
<div class="info-value" id="modal-amount">--</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-footer-note">
|
|
<?php echo __('options_instruction'); ?>: <?php echo __('options_wait_settle'); ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
|
<script>
|
|
TradingView.onready(function() {
|
|
let currentPair = 'BTCUSDT';
|
|
let currentPrice = 0;
|
|
let 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 = {};
|
|
|
|
function getIcon(s) {
|
|
const symbol = s.replace('USDT', '').toLowerCase();
|
|
return `https://assets.coincap.io/assets/icons/${symbol}@2x.png`;
|
|
}
|
|
|
|
function initChart(symbol) {
|
|
const container = document.getElementById('tv_chart_container');
|
|
if (container) container.innerHTML = ''; // 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
|
|
});
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
document.getElementById('curr-price').style.color = color;
|
|
document.getElementById('ob-mid-price').style.color = color;
|
|
document.getElementById('curr-change').innerText = (d.P >= 0 ? '+' : '') + parseFloat(d.P).toFixed(2) + '%';
|
|
document.getElementById('curr-change').style.color = color;
|
|
document.getElementById('h-high').innerText = parseFloat(d.h).toLocaleString();
|
|
document.getElementById('h-low').innerText = parseFloat(d.l).toLocaleString();
|
|
document.getElementById('h-vol').innerText = (parseFloat(d.v)/1000).toFixed(1) + 'K';
|
|
}
|
|
|
|
function updateOrderBook() {
|
|
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 = 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 = 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;
|
|
}
|
|
|
|
function renderPairs() {
|
|
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';
|
|
|
|
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));
|
|
});
|
|
}
|
|
|
|
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';
|
|
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();
|
|
const d = marketData[p] || {c:0, P:0, h:0, l:0, v:0};
|
|
updatePriceUI(d);
|
|
}
|
|
|
|
function updatePotentialProfit() {
|
|
const amt = parseFloat(document.getElementById('option-amount').value) || 0;
|
|
const profit = (amt * currentRate).toFixed(2);
|
|
document.getElementById('potential-profit').innerText = '+' + profit;
|
|
}
|
|
|
|
async function placeOptionOrder(dir) {
|
|
const amt = parseFloat(document.getElementById('option-amount').value);
|
|
if (!amt || amt < currentMin) return alert('<?php echo __('min_amount'); ?>: ' + currentMin + ' USDT');
|
|
if (currentPrice === 0) {
|
|
alert('<?php echo __('error_no_live_price'); ?>');
|
|
return;
|
|
}
|
|
|
|
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');
|
|
const dirEl = document.getElementById('modal-dir');
|
|
const progress = document.getElementById('circular-progress');
|
|
|
|
modalActiveRate = rate;
|
|
document.getElementById('modal-symbol').innerText = currentPair.replace('USDT','') + '/USDT';
|
|
document.getElementById('modal-open-price').innerText = openPrice.toLocaleString();
|
|
document.getElementById('modal-amount').innerText = amount + ' USDT';
|
|
|
|
dirEl.innerText = dir === 'up' ? '<?php echo __('buy_up'); ?>' : '<?php echo __('buy_down'); ?>';
|
|
dirEl.style.background = dir === 'up' ? 'rgba(14, 203, 129, 0.2)' : 'rgba(246, 70, 93, 0.2)';
|
|
dirEl.style.color = dir === 'up' ? '#0ecb81' : '#f6465d';
|
|
|
|
let remaining = duration;
|
|
const total = duration;
|
|
timer.innerText = remaining;
|
|
modal.style.display = 'flex';
|
|
|
|
const circumference = 2 * Math.PI * 56;
|
|
progress.style.strokeDasharray = circumference;
|
|
|
|
if(modalInterval) clearInterval(modalInterval);
|
|
modalInterval = setInterval(() => {
|
|
remaining--;
|
|
timer.innerText = remaining < 10 ? '0' + remaining : remaining;
|
|
const offset = circumference - (remaining / total) * circumference;
|
|
progress.style.strokeDashoffset = offset;
|
|
|
|
if(remaining <= 0) {
|
|
clearInterval(modalInterval);
|
|
setTimeout(() => { modal.style.display = 'none'; fetchOrders(); }, 2000);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
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);
|
|
|
|
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'));
|
|
document.querySelector(`.record-tab[data-status="${s}"]`).classList.add('active');
|
|
fetchOrders();
|
|
}
|
|
|
|
async function fetchOrders() {
|
|
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>
|
|
`).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');
|
|
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() {
|
|
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'); ?>');
|
|
}
|
|
}
|
|
|
|
// ===== 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'; ?>
|