785 lines
32 KiB
PHP
785 lines
32 KiB
PHP
<?php
|
|
session_start();
|
|
include 'header.php';
|
|
require_once 'db/config.php';
|
|
|
|
$user_id = $_SESSION['user_id'] ?? null;
|
|
$balance = 0;
|
|
if ($user_id) {
|
|
$stmt = db()->prepare("SELECT balance FROM users WHERE id = ?");
|
|
$stmt->execute([$user_id]);
|
|
$user = $stmt->fetch();
|
|
$balance = $user['balance'] ?? 0;
|
|
}
|
|
?>
|
|
|
|
<style>
|
|
:root {
|
|
--bg-color: #0b0e11;
|
|
--panel-bg: #161a1e;
|
|
--border-color: #2b3139;
|
|
--text-primary: #EAECEF;
|
|
--text-secondary: #848e9c;
|
|
--accent-color: #f0b90b;
|
|
--up-color: #00c087;
|
|
--down-color: #f6465d;
|
|
--input-bg: #1e2329;
|
|
}
|
|
|
|
body {
|
|
background-color: var(--bg-color);
|
|
color: var(--text-primary);
|
|
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.trading-layout {
|
|
display: flex;
|
|
height: calc(100vh - 60px);
|
|
gap: 1px;
|
|
background: var(--border-color);
|
|
padding: 0;
|
|
}
|
|
|
|
.panel {
|
|
background: var(--panel-bg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Left: Markets */
|
|
.market-panel {
|
|
width: 280px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.search-box {
|
|
padding: 12px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.search-input-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.search-input-wrapper input {
|
|
width: 100%;
|
|
background: var(--input-bg);
|
|
border: 1px solid var(--border-color);
|
|
color: white;
|
|
padding: 6px 12px 6px 32px;
|
|
border-radius: 4px;
|
|
font-size: 13px;
|
|
outline: none;
|
|
}
|
|
|
|
.search-input-wrapper i {
|
|
position: absolute;
|
|
left: 10px;
|
|
top: 8px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.pairs-list-header {
|
|
display: flex;
|
|
padding: 8px 12px;
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.pair-item {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 8px 12px;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
border-bottom: 1px solid rgba(255,255,255,0.02);
|
|
}
|
|
|
|
.pair-item:hover { background: rgba(255,255,255,0.05); }
|
|
.pair-item.active { background: rgba(240, 185, 11, 0.1); }
|
|
|
|
/* Center: Chart, Form, Tabs */
|
|
.center-panel {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.info-bar {
|
|
height: 50px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 15px;
|
|
gap: 20px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
}
|
|
|
|
.chart-container {
|
|
height: 450px;
|
|
background: var(--bg-color);
|
|
}
|
|
|
|
.order-placement-panel {
|
|
display: flex;
|
|
gap: 20px;
|
|
padding: 20px;
|
|
border-bottom: 1px solid var(--border-color);
|
|
background: var(--panel-bg);
|
|
}
|
|
|
|
.order-side-column {
|
|
flex: 1;
|
|
}
|
|
|
|
.order-type-tabs {
|
|
display: flex;
|
|
gap: 15px;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.order-type-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
}
|
|
.order-type-btn.active { color: var(--accent-color); font-weight: bold; }
|
|
|
|
.input-row {
|
|
background: var(--input-bg);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 10px;
|
|
padding: 8px 12px;
|
|
}
|
|
|
|
.input-label {
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
width: 50px;
|
|
text-align: left;
|
|
}
|
|
|
|
.input-row input {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: none;
|
|
color: white;
|
|
text-align: right;
|
|
outline: none;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.input-row input:disabled {
|
|
color: var(--text-secondary);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.input-unit {
|
|
color: var(--text-secondary);
|
|
font-size: 12px;
|
|
margin-left: 8px;
|
|
width: 40px;
|
|
text-align: right;
|
|
}
|
|
|
|
.slider-container {
|
|
margin: 15px 0 25px 0;
|
|
padding: 0 5px;
|
|
position: relative;
|
|
}
|
|
|
|
.slider-marks {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: -10px;
|
|
position: relative;
|
|
padding: 0 8px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.mark-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
background: var(--border-color);
|
|
border: 2px solid var(--input-bg);
|
|
border-radius: 50%;
|
|
cursor: pointer;
|
|
z-index: 2;
|
|
pointer-events: auto;
|
|
transform: translateY(-2px);
|
|
}
|
|
.mark-dot.active { background: var(--accent-color); border-color: var(--accent-color); }
|
|
|
|
.slider-labels {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-top: 8px;
|
|
padding: 0 2px;
|
|
}
|
|
.label-item {
|
|
color: var(--text-secondary);
|
|
font-size: 10px;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
width: 20px;
|
|
text-align: center;
|
|
}
|
|
.label-item.active { color: var(--text-primary); }
|
|
|
|
.execute-btn {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
font-weight: bold;
|
|
font-size: 15px;
|
|
cursor: pointer;
|
|
color: white;
|
|
transition: opacity 0.2s;
|
|
}
|
|
.execute-btn:active { opacity: 0.8; }
|
|
.execute-btn.buy { background: var(--up-color); }
|
|
.execute-btn.sell { background: var(--down-color); }
|
|
|
|
.bottom-tabs {
|
|
flex: 1;
|
|
}
|
|
|
|
.tabs-header {
|
|
display: flex;
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding: 0 15px;
|
|
}
|
|
|
|
.tab-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
padding: 12px 15px;
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
}
|
|
.tab-btn.active { color: var(--accent-color); border-bottom-color: var(--accent-color); }
|
|
|
|
/* Right: Order Book */
|
|
.order-book-panel {
|
|
width: 300px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.order-book-header {
|
|
padding: 10px 15px;
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
border-bottom: 1px solid var(--border-color);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.ob-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
padding: 3px 15px;
|
|
font-size: 12px;
|
|
position: relative;
|
|
}
|
|
|
|
.ob-bar {
|
|
position: absolute;
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
z-index: 0;
|
|
}
|
|
|
|
.ob-val { z-index: 1; position: relative; }
|
|
|
|
input[type=range] {
|
|
-webkit-appearance: none;
|
|
width: 100%;
|
|
background: transparent;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
input[type=range]:focus { outline: none; }
|
|
input[type=range]::-webkit-slider-runnable-track {
|
|
width: 100%;
|
|
height: 4px;
|
|
cursor: pointer;
|
|
background: var(--border-color);
|
|
border-radius: 2px;
|
|
}
|
|
input[type=range]::-webkit-slider-thumb {
|
|
height: 14px;
|
|
width: 14px;
|
|
border-radius: 50%;
|
|
background: white;
|
|
cursor: pointer;
|
|
-webkit-appearance: none;
|
|
margin-top: -5px;
|
|
box-shadow: 0 0 2px rgba(0,0,0,0.5);
|
|
border: 2px solid var(--accent-color);
|
|
z-index: 3;
|
|
position: relative;
|
|
}
|
|
|
|
::-webkit-scrollbar { width: 4px; }
|
|
::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
|
|
</style>
|
|
|
|
<div class="trading-layout">
|
|
<div class="panel market-panel">
|
|
<div class="search-box">
|
|
<div class="search-input-wrapper">
|
|
<i class="fas fa-search"></i>
|
|
<input type="text" id="market-search" placeholder="搜索币种" autocomplete="off">
|
|
</div>
|
|
</div>
|
|
<div class="pairs-list-header">
|
|
<span style="flex: 1.5;">币对</span>
|
|
<span style="flex: 1; text-align: right;">价格</span>
|
|
<span style="flex: 1; text-align: right;">涨跌</span>
|
|
</div>
|
|
<div id="pairs-list" style="flex: 1; overflow-y: auto;"></div>
|
|
</div>
|
|
|
|
<div class="panel center-panel">
|
|
<div class="info-bar">
|
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|
<img id="current-logo" src="https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/btc.png" width="24" height="24" onerror="this.src='https://cdn-icons-png.flaticon.com/512/2585/2585274.png'">
|
|
<span id="current-pair-display" style="font-size: 16px; font-weight: bold;">BTC/USDT</span>
|
|
</div>
|
|
<div id="last-price" style="font-size: 16px; font-weight: bold; color: var(--up-color);">--</div>
|
|
<div id="price-change" style="font-size: 12px; color: var(--up-color);">--</div>
|
|
<div style="display: flex; gap: 20px; margin-left: auto; font-size: 12px;">
|
|
<div style="color: var(--text-secondary);">24h高 <span id="high-24h" style="color: white; margin-left: 4px;">--</span></div>
|
|
<div style="color: var(--text-secondary);">24h低 <span id="low-24h" style="color: white; margin-left: 4px;">--</span></div>
|
|
<div style="color: var(--text-secondary);">24h量 <span id="vol-24h" style="color: white; margin-left: 4px;">--</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<div id="tv_chart_container" style="height: 100%;"></div>
|
|
</div>
|
|
|
|
<div class="order-placement-panel">
|
|
<div class="order-side-column" id="buy-column">
|
|
<div class="order-type-tabs">
|
|
<button class="order-type-btn active" onclick="setOrderType('buy', 'limit')">限价委托</button>
|
|
<button class="order-type-btn" onclick="setOrderType('buy', 'market')">市价委托</button>
|
|
</div>
|
|
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;">
|
|
<span style="color: var(--text-secondary);">买入 <span class="asset-name">BTC</span></span>
|
|
<span style="color: var(--text-secondary);">可用: <span id="buy-available" style="color: white;"><?php echo number_format($balance, 2); ?></span> USDT</span>
|
|
</div>
|
|
<div class="input-row" id="buy-price-row">
|
|
<span class="input-label">价格</span>
|
|
<input type="number" id="buy-price" placeholder="0.00" step="0.01">
|
|
<span class="input-unit">USDT</span>
|
|
</div>
|
|
<div class="input-row" id="buy-market-price-row" style="display: none;">
|
|
<span class="input-label">价格</span>
|
|
<input type="text" value="以市场最优价格成交" disabled>
|
|
<span class="input-unit">USDT</span>
|
|
</div>
|
|
<div class="input-row">
|
|
<span class="input-label">数量</span>
|
|
<input type="number" id="buy-amount" placeholder="0.00" step="0.0001">
|
|
<span class="input-unit asset-name">BTC</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" min="0" max="100" value="0" id="buy-slider" oninput="updateFromSlider('buy', this.value)">
|
|
<div class="slider-marks">
|
|
<div class="mark-dot" onclick="setSlider('buy', 0)"></div>
|
|
<div class="mark-dot" onclick="setSlider('buy', 25)"></div>
|
|
<div class="mark-dot" onclick="setSlider('buy', 50)"></div>
|
|
<div class="mark-dot" onclick="setSlider('buy', 75)"></div>
|
|
<div class="mark-dot" onclick="setSlider('buy', 100)"></div>
|
|
</div>
|
|
<div class="slider-labels">
|
|
<div class="label-item" onclick="setSlider('buy', 0)">0%</div>
|
|
<div class="label-item" onclick="setSlider('buy', 25)">25%</div>
|
|
<div class="label-item" onclick="setSlider('buy', 50)">50%</div>
|
|
<div class="label-item" onclick="setSlider('buy', 75)">75%</div>
|
|
<div class="label-item" onclick="setSlider('buy', 100)">100%</div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom: 15px; font-size: 13px; display: flex; justify-content: space-between;">
|
|
<span style="color: var(--text-secondary);">交易额</span>
|
|
<span><span id="buy-total">0.00</span> USDT</span>
|
|
</div>
|
|
<button class="execute-btn buy" onclick="placeOrder('buy')">买入 BTC</button>
|
|
</div>
|
|
|
|
<div class="order-side-column" id="sell-column">
|
|
<div class="order-type-tabs">
|
|
<button class="order-type-btn active" onclick="setOrderType('sell', 'limit')">限价委托</button>
|
|
<button class="order-type-btn" onclick="setOrderType('sell', 'market')">市价委托</button>
|
|
</div>
|
|
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;">
|
|
<span style="color: var(--text-secondary);">卖出 <span class="asset-name">BTC</span></span>
|
|
<span style="color: var(--text-secondary);">可用: <span id="sell-available" style="color: white;">0.00</span> <span class="asset-name">BTC</span></span>
|
|
</div>
|
|
<div class="input-row" id="sell-price-row">
|
|
<span class="input-label">价格</span>
|
|
<input type="number" id="sell-price" placeholder="0.00" step="0.01">
|
|
<span class="input-unit">USDT</span>
|
|
</div>
|
|
<div class="input-row" id="sell-market-price-row" style="display: none;">
|
|
<span class="input-label">价格</span>
|
|
<input type="text" value="以市场最优价格成交" disabled>
|
|
<span class="input-unit">USDT</span>
|
|
</div>
|
|
<div class="input-row">
|
|
<span class="input-label">数量</span>
|
|
<input type="number" id="sell-amount" placeholder="0.00" step="0.0001">
|
|
<span class="input-unit asset-name">BTC</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" min="0" max="100" value="0" id="sell-slider" oninput="updateFromSlider('sell', this.value)">
|
|
<div class="slider-marks">
|
|
<div class="mark-dot" onclick="setSlider('sell', 0)"></div>
|
|
<div class="mark-dot" onclick="setSlider('sell', 25)"></div>
|
|
<div class="mark-dot" onclick="setSlider('sell', 50)"></div>
|
|
<div class="mark-dot" onclick="setSlider('sell', 75)"></div>
|
|
<div class="mark-dot" onclick="setSlider('sell', 100)"></div>
|
|
</div>
|
|
<div class="slider-labels">
|
|
<div class="label-item" onclick="setSlider('sell', 0)">0%</div>
|
|
<div class="label-item" onclick="setSlider('sell', 25)">25%</div>
|
|
<div class="label-item" onclick="setSlider('sell', 50)">50%</div>
|
|
<div class="label-item" onclick="setSlider('sell', 75)">75%</div>
|
|
<div class="label-item" onclick="setSlider('sell', 100)">100%</div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-bottom: 15px; font-size: 13px; display: flex; justify-content: space-between;">
|
|
<span style="color: var(--text-secondary);">交易额</span>
|
|
<span><span id="sell-total">0.00</span> USDT</span>
|
|
</div>
|
|
<button class="execute-btn sell" onclick="placeOrder('sell')">卖出 BTC</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="bottom-tabs">
|
|
<div class="tabs-header">
|
|
<button class="tab-btn active" onclick="switchTab(this, 'open')">当前委托</button>
|
|
<button class="tab-btn" onclick="switchTab(this, 'history')">历史委托</button>
|
|
<button class="tab-btn" onclick="switchTab(this, 'assets')">资产余额</button>
|
|
</div>
|
|
<div id="tab-content" style="padding: 15px;">
|
|
<table style="width: 100%; font-size: 12px; border-collapse: collapse;">
|
|
<thead style="color: var(--text-secondary); text-align: left;">
|
|
<tr>
|
|
<th style="padding: 8px;">时间</th>
|
|
<th style="padding: 8px;">币对</th>
|
|
<th style="padding: 8px;">类型</th>
|
|
<th style="padding: 8px;">方向</th>
|
|
<th style="padding: 8px;">价格</th>
|
|
<th style="padding: 8px;">数量</th>
|
|
<th style="padding: 8px;">状态</th>
|
|
<th style="padding: 8px; text-align: right;">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="orders-tbody">
|
|
<tr><td colspan="8" style="text-align: center; padding: 40px; color: var(--text-secondary);">暂无记录</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="panel order-book-panel">
|
|
<div class="order-book-header">
|
|
<span>价格(USDT)</span>
|
|
<span>数量(BTC)</span>
|
|
</div>
|
|
<div id="asks-list" style="display: flex; flex-direction: column-reverse; flex: 1; overflow: hidden;"></div>
|
|
<div id="mid-price-container" style="padding: 10px 15px; border-top: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color); text-align: center;">
|
|
<div id="ob-mid-price" style="font-size: 16px; font-weight: bold;">--</div>
|
|
<div id="ob-index-price" style="font-size: 11px; color: var(--text-secondary);">指数价格 --</div>
|
|
</div>
|
|
<div id="bids-list" style="flex: 1; overflow: hidden;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
|
<script>
|
|
let currentPair = 'BTCUSDT';
|
|
let currentPrice = 0;
|
|
let usdtBalance = <?php echo $balance; ?>;
|
|
let assetBalance = 1.25;
|
|
let marketData = {};
|
|
let orderTypes = { buy: 'limit', sell: 'limit' };
|
|
|
|
const pairs = [
|
|
'BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'TRXUSDT', 'DOTUSDT',
|
|
'LINKUSDT', 'PEPEUSDT', 'SHIBUSDT', 'NEARUSDT', 'LTCUSDT', 'MATICUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'FILUSDT',
|
|
'APTUSDT', 'ARBUSDT', 'OPUSDT', 'TIAUSDT', 'ORDIUSDT', 'SUIUSDT', 'RNDRUSDT', 'FETUSDT', 'AGIXUSDT', 'WLDUSDT',
|
|
'LDOUSDT', 'AAVEUSDT', 'MKRUSDT', 'GMXUSDT', 'SNXUSDT', 'STXUSDT', 'IMXUSDT', 'KASUSDT', 'INJUSDT', 'RUNEUSDT',
|
|
'ICPUSDT', 'SEIUSDT', 'BONKUSDT', 'FLOKIUSDT', 'BCHUSDT', 'DYDXUSDT', 'CRVUSDT', 'GRTUSDT', 'FTMUSDT'
|
|
];
|
|
|
|
function initChart(symbol) {
|
|
new TradingView.widget({
|
|
"width": "100%", "height": "100%", "symbol": "BINANCE:" + symbol, "interval": "15", "timezone": "Etc/UTC", "theme": "dark", "style": "1", "locale": "zh_CN", "container_id": "tv_chart_container", "backgroundColor": "#0b0e11", "gridColor": "rgba(42, 46, 57, 0.05)", "hide_side_toolbar": true, "allow_symbol_change": false, "save_image": false, "toolbar_bg": "#161a1e"
|
|
});
|
|
}
|
|
initChart(currentPair);
|
|
|
|
let ws;
|
|
function connectWS() {
|
|
if (ws) ws.close();
|
|
const streams = pairs.map(p => p.toLowerCase() + '@ticker').join('/');
|
|
ws = new WebSocket(`wss://stream.binance.com:9443/ws/${streams}`);
|
|
ws.onmessage = (e) => {
|
|
const data = JSON.parse(e.data);
|
|
marketData[data.s] = data;
|
|
renderPairs();
|
|
if (data.s === currentPair) {
|
|
currentPrice = parseFloat(data.c);
|
|
document.getElementById('last-price').innerText = currentPrice.toLocaleString();
|
|
document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
|
|
document.getElementById('ob-mid-price').innerText = currentPrice.toLocaleString();
|
|
document.getElementById('ob-mid-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
|
|
document.getElementById('price-change').innerText = (data.P >= 0 ? '+' : '') + data.P + '%';
|
|
document.getElementById('price-change').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
|
|
document.getElementById('high-24h').innerText = parseFloat(data.h).toLocaleString();
|
|
document.getElementById('low-24h').innerText = parseFloat(data.l).toLocaleString();
|
|
document.getElementById('vol-24h').innerText = parseFloat(data.v).toLocaleString();
|
|
updateOrderBook();
|
|
|
|
const bp = document.getElementById('buy-price');
|
|
const sp = document.getElementById('sell-price');
|
|
if (!bp.value) bp.value = currentPrice;
|
|
if (!sp.value) sp.value = currentPrice;
|
|
}
|
|
};
|
|
}
|
|
connectWS();
|
|
|
|
function renderPairs() {
|
|
const list = document.getElementById('pairs-list');
|
|
const search = document.getElementById('market-search').value.toUpperCase();
|
|
let html = '';
|
|
pairs.forEach(p => {
|
|
if (search && !p.includes(search)) return;
|
|
const d = marketData[p] || {c: 0, P: 0};
|
|
const name = p.replace('USDT', '');
|
|
html += `
|
|
<div class="pair-item ${currentPair === p ? 'active' : ''}" onclick="switchPair('${p}')">
|
|
<div style="display: flex; align-items: center; gap: 8px; flex: 1.5;">
|
|
<img src="https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/${name.toLowerCase()}.png" width="18" height="18" onerror="this.src='https://cdn-icons-png.flaticon.com/512/2585/2585274.png'">
|
|
<span style="font-weight: 500; font-size: 13px;">${name}/USDT</span>
|
|
</div>
|
|
<span style="flex: 1; text-align: right; font-size: 13px;">${parseFloat(d.c).toLocaleString()}</span>
|
|
<span style="flex: 1; text-align: right; font-size: 13px; color: ${d.P >= 0 ? 'var(--up-color)' : 'var(--down-color)'}">${(d.P >= 0 ? '+' : '') + d.P}%</span>
|
|
</div>
|
|
`;
|
|
});
|
|
list.innerHTML = html;
|
|
}
|
|
|
|
function switchPair(p) {
|
|
currentPair = p;
|
|
const name = p.replace('USDT', '');
|
|
document.getElementById('current-pair-display').innerText = name + '/USDT';
|
|
document.getElementById('current-logo').src = `https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/${name.toLowerCase()}.png`;
|
|
|
|
document.querySelectorAll('.asset-name').forEach(el => { el.innerText = name; });
|
|
document.querySelector('.execute-btn.buy').innerText = '买入 ' + name;
|
|
document.querySelector('.execute-btn.sell').innerText = '卖出 ' + name;
|
|
|
|
assetBalance = (Math.random() * 2).toFixed(4);
|
|
document.getElementById('sell-available').innerText = assetBalance;
|
|
|
|
if (currentPrice) {
|
|
document.getElementById('buy-price').value = currentPrice;
|
|
document.getElementById('sell-price').value = currentPrice;
|
|
}
|
|
document.getElementById('buy-amount').value = '';
|
|
document.getElementById('sell-amount').value = '';
|
|
document.getElementById('buy-slider').value = 0;
|
|
document.getElementById('sell-slider').value = 0;
|
|
document.getElementById('buy-total').innerText = '0.00';
|
|
document.getElementById('sell-total').innerText = '0.00';
|
|
updateSliderMarks('buy', 0);
|
|
updateSliderMarks('sell', 0);
|
|
|
|
initChart(p);
|
|
renderPairs();
|
|
}
|
|
|
|
function setOrderType(side, type) {
|
|
orderTypes[side] = type;
|
|
const column = document.getElementById(side + '-column');
|
|
column.querySelectorAll('.order-type-btn').forEach(btn => {
|
|
btn.classList.toggle('active', btn.innerText.includes(type === 'limit' ? '限价' : '市价'));
|
|
});
|
|
document.getElementById(`${side}-price-row`).style.display = type === 'limit' ? 'flex' : 'none';
|
|
document.getElementById(`${side}-market-price-row`).style.display = type === 'market' ? 'flex' : 'none';
|
|
|
|
updateFromSlider(side, document.getElementById(side + '-slider').value);
|
|
}
|
|
|
|
function updateOrderBook() {
|
|
const asks = document.getElementById('asks-list');
|
|
const bids = document.getElementById('bids-list');
|
|
let asksHtml = ''; let bidsHtml = '';
|
|
for(let i=0; i<15; i++) {
|
|
const askPrice = currentPrice * (1 + (i + 1) * 0.0002);
|
|
const bidPrice = currentPrice * (1 - (i + 1) * 0.0002);
|
|
asksHtml += `<div class="ob-row"><div class="ob-bar" style="background: rgba(246, 70, 93, 0.1); width: ${Math.random()*100}%;"></div><span class="ob-val" style="color: var(--down-color);">${askPrice.toFixed(2)}</span><span class="ob-val">${(Math.random()*2).toFixed(4)}</span></div>`;
|
|
bidsHtml += `<div class="ob-row"><div class="ob-bar" style="background: rgba(0, 192, 135, 0.1); width: ${Math.random()*100}%;"></div><span class="ob-val" style="color: var(--up-color);">${bidPrice.toFixed(2)}</span><span class="ob-val">${(Math.random()*2).toFixed(4)}</span></div>`;
|
|
}
|
|
asks.innerHTML = asksHtml; bids.innerHTML = bidsHtml;
|
|
}
|
|
|
|
function updateFromSlider(side, val) {
|
|
val = parseFloat(val);
|
|
const isBuy = side === 'buy';
|
|
const isLimit = orderTypes[side] === 'limit';
|
|
const price = isLimit ? (parseFloat(document.getElementById(side + '-price').value) || currentPrice) : currentPrice;
|
|
|
|
if (isBuy) {
|
|
const totalUSDT = usdtBalance * (val / 100);
|
|
const amount = price > 0 ? (totalUSDT / price) : 0;
|
|
document.getElementById('buy-amount').value = amount > 0 ? amount.toFixed(6) : '';
|
|
document.getElementById('buy-total').innerText = totalUSDT.toFixed(2);
|
|
} else {
|
|
const amount = assetBalance * (val / 100);
|
|
const totalUSDT = amount * price;
|
|
document.getElementById('sell-amount').value = amount > 0 ? amount.toFixed(6) : '';
|
|
document.getElementById('sell-total').innerText = totalUSDT.toFixed(2);
|
|
}
|
|
updateSliderMarks(side, val);
|
|
}
|
|
|
|
function updateFromInputs(side) {
|
|
const isBuy = side === 'buy';
|
|
const isLimit = orderTypes[side] === 'limit';
|
|
const price = isLimit ? (parseFloat(document.getElementById(side + '-price').value) || currentPrice) : currentPrice;
|
|
const amount = parseFloat(document.getElementById(side + '-amount').value) || 0;
|
|
|
|
const total = price * amount;
|
|
document.getElementById(side + '-total').innerText = total.toFixed(2);
|
|
|
|
let percentage = 0;
|
|
if (isBuy) {
|
|
percentage = usdtBalance > 0 ? (total / usdtBalance) * 100 : 0;
|
|
} else {
|
|
percentage = assetBalance > 0 ? (amount / assetBalance) * 100 : 0;
|
|
}
|
|
percentage = Math.min(Math.max(percentage, 0), 100);
|
|
document.getElementById(side + '-slider').value = percentage;
|
|
updateSliderMarks(side, percentage);
|
|
}
|
|
|
|
function updateSliderMarks(side, val) {
|
|
const column = document.getElementById(side + '-column');
|
|
const marks = column.querySelectorAll('.mark-dot');
|
|
const labels = column.querySelectorAll('.label-item');
|
|
marks.forEach((m, i) => {
|
|
if (i * 25 <= val) m.classList.add('active');
|
|
else m.classList.remove('active');
|
|
});
|
|
labels.forEach((l, i) => {
|
|
if (Math.abs(i * 25 - val) < 5) l.classList.add('active');
|
|
else l.classList.remove('active');
|
|
});
|
|
}
|
|
|
|
function setSlider(side, val) {
|
|
document.getElementById(side + '-slider').value = val;
|
|
updateFromSlider(side, val);
|
|
}
|
|
|
|
['buy', 'sell'].forEach(side => {
|
|
document.getElementById(side + '-price').addEventListener('input', () => updateFromInputs(side));
|
|
document.getElementById(side + '-amount').addEventListener('input', () => updateFromInputs(side));
|
|
});
|
|
|
|
async function placeOrder(side) {
|
|
const type = orderTypes[side];
|
|
const priceInput = document.getElementById(side + '-price').value;
|
|
const amount = parseFloat(document.getElementById(side + '-amount').value);
|
|
if (!amount) return alert('请输入数量');
|
|
const price = type === 'limit' ? (priceInput ? parseFloat(priceInput) : currentPrice) : currentPrice;
|
|
|
|
const response = await fetch('api/place_order.php', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
symbol: currentPair, type: 'spot', side: side,
|
|
order_type: type,
|
|
price: price, amount: amount, total: price * amount
|
|
})
|
|
});
|
|
const res = await response.json();
|
|
if (res.success) {
|
|
alert('下单成功');
|
|
fetchOrders();
|
|
} else {
|
|
alert('失败: ' + res.error);
|
|
}
|
|
}
|
|
|
|
async function fetchOrders() {
|
|
const response = await fetch('api/get_orders.php?type=spot&status=open');
|
|
const res = await response.json();
|
|
const tbody = document.getElementById('orders-tbody');
|
|
if (res.success && res.data.length > 0) {
|
|
let html = '';
|
|
res.data.forEach(o => {
|
|
html += `
|
|
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
<td style="padding: 10px 8px;">${o.created_at}</td>
|
|
<td style="padding: 10px 8px; font-weight: bold;">${o.symbol}</td>
|
|
<td style="padding: 10px 8px;">${o.order_type === 'market' ? '市价' : '限价'}</td>
|
|
<td style="padding: 10px 8px; color: ${o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)'}">${o.side === 'buy' ? '买入' : '卖出'}</td>
|
|
<td style="padding: 10px 8px;">${parseFloat(o.price).toLocaleString()}</td>
|
|
<td style="padding: 10px 8px;">${parseFloat(o.amount).toFixed(6)}</td>
|
|
<td style="padding: 10px 8px;">待成交</td>
|
|
<td style="padding: 10px 8px; text-align: right;">
|
|
<button onclick="cancelOrder(${o.id})" style="background: none; border: 1px solid var(--down-color); color: var(--down-color); padding: 2px 8px; border-radius: 4px; cursor: pointer;">取消</button>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
tbody.innerHTML = html;
|
|
} else {
|
|
tbody.innerHTML = '<tr><td colspan="8" style="text-align: center; padding: 40px; color: var(--text-secondary);">暂无记录</td></tr>';
|
|
}
|
|
}
|
|
|
|
function switchTab(btn, tab) {
|
|
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
btn.classList.add('active');
|
|
fetchOrders();
|
|
}
|
|
|
|
async function cancelOrder(id) {
|
|
if (!confirm('确定取消订单吗?')) return;
|
|
const resp = await fetch('api/cancel_order.php', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({order_id: id})
|
|
});
|
|
if ((await resp.json()).success) fetchOrders();
|
|
}
|
|
|
|
document.getElementById('market-search').addEventListener('input', renderPairs);
|
|
fetchOrders();
|
|
setInterval(fetchOrders, 5000);
|
|
</script>
|
|
|
|
<?php include 'footer.php'; ?>
|