交易所

This commit is contained in:
Flatlogic Bot 2026-02-11 15:18:19 +00:00
parent 7c4904baad
commit 79b07243ce
13 changed files with 1120 additions and 826 deletions

View File

@ -1,5 +1,6 @@
<?php <?php
require_once '../db/config.php'; require_once '../db/config.php';
require_once '../includes/currency_helper.php';
session_start(); session_start();
$pdo = db(); $pdo = db();
@ -20,18 +21,37 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['message'])) {
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) { if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
$oid = $_POST['order_id']; $oid = $_POST['order_id'];
if ($_POST['action'] == 'match') { if ($_POST['action'] == 'match') {
$info = $_POST['account_info']; $bank = $_POST['bank_name'] ?? '';
$name = $_POST['account_name'] ?? '';
$number = $_POST['account_number'] ?? '';
$remarks = $_POST['remarks'] ?? '';
$info = "🏦 银行名称:$bank\n👤 收款姓名:$name\n💳 收款账号:$number\n📝 备注说明:$remarks";
$pdo->prepare("UPDATE fiat_orders SET status = 'matched', bank_account_info = ? WHERE id = ?")->execute([$info, $oid]); $pdo->prepare("UPDATE fiat_orders SET status = 'matched', bank_account_info = ? WHERE id = ?")->execute([$info, $oid]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "匹配成功!收款账户:$info\n请转账并上传凭证。 οδηγός"]);
$notif = "✅ 匹配成功!收款账户已下发。请在页面强制弹窗中查看详细信息并进行转账。";
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $notif]);
} elseif ($_POST['action'] == 'complete') { } elseif ($_POST['action'] == 'complete') {
$stmt = $pdo->prepare("SELECT amount FROM fiat_orders WHERE id = ?"); $stmt = $pdo->prepare("SELECT amount, currency, exchange_rate FROM fiat_orders WHERE id = ?");
$stmt->execute([$oid]); $stmt->execute([$oid]);
$amt = $stmt->fetchColumn(); $order = $stmt->fetch();
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$amt, $user_id]); $amt = $order['amount'];
$pdo->prepare("UPDATE fiat_orders SET status = 'completed' WHERE id = ?")->execute([$oid]); $cur = $order['currency'];
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "充值已到账,金额:$amt USDT"]);
$fiat_rates = get_fiat_rates();
$real_time_rate = $fiat_rates[$cur] ?? $order['exchange_rate'];
$usdt_amt = ($real_time_rate > 0) ? ($amt / $real_time_rate) : $amt;
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$usdt_amt, $user_id]);
$pdo->prepare("UPDATE fiat_orders SET status = 'completed', usdt_amount = ?, exchange_rate = ? WHERE id = ?")
->execute([$usdt_amt, $real_time_rate, $oid]);
$notif = "🎉 充值已确认到账!\n金额:" . number_format($amt, 2) . " $cur\n实时汇率1 USDT = " . number_format($real_time_rate, 4) . " $cur\n入账" . number_format($usdt_amt, 2) . " USDT";
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $notif]);
} elseif ($_POST['action'] == 'reject') { } elseif ($_POST['action'] == 'reject') {
$pdo->prepare("UPDATE fiat_orders SET status = 'rejected' WHERE id = ?")->execute([$oid]); $pdo->prepare("UPDATE fiat_orders SET status = 'rejected' WHERE id = ?")->execute([$oid]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "❌ 您的充值申请 #$oid 已被拒绝。"]);
} }
} }
@ -46,6 +66,8 @@ $msgs = $messages->fetchAll();
$orders = $pdo->prepare("SELECT * FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'submitting', 'matched') ORDER BY id DESC"); $orders = $pdo->prepare("SELECT * FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'submitting', 'matched') ORDER BY id DESC");
$orders->execute([$user_id]); $orders->execute([$user_id]);
$pending_orders = $orders->fetchAll(); $pending_orders = $orders->fetchAll();
$current_rates = get_fiat_rates();
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
@ -53,78 +75,90 @@ $pending_orders = $orders->fetchAll();
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style> <style>
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #0B0E11; color: white; display: flex; flex-direction: column; height: 100vh; } body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: #0B0E11; color: white; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.chat-header { padding: 15px; background: #1E2329; border-bottom: 1px solid #2B3139; display: flex; justify-content: space-between; align-items: center; } .chat-header { padding: 15px 20px; background: #1E2329; border-bottom: 1px solid #2B3139; display: flex; justify-content: space-between; align-items: center; z-index: 10; }
.chat-box { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; } .main-content { flex: 1; display: flex; flex-direction: column; overflow-y: auto; }
.msg { max-width: 70%; padding: 10px 15px; border-radius: 8px; font-size: 14px; line-height: 1.5; } .chat-box { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 15px; }
.msg.admin { align-self: flex-end; background: #377aff; color: white; } .msg { max-width: 85%; padding: 12px 16px; border-radius: 12px; font-size: 14px; line-height: 1.6; position: relative; }
.msg.user { align-self: flex-start; background: #2B3139; color: #EAECEF; } .msg.admin { align-self: flex-end; background: #377aff; color: white; border-bottom-right-radius: 2px; }
.msg-time { font-size: 11px; color: #848E9C; margin-top: 5px; display: block; } .msg.user { align-self: flex-start; background: #2B3139; color: #EAECEF; border-bottom-left-radius: 2px; }
.msg-time { font-size: 10px; color: #848E9C; margin-top: 5px; display: block; text-align: right; }
.recharge-panel { background: #1E2329; border-bottom: 1px solid #2B3139; padding: 15px; } .recharge-panel { background: #1E2329; border-bottom: 1px solid #2B3139; padding: 15px 20px; }
.order-card { background: #0B0E11; border: 1px solid #2B3139; border-radius: 6px; padding: 12px; margin-bottom: 10px; } .order-card { background: #161A1E; border: 1px solid #2B3139; border-radius: 16px; padding: 15px; margin-bottom: 12px; }
.input-area { padding: 15px; background: #1E2329; border-top: 1px solid #2B3139; display: flex; gap: 10px; } .input-area { padding: 15px 20px; background: #1E2329; border-top: 1px solid #2B3139; display: flex; gap: 12px; }
input[type="text"] { flex: 1; background: #0B0E11; border: 1px solid #2B3139; color: white; padding: 10px; border-radius: 4px; outline: none; } input[type="text"], input[type="number"], textarea { width: 100%; background: #0B0E11; border: 1px solid #2B3139; color: white; padding: 10px; border-radius: 8px; outline: none; margin-bottom: 8px; font-size: 13px; }
button { background: #377aff; border: none; color: white; padding: 10px 20px; border-radius: 4px; cursor: pointer; } button { background: #377aff; border: none; color: white; padding: 10px 20px; border-radius: 8px; cursor: pointer; font-weight: bold; }
.status-badge { font-size: 10px; padding: 4px 10px; border-radius: 6px; font-weight: 800; }
.status-badge { font-size: 11px; padding: 2px 6px; border-radius: 3px; } .matching { background: rgba(240, 185, 11, 0.1); color: #f0b90b; }
.matching { background: #ff3c00; } .submitting { background: rgba(0, 192, 135, 0.1); color: #00c087; }
.submitting { background: #00c087; } .btn-complete { background: #00c087; }
.btn-reject { background: #f6465d; }
</style> </style>
</head> </head>
<body> <body>
<div class="chat-header"> <div class="chat-header">
<div> <div>
<span style="font-weight: bold;"><?php echo htmlspecialchars($userData['username']); ?></span> <span style="font-weight: 800;"><?php echo htmlspecialchars($userData['username'] ?? 'User'); ?></span>
<span style="color: #848E9C; font-size: 0.8rem; margin-left: 10px;">UID: <?php echo $userData['uid']; ?></span> <span style="color: #848E9C; font-size: 11px; margin-left: 8px;">UID: <?php echo $userData['uid'] ?? 'N/A'; ?></span>
<span style="color: #848E9C; font-size: 0.8rem; margin-left: 10px;">IP: <?php echo htmlspecialchars($userData['last_ip'] ?? '未知'); ?></span>
</div> </div>
<div style="font-size: 0.8rem; color: #00c087;"> <div style="text-align: right;">
余额: <?php echo number_format($userData['balance'], 2); ?> USDT <div style="font-size: 11px; color: #848E9C;">余额: <span style="color: #00c087;"><?php echo number_format($userData['balance'] ?? 0, 2); ?> USDT</span></div>
</div> </div>
</div> </div>
<div class="main-content">
<?php if (!empty($pending_orders)): ?> <?php if (!empty($pending_orders)): ?>
<div class="recharge-panel"> <div class="recharge-panel">
<h4 style="margin: 0 0 10px 0; color: #F0B90B; font-size: 0.9rem;"><i class="fas fa-wallet"></i> 待处理充值申请</h4> <div style="font-weight: bold; color: #F0B90B; margin-bottom: 10px; font-size: 12px;">待处理充值</div>
<?php foreach($pending_orders as $o): <?php foreach($pending_orders as $o):
// Corrected the newline escape sequence here $is_matching = ($o['status'] == 'matching');
$live_rate = $current_rates[$o['currency']] ?? $o['exchange_rate'];
$live_usdt = ($live_rate > 0) ? ($o['amount'] / $live_rate) : $o['amount'];
?> ?>
<div class="order-card"> <div class="order-card">
<div style="display: flex; justify-content: space-between; align-items: center;"> <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<span style="font-size: 0.9rem;">充值: <b><?php echo number_format($o['amount'], 2); ?> <?php echo $o['currency']; ?></b></span> <div>
<span class="status-badge <?php echo $o['status']; ?>"><?php echo $o['status'] == 'matching' ? '待匹配' : '待审核'; ?></span> <div style="font-size: 1rem; font-weight: 900;"><?php echo number_format($o['amount'], 2); ?> <?php echo $o['currency']; ?></div>
<div style="font-size: 11px; color: #848E9C;"> <?php echo number_format($live_usdt, 2); ?> USDT (汇率: <?php echo number_format($live_rate, 4); ?>)</div>
</div>
<span class="status-badge <?php echo $o['status']; ?>">
<?php
if($o['status'] == 'matching') echo '等待分配账户';
elseif($o['status'] == 'matched') echo '已分配/待支付';
elseif($o['status'] == 'submitting') echo '已提交凭证';
?>
</span>
</div> </div>
<?php if($o['status'] == 'matching'): ?> <?php if($is_matching): ?>
<form method="POST" style="margin-top: 10px; display: flex; gap: 5px;"> <form method="POST">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
<input type="hidden" name="action" value="match"> <input type="hidden" name="action" value="match">
<input type="text" name="account_info" placeholder="在此输入收款卡号/姓名" required style="font-size: 0.8rem;"> <input type="text" name="bank_name" placeholder="银行名称 (如: 中国工商银行)" required>
<button type="submit" style="font-size: 0.8rem; padding: 5px 10px;">分配账户</button> <input type="text" name="account_name" placeholder="收款人姓名" required>
<input type="text" name="account_number" placeholder="银行账号" required>
<textarea name="remarks" placeholder="注意事项 (可选)" style="height: 50px;"></textarea>
<button type="submit" style="width: 100%;">确认匹配账户</button>
</form> </form>
<?php elseif($o['status'] == 'submitting' || $o['status'] == 'matched'): ?> <?php else: ?>
<div style="margin-top: 10px; display: flex; justify-content: space-between; align-items: center;"> <div style="background: rgba(255,255,255,0.03); padding: 10px; border-radius: 8px; margin-bottom: 10px; font-size: 12px;">
<?php if($o['proof_image']): <?php echo nl2br(htmlspecialchars($o['bank_account_info'])); ?>
// Corrected the path concatenation here </div>
?> <div style="display: flex; gap: 10px; align-items: center;">
<a href="../<?php echo $o['proof_image']; ?>" target="_blank" style="color: #00c087; font-size: 0.8rem;"><i class="fas fa-image"></i> 查看凭证</a> <?php if($o['proof_image']): ?>
<?php else: <a href="../<?php echo $o['proof_image']; ?>" target="_blank" style="color: #00c087; font-size: 12px; text-decoration: none; border: 1px solid #00c087; padding: 5px 10px; border-radius: 5px;">查看凭证</a>
// Corrected the string literal here
?>
<span style="color: #5e6673; font-size: 0.8rem;">未上传凭证</span>
<?php endif; ?> <?php endif; ?>
<div style="display: flex; gap: 5px;"> <div style="flex: 1; display: flex; gap: 5px; justify-content: flex-end;">
<form method="POST"> <form method="POST">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
<input type="hidden" name="action" value="complete"> <input type="hidden" name="action" value="complete">
<button type="submit" style="background: #00c087; font-size: 0.8rem; padding: 5px 10px;">同意入金</button> <button type="submit" class="btn-complete" style="padding: 5px 10px; font-size: 12px;">同意</button>
</form> </form>
<form method="POST"> <form method="POST">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
<input type="hidden" name="action" value="reject"> <input type="hidden" name="action" value="reject">
<button type="submit" style="background: #f6465d; font-size: 0.8rem; padding: 5px 10px;">拒绝</button> <button type="submit" class="btn-reject" style="padding: 5px 10px; font-size: 12px;">拒绝</button>
</form> </form>
</div> </div>
</div> </div>
@ -135,18 +169,17 @@ $pending_orders = $orders->fetchAll();
<?php endif; ?> <?php endif; ?>
<div class="chat-box" id="chat-box"> <div class="chat-box" id="chat-box">
<?php foreach($msgs as $m): <?php foreach($msgs as $m): ?>
// Corrected the nl2br usage here
?>
<div class="msg <?php echo $m['sender']; ?>"> <div class="msg <?php echo $m['sender']; ?>">
<?php echo nl2br(htmlspecialchars($m['message'])); ?> <?php echo nl2br(htmlspecialchars($m['message'])); ?>
<span class="msg-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span> <span class="msg-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
</div>
<form class="input-area" id="msg-form"> <form class="input-area" id="msg-form">
<input type="text" id="msg-input" placeholder="输入回复内容..." autocomplete="off"> <input type="text" id="msg-input" placeholder="输入消息..." autocomplete="off">
<button type="submit"><i class="fas fa-paper-plane"></i></button> <button type="submit"><i class="fas fa-paper-plane"></i></button>
</form> </form>
@ -162,23 +195,21 @@ $pending_orders = $orders->fetchAll();
const formData = new FormData(); const formData = new FormData();
formData.append('message', msg); formData.append('message', msg);
input.value = ''; input.value = '';
const msgDiv = document.createElement('div'); const msgDiv = document.createElement('div');
msgDiv.className = 'msg admin'; msgDiv.className = 'msg admin';
// Corrected the newline replacement here msgDiv.innerHTML = msg.replace(/\n/g, '<br>') + `<span class="msg-time">${new Date().getHours()}:${new Date().getMinutes()}</span>`;
msgDiv.innerHTML = msg.replace(/\n/g, '<br>') + '<span class="msg-time">刚刚</span>';
chatBox.appendChild(msgDiv); chatBox.appendChild(msgDiv);
chatBox.scrollTop = chatBox.scrollHeight; chatBox.scrollTop = chatBox.scrollHeight;
await fetch(window.location.href, { method: 'POST', body: formData }); await fetch(window.location.href, { method: 'POST', body: formData });
}; };
// Auto refresh
setInterval(async () => { setInterval(async () => {
const res = await fetch('../api/get_messages.php?user_id=<?php echo $user_id; ?>'); const res = await fetch('../api/get_messages.php?user_id=<?php echo $user_id; ?>');
const data = await res.json(); const data = await res.json();
if (data.new_messages) { if (data && data.count > <?php echo count($msgs); ?>) {
location.reload(); location.reload();
} }
}, 5000); }, 5000);

View File

@ -3,6 +3,8 @@ require_once '../db/config.php';
session_start(); session_start();
$pdo = db(); $pdo = db();
$faceValue = 10;
if (isset($_POST['action']) && isset($_POST['order_id'])) { if (isset($_POST['action']) && isset($_POST['order_id'])) {
$oid = $_POST['order_id']; $oid = $_POST['order_id'];
$action = $_POST['action']; $action = $_POST['action'];
@ -11,22 +13,30 @@ if (isset($_POST['action']) && isset($_POST['order_id'])) {
$orderStmt->execute([$oid]); $orderStmt->execute([$oid]);
$order = $orderStmt->fetch(); $order = $orderStmt->fetch();
if ($order) { if ($order && $order['status'] == 'open') {
$user_id = $order['user_id'];
$margin = $order['total'] / $order['leverage'];
if ($action == 'approve') { if ($action == 'approve') {
// Logic for closing a futures position with profit // Settle at TP price (or current price if tp not set, but here we assume admin uses the TP field as exit price)
// Simplified: adding the total + profit (if side is buy and price went up) $exit_price = (float)($order['tp_price'] ?: $order['price']);
// But usually the user wants to "approve" the TP execution. $entry_price = (float)$order['price'];
$nominal = (float)$order['amount'] * $faceValue;
$profit = 0; $profit = 0;
if ($order['side'] == 'buy') { if ($order['side'] == 'buy') {
$profit = ($order['tp_price'] - $order['price']) * $order['amount'] * $order['leverage']; $profit = ($exit_price / $entry_price - 1) * $nominal;
} else { } else {
$profit = ($order['price'] - $order['tp_price']) * $order['amount'] * $order['leverage']; $profit = (1 - $exit_price / $entry_price) * $nominal;
} }
$new_balance = $order['balance'] + $order['total'] + $profit; $payout = $margin + $profit;
$pdo->prepare("UPDATE users SET balance = ? WHERE id = ?")->execute([$new_balance, $order['user_id']]); if ($payout < 0) $payout = 0; // Negative equity handled as 0 payout (liquidation)
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$payout, $user_id]);
$pdo->prepare("UPDATE trading_orders SET status = 'closed', admin_status = 'approved', win_loss = 'win' WHERE id = ?")->execute([$oid]); $pdo->prepare("UPDATE trading_orders SET status = 'closed', admin_status = 'approved', win_loss = 'win' WHERE id = ?")->execute([$oid]);
} elseif ($action == 'reject') { } elseif ($action == 'reject') {
// Reject usually means loss/liquidation. No payout.
$pdo->prepare("UPDATE trading_orders SET status = 'cancelled', admin_status = 'rejected', win_loss = 'loss' WHERE id = ?")->execute([$oid]); $pdo->prepare("UPDATE trading_orders SET status = 'cancelled', admin_status = 'rejected', win_loss = 'loss' WHERE id = ?")->execute([$oid]);
} elseif ($action == 'set_win_loss') { } elseif ($action == 'set_win_loss') {
$win_loss = $_POST['win_loss']; $win_loss = $_POST['win_loss'];
@ -91,8 +101,7 @@ $pending_orders = $pdo->query("SELECT COUNT(*) FROM fiat_orders WHERE status IN
<th>方向</th> <th>方向</th>
<th>杠杆</th> <th>杠杆</th>
<th>开仓价</th> <th>开仓价</th>
<th>止盈价</th> <th>退出价(止盈)</th>
<th>止损价</th>
<th>保证金</th> <th>保证金</th>
<th>盈亏控制</th> <th>盈亏控制</th>
<th>状态</th> <th>状态</th>
@ -109,8 +118,7 @@ $pending_orders = $pdo->query("SELECT COUNT(*) FROM fiat_orders WHERE status IN
<td><?php echo $o['leverage']; ?>x</td> <td><?php echo $o['leverage']; ?>x</td>
<td><?php echo number_format($o['price'], 4); ?></td> <td><?php echo number_format($o['price'], 4); ?></td>
<td><?php echo number_format($o['tp_price'], 4); ?></td> <td><?php echo number_format($o['tp_price'], 4); ?></td>
<td><?php echo number_format($o['sl_price'], 4); ?></td> <td><?php echo number_format($o['total'] / $o['leverage'], 2); ?></td>
<td><?php echo number_format($o['total'], 2); ?></td>
<td> <td>
<form method="POST" style="display: flex; gap: 5px;"> <form method="POST" style="display: flex; gap: 5px;">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
@ -135,12 +143,12 @@ $pending_orders = $pdo->query("SELECT COUNT(*) FROM fiat_orders WHERE status IN
<form method="POST"> <form method="POST">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
<input type="hidden" name="action" value="approve"> <input type="hidden" name="action" value="approve">
<button type="submit" class="btn-sm btn-approve" title="止盈价结算">同意结算</button> <button type="submit" class="btn-sm btn-approve" title="退出价结算">同意结算</button>
</form> </form>
<form method="POST"> <form method="POST">
<input type="hidden" name="order_id" value="<?php echo $o['id']; ?>"> <input type="hidden" name="order_id" value="<?php echo $o['id']; ?>">
<input type="hidden" name="action" value="reject"> <input type="hidden" name="action" value="reject">
<button type="submit" class="btn-sm btn-reject" title="止损/爆仓处理">拒绝结算</button> <button type="submit" class="btn-sm btn-reject" title="亏损处理">拒绝结算</button>
</form> </form>
</div> </div>
<?php endif; ?> <?php endif; ?>

View File

@ -7,21 +7,34 @@ if (isset($_POST['action']) && isset($_POST['order_id'])) {
$oid = $_POST['order_id']; $oid = $_POST['order_id'];
$action = $_POST['action']; $action = $_POST['action'];
$orderStmt = $pdo->prepare("SELECT o.*, u.balance FROM trading_orders o JOIN users u ON o.user_id = u.id WHERE o.id = ?"); $orderStmt = $pdo->prepare("SELECT * FROM trading_orders WHERE id = ?");
$orderStmt->execute([$oid]); $orderStmt->execute([$oid]);
$order = $orderStmt->fetch(); $order = $orderStmt->fetch();
if ($order) { if ($order && $order['status'] == 'open') {
$user_id = $order['user_id'];
$symbol = $order['symbol'];
$coin_symbol = str_replace('USDT', '', $symbol);
if ($action == 'approve') { if ($action == 'approve') {
// If it's a sell order, we close it and update user balance if ($order['side'] == 'buy') {
if ($order['side'] == 'sell') { // Buy approved: Add coins to user_assets
$new_balance = $order['balance'] + $order['total']; $stmt = $pdo->prepare("INSERT INTO user_assets (user_id, symbol, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = amount + ?");
$pdo->prepare("UPDATE users SET balance = ? WHERE id = ?")->execute([$new_balance, $order['user_id']]); $stmt->execute([$user_id, $coin_symbol, $order['amount'], $order['amount']]);
} else {
// Sell approved: Add USDT to users balance
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['total'], $user_id]);
} }
$pdo->prepare("UPDATE trading_orders SET status = 'closed', admin_status = 'approved' WHERE id = ?")->execute([$oid]); $pdo->prepare("UPDATE trading_orders SET status = 'closed', admin_status = 'approved' WHERE id = ?")->execute([$oid]);
} elseif ($action == 'reject') { } elseif ($action == 'reject') {
// If rejected, we might need to refund the amount if it was a buy order, if ($order['side'] == 'buy') {
// but usually "rejecting" a profitable sell means it just stays open or gets cancelled. // Buy rejected: Refund USDT to users balance
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['total'], $user_id]);
} else {
// Sell rejected: Refund coins to user_assets
$stmt = $pdo->prepare("INSERT INTO user_assets (user_id, symbol, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = amount + ?");
$stmt->execute([$user_id, $coin_symbol, $order['amount'], $order['amount']]);
}
$pdo->prepare("UPDATE trading_orders SET status = 'cancelled', admin_status = 'rejected' WHERE id = ?")->execute([$oid]); $pdo->prepare("UPDATE trading_orders SET status = 'cancelled', admin_status = 'rejected' WHERE id = ?")->execute([$oid]);
} }
} }

View File

@ -29,7 +29,7 @@ try {
if (!$order) { if (!$order) {
$db->rollBack(); $db->rollBack();
echo json_encode(['success' => false, 'error' => 'Order not found or already processed']); echo json_encode(['success' => false, 'error' => '订单未找到或已处理']);
exit; exit;
} }
@ -37,12 +37,22 @@ try {
$stmt = $db->prepare("UPDATE trading_orders SET status = 'cancelled' WHERE id = ?"); $stmt = $db->prepare("UPDATE trading_orders SET status = 'cancelled' WHERE id = ?");
$stmt->execute([$order_id]); $stmt->execute([$order_id]);
// Refund balance (simplified: return the total/cost to balance) if ($order['type'] === 'spot') {
$cost = ($order['type'] === 'futures') ? ($order['total'] / $order['leverage']) : ($order['side'] === 'buy' ? $order['total'] : 0); if ($order['side'] === 'buy') {
// Refund USDT
if ($cost > 0) {
$stmt = $db->prepare("UPDATE users SET balance = balance + ? WHERE id = ?"); $stmt = $db->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
$stmt->execute([$cost, $user_id]); $stmt->execute([$order['total'], $user_id]);
} else {
// Refund coins
$coin_symbol = str_replace('USDT', '', $order['symbol']);
$stmt = $db->prepare("INSERT INTO user_assets (user_id, symbol, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = amount + ?");
$stmt->execute([$user_id, $coin_symbol, $order['amount'], $order['amount']]);
}
} else {
// Futures: Refund margin
$margin = $order['total'] / $order['leverage'];
$stmt = $db->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
$stmt->execute([$margin, $user_id]);
} }
$db->commit(); $db->commit();

39
api/get_assets.php Normal file
View File

@ -0,0 +1,39 @@
<?php
session_start();
require_once '../db/config.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
exit;
}
$user_id = $_SESSION['user_id'];
try {
$db = db();
// Get USDT balance
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$usdt = $stmt->fetchColumn();
// Get other assets
$stmt = $db->prepare("SELECT symbol, amount FROM user_assets WHERE user_id = ? AND amount > 0");
$stmt->execute([$user_id]);
$other_assets = $stmt->fetchAll(PDO::FETCH_ASSOC);
$assets = [['symbol' => 'USDT', 'amount' => (float)$usdt]];
foreach ($other_assets as $asset) {
$assets[] = [
'symbol' => $asset['symbol'],
'amount' => (float)$asset['amount']
];
}
echo json_encode(['success' => true, 'data' => $assets]);
} catch (Exception $e) {
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}

View File

@ -9,9 +9,10 @@ if (!isset($_SESSION['user_id'])) {
exit; exit;
} }
$user_id = $_SESSION['user_id']; // Support both regular user and admin polling
$user_id = isset($_GET['user_id']) ? $_GET['user_id'] : $_SESSION['user_id'];
// Admin Poll Logic // Admin Poll Logic (Global notifications)
if (isset($_GET['admin_poll'])) { if (isset($_GET['admin_poll'])) {
// Check for latest message ID // Check for latest message ID
$last_id = db()->query("SELECT MAX(id) FROM messages")->fetchColumn() ?: 0; $last_id = db()->query("SELECT MAX(id) FROM messages")->fetchColumn() ?: 0;
@ -21,7 +22,7 @@ if (isset($_GET['admin_poll'])) {
// Get latest message content if it's a recharge notification // Get latest message content if it's a recharge notification
$latest_msg = db()->query("SELECT message FROM messages WHERE sender = 'user' ORDER BY id DESC LIMIT 1")->fetchColumn(); $latest_msg = db()->query("SELECT message FROM messages WHERE sender = 'user' ORDER BY id DESC LIMIT 1")->fetchColumn();
$is_recharge = (strpos($latest_msg, '[RECHARGE_') === 0); $is_recharge = ($latest_msg && strpos($latest_msg, '[RECHARGE_') === 0);
echo json_encode([ echo json_encode([
'last_id' => (int)$last_id, 'last_id' => (int)$last_id,
@ -32,7 +33,7 @@ if (isset($_GET['admin_poll'])) {
exit; exit;
} }
// Regular User Poll Logic // Regular User or specific Chat Polling Logic
$stmt = db()->prepare("SELECT COUNT(*) FROM messages WHERE user_id = ?"); $stmt = db()->prepare("SELECT COUNT(*) FROM messages WHERE user_id = ?");
$stmt->execute([$user_id]); $stmt->execute([$user_id]);
$count = $stmt->fetchColumn(); $count = $stmt->fetchColumn();

View File

@ -17,41 +17,73 @@ if (!$data) {
exit; exit;
} }
$symbol = $data['symbol']; $symbol = $data['symbol']; // e.g., BTCUSDT
$type = $data['type']; // spot or futures $type = $data['type']; // spot or futures
$side = $data['side']; // buy or sell $side = $data['side']; // buy or sell
$order_type = $data['order_type']; // limit or market $order_type = $data['order_type']; // limit or market
$price = $data['price']; $price = (float)$data['price'];
$amount = $data['amount']; $amount = (float)$data['amount'];
$total = $data['total']; $total = (float)$data['total'];
$leverage = $data['leverage'] ?? 1; $leverage = (int)($data['leverage'] ?? 1);
$tp_price = $data['tp_price'] ?? null; $tp_price = isset($data['tp_price']) ? (float)$data['tp_price'] : null;
$sl_price = $data['sl_price'] ?? null; $sl_price = isset($data['sl_price']) ? (float)$data['sl_price'] : null;
try { try {
$db = db(); $db = db();
$db->beginTransaction(); $db->beginTransaction();
// Check balance if buying spot or opening long/short futures (simplified) if ($type === 'spot') {
if ($side === 'buy') {
// Check USDT balance
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ? FOR UPDATE"); $stmt = $db->prepare("SELECT balance FROM users WHERE id = ? FOR UPDATE");
$stmt->execute([$user_id]); $stmt->execute([$user_id]);
$user = $stmt->fetch(); $balance = (float)$stmt->fetchColumn();
if ($side === 'buy' || $type === 'futures') { if ($balance < $total) {
$cost = $type === 'futures' ? $total / $leverage : $total;
if ($user['balance'] < $cost) {
$db->rollBack(); $db->rollBack();
echo json_encode(['success' => false, 'error' => 'Insufficient balance']); echo json_encode(['success' => false, 'error' => '余额不足 (USDT)']);
exit; exit;
} }
// Deduct balance // Deduct USDT
$stmt = $db->prepare("UPDATE users SET balance = balance - ? WHERE id = ?"); $stmt = $db->prepare("UPDATE users SET balance = balance - ? WHERE id = ?");
$stmt->execute([$cost, $user_id]); $stmt->execute([$total, $user_id]);
} else {
// Spot Sell: Check coin balance
$coin_symbol = str_replace('USDT', '', $symbol);
$stmt = $db->prepare("SELECT amount FROM user_assets WHERE user_id = ? AND symbol = ? FOR UPDATE");
$stmt->execute([$user_id, $coin_symbol]);
$asset_amount = (float)$stmt->fetchColumn();
if ($asset_amount < $amount) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => '资产余额不足 (' . $coin_symbol . ')']);
exit;
}
// Deduct coin
$stmt = $db->prepare("UPDATE user_assets SET amount = amount - ? WHERE user_id = ? AND symbol = ?");
$stmt->execute([$amount, $user_id, $coin_symbol]);
}
} else {
// Futures: Deduct margin (USDT)
$margin = $total / $leverage;
$stmt = $db->prepare("SELECT balance FROM users WHERE id = ? FOR UPDATE");
$stmt->execute([$user_id]);
$balance = (float)$stmt->fetchColumn();
if ($balance < $margin) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => '余额不足 (USDT)']);
exit;
}
$stmt = $db->prepare("UPDATE users SET balance = balance - ? WHERE id = ?");
$stmt->execute([$margin, $user_id]);
} }
// Insert order // Insert order
$stmt = $db->prepare("INSERT INTO trading_orders (user_id, symbol, type, side, order_type, price, amount, total, leverage, tp_price, sl_price) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); $stmt = $db->prepare("INSERT INTO trading_orders (user_id, symbol, type, side, order_type, price, amount, total, leverage, tp_price, sl_price, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'open')");
$stmt->execute([$user_id, $symbol, $type, $side, $order_type, $price, $amount, $total, $leverage, $tp_price, $sl_price]); $stmt->execute([$user_id, $symbol, $type, $side, $order_type, $price, $amount, $total, $leverage, $tp_price, $sl_price]);
$db->commit(); $db->commit();

View File

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS user_assets (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
symbol VARCHAR(20) NOT NULL,
amount DECIMAL(30, 10) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY user_symbol (user_id, symbol)
);

View File

@ -7,25 +7,38 @@ if (!isset($_SESSION['user_id'])) {
} }
require_once 'db/config.php'; require_once 'db/config.php';
require_once 'includes/currency_helper.php';
$db = db(); $db = db();
// Check for existing pending orders to force workflow
$stmt = $db->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'matched', 'submitting') ORDER BY id DESC LIMIT 1");
$stmt->execute([$_SESSION['user_id']]);
$pending_order = $stmt->fetch();
if ($pending_order) {
header("Location: matching.php?order_id=" . $pending_order['id']);
exit;
}
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?"); $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]); $stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch(); $user = $stmt->fetch();
$fiat_currencies = [ $fiat_rates = get_fiat_rates();
'USD' => ['name' => 'US Dollar', 'rate' => 1.00], $fiat_currencies_info = [
'EUR' => ['name' => 'Euro', 'rate' => 0.92], 'USD' => 'US Dollar',
'GBP' => ['name' => 'British Pound', 'rate' => 0.79], 'EUR' => 'Euro',
'CNY' => ['name' => 'Chinese Yuan', 'rate' => 7.23], 'GBP' => 'British Pound',
'HKD' => ['name' => 'Hong Kong Dollar', 'rate' => 7.82], 'CNY' => 'Chinese Yuan',
'JPY' => ['name' => 'Japanese Yen', 'rate' => 151.45], 'HKD' => 'Hong Kong Dollar',
'KRW' => ['name' => 'Korean Won', 'rate' => 1350.20], 'JPY' => 'Japanese Yen',
'SGD' => ['name' => 'Singapore Dollar', 'rate' => 1.35], 'KRW' => 'Korean Won',
'TWD' => ['name' => 'Taiwan Dollar', 'rate' => 32.10], 'SGD' => 'Singapore Dollar',
'THB' => ['name' => 'Thai Baht', 'rate' => 36.50], 'TWD' => 'Taiwan Dollar',
'VND' => ['name' => 'Vietnamese Dong', 'rate' => 24800], 'THB' => 'Thai Baht',
'IDR' => ['name' => 'Indonesian Rupiah', 'rate' => 15850], 'VND' => 'Vietnamese Dong',
'MYR' => ['name' => 'Malaysian Ringgit', 'rate' => 4.74], 'IDR' => 'Indonesian Rupiah',
'MYR' => 'Malaysian Ringgit',
]; ];
?> ?>
@ -74,14 +87,14 @@ $fiat_currencies = [
</div> </div>
<div id="fiat-section" style="background: var(--card-bg); padding: 40px; border-radius: 24px; border: 1px solid var(--border-color);"> <div id="fiat-section" style="background: var(--card-bg); padding: 40px; border-radius: 24px; border: 1px solid var(--border-color);">
<form action="matching.php" method="POST" id="fiat-form"> <form action="matching.php" method="POST" id="fiat-form" onsubmit="return handleDeposit(event, 'fiat')">
<input type="hidden" name="type" value="fiat"> <input type="hidden" name="type" value="fiat">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px;">
<div> <div>
<label style="display: block; margin-bottom: 12px; color: var(--text-muted); font-size: 14px;"><?php echo __('select_currency', '选择币种'); ?></label> <label style="display: block; margin-bottom: 12px; color: var(--text-muted); font-size: 14px;"><?php echo __('select_currency', '选择币种'); ?></label>
<select name="currency" id="currency-select" style="width: 100%; padding: 15px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none;"> <select name="currency" id="currency-select" style="width: 100%; padding: 15px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none;">
<?php foreach ($fiat_currencies as $code => $info): ?> <?php foreach ($fiat_rates as $code => $rate): ?>
<option value="<?php echo $code; ?>" data-rate="<?php echo $info['rate']; ?>"><?php echo $code; ?> - <?php echo $info['name']; ?></option> <option value="<?php echo $code; ?>" data-rate="<?php echo $rate; ?>"><?php echo $code; ?> - <?php echo $fiat_currencies_info[$code] ?? $code; ?></option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
</div> </div>
@ -93,8 +106,8 @@ $fiat_currencies = [
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
<div id="rate-display" style="padding: 20px; background: #161a1e; border-radius: 12px; border: 1px dashed var(--border-color); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;"> <div id="rate-display" style="padding: 20px; background: #161a1e; border-radius: 12px; border: 1px dashed var(--border-color); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;">
<div style="color: var(--text-muted); font-size: 12px; margin-bottom: 5px;">Exchange Rate</div> <div style="color: var(--text-muted); font-size: 12px; margin-bottom: 5px;">Real-time Exchange Rate</div>
<div style="font-weight: bold; font-size: 1.1rem;"><span id="rate-value">7.23</span> <span id="rate-currency">CNY</span> = 1 USDT</div> <div style="font-weight: bold; font-size: 1.1rem;"><span id="rate-value">...</span> <span id="rate-currency">...</span> = 1 USDT</div>
</div> </div>
<div id="result-display" style="padding: 20px; background: rgba(79, 172, 254, 0.05); border-radius: 12px; border: 1px solid rgba(79, 172, 254, 0.2); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;"> <div id="result-display" style="padding: 20px; background: rgba(79, 172, 254, 0.05); border-radius: 12px; border: 1px solid rgba(79, 172, 254, 0.2); display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;">
<div style="color: var(--primary-color); font-size: 12px; margin-bottom: 5px;">You will receive</div> <div style="color: var(--primary-color); font-size: 12px; margin-bottom: 5px;">You will receive</div>
@ -110,12 +123,12 @@ $fiat_currencies = [
Please complete the payment within the time limit after matching. Once submitted, our customer service will verify your deposit. Please complete the payment within the time limit after matching. Once submitted, our customer service will verify your deposit.
</p> </p>
</div> </div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-size: 1.1rem; border-radius: 12px;">Continue to Match Account</button> <button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-size: 1.1rem; border-radius: 12px;">Match Account / 匹配充值账户</button>
</form> </form>
</div> </div>
<div id="usdt-section" style="background: var(--card-bg); padding: 40px; border-radius: 24px; border: 1px solid var(--border-color); display: none;"> <div id="usdt-section" style="background: var(--card-bg); padding: 40px; border-radius: 24px; border: 1px solid var(--border-color); display: none;">
<form action="matching.php" method="POST"> <form action="matching.php" method="POST" id="usdt-form" onsubmit="return handleDeposit(event, 'usdt')">
<input type="hidden" name="type" value="usdt"> <input type="hidden" name="type" value="usdt">
<div style="margin-bottom: 30px;"> <div style="margin-bottom: 30px;">
<label style="display: block; margin-bottom: 15px; color: var(--text-muted); font-size: 14px;">Select Network</label> <label style="display: block; margin-bottom: 15px; color: var(--text-muted); font-size: 14px;">Select Network</label>
@ -141,19 +154,147 @@ $fiat_currencies = [
<label style="display: block; margin-bottom: 12px; color: var(--text-muted); font-size: 14px;">Deposit Amount (USDT)</label> <label style="display: block; margin-bottom: 12px; color: var(--text-muted); font-size: 14px;">Deposit Amount (USDT)</label>
<input type="number" name="amount" placeholder="Min. 10" required style="width: 100%; padding: 15px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1.2rem; font-weight: bold; outline: none;"> <input type="number" name="amount" placeholder="Min. 10" required style="width: 100%; padding: 15px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1.2rem; font-weight: bold; outline: none;">
</div> </div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-size: 1.1rem; border-radius: 12px; background: var(--success-color);">Get Address</button> <button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-size: 1.1rem; border-radius: 12px; background: var(--success-color);">Get Address / 获取充值地址</button>
</form> </form>
</div> </div>
</div> </div>
</main> </main>
<!-- Enhanced Deposit Matching Modal -->
<div id="matching-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); display: none; align-items: center; justify-content: center; z-index: 10000; backdrop-filter: blur(10px);">
<div style="background: #161a1e; width: 500px; border-radius: 32px; border: 1px solid rgba(255,255,255,0.1); overflow: hidden; box-shadow: 0 30px 70px rgba(0,0,0,0.8); position: relative;">
<div style="height: 6px; width: 100%; background: #2b3139;">
<div id="modal-progress" style="height: 100%; width: 0%; background: var(--primary-color); transition: width 0.5s;"></div>
</div>
<div style="padding: 30px; background: #1e2329; border-bottom: 1px solid #2b3139; display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 15px;">
<div style="width: 45px; height: 45px; background: var(--primary-color); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; box-shadow: 0 10px 20px rgba(0,82,255,0.3);">
<i class="fas fa-headset"></i>
</div>
<div>
<div style="font-weight: 800; font-size: 18px; letter-spacing: 0.5px;">在线客服 / ONLINE SUPPORT</div>
<div id="matching-status-text" style="font-size: 11px; color: #00c087; font-weight: bold;"><i class="fas fa-circle-notch fa-spin"></i> 正在安全握手 / Establishing Connection...</div>
</div>
</div>
<button onclick="document.getElementById('matching-modal').style.display='none'" style="background: none; border: none; color: #848e9c; cursor: pointer; font-size: 20px;"><i class="fas fa-times"></i></button>
</div>
<div style="padding: 40px; text-align: center;">
<div id="step-visual" style="margin-bottom: 30px; position: relative; height: 100px; display: flex; align-items: center; justify-content: center;">
<div class="pulse-ring-large"></div>
<div class="pulse-ring-small"></div>
<i id="step-icon" class="fas fa-shield-alt" style="font-size: 50px; color: var(--primary-color); position: relative; z-index: 2; transition: 0.3s;"></i>
</div>
<h3 id="step-title" style="margin: 0 0 10px 0; font-size: 1.6rem; font-weight: 800;">账户安全检测中...</h3>
<p id="step-desc" style="color: var(--text-muted); font-size: 14px; line-height: 1.8; margin-bottom: 30px; height: 50px;">
系统正在对您的交易环境进行全方位安全扫描,确保资金链路 100% 可信。
</p>
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05); border-radius: 20px; padding: 25px; margin-bottom: 35px; text-align: left;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 15px;">
<div style="width: 24px; height: 24px; background: rgba(240, 185, 11, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #f0b90b; font-size: 12px;">
<i class="fas fa-lock"></i>
</div>
<div style="font-weight: bold; font-size: 14px; color: #f0b90b;">入金安全协议声明</div>
</div>
<div style="font-size: 12px; color: #848e9c; line-height: 1.8;">
本次充值将由官方承兑商提供实时结算支持。<br>
严禁向任何非系统分配的账户进行转账。<br>
确认订单后,系统将为您开启专属绿色通道。
</div>
</div>
<button id="confirm-deposit-btn" disabled style="width: 100%; padding: 20px; background: #2b3139; border: none; color: #5e6673; border-radius: 16px; font-weight: 800; font-size: 1.1rem; cursor: not-allowed; transition: 0.3s; box-shadow: 0 15px 30px rgba(0,0,0,0.2);">
请等待安全匹配 / PLEASE WAIT...
</button>
</div>
</div>
</div>
<style> <style>
.network-label { padding: 20px; background: #161a1e; border: 1px solid var(--border-color); border-radius: 16px; cursor: pointer; text-align: center; transition: 0.2s; } .network-label { padding: 20px; background: #161a1e; border: 1px solid var(--border-color); border-radius: 16px; cursor: pointer; text-align: center; transition: 0.2s; }
.network-label.active { border-color: var(--success-color); background: rgba(14,203,129,0.05); color: var(--success-color); } .network-label.active { border-color: var(--success-color); background: rgba(14,203,129,0.05); color: var(--success-color); }
.pulse-ring-large { position: absolute; width: 120px; height: 120px; border: 2px solid var(--primary-color); border-radius: 50%; animation: pulse-anim 2s infinite; opacity: 0; }
.pulse-ring-small { position: absolute; width: 80px; height: 80px; border: 1px solid var(--primary-color); border-radius: 50%; animation: pulse-anim 2s infinite 0.5s; opacity: 0; }
@keyframes pulse-anim {
0% { transform: scale(0.6); opacity: 0; }
50% { opacity: 0.5; }
100% { transform: scale(1.4); opacity: 0; }
}
</style> </style>
<script> <script>
let currentFormId = '';
const steps = [
{
title: "安全链路检测中...",
desc: "系统正在对您的交易环境进行全方位安全扫描,确保资金链路 100% 可信。",
icon: "fa-shield-alt",
progress: 33,
status: "Establishing Secure Link..."
},
{
title: "匹配最优承兑商...",
desc: "正在为您筛选当前响应最快、额度最充足的合规承兑商账户。",
icon: "fa-user-check",
progress: 66,
status: "Matching Liquidator..."
},
{
title: "准备就绪",
desc: "安全验证已通过。点击下方按钮,进入专属客服对话窗口获取收款信息。",
icon: "fa-check-double",
progress: 100,
status: "Ready for Connection"
}
];
function handleDeposit(event, type) {
event.preventDefault();
currentFormId = type + '-form';
document.getElementById('matching-modal').style.display = 'flex';
let step = 0;
const interval = setInterval(() => {
if (step >= steps.length) {
clearInterval(interval);
const btn = document.getElementById('confirm-deposit-btn');
btn.disabled = false;
btn.style.background = 'var(--primary-color)';
btn.style.color = 'white';
btn.style.cursor = 'pointer';
btn.innerText = '确认充值订单 / CONFIRM ORDER';
return;
}
const s = steps[step];
document.getElementById('modal-progress').style.width = s.progress + '%';
document.getElementById('matching-status-text').innerHTML = `<i class="fas fa-circle-notch fa-spin"></i> ${s.status}`;
document.getElementById('step-title').innerText = s.title;
document.getElementById('step-desc').innerText = s.desc;
document.getElementById('step-icon').className = `fas ${s.icon}`;
step++;
}, 1500);
return false;
}
document.getElementById('confirm-deposit-btn').onclick = function() {
this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 正在连接客服...';
this.disabled = true;
setTimeout(() => {
document.getElementById(currentFormId).submit();
}, 800);
};
function switchMethod(method) { function switchMethod(method) {
if(method === 'fiat') { if(method === 'fiat') {
document.getElementById('fiat-section').style.display = 'block'; document.getElementById('fiat-section').style.display = 'block';
@ -179,7 +320,7 @@ $fiat_currencies = [
const rate = parseFloat(option.getAttribute('data-rate')); const rate = parseFloat(option.getAttribute('data-rate'));
const amount = parseFloat(amountInput.value) || 0; const amount = parseFloat(amountInput.value) || 0;
rateVal.innerText = rate; rateVal.innerText = rate.toFixed(4);
rateCur.innerText = select.value; rateCur.innerText = select.value;
if (rate > 0) { if (rate > 0) {
@ -191,8 +332,6 @@ $fiat_currencies = [
select.onchange = calculate; select.onchange = calculate;
amountInput.oninput = calculate; amountInput.oninput = calculate;
// Initial calculation
calculate(); calculate();
document.querySelectorAll('.network-label').forEach(label => { document.querySelectorAll('.network-label').forEach(label => {

View File

@ -26,95 +26,62 @@ if ($user_id) {
--input-bg: #1e2329; --input-bg: #1e2329;
} }
body { body { background-color: var(--bg-color); color: var(--text-primary); font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif; margin: 0; overflow: hidden; }
background-color: var(--bg-color); .trading-layout { display: flex; height: calc(100vh - 60px); gap: 1px; background: var(--border-color); }
color: var(--text-primary); .panel { background: var(--panel-bg); display: flex; flex-direction: column; overflow: hidden; }
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);
}
.panel {
background: var(--panel-bg);
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Left: Markets */
.market-panel { width: 280px; flex-shrink: 0; } .market-panel { width: 280px; flex-shrink: 0; }
.search-box { padding: 12px; border-bottom: 1px solid var(--border-color); } .search-box { padding: 12px; border-bottom: 1px solid var(--border-color); }
.search-input-wrapper { position: relative; } .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 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); } .search-input-wrapper i { position: absolute; left: 10px; top: 8px; color: var(--text-secondary); }
.pair-item { display: flex; justify-content: space-between; padding: 10px 12px; cursor: pointer; transition: background 0.2s; border-bottom: 1px solid rgba(255,255,255,0.02); } .pair-item { display: flex; justify-content: space-between; padding: 10px 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:hover { background: rgba(255,255,255,0.05); }
.pair-item.active { background: rgba(79, 172, 254, 0.1); } .pair-item.active { background: rgba(79, 172, 254, 0.1); }
/* Center: Main Column */
.center-panel { flex: 1; overflow-y: auto; } .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); } .info-bar { height: 50px; display: flex; align-items: center; padding: 0 15px; gap: 20px; border-bottom: 1px solid var(--border-color); }
.chart-container { height: 400px; background: var(--bg-color); } .chart-container { height: 400px; background: var(--bg-color); }
.order-form-panel { padding: 20px; background: var(--panel-bg); border-bottom: 1px solid var(--border-color); } .order-form-panel { padding: 20px; background: var(--panel-bg); border-bottom: 1px solid var(--border-color); }
.order-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; } .order-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; }
.margin-controls { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; } .margin-controls { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; }
.ctrl-btn { background: var(--input-bg); border: 1px solid var(--border-color); color: white; padding: 5px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.2s; } .ctrl-btn { background: var(--input-bg); border: 1px solid var(--border-color); color: white; padding: 5px 12px; border-radius: 4px; font-size: 12px; cursor: pointer; transition: all 0.2s; }
.ctrl-btn.active { border-color: var(--accent-color); color: var(--accent-color); } .ctrl-btn.active { border-color: var(--accent-color); color: var(--accent-color); }
.order-type-tabs { display: flex; gap: 15px; margin-left: 10px; } .order-type-tabs { display: flex; gap: 15px; margin-left: 10px; }
.order-type-btn { background: none; border: none; color: var(--text-secondary); font-size: 13px; cursor: pointer; padding: 0; } .order-type-btn { background: none; border: none; color: var(--text-secondary); font-size: 13px; cursor: pointer; padding: 0; }
.order-type-btn.active { color: var(--accent-color); font-weight: bold; } .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: 12px; padding: 8px 12px; } .input-row { background: var(--input-bg); border: 1px solid var(--border-color); border-radius: 4px; display: flex; align-items: center; margin-bottom: 12px; padding: 8px 12px; }
.input-label { color: var(--text-secondary); font-size: 13px; width: 60px; } .input-label { color: var(--text-secondary); font-size: 13px; width: 60px; }
.input-row input { flex: 1; background: transparent; border: none; color: white; text-align: right; outline: none; font-size: 14px; } .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-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; } .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-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; } .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 { 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); } .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; } .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: 25px; text-align: center; } .label-item { color: var(--text-secondary); font-size: 10px; cursor: pointer; user-select: none; width: 25px; text-align: center; }
.label-item.active { color: var(--text-primary); } .label-item.active { color: var(--text-primary); }
.trade-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 20px; } .trade-btns { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 20px; }
.btn-trade { padding: 12px; border: none; border-radius: 4px; font-weight: bold; font-size: 15px; cursor: pointer; color: white; transition: opacity 0.2s; } .btn-trade { padding: 12px; border: none; border-radius: 4px; font-weight: bold; font-size: 15px; cursor: pointer; color: white; transition: opacity 0.2s; }
.btn-trade:active { opacity: 0.8; } .btn-trade:active { opacity: 0.8; }
.btn-trade.buy { background: var(--up-color); } .btn-trade.buy { background: var(--up-color); }
.btn-trade.sell { background: var(--down-color); } .btn-trade.sell { background: var(--down-color); }
.tabs-section { flex: 1; } .tabs-section { flex: 1; }
.tabs-header { display: flex; border-bottom: 1px solid var(--border-color); padding: 0 15px; } .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 { 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); } .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-panel { width: 300px; flex-shrink: 0; }
.ob-header { padding: 10px 15px; font-size: 11px; color: var(--text-secondary); border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; } .ob-header { padding: 10px 15px; font-size: 11px; 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-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-bar { position: absolute; right: 0; top: 0; bottom: 0; z-index: 0; }
.ob-val { z-index: 1; position: relative; } .ob-val { z-index: 1; position: relative; }
input[type=range] { -webkit-appearance: none; width: 100%; background: transparent; margin: 0; padding: 0; } input[type=range] { -webkit-appearance: none; width: 100%; background: transparent; margin: 0; padding: 0; }
input[type=range]:focus { outline: none; } 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-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; } 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; }
.modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 1000; } .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); display: none; align-items: center; justify-content: center; z-index: 1000; }
.modal-box { background: var(--panel-bg); padding: 30px; border-radius: 8px; width: 400px; border: 1px solid var(--border-color); } .modal-box { background: var(--panel-bg); padding: 30px; border-radius: 8px; width: 400px; border: 1px solid var(--border-color); }
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
</style> </style>
<div class="trading-layout"> <div class="trading-layout">
@ -221,23 +188,13 @@ if ($user_id) {
<div class="tabs-section"> <div class="tabs-section">
<div class="tabs-header"> <div class="tabs-header">
<button class="tab-btn active" onclick="switchTab(this, 'positions')">当前持仓</button> <button class="tab-btn active" id="tab-positions" onclick="switchTab(this, 'positions')">当前持仓</button>
<button class="tab-btn" onclick="switchTab(this, 'orders')">当前委托</button> <button class="tab-btn" id="tab-orders" onclick="switchTab(this, 'open')">当前委托</button>
<button class="tab-btn" onclick="switchTab(this, 'tpsl')">止盈止损</button> <button class="tab-btn" id="tab-history" onclick="switchTab(this, 'history')">历史委托</button>
<button class="tab-btn" onclick="switchTab(this, 'history')">历史委托</button>
</div> </div>
<div id="tab-content" style="padding: 0; min-height: 250px;"> <div id="tab-content" style="padding: 0; min-height: 250px;">
<table style="width: 100%; font-size: 12px; border-collapse: collapse;"> <table style="width: 100%; font-size: 12px; border-collapse: collapse;">
<thead style="color: var(--text-secondary); text-align: left; background: rgba(0,0,0,0.1);"> <thead id="data-thead" style="color: var(--text-secondary); text-align: left; background: rgba(0,0,0,0.1);">
<tr>
<th style="padding: 12px;">合约</th>
<th style="padding: 12px;">仓位</th>
<th style="padding: 12px;">开仓价</th>
<th style="padding: 12px;">标记价</th>
<th style="padding: 12px;">强平价</th>
<th style="padding: 12px;">未实现盈亏</th>
<th style="padding: 12px; text-align: right;">操作</th>
</tr>
</thead> </thead>
<tbody id="data-tbody"></tbody> <tbody id="data-tbody"></tbody>
</table> </table>
@ -273,9 +230,10 @@ if ($user_id) {
let currentPair = 'BTCUSDT'; let currentPair = 'BTCUSDT';
let currentPrice = 0; let currentPrice = 0;
let leverage = 20; let leverage = 20;
let balance = <?php echo $balance; ?>; let usdtBalance = <?php echo $balance; ?>;
let marketData = {}; let marketData = {};
let orderType = 'limit'; let orderType = 'limit';
let activeTab = 'positions';
const faceValue = 10; const faceValue = 10;
const pairs = [ const pairs = [
@ -295,12 +253,20 @@ if ($user_id) {
function connectWS() { function connectWS() {
if (ws) ws.close(); if (ws) ws.close();
const streams = pairs.map(p => p.toLowerCase() + '@ticker').join('/'); const streams = pairs.map(p => p.toLowerCase() + '@ticker').join('/');
ws = new WebSocket(`wss://stream.binance.com:9443/ws/${streams}`); ws = new WebSocket(`wss://fstream.binance.com/ws/${streams}`);
ws.onmessage = (e) => { ws.onmessage = (e) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
marketData[data.s] = data; marketData[data.s] = data;
renderPairs(); renderPairs();
if (data.s === currentPair) { if (data.s === currentPair) {
updateUIWithData(data);
}
if (activeTab === 'positions') fetchOrders();
};
}
connectWS();
function updateUIWithData(data) {
currentPrice = parseFloat(data.c); currentPrice = parseFloat(data.c);
document.getElementById('last-price').innerText = currentPrice.toLocaleString(); document.getElementById('last-price').innerText = currentPrice.toLocaleString();
document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)'; document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
@ -313,13 +279,12 @@ if ($user_id) {
document.getElementById('ob-index-price').innerText = (currentPrice * 0.9998).toFixed(2); document.getElementById('ob-index-price').innerText = (currentPrice * 0.9998).toFixed(2);
updateOrderBook(); updateOrderBook();
if (!document.getElementById('order-price').value) { const op = document.getElementById('order-price');
document.getElementById('order-price').value = currentPrice; if (!op.value || op.getAttribute('data-auto') === 'true') {
op.value = currentPrice;
op.setAttribute('data-auto', 'true');
} }
} }
};
}
connectWS();
function renderPairs() { function renderPairs() {
const list = document.getElementById('pairs-list'); const list = document.getElementById('pairs-list');
@ -352,23 +317,25 @@ if ($user_id) {
document.getElementById('current-pair-display').innerText = name + '/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.getElementById('current-logo').src = `https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/${name.toLowerCase()}.png`;
if (currentPrice) { if (marketData[p]) {
document.getElementById('order-price').value = currentPrice; updateUIWithData(marketData[p]);
} else {
document.getElementById('last-price').innerText = '--';
document.getElementById('order-price').value = '';
} }
document.getElementById('order-price').setAttribute('data-auto', 'true');
document.getElementById('order-amount').value = ''; document.getElementById('order-amount').value = '';
document.getElementById('order-slider').value = 0; document.getElementById('order-slider').value = 0;
document.getElementById('order-cost').innerText = '0.00'; document.getElementById('order-cost').innerText = '0.00';
updateSliderMarks(0); updateSliderMarks(0);
initChart(p); initChart(p);
renderPairs(); renderPairs();
} }
function setOrderType(type) { function setOrderType(type) {
orderType = type; orderType = type;
document.querySelectorAll('.order-type-btn').forEach(btn => { document.querySelectorAll('.order-type-btn').forEach(btn => btn.classList.toggle('active', btn.innerText.includes(type === 'limit' ? '限价' : '市价')));
btn.classList.toggle('active', btn.innerText.includes(type === 'limit' ? '限价' : '市价'));
});
document.getElementById('price-row').style.display = type === 'limit' ? 'flex' : 'none'; document.getElementById('price-row').style.display = type === 'limit' ? 'flex' : 'none';
document.getElementById('market-price-row').style.display = type === 'market' ? 'flex' : 'none'; document.getElementById('market-price-row').style.display = type === 'market' ? 'flex' : 'none';
updateFromSlider(document.getElementById('order-slider').value); updateFromSlider(document.getElementById('order-slider').value);
@ -378,9 +345,10 @@ if ($user_id) {
const asks = document.getElementById('asks-list'); const asks = document.getElementById('asks-list');
const bids = document.getElementById('bids-list'); const bids = document.getElementById('bids-list');
let asksHtml = ''; let bidsHtml = ''; let asksHtml = ''; let bidsHtml = '';
const step = currentPrice * 0.0001;
for(let i=0; i<15; i++) { for(let i=0; i<15; i++) {
const askPrice = currentPrice * (1 + (i + 1) * 0.0001); const askPrice = currentPrice + (i + 1) * step;
const bidPrice = currentPrice * (1 - (i + 1) * 0.0001); const bidPrice = currentPrice - (i + 1) * step;
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(1)}</span><span class="ob-val">${(Math.random()*200).toFixed(0)}</span></div>`; 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(1)}</span><span class="ob-val">${(Math.random()*200).toFixed(0)}</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(1)}</span><span class="ob-val">${(Math.random()*200).toFixed(0)}</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(1)}</span><span class="ob-val">${(Math.random()*200).toFixed(0)}</span></div>`;
} }
@ -389,7 +357,7 @@ if ($user_id) {
function updateFromSlider(val) { function updateFromSlider(val) {
val = parseFloat(val); val = parseFloat(val);
const cost = balance * (val / 100); const cost = usdtBalance * (val / 100);
const amount = Math.floor((cost * leverage) / faceValue); const amount = Math.floor((cost * leverage) / faceValue);
document.getElementById('order-amount').value = amount > 0 ? amount : ''; document.getElementById('order-amount').value = amount > 0 ? amount : '';
document.getElementById('order-cost').innerText = cost.toFixed(2); document.getElementById('order-cost').innerText = cost.toFixed(2);
@ -400,28 +368,24 @@ if ($user_id) {
const amount = parseFloat(document.getElementById('order-amount').value) || 0; const amount = parseFloat(document.getElementById('order-amount').value) || 0;
const cost = (amount * faceValue) / leverage; const cost = (amount * faceValue) / leverage;
document.getElementById('order-cost').innerText = cost.toFixed(2); document.getElementById('order-cost').innerText = cost.toFixed(2);
let percentage = usdtBalance > 0 ? (cost / usdtBalance) * 100 : 0;
let percentage = balance > 0 ? (cost / balance) * 100 : 0;
percentage = Math.min(Math.max(percentage, 0), 100); percentage = Math.min(Math.max(percentage, 0), 100);
document.getElementById('order-slider').value = percentage; document.getElementById('order-slider').value = percentage;
updateSliderMarks(percentage); updateSliderMarks(percentage);
} }
function updateSliderMarks(val) { function updateSliderMarks(val) {
document.querySelectorAll('.mark-dot').forEach((m, i) => { document.querySelectorAll('.mark-dot').forEach((m, i) => { if (i * 25 <= val) m.classList.add('active'); else m.classList.remove('active'); });
if (i * 25 <= val) m.classList.add('active'); else m.classList.remove('active'); document.querySelectorAll('.label-item').forEach((l, i) => { if (Math.abs(i * 25 - val) < 5) l.classList.add('active'); else l.classList.remove('active'); });
});
document.querySelectorAll('.label-item').forEach((l, i) => {
if (Math.abs(i * 25 - val) < 5) l.classList.add('active'); else l.classList.remove('active');
});
} }
function setSlider(val) { function setSlider(val) { document.getElementById('order-slider').value = val; updateFromSlider(val); }
document.getElementById('order-slider').value = val;
updateFromSlider(val);
}
document.getElementById('order-amount').addEventListener('input', updateFromInputs); document.getElementById('order-amount').addEventListener('input', updateFromInputs);
document.getElementById('order-price').addEventListener('input', (e) => {
e.target.setAttribute('data-auto', 'false');
updateFromInputs();
});
function showLevModal() { function showLevModal() {
document.getElementById('lev-range').value = leverage; document.getElementById('lev-range').value = leverage;
@ -438,83 +402,116 @@ if ($user_id) {
} }
function setMargin(type) { function setMargin(type) {
document.querySelectorAll('.ctrl-btn').forEach(btn => { document.querySelectorAll('.ctrl-btn').forEach(btn => { if (btn.innerText.includes('仓')) btn.classList.toggle('active', btn.id.includes(type)); });
if (btn.innerText.includes('仓')) {
btn.classList.toggle('active', btn.id.includes(type));
}
});
} }
async function placeOrder(side) { async function placeOrder(side) {
const amount = parseFloat(document.getElementById('order-amount').value); const amount = parseFloat(document.getElementById('order-amount').value);
if (!amount) return alert('请输入数量'); if (!amount) return alert('请输入数量');
const price = orderType === 'limit' ? parseFloat(document.getElementById('order-price').value || currentPrice) : currentPrice; const price = orderType === 'limit' ? parseFloat(document.getElementById('order-price').value || currentPrice) : currentPrice;
const response = await fetch('api/place_order.php', { const response = await fetch('api/place_order.php', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ body: JSON.stringify({
symbol: currentPair, type: 'futures', side: side, symbol: currentPair, type: 'futures', side: side,
order_type: orderType, order_type: orderType, price: price, amount: amount, leverage: leverage, total: amount * faceValue
price: price, amount: amount, leverage: leverage, total: price * amount
}) })
}); });
const res = await response.json(); const res = await response.json();
if (res.success) { alert('下单成功'); fetchOrders(); updateBalance(); } else { alert('失败: ' + res.error); }
}
async function updateBalance() {
const resp = await fetch('api/get_assets.php');
const res = await resp.json();
if (res.success) { if (res.success) {
alert('下单成功'); const usdt = res.data.find(a => a.symbol === 'USDT');
fetchOrders(); if (usdt) {
} else { usdtBalance = usdt.amount;
alert('失败: ' + res.error); document.getElementById('available-bal').innerText = usdtBalance.toFixed(2);
}
} }
} }
async function fetchOrders() { async function fetchOrders() {
const response = await fetch('api/get_orders.php?type=futures&status=open'); const response = await fetch(`api/get_orders.php?type=futures&status=${activeTab}`);
const res = await response.json(); const res = await response.json();
const tbody = document.getElementById('data-tbody'); const tbody = document.getElementById('data-tbody');
const thead = document.getElementById('data-thead');
if (activeTab === 'positions') {
thead.innerHTML = `<tr><th style="padding: 12px;">合约</th><th style="padding: 12px;">仓位</th><th style="padding: 12px;">开仓价</th><th style="padding: 12px;">当前价</th><th style="padding: 12px;">强平价</th><th style="padding: 12px;">未实现盈亏</th><th style="padding: 12px; text-align: right;">操作</th></tr>`;
} else {
thead.innerHTML = `<tr><th style="padding: 12px;">时间</th><th style="padding: 12px;">合约</th><th style="padding: 12px;">方向</th><th style="padding: 12px;">委托价</th><th style="padding: 12px;">数量(张)</th><th style="padding: 12px;">状态</th><th style="padding: 12px; text-align: right;">操作</th></tr>`;
}
if (res.success && res.data.length > 0) { if (res.success && res.data.length > 0) {
let html = ''; let html = '';
res.data.forEach(o => { res.data.forEach(o => {
if (activeTab === 'positions') {
const price = marketData[o.symbol] ? parseFloat(marketData[o.symbol].c) : o.price;
const nominal = o.amount * faceValue;
let pnl = 0;
if (o.side === 'buy') pnl = (price / o.price - 1) * nominal;
else pnl = (1 - price / o.price) * nominal;
const color = o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)'; const color = o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)';
html += ` html += `
<tr style="border-bottom: 1px solid var(--border-color);"> <tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 12px;"><div style="font-weight: bold;">${o.symbol}</div><div style="font-size: 10px; color: var(--text-secondary);">永续</div></td> <td style="padding: 12px;"><div style="font-weight: bold;">${o.symbol}</div><div style="font-size: 10px; color: var(--text-secondary);">永续</div></td>
<td style="padding: 12px;"><span style="color: ${color};">${o.side === 'buy' ? '多' : '空'} ${o.leverage}x</span><br>${o.amount} </td> <td style="padding: 12px;"><span style="color: color;">${o.side === 'buy' ? '多' : '空'} ${o.leverage}x</span><br>${o.amount} </td>
<td style="padding: 12px;">${parseFloat(o.price).toLocaleString()}</td> <td style="padding: 12px;">${parseFloat(o.price).toLocaleString()}</td>
<td style="padding: 12px;">${currentPrice.toLocaleString()}</td> <td style="padding: 12px;">${price.toLocaleString()}</td>
<td style="padding: 12px;">${(o.price * 0.85).toFixed(1)}</td> <td style="padding: 12px;">${(o.side === 'buy' ? o.price * (1 - 0.9/o.leverage) : o.price * (1 + 0.9/o.leverage)).toFixed(2)}</td>
<td style="padding: 12px; color: var(--up-color);">+0.00</td> <td style="padding: 12px; color: ${pnl >= 0 ? 'var(--up-color)' : 'var(--down-color)'}">${(pnl >= 0 ? '+' : '') + pnl.toFixed(2)} USDT</td>
<td style="padding: 12px; text-align: right;"> <td style="padding: 12px; text-align: right;">
<button onclick="closePosition(${o.id})" style="background: none; border: 1px solid var(--text-secondary); color: white; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;">平仓</button> <button onclick="closePosition(${o.id})" style="background: none; border: 1px solid var(--text-secondary); color: white; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px;">平仓</button>
</td> </td>
</tr> </tr>
`; `;
} else {
html += `
<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 12px;">${o.created_at}</td>
<td style="padding: 12px; font-weight: bold;">${o.symbol}</td>
<td style="padding: 12px; color: ${o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)'}">${o.side === 'buy' ? '开多' : '开空'}</td>
<td style="padding: 12px;">${parseFloat(o.price).toLocaleString()}</td>
<td style="padding: 12px;">${o.amount}</td>
<td style="padding: 12px;">${o.status === 'open' ? '委托中' : '已完成'}</td>
<td style="padding: 12px; text-align: right;">
${o.status === 'open' ? `<button onclick="closePosition(${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; tbody.innerHTML = html;
} else { } else {
tbody.innerHTML = '<tr><td colspan="7" style="text-align: center; padding: 50px; color: var(--text-secondary); opacity: 0.5;">暂无持仓</td></tr>'; tbody.innerHTML = `<tr><td colspan="7" style="text-align: center; padding: 50px; color: var(--text-secondary); opacity: 0.5;">暂无记录</td></tr>`;
} }
} }
function switchTab(btn, tab) { function switchTab(btn, tab) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active'); btn.classList.add('active');
activeTab = tab;
fetchOrders(); fetchOrders();
} }
async function closePosition(id) { async function closePosition(id) {
if (!confirm('确定平仓?')) return; if (!confirm('确定执行此操作?')) return;
const resp = await fetch('api/cancel_order.php', { const resp = await fetch('api/cancel_order.php', {
method: 'POST', method: 'POST',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({order_id: id}) body: JSON.stringify({order_id: id})
}); });
if ((await resp.json()).success) fetchOrders(); if ((await resp.json()).success) { fetchOrders(); updateBalance(); }
} }
document.getElementById('market-search').addEventListener('input', renderPairs); document.getElementById('market-search').addEventListener('input', renderPairs);
fetchOrders(); fetchOrders();
setInterval(fetchOrders, 5000); updateBalance();
setInterval(() => { if (activeTab === 'positions') fetchOrders(); }, 3000);
</script> </script>
<?php include 'footer.php'; ?> <?php include 'footer.php'; ?>

View File

@ -0,0 +1,33 @@
<?php
function get_fiat_rates() {
$fiat_currencies = [
'USD' => 1.00,
'EUR' => 0.92,
'GBP' => 0.79,
'CNY' => 7.23,
'HKD' => 7.82,
'JPY' => 151.45,
'KRW' => 1350.20,
'SGD' => 1.35,
'TWD' => 32.10,
'THB' => 36.50,
'VND' => 24800,
'IDR' => 15850,
'MYR' => 4.74,
];
$api_url = 'https://api.exchangerate-api.com/v4/latest/USD';
$ctx = stream_context_create(['http' => ['timeout' => 2]]);
$json = @file_get_contents($api_url, false, $ctx);
if ($json) {
$data = json_decode($json, true);
if (isset($data['rates'])) {
foreach ($fiat_currencies as $code => $rate) {
if (isset($data['rates'][$code])) {
$fiat_currencies[$code] = $data['rates'][$code];
}
}
}
}
return $fiat_currencies;
}

View File

@ -7,21 +7,28 @@ if (!isset($_SESSION['user_id'])) {
} }
require_once 'db/config.php'; require_once 'db/config.php';
require_once 'includes/currency_helper.php';
$user_id = $_SESSION['user_id']; $user_id = $_SESSION['user_id'];
// Handle initial order creation from deposit.php // Step 1: Handle initial order creation from deposit.php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount']) && !isset($_GET['order_id'])) {
$amount = $_POST['amount']; $amount = (float)$_POST['amount'];
$type = $_POST['type'] ?? 'fiat'; $type = $_POST['type'] ?? 'fiat';
$currency = $_POST['currency'] ?? 'USDT'; $currency = $_POST['currency'] ?? 'USDT';
$fiat_rates = get_fiat_rates();
$rate = $fiat_rates[$currency] ?? 1.0;
$usdt_amount = ($rate > 0) ? ($amount / $rate) : $amount;
$expires_at = date('Y-m-d H:i:s', strtotime('+30 minutes')); $expires_at = date('Y-m-d H:i:s', strtotime('+30 minutes'));
$stmt = db()->prepare("INSERT INTO fiat_orders (user_id, amount, currency, status, expires_at, created_at) VALUES (?, ?, ?, 'matching', ?, CURRENT_TIMESTAMP)"); // Status starts as 'matching' (待匹配)
$stmt->execute([$user_id, $amount, $currency, $expires_at]); $stmt = db()->prepare("INSERT INTO fiat_orders (user_id, amount, usdt_amount, exchange_rate, currency, status, expires_at, created_at) VALUES (?, ?, ?, ?, ?, 'matching', ?, CURRENT_TIMESTAMP)");
$stmt->execute([$user_id, $amount, $usdt_amount, $rate, $currency, $expires_at]);
$order_id = db()->lastInsertId(); $order_id = db()->lastInsertId();
$msg = "[RECHARGE_NOTIFICATION] 用户 {$_SESSION['email']} (UID: {$_SESSION['uid']}) 正在发起充值匹配: $amount $currency。订单号: #$order_id。请尽快提供收款账户详情。"; // Immediate notification to admin (用户一点击确认,后台立刻收到请求)
$msg = "[RECHARGE_NOTIFICATION] 🚨 新充值申请! 用户 {$_SESSION['email']} (UID: {$_SESSION['uid']}) 正在等待匹配: **$amount $currency**。请尽快分配收款账户。";
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)"); $stmt = db()->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)");
$stmt->execute([$user_id, $msg]); $stmt->execute([$user_id, $msg]);
@ -49,9 +56,13 @@ if ($order['status'] === 'completed') {
exit; exit;
} }
// Step 6: Handle Proof Submission (已提交凭证)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['proof'])) { if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['proof'])) {
$file = $_FILES['proof']; $file = $_FILES['proof'];
$ext = pathinfo($file['name'], PATHINFO_EXTENSION); $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
if (in_array($ext, $allowed)) {
$filename = 'proof_' . $order_id . '_' . time() . '.' . $ext; $filename = 'proof_' . $order_id . '_' . time() . '.' . $ext;
$target = 'assets/images/proofs/' . $filename; $target = 'assets/images/proofs/' . $filename;
@ -61,7 +72,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['proof'])) {
$stmt = db()->prepare("UPDATE fiat_orders SET proof_image = ?, status = 'submitting' WHERE id = ?"); $stmt = db()->prepare("UPDATE fiat_orders SET proof_image = ?, status = 'submitting' WHERE id = ?");
$stmt->execute(['assets/images/proofs/' . $filename, $order_id]); $stmt->execute(['assets/images/proofs/' . $filename, $order_id]);
$msg = "[RECHARGE_SUBMITTED] 用户已上传支付凭证并确认转账 (订单 #$order_id)。请进入管理后台核对入账"; $msg = "[RECHARGE_SUBMITTED] ✅ 用户已完成转账并提交凭证 (订单 #$order_id)。请进入后台核对";
$stmt = db()->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)"); $stmt = db()->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)");
$stmt->execute([$user_id, $msg]); $stmt->execute([$user_id, $msg]);
@ -69,241 +80,240 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['proof'])) {
exit; exit;
} }
} }
}
$stmt = db()->prepare("SELECT * FROM messages WHERE user_id = ? AND message NOT LIKE '[RECHARGE_%' ORDER BY created_at ASC"); $stmt = db()->prepare("SELECT * FROM messages WHERE user_id = ? AND message NOT LIKE '[RECHARGE_%' ORDER BY created_at ASC");
$stmt->execute([$user_id]); $stmt->execute([$user_id]);
$messages = $stmt->fetchAll(); $messages = $stmt->fetchAll();
?> ?>
<main style="background: #0b0e11; min-height: calc(100vh - 64px); padding: 20px;"> <main style="background: #0b0e11; min-height: calc(100vh - 64px); padding: 40px 20px;">
<div style="max-width: 1200px; margin: 0 auto; background: #161a1e; border: 1px solid #2b3139; border-radius: 24px; height: 85vh; display: grid; grid-template-columns: 1fr 380px; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.5);">
<!-- Main Panel: Deposit Matching Workflow (The "Enlarged Dialog") --> <!-- Step 4: MANDATORY LOCK OVERLAY for matched status (已匹配) -->
<div style="display: flex; flex-direction: column; border-right: 1px solid #2b3139; background: #0b0e11;"> <?php if ($order['status'] === 'matched' || ($order['bank_account_info'] && $order['status'] === 'matching')): ?>
<div style="padding: 20px 30px; border-bottom: 1px solid #2b3139; background: #161a1e; display: flex; align-items: center; justify-content: space-between;"> <div id="mandatory-lock" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #0b0e11; z-index: 100000; overflow-y: auto; padding: 20px;">
<div style="display: flex; align-items: center; gap: 15px;"> <div style="max-width: 600px; margin: 20px auto;">
<div style="width: 45px; height: 45px; background: var(--primary-color); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; color: white;"> <div style="background: #161a1e; border-radius: 32px; border: 1px solid rgba(255,255,255,0.1); overflow: hidden; box-shadow: 0 40px 100px rgba(0,0,0,0.8);">
<i class="fas fa-wallet"></i>
<!-- Success Header -->
<div style="background: linear-gradient(135deg, #00c087, #008960); padding: 40px 30px; text-align: center; color: white; position: relative; overflow: hidden;">
<div style="position: absolute; top: -50px; right: -50px; width: 150px; height: 150px; background: rgba(255,255,255,0.05); border-radius: 50%;"></div>
<div style="width: 80px; height: 80px; background: rgba(255,255,255,0.2); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px; border: 3px solid white; box-shadow: 0 10px 20px rgba(0,0,0,0.2);">
<i class="fas fa-check" style="font-size: 35px;"></i>
</div> </div>
<div> <h2 style="margin: 0; font-size: 2rem; font-weight: 900; letter-spacing: -0.5px;">账户匹配成功</h2>
<div style="font-weight: 800; font-size: 16px;">充值订单匹配 / Recharge Matching</div> <p style="margin: 10px 0 0; opacity: 0.9; font-size: 15px; font-weight: 500;">请按以下信息完成转账并上传凭证</p>
<div style="font-size: 11px; color: #848e9c;">订单号: #<?php echo $order_id; ?></div>
</div> </div>
<div style="padding: 40px;">
<!-- Step Indicator -->
<div style="display: flex; justify-content: space-between; margin-bottom: 40px; position: relative;">
<div style="position: absolute; top: 12px; left: 0; width: 100%; height: 2px; background: rgba(255,255,255,0.05); z-index: 1;"></div>
<div style="z-index: 2; text-align: center; width: 33%;">
<div style="width: 26px; height: 26px; background: #00c087; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 8px; font-size: 12px; font-weight: bold; border: 4px solid #161a1e;">1</div>
<div style="font-size: 11px; color: #00c087; font-weight: bold;">查看账户</div>
</div> </div>
<div style="text-align: right;"> <div style="z-index: 2; text-align: center; width: 33%;">
<div id="countdown" style="font-size: 1.5rem; font-weight: 900; color: #f0b90b; font-family: 'Roboto Mono', monospace;">30:00</div> <div id="step-dot-2" style="width: 26px; height: 26px; background: #2b3139; color: #848e9c; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 8px; font-size: 12px; font-weight: bold; border: 4px solid #161a1e;">2</div>
<div style="font-size: 10px; color: #848e9c;">剩余有效时间 / Time Left</div> <div id="step-text-2" style="font-size: 11px; color: #848e9c; font-weight: bold;">完成支付</div>
</div>
<div style="z-index: 2; text-align: center; width: 33%;">
<div id="step-dot-3" style="width: 26px; height: 26px; background: #2b3139; color: #848e9c; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 8px; font-size: 12px; font-weight: bold; border: 4px solid #161a1e;">3</div>
<div id="step-text-3" style="font-size: 11px; color: #848e9c; font-weight: bold;">上传凭证</div>
</div> </div>
</div> </div>
<div style="flex: 1; overflow-y: auto; padding: 40px;"> <!-- Amount Highlight -->
<?php if ($order['status'] === 'matching' && !$order['bank_account_info']): ?> <div style="background: rgba(240, 185, 11, 0.05); padding: 30px; border-radius: 24px; border: 1px solid rgba(240, 185, 11, 0.2); text-align: center; margin-bottom: 35px; box-shadow: inset 0 0 20px rgba(0,0,0,0.2);">
<div style="text-align: center; max-width: 600px; margin: 40px auto;"> <div style="font-size: 12px; color: #f0b90b; font-weight: bold; margin-bottom: 15px; text-transform: uppercase; letter-spacing: 2px;">应转账金额 / Amount Due</div>
<div class="matching-loader" style="margin-bottom: 30px;"> <div style="font-size: 3rem; font-weight: 900; color: white; display: flex; align-items: baseline; justify-content: center; gap: 10px;">
<div class="ring"></div> <?php echo number_format($order['amount'], 2); ?> <span style="font-size: 20px; color: #f0b90b;"><?php echo $order['currency']; ?></span>
<div class="ring"></div>
<div class="ring"></div>
<i class="fas fa-search-dollar" style="font-size: 40px; color: var(--primary-color);"></i>
</div>
<h2 style="font-size: 2rem; margin-bottom: 10px; color: white;">正在为您匹配充值账户...</h2>
<p style="color: #848e9c; margin-bottom: 40px;">匹配过程中请勿关闭此页面,平均匹配时间 1-3 分钟。</p>
<div style="background: #161a1e; border-radius: 20px; padding: 30px; border: 1px solid #2b3139; text-align: left;">
<h4 style="margin-top: 0; margin-bottom: 20px; color: #f0b90b;"><i class="fas fa-exclamation-triangle"></i> 转账说明 & 安全提示</h4>
<div style="font-size: 14px; color: #EAECEF; line-height: 2;">
<p> 系统正在随机为您匹配承兑商账户,请稍等片刻。</p>
<p> 匹配成功后将显示收款银行卡、姓名及账号等详细信息。</p>
<p> 请务必使用<b>本人姓名</b>对应的账户进行转账,否则无法入账。</p>
<p> 转账时<b>严禁备注</b>任何关于加密货币、USDT等字眼。</p>
</div>
</div> </div>
<div style="font-size: 13px; color: #848e9c; margin-top: 15px;"><i class="fas fa-exclamation-circle"></i> 请务必转入精确金额,包含小数点</div>
</div> </div>
<?php elseif ($order['bank_account_info'] && $order['status'] !== 'submitting' && $order['status'] !== 'completed'): ?> <!-- Bank Details -->
<div style="max-width: 700px; margin: 0 auto;"> <div style="background: #0b0e11; padding: 30px; border-radius: 24px; border: 1px solid rgba(255,255,255,0.05); margin-bottom: 35px; position: relative;">
<div style="background: #161a1e; border-radius: 24px; border: 1px solid #2b3139; overflow: hidden;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px;">
<div style="background: rgba(0, 192, 135, 0.1); padding: 20px; display: flex; align-items: center; gap: 15px; border-bottom: 1px solid #2b3139;"> <div style="font-weight: 800; color: #848e9c; font-size: 14px; text-transform: uppercase; letter-spacing: 1px;"><i class="fas fa-university" style="margin-right: 8px;"></i> 收款账户信息</div>
<i class="fas fa-check-circle" style="color: #00c087; font-size: 24px;"></i> <button onclick="copyToClipboard('<?php echo str_replace(["\r", "\n"], ' ', $order['bank_account_info']); ?>')" style="background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: white; padding: 8px 16px; border-radius: 12px; font-size: 12px; cursor: pointer; transition: 0.2s; font-weight: bold;">
<div style="font-weight: bold; color: #00c087;">账户匹配成功,请在倒计时结束前完成转账</div>
</div>
<div style="padding: 30px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 30px;">
<div style="background: #0b0e11; padding: 15px; border-radius: 12px; border: 1px solid #2b3139;">
<div style="font-size: 11px; color: #848e9c; margin-bottom: 5px;">充值金额 / Amount</div>
<div style="font-size: 1.5rem; font-weight: 900; color: #f0b90b;"><?php echo number_format($order['amount'], 2); ?> <span style="font-size: 14px;"><?php echo $order['currency']; ?></span></div>
</div>
<div style="background: #0b0e11; padding: 15px; border-radius: 12px; border: 1px solid #2b3139;">
<div style="font-size: 11px; color: #848e9c; margin-bottom: 5px;">收款方式 / Method</div>
<div style="font-size: 1.1rem; font-weight: bold;">银行卡转账</div>
</div>
</div>
<div style="background: #0b0e11; padding: 25px; border-radius: 16px; border: 2px solid #2b3139; margin-bottom: 30px; position: relative;">
<div style="font-size: 12px; color: var(--primary-color); font-weight: bold; margin-bottom: 15px;">收款账户信息 / RECEIVING ACCOUNT</div>
<div style="font-size: 1.2rem; line-height: 2; color: white; font-family: 'Roboto Mono', monospace;">
<?php echo nl2br(htmlspecialchars($order['bank_account_info'])); ?>
</div>
<button onclick="copyToClipboard('<?php echo str_replace(["\r", "\n"], ' ', $order['bank_account_info']); ?>')" style="position: absolute; top: 20px; right: 20px; background: #2b3139; border: none; color: white; padding: 5px 10px; border-radius: 6px; font-size: 12px; cursor: pointer;">
<i class="fas fa-copy"></i> 复制 <i class="fas fa-copy"></i> 复制
</button> </button>
</div> </div>
<div style="font-size: 1.2rem; line-height: 2; color: white; font-family: 'Roboto Mono', monospace; background: rgba(255,255,255,0.02); padding: 20px; border-radius: 16px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-bottom: 30px;"> <?php echo nl2br(htmlspecialchars($order['bank_account_info'])); ?>
<div style="font-size: 13px; color: #848e9c; line-height: 1.8;">
<h5 style="color: white; margin-bottom: 10px;">安全提示:</h5>
请勿在此聊天框外进行任何转账。<br>
官方客服不会向您索要密码。<br>
请确认收款人姓名后再汇款。
</div>
<div style="font-size: 13px; color: #848e9c; line-height: 1.8;">
<h5 style="color: white; margin-bottom: 10px;">转账说明:</h5>
转账完成后请务必保存截图。<br>
点击下方按钮上传凭证确认。<br>
资金通常在 15 分钟内到账。
</div> </div>
</div> </div>
<form method="POST" enctype="multipart/form-data" id="proof-form"> <!-- Action Buttons -->
<div style="background: #0b0e11; padding: 25px; border-radius: 16px; border: 2px dashed #2b3139; text-align: center; cursor: pointer; transition: 0.3s;" id="upload-zone" onclick="document.getElementById('proof-file').click()"> <div id="action-area">
<div id="upload-idle"> <button onclick="showUploadForm()" style="width: 100%; padding: 24px; background: #00c087; color: white; border: none; border-radius: 20px; font-size: 1.3rem; font-weight: 900; cursor: pointer; box-shadow: 0 15px 35px rgba(0,192,135,0.4); transition: 0.3s; transform: perspective(1px) translateZ(0);">
<i class="fas fa-cloud-upload-alt" style="font-size: 30px; color: #5e6673; margin-bottom: 10px;"></i> 我已完成转账,去上传凭证
<div style="font-weight: bold; margin-bottom: 5px; font-size: 14px;">上传支付凭证 / Upload Voucher</div> </button>
<div style="color: #f0b90b; font-size: 11px;">转账完成后点击此处上传凭证截图</div> <p style="text-align: center; color: #5e6673; font-size: 12px; margin-top: 20px;">
</div> <i class="fas fa-lock"></i> 资金安全由区块链技术加密保护
<div id="upload-preview" style="display: none;"> </p>
<i class="fas fa-file-image" style="font-size: 30px; color: #00c087; margin-bottom: 10px;"></i>
<div style="font-weight: bold; color: #00c087; font-size: 14px;">凭证已选择</div>
<div id="filename-display" style="font-size: 12px; color: #848e9c;"></div>
</div>
<input type="file" name="proof" id="proof-file" accept="image/*" style="display: none;" onchange="handleFileSelect(this)">
</div> </div>
<button type="submit" id="submit-btn" disabled class="btn-primary" style="width: 100%; padding: 18px; font-size: 1.1rem; border-radius: 12px; margin-top: 20px; opacity: 0.5; cursor: not-allowed; font-weight: bold;"> <!-- Step 5: Hidden Upload Form (上传转账凭证界面) -->
完成转账 / Transfer Completed <div id="upload-form-container" style="display: none; animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1);">
<div style="text-align: center; margin-bottom: 30px;">
<h3 style="margin: 0 0 10px 0; font-size: 1.5rem; font-weight: 900;">上传转账凭证</h3>
<p style="color: #848e9c; font-size: 14px;">请上传包含交易单号、金额、时间的支付截图</p>
</div>
<form method="POST" enctype="multipart/form-data" id="main-proof-form">
<div id="upload-dropzone" style="background: #0b0e11; border: 2px dashed rgba(255,255,255,0.1); border-radius: 24px; padding: 50px 30px; text-align: center; cursor: pointer; margin-bottom: 30px; transition: 0.3s;" onclick="document.getElementById('file-input').click()">
<div id="file-idle">
<div style="width: 80px; height: 80px; background: rgba(132, 142, 156, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px;">
<i class="fas fa-cloud-upload-alt" style="font-size: 40px; color: #848e9c;"></i>
</div>
<div style="font-weight: 900; font-size: 18px; color: white;">点击选择转账截图</div>
<div style="font-size: 13px; color: #5e6673; margin-top: 8px;">支持 JPG, PNG, JPEG 格式</div>
</div>
<div id="file-selected" style="display: none;">
<div style="width: 80px; height: 80px; background: rgba(0, 192, 135, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 20px;">
<i class="fas fa-file-image" style="font-size: 40px; color: #00c087;"></i>
</div>
<div style="font-weight: 900; font-size: 18px; color: #00c087;" id="selected-name"></div>
<div style="font-size: 13px; color: #848e9c; margin-top: 8px;">文件已就绪,点击重新选择</div>
</div>
<input type="file" name="proof" id="file-input" accept="image/*" style="display: none;" onchange="updateFilePreview(this)">
</div>
<!-- 逻辑限制:没有图片不允许提交 -->
<button type="submit" id="final-submit" disabled style="width: 100%; padding: 22px; background: #2b3139; color: #5e6673; border: none; border-radius: 20px; font-size: 1.2rem; font-weight: 900; cursor: not-allowed; transition: 0.3s; box-shadow: 0 10px 30px rgba(0,0,0,0.2);">
确认提交并通知客服
</button>
<button type="button" onclick="hideUploadForm()" style="width: 100%; margin-top: 20px; background: transparent; color: #848e9c; border: none; font-size: 14px; cursor: pointer; font-weight: bold;">
<i class="fas fa-arrow-left"></i> 返回查看账户
</button> </button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<?php else: ?>
<div style="text-align: center; padding: 60px 0;">
<div style="width: 120px; height: 120px; background: rgba(240, 185, 11, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 30px;">
<i class="fas fa-history" style="font-size: 50px; color: #f0b90b; animation: pulse-scale 2s infinite;"></i>
</div>
<h2 style="font-size: 2.2rem; margin-bottom: 20px; color: white;">充值完成请等待确认</h2>
<p style="color: #848e9c; line-height: 2; max-width: 500px; margin: 0 auto 40px; font-size: 15px;">
您的支付凭证已成功提交。客服人员正在核实资金到账情况,请稍候。核实完成后,本页面将自动跳转至您的资产页面。
</p>
<div style="display: flex; gap: 20px; justify-content: center;">
<a href="profile.php" style="background: var(--primary-color); color: white; padding: 15px 40px; border-radius: 12px; text-decoration: none; font-weight: bold;">查看我的资产</a>
<button onclick="location.reload()" style="background: transparent; border: 1px solid #2b3139; color: white; padding: 15px 40px; border-radius: 12px; font-weight: bold; cursor: pointer;">刷新状态</button>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div>
</div>
<!-- Right Side: Chat Sidebar (The "Dialog" sidebar) --> <div style="max-width: 1200px; margin: 0 auto; background: #161a1e; border: 1px solid rgba(255,255,255,0.08); border-radius: 32px; height: 85vh; display: grid; grid-template-columns: 1fr 400px; overflow: hidden; box-shadow: 0 40px 100px rgba(0,0,0,0.6);">
<div style="display: flex; flex-direction: column; background: #161a1e;">
<div style="padding: 20px; border-bottom: 1px solid #2b3139; background: #1e2329; display: flex; align-items: center; gap: 12px;"> <!-- Left Panel: Status Info -->
<div style="position: relative;"> <div style="display: flex; flex-direction: column; background: #0b0e11; border-right: 1px solid rgba(255,255,255,0.08);">
<div style="width: 45px; height: 45px; background: var(--primary-color); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 20px;">
<i class="fas fa-headset" style="color: white;"></i> <div style="padding: 25px 40px; border-bottom: 1px solid rgba(255,255,255,0.08); background: #161a1e; display: flex; align-items: center; justify-content: space-between;">
</div> <div style="display: flex; align-items: center; gap: 15px;">
<div style="position: absolute; bottom: 0; right: 0; width: 12px; height: 12px; background: #00c087; border: 2px solid #1e2329; border-radius: 50%;"></div> <div style="width: 45px; height: 45px; background: var(--primary-color); border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white;">
<i class="fas fa-shield-alt"></i>
</div> </div>
<div> <div>
<div style="font-weight: 800; font-size: 14px; color: white;">在线客服 (Support)</div> <div style="font-weight: 800; font-size: 16px;">安全充值通道</div>
<div style="font-size: 10px; color: #848e9c;">24/7 实时响应中</div> <div style="font-size: 10px; color: #848e9c; font-family: monospace;">#<?php echo $order_id; ?></div>
</div>
</div>
<div style="text-align: right;">
<div id="countdown" style="font-size: 1.4rem; font-weight: 900; color: #f0b90b; font-family: monospace;">30:00</div>
<div style="font-size: 10px; color: #848e9c;">剩余支付时间</div>
</div> </div>
</div> </div>
<div id="chat-box" style="flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 15px; background: #0b0e11;"> <div style="flex: 1; overflow-y: auto; padding: 60px;">
<?php foreach ($messages as $m): <!-- Step 2: 前端显示“匹配中” (待匹配) -->
if (strpos($m['message'], '[SYSTEM]') === 0): ?> <?php if ($order['status'] === 'matching' && !$order['bank_account_info']): ?>
<div style="align-self: center; font-size: 10px; color: #5e6673; background: rgba(255,255,255,0.05); padding: 5px 15px; border-radius: 20px;"> <div style="text-align: center;">
<?php echo htmlspecialchars(str_replace('[SYSTEM] ', '', $m['message'])); ?> <div class="loader-container" style="margin-bottom: 40px;">
<div class="loader"></div>
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center;">
<i class="fas fa-user-clock" style="font-size: 40px; color: var(--primary-color);"></i>
</div> </div>
<?php else:
$align = $m['sender'] === 'user' ? 'flex-end' : 'flex-start';
$bgColor = $m['sender'] === 'user' ? 'var(--primary-color)' : '#2b3139';
$textColor = $m['sender'] === 'user' ? 'white' : '#EAECEF';
$borderRadius = $m['sender'] === 'user' ? 'border-bottom-right-radius: 2px;' : 'border-bottom-left-radius: 2px;';
?>
<div style="display: flex; flex-direction: column; align-items: <?php echo $align; ?>;">
<div style="max-width: 85%; padding: 10px 14px; border-radius: 14px; font-size: 13px; line-height: 1.5;
background: <?php echo $bgColor; ?>; color: <?php echo $textColor; ?>; <?php echo $borderRadius; ?>">
<?php echo nl2br(htmlspecialchars($m['message'])); ?>
</div> </div>
<div style="font-size: 9px; color: #5e6673; margin-top: 4px;"><?php echo date('H:i', strtotime($m['created_at'])); ?></div> <h2 style="font-size: 2.2rem; font-weight: 900; margin-bottom: 20px;">正在为您匹配收款账户</h2>
<div style="background: rgba(255,255,255,0.02); padding: 25px; border-radius: 20px; border: 1px solid rgba(255,255,255,0.05); max-width: 500px; margin: 0 auto;">
<p style="color: #848e9c; font-size: 16px; line-height: 1.8; margin: 0;">
系统正在为您对接在线财务专员,匹配成功后将自动弹出收款账户详情,请保持页面开启不要离开。
</p>
</div>
<div style="margin-top: 40px; display: flex; justify-content: center; gap: 15px;">
<div class="dot-typing"></div>
</div>
</div>
<?php elseif ($order['status'] === 'submitting'): ?>
<div style="text-align: center;">
<div style="width: 140px; height: 140px; background: rgba(0, 192, 135, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 35px; border: 2px solid rgba(0, 192, 135, 0.2);">
<i class="fas fa-file-invoice-dollar" style="font-size: 60px; color: #00c087;"></i>
</div>
<h2 style="font-size: 2.2rem; font-weight: 900; margin-bottom: 20px;">支付凭证已提交</h2>
<p style="color: #848e9c; font-size: 16px; line-height: 1.8; max-width: 500px; margin: 0 auto;">
我们已收到您的转账凭证,财务人员正在核实入账情况。通常在 5-10 分钟内完成,完成后您的余额将自动增加。
</p>
<div style="margin-top: 50px; display: flex; gap: 20px; justify-content: center;">
<a href="profile.php" style="background: var(--primary-color); color: white; padding: 18px 45px; border-radius: 16px; text-decoration: none; font-weight: 900; box-shadow: 0 10px 25px rgba(0,82,255,0.3);">查看我的钱包</a>
<a href="chat.php" style="background: #2b3139; color: white; padding: 18px 45px; border-radius: 16px; text-decoration: none; font-weight: 900;">联系人工客服</a>
</div>
</div>
<?php elseif ($order['status'] === 'rejected'): ?>
<div style="text-align: center;">
<div style="width: 140px; height: 140px; background: rgba(246, 70, 93, 0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 35px; border: 2px solid rgba(246, 70, 93, 0.2);">
<i class="fas fa-times-circle" style="font-size: 60px; color: #f6465d;"></i>
</div>
<h2 style="font-size: 2.2rem; font-weight: 900; margin-bottom: 20px; color: #f6465d;">订单审核未通过</h2>
<p style="color: #848e9c; font-size: 16px; line-height: 1.8; max-width: 500px; margin: 0 auto;">
非常抱歉,您的充值申请未能通过。可能是因为凭证不清晰、金额不匹配或转账未到账。
</p>
<a href="deposit.php" style="display: inline-block; margin-top: 50px; background: #f6465d; color: white; padding: 18px 45px; border-radius: 16px; text-decoration: none; font-weight: 900;">重新发起充值</a>
</div> </div>
<?php endif; ?> <?php endif; ?>
</div>
</div>
<!-- Right Panel: Chat -->
<div style="display: flex; flex-direction: column; background: #161a1e;">
<div style="padding: 20px 25px; border-bottom: 1px solid rgba(255,255,255,0.08); background: #1e2329; display: flex; align-items: center; gap: 12px;">
<div style="width: 10px; height: 10px; background: #00c087; border-radius: 50%; box-shadow: 0 0 10px #00c087;"></div>
<div style="font-weight: bold; font-size: 14px;">在线客服 (Online)</div>
</div>
<div id="chat-box" style="flex: 1; overflow-y: auto; padding: 25px; display: flex; flex-direction: column; gap: 15px; background: #0b0e11;">
<?php foreach ($messages as $m):
$align = $m['sender'] === 'user' ? 'flex-end' : 'flex-start';
$bg = $m['sender'] === 'user' ? 'var(--primary-color)' : '#2b3139';
?>
<div style="align-self: <?php echo $align; ?>; max-width: 85%;">
<div style="padding: 12px 16px; border-radius: 15px; background: <?php echo $bg; ?>; color: white; font-size: 14px; line-height: 1.5;">
<?php echo nl2br(htmlspecialchars($m['message'])); ?>
</div>
<div style="font-size: 10px; color: #5e6673; margin-top: 5px; text-align: <?php echo $m['sender'] === 'user' ? 'right' : 'left'; ?>;">
<?php echo date('H:i', strtotime($m['created_at'])); ?>
</div>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div style="padding: 20px; background: #1e2329; border-top: 1px solid #2b3139;"> <div style="padding: 20px; background: #1e2329;">
<form id="chat-form" style="display: flex; gap: 10px;"> <form id="chat-form" style="display: flex; gap: 10px;">
<input type="text" id="chat-input" placeholder="输入您的问题..." style="flex: 1; background: #0b0e11; border: 1px solid #2b3139; border-radius: 12px; padding: 12px 15px; color: white; outline: none; font-size: 13px;"> <input type="text" id="chat-input" placeholder="输入消息..." style="flex: 1; background: #0b0e11; border: 1px solid rgba(255,255,255,0.1); border-radius: 10px; padding: 12px 15px; color: white; outline: none;">
<button type="submit" style="width: 45px; height: 45px; border-radius: 12px; background: var(--primary-color); border: none; color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: 0.2s;"> <button type="submit" style="background: var(--primary-color); border: none; color: white; width: 45px; height: 45px; border-radius: 10px; cursor: pointer;">
<i class="fas fa-paper-plane"></i> <i class="fas fa-paper-plane"></i>
</button> </button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</main> </main>
<style> <style>
.matching-loader { position: relative; width: 100px; height: 100px; margin: 0 auto; display: flex; align-items: center; justify-content: center; }
.matching-loader .ring { position: absolute; width: 100%; height: 100%; border: 4px solid transparent; border-top: 4px solid var(--primary-color); border-radius: 50%; animation: spin 2s linear infinite; }
.matching-loader .ring:nth-child(2) { width: 80%; height: 80%; border-top: 4px solid #f0b90b; animation: spin-reverse 1.5s linear infinite; }
.matching-loader .ring:nth-child(3) { width: 60%; height: 60%; border-top: 4px solid #00c087; animation: spin 1s linear infinite; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
@keyframes spin-reverse { 0% { transform: rotate(0deg); } 100% { transform: rotate(-360deg); } } @keyframes slideUp { from { transform: translateY(40px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes pulse-scale { 0% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.1); opacity: 0.8; } 100% { transform: scale(1); opacity: 1; } } .loader-container { position: relative; width: 120px; height: 120px; margin: 0 auto; }
.loader { width: 120px; height: 120px; border: 4px solid rgba(0,82,255,0.05); border-top-color: var(--primary-color); border-radius: 50%; animation: spin 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite; }
#upload-zone:hover { border-color: var(--primary-color); background: rgba(79, 172, 254, 0.05); } .dot-typing { position: relative; left: -9999px; width: 10px; height: 10px; border-radius: 5px; background-color: var(--primary-color); color: var(--primary-color); box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); animation: dot-typing 1.5s infinite linear; }
.btn-primary:active { transform: scale(0.98); } @keyframes dot-typing { 0% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } 16.667% { box-shadow: 9984px -10px 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } 33.333% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } 50% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px -10px 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } 66.667% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } 83.333% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px -10px 0 0 var(--primary-color); } 100% { box-shadow: 9984px 0 0 0 var(--primary-color), 9999px 0 0 0 var(--primary-color), 10014px 0 0 0 var(--primary-color); } }
body { overflow: hidden; }
</style> </style>
<script> <script>
let expiresAt = new Date('<?php echo $order['expires_at']; ?>').getTime(); // Copy function
function updateCountdown() {
let now = new Date().getTime();
let diff = expiresAt - now;
let elements = document.querySelectorAll('#countdown');
if (diff <= 0) { elements.forEach(el => { el.innerText = "00:00"; el.style.color = "#ff4d4f"; }); return; }
let mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
let secs = Math.floor((diff % (1000 * 60)) / 1000);
let timeStr = (mins < 10 ? "0" + mins : mins) + ":" + (secs < 10 ? "0" + secs : secs);
elements.forEach(el => el.innerText = timeStr);
}
if (document.querySelectorAll('#countdown').length > 0) { updateCountdown(); setInterval(updateCountdown, 1000); }
// Order status polling
setInterval(() => {
fetch('api/check_order_status.php?order_id=<?php echo $order_id; ?>')
.then(r => r.json())
.then(data => {
if (data.status === 'completed') { location.href = 'profile.php'; }
else if (data.bank_account_info && !document.getElementById('upload-zone') && '<?php echo $order['status']; ?>' === 'matching') { location.reload(); }
});
}, 4000);
function handleFileSelect(input) {
const btn = document.getElementById('submit-btn');
if (input.files && input.files[0]) {
btn.disabled = false; btn.style.opacity = "1"; btn.style.cursor = "pointer";
document.getElementById('upload-idle').style.display = "none";
document.getElementById('upload-preview').style.display = "block";
document.getElementById('filename-display').innerText = input.files[0].name;
}
}
function copyToClipboard(text) { function copyToClipboard(text) {
const el = document.createElement('textarea'); const el = document.createElement('textarea');
el.value = text; el.value = text;
@ -311,11 +321,105 @@ $messages = $stmt->fetchAll();
el.select(); el.select();
document.execCommand('copy'); document.execCommand('copy');
document.body.removeChild(el); document.body.removeChild(el);
alert('已复制到剪贴板 / Copied to clipboard'); alert('复制成功 / Copied');
} }
// Force Lock Logic
const isMatched = <?php echo ($order['bank_account_info'] ? 'true' : 'false'); ?>;
const status = '<?php echo $order['status']; ?>';
if (isMatched && status !== 'submitting' && status !== 'completed' && status !== 'rejected') {
// Prevent back button and refresh alert
window.history.pushState(null, null, window.location.href);
window.onpopstate = function() {
window.history.pushState(null, null, window.location.href);
};
window.onbeforeunload = function() {
return "交易正在进行中,请勿离开!";
};
}
// Polling for status updates
setInterval(() => {
fetch('api/check_order_status.php?order_id=<?php echo $order_id; ?>')
.then(r => r.json())
.then(data => {
if (data.status === 'completed') {
window.onbeforeunload = null;
location.href = 'profile.php';
} else if (data.status === 'rejected' && status !== 'rejected') {
location.reload();
} else if (data.status === 'matched' && status === 'matching') {
location.reload(); // Trigger mandatory Step 4 popup
} else if (data.bank_account_info && !isMatched) {
location.reload(); // Admin matched while polling
}
});
}, 3000);
// Countdown logic
let expiresAt = new Date('<?php echo $order['expires_at']; ?>').getTime();
function updateCountdown() {
let now = new Date().getTime();
let diff = expiresAt - now;
let el = document.getElementById('countdown');
if (!el) return;
if (diff <= 0) { el.innerText = "00:00"; el.style.color = "#f6465d"; return; }
let mins = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
let secs = Math.floor((diff % (1000 * 60)) / 1000);
el.innerText = (mins < 10 ? "0" + mins : mins) + ":" + (secs < 10 ? "0" + secs : secs);
}
updateCountdown();
setInterval(updateCountdown, 1000);
// Upload Form Handlers
function showUploadForm() {
document.getElementById('action-area').style.display = 'none';
document.getElementById('upload-form-container').style.display = 'block';
// Update Steps
document.getElementById('step-dot-2').style.background = '#00c087';
document.getElementById('step-dot-2').style.color = 'white';
document.getElementById('step-text-2').style.color = '#00c087';
}
function hideUploadForm() {
document.getElementById('action-area').style.display = 'block';
document.getElementById('upload-form-container').style.display = 'none';
// Revert Steps
document.getElementById('step-dot-2').style.background = '#2b3139';
document.getElementById('step-dot-2').style.color = '#848e9c';
document.getElementById('step-text-2').style.color = '#848e9c';
}
function updateFilePreview(input) {
if (input.files && input.files[0]) {
document.getElementById('file-idle').style.display = 'none';
document.getElementById('file-selected').style.display = 'block';
document.getElementById('selected-name').innerText = input.files[0].name;
const btn = document.getElementById('final-submit');
btn.disabled = false;
btn.style.background = '#00c087';
btn.style.color = 'white';
btn.style.cursor = 'pointer';
btn.style.boxShadow = '0 10px 20px rgba(0,192,135,0.2)';
// Step 3 active
document.getElementById('step-dot-3').style.background = '#00c087';
document.getElementById('step-dot-3').style.color = 'white';
document.getElementById('step-text-3').style.color = '#00c087';
// Highlight dropzone
document.getElementById('upload-dropzone').style.borderColor = '#00c087';
document.getElementById('upload-dropzone').style.background = 'rgba(0,192,135,0.02)';
}
}
// Chat Logic
const chatBox = document.getElementById('chat-box'); const chatBox = document.getElementById('chat-box');
if(chatBox) chatBox.scrollTop = chatBox.scrollHeight; chatBox.scrollTop = chatBox.scrollHeight;
document.getElementById('chat-form').onsubmit = function(e) { document.getElementById('chat-form').onsubmit = function(e) {
e.preventDefault(); e.preventDefault();
@ -323,25 +427,25 @@ $messages = $stmt->fetchAll();
const msg = input.value.trim(); const msg = input.value.trim();
if (!msg) return; if (!msg) return;
// Add message locally for instant feedback
const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); const time = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const msgHtml = `<div style="display: flex; flex-direction: column; align-items: flex-end;"> const html = `
<div style="max-width: 85%; padding: 10px 14px; border-radius: 14px; font-size: 13px; line-height: 1.5; background: var(--primary-color); color: white; border-bottom-right-radius: 2px;">${msg}</div> <div style="align-self: flex-end; max-width: 85%;">
<div style="font-size: 9px; color: #5e6673; margin-top: 4px;">${time}</div> <div style="padding: 12px 16px; border-radius: 15px; background: var(--primary-color); color: white; font-size: 14px; line-height: 1.5;">${msg}</div>
</div>`; <div style="font-size: 10px; color: #5e6673; margin-top: 5px; text-align: right;">
chatBox.insertAdjacentHTML('beforeend', msgHtml); ${time}
</div>
</div>
`;
chatBox.insertAdjacentHTML('beforeend', html);
chatBox.scrollTop = chatBox.scrollHeight; chatBox.scrollTop = chatBox.scrollHeight;
input.value = ''; input.value = '';
fetch('chat.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'message=' + encodeURIComponent(msg) }); fetch('chat.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'message=' + encodeURIComponent(msg)
});
}; };
let messageCount = <?php echo count($messages); ?>;
setInterval(() => {
fetch('api/get_messages.php')
.then(r => r.json())
.then(data => { if (data.count > messageCount) { location.reload(); } });
}, 5000);
</script> </script>
<?php include 'footer.php'; ?> <?php include 'footer.php'; ?>

480
spot.php
View File

@ -50,287 +50,60 @@ if ($user_id) {
} }
/* Left: Markets */ /* Left: Markets */
.market-panel { .market-panel { width: 280px; flex-shrink: 0; }
width: 280px; .search-box { padding: 12px; border-bottom: 1px solid var(--border-color); }
flex-shrink: 0; .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); }
.search-box { .pairs-list-header { display: flex; padding: 8px 12px; font-size: 11px; color: var(--text-secondary); border-bottom: 1px solid var(--border-color); }
padding: 12px; .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); }
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:hover { background: rgba(255,255,255,0.05); }
.pair-item.active { background: rgba(240, 185, 11, 0.1); } .pair-item.active { background: rgba(240, 185, 11, 0.1); }
/* Center: Chart, Form, Tabs */ /* Center: Chart, Form, Tabs */
.center-panel { .center-panel { flex: 1; overflow-y: auto; }
flex: 1; .info-bar { height: 50px; display: flex; align-items: center; padding: 0 15px; gap: 20px; border-bottom: 1px solid var(--border-color); }
overflow-y: auto; .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; }
.info-bar { .order-type-tabs { display: flex; gap: 15px; margin-bottom: 15px; }
height: 50px; .order-type-btn { background: none; border: none; color: var(--text-secondary); font-size: 14px; cursor: pointer; padding: 0; }
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; } .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; }
.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; }
.input-row { .slider-container { margin: 15px 0 25px 0; padding: 0 5px; position: relative; }
background: var(--input-bg); .slider-marks { display: flex; justify-content: space-between; margin-top: -10px; position: relative; padding: 0 8px; pointer-events: none; }
border: 1px solid var(--border-color); .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); }
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); } .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; }
.slider-labels { .label-item { color: var(--text-secondary); font-size: 10px; cursor: pointer; user-select: none; width: 20px; text-align: center; }
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); } .label-item.active { color: var(--text-primary); }
.execute-btn { .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; }
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:active { opacity: 0.8; }
.execute-btn.buy { background: var(--up-color); } .execute-btn.buy { background: var(--up-color); }
.execute-btn.sell { background: var(--down-color); } .execute-btn.sell { background: var(--down-color); }
.bottom-tabs { .bottom-tabs { flex: 1; }
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; }
.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); } .tab-btn.active { color: var(--accent-color); border-bottom-color: var(--accent-color); }
/* Right: Order Book */ /* Right: Order Book */
.order-book-panel { .order-book-panel { width: 300px; flex-shrink: 0; }
width: 300px; .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; }
flex-shrink: 0; .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; }
.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; } .ob-val { z-index: 1; position: relative; }
input[type=range] { input[type=range] { -webkit-appearance: none; width: 100%; background: transparent; margin: 0; padding: 0; }
-webkit-appearance: none;
width: 100%;
background: transparent;
margin: 0;
padding: 0;
}
input[type=range]:focus { outline: none; } input[type=range]:focus { outline: none; }
input[type=range]::-webkit-slider-runnable-track { input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 4px; cursor: pointer; background: var(--border-color); border-radius: 2px; }
width: 100%; 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; }
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 { width: 4px; }
::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; } ::-webkit-scrollbar-thumb { background: #333; border-radius: 10px; }
@ -379,7 +152,7 @@ if ($user_id) {
</div> </div>
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;"> <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 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> <span style="color: var(--text-secondary);">可用: <span id="buy-available" style="color: white;">--</span> USDT</span>
</div> </div>
<div class="input-row" id="buy-price-row"> <div class="input-row" id="buy-price-row">
<span class="input-label">价格</span> <span class="input-label">价格</span>
@ -427,7 +200,7 @@ if ($user_id) {
</div> </div>
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 8px;"> <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 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> <span style="color: var(--text-secondary);">可用: <span id="sell-available" style="color: white;">--</span> <span class="asset-name">BTC</span></span>
</div> </div>
<div class="input-row" id="sell-price-row"> <div class="input-row" id="sell-price-row">
<span class="input-label">价格</span> <span class="input-label">价格</span>
@ -471,14 +244,14 @@ if ($user_id) {
<div class="bottom-tabs"> <div class="bottom-tabs">
<div class="tabs-header"> <div class="tabs-header">
<button class="tab-btn active" onclick="switchTab(this, 'open')">当前委托</button> <button class="tab-btn active" id="tab-open" onclick="switchTab(this, 'open')">当前委托</button>
<button class="tab-btn" onclick="switchTab(this, 'history')">历史委托</button> <button class="tab-btn" id="tab-history" onclick="switchTab(this, 'history')">历史委托</button>
<button class="tab-btn" onclick="switchTab(this, 'assets')">资产余额</button> <button class="tab-btn" id="tab-assets" onclick="switchTab(this, 'assets')">资产余额</button>
</div> </div>
<div id="tab-content" style="padding: 15px;"> <div id="tab-content-area" style="padding: 15px;">
<table style="width: 100%; font-size: 12px; border-collapse: collapse;"> <table id="orders-table" style="width: 100%; font-size: 12px; border-collapse: collapse;">
<thead style="color: var(--text-secondary); text-align: left;"> <thead style="color: var(--text-secondary); text-align: left;">
<tr> <tr id="table-header">
<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>
@ -515,10 +288,11 @@ if ($user_id) {
<script> <script>
let currentPair = 'BTCUSDT'; let currentPair = 'BTCUSDT';
let currentPrice = 0; let currentPrice = 0;
let usdtBalance = <?php echo $balance; ?>; let usdtBalance = 0;
let assetBalance = 1.25; let userAssets = {};
let marketData = {}; let marketData = {};
let orderTypes = { buy: 'limit', sell: 'limit' }; let orderTypes = { buy: 'limit', sell: 'limit' };
let activeTab = 'open';
const pairs = [ const pairs = [
'BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'TRXUSDT', 'DOTUSDT', 'BTCUSDT', 'ETHUSDT', 'SOLUSDT', 'BNBUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'TRXUSDT', 'DOTUSDT',
@ -545,6 +319,13 @@ if ($user_id) {
marketData[data.s] = data; marketData[data.s] = data;
renderPairs(); renderPairs();
if (data.s === currentPair) { if (data.s === currentPair) {
updateUIWithData(data);
}
};
}
connectWS();
function updateUIWithData(data) {
currentPrice = parseFloat(data.c); currentPrice = parseFloat(data.c);
document.getElementById('last-price').innerText = currentPrice.toLocaleString(); document.getElementById('last-price').innerText = currentPrice.toLocaleString();
document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)'; document.getElementById('last-price').style.color = data.P >= 0 ? 'var(--up-color)' : 'var(--down-color)';
@ -559,12 +340,16 @@ if ($user_id) {
const bp = document.getElementById('buy-price'); const bp = document.getElementById('buy-price');
const sp = document.getElementById('sell-price'); const sp = document.getElementById('sell-price');
if (!bp.value) bp.value = currentPrice; // Only set value if empty or if user hasn't typed anything yet (simple heuristic)
if (!sp.value) sp.value = currentPrice; if (!bp.value || bp.getAttribute('data-auto') === 'true') {
bp.value = currentPrice;
bp.setAttribute('data-auto', 'true');
}
if (!sp.value || sp.getAttribute('data-auto') === 'true') {
sp.value = currentPrice;
sp.setAttribute('data-auto', 'true');
} }
};
} }
connectWS();
function renderPairs() { function renderPairs() {
const list = document.getElementById('pairs-list'); const list = document.getElementById('pairs-list');
@ -588,6 +373,25 @@ if ($user_id) {
list.innerHTML = html; list.innerHTML = html;
} }
async function fetchAssets() {
const resp = await fetch('api/get_assets.php');
const res = await resp.json();
if (res.success) {
userAssets = {};
res.data.forEach(a => {
userAssets[a.symbol] = a.amount;
});
usdtBalance = userAssets['USDT'] || 0;
updateAvailableDisplay();
}
}
function updateAvailableDisplay() {
const coin = currentPair.replace('USDT', '');
document.getElementById('buy-available').innerText = usdtBalance.toFixed(2);
document.getElementById('sell-available').innerText = (userAssets[coin] || 0).toFixed(6);
}
function switchPair(p) { function switchPair(p) {
currentPair = p; currentPair = p;
const name = p.replace('USDT', ''); const name = p.replace('USDT', '');
@ -598,13 +402,20 @@ if ($user_id) {
document.querySelector('.execute-btn.buy').innerText = '买入 ' + name; document.querySelector('.execute-btn.buy').innerText = '买入 ' + name;
document.querySelector('.execute-btn.sell').innerText = '卖出 ' + name; document.querySelector('.execute-btn.sell').innerText = '卖出 ' + name;
assetBalance = (Math.random() * 2).toFixed(4); updateAvailableDisplay();
document.getElementById('sell-available').innerText = assetBalance;
if (currentPrice) { if (marketData[p]) {
document.getElementById('buy-price').value = currentPrice; updateUIWithData(marketData[p]);
document.getElementById('sell-price').value = currentPrice; } else {
document.getElementById('last-price').innerText = '--';
document.getElementById('buy-price').value = '';
document.getElementById('sell-price').value = '';
} }
// Reset auto flag on switch so it picks up the new price
document.getElementById('buy-price').setAttribute('data-auto', 'true');
document.getElementById('sell-price').setAttribute('data-auto', 'true');
document.getElementById('buy-amount').value = ''; document.getElementById('buy-amount').value = '';
document.getElementById('sell-amount').value = ''; document.getElementById('sell-amount').value = '';
document.getElementById('buy-slider').value = 0; document.getElementById('buy-slider').value = 0;
@ -626,7 +437,6 @@ if ($user_id) {
}); });
document.getElementById(`${side}-price-row`).style.display = type === 'limit' ? 'flex' : 'none'; document.getElementById(`${side}-price-row`).style.display = type === 'limit' ? 'flex' : 'none';
document.getElementById(`${side}-market-price-row`).style.display = type === 'market' ? 'flex' : 'none'; document.getElementById(`${side}-market-price-row`).style.display = type === 'market' ? 'flex' : 'none';
updateFromSlider(side, document.getElementById(side + '-slider').value); updateFromSlider(side, document.getElementById(side + '-slider').value);
} }
@ -634,9 +444,10 @@ if ($user_id) {
const asks = document.getElementById('asks-list'); const asks = document.getElementById('asks-list');
const bids = document.getElementById('bids-list'); const bids = document.getElementById('bids-list');
let asksHtml = ''; let bidsHtml = ''; let asksHtml = ''; let bidsHtml = '';
const step = currentPrice * 0.0002;
for(let i=0; i<15; i++) { for(let i=0; i<15; i++) {
const askPrice = currentPrice * (1 + (i + 1) * 0.0002); const askPrice = currentPrice + (i + 1) * step;
const bidPrice = currentPrice * (1 - (i + 1) * 0.0002); const bidPrice = currentPrice - (i + 1) * step;
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>`; 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>`; 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>`;
} }
@ -648,6 +459,7 @@ if ($user_id) {
const isBuy = side === 'buy'; const isBuy = side === 'buy';
const isLimit = orderTypes[side] === 'limit'; const isLimit = orderTypes[side] === 'limit';
const price = isLimit ? (parseFloat(document.getElementById(side + '-price').value) || currentPrice) : currentPrice; const price = isLimit ? (parseFloat(document.getElementById(side + '-price').value) || currentPrice) : currentPrice;
const coin = currentPair.replace('USDT', '');
if (isBuy) { if (isBuy) {
const totalUSDT = usdtBalance * (val / 100); const totalUSDT = usdtBalance * (val / 100);
@ -655,7 +467,8 @@ if ($user_id) {
document.getElementById('buy-amount').value = amount > 0 ? amount.toFixed(6) : ''; document.getElementById('buy-amount').value = amount > 0 ? amount.toFixed(6) : '';
document.getElementById('buy-total').innerText = totalUSDT.toFixed(2); document.getElementById('buy-total').innerText = totalUSDT.toFixed(2);
} else { } else {
const amount = assetBalance * (val / 100); const coinBal = userAssets[coin] || 0;
const amount = coinBal * (val / 100);
const totalUSDT = amount * price; const totalUSDT = amount * price;
document.getElementById('sell-amount').value = amount > 0 ? amount.toFixed(6) : ''; document.getElementById('sell-amount').value = amount > 0 ? amount.toFixed(6) : '';
document.getElementById('sell-total').innerText = totalUSDT.toFixed(2); document.getElementById('sell-total').innerText = totalUSDT.toFixed(2);
@ -666,8 +479,10 @@ if ($user_id) {
function updateFromInputs(side) { function updateFromInputs(side) {
const isBuy = side === 'buy'; const isBuy = side === 'buy';
const isLimit = orderTypes[side] === 'limit'; const isLimit = orderTypes[side] === 'limit';
const price = isLimit ? (parseFloat(document.getElementById(side + '-price').value) || currentPrice) : currentPrice; const priceInput = document.getElementById(side + '-price');
const price = isLimit ? (parseFloat(priceInput.value) || currentPrice) : currentPrice;
const amount = parseFloat(document.getElementById(side + '-amount').value) || 0; const amount = parseFloat(document.getElementById(side + '-amount').value) || 0;
const coin = currentPair.replace('USDT', '');
const total = price * amount; const total = price * amount;
document.getElementById(side + '-total').innerText = total.toFixed(2); document.getElementById(side + '-total').innerText = total.toFixed(2);
@ -676,7 +491,8 @@ if ($user_id) {
if (isBuy) { if (isBuy) {
percentage = usdtBalance > 0 ? (total / usdtBalance) * 100 : 0; percentage = usdtBalance > 0 ? (total / usdtBalance) * 100 : 0;
} else { } else {
percentage = assetBalance > 0 ? (amount / assetBalance) * 100 : 0; const coinBal = userAssets[coin] || 0;
percentage = coinBal > 0 ? (amount / coinBal) * 100 : 0;
} }
percentage = Math.min(Math.max(percentage, 0), 100); percentage = Math.min(Math.max(percentage, 0), 100);
document.getElementById(side + '-slider').value = percentage; document.getElementById(side + '-slider').value = percentage;
@ -687,14 +503,8 @@ if ($user_id) {
const column = document.getElementById(side + '-column'); const column = document.getElementById(side + '-column');
const marks = column.querySelectorAll('.mark-dot'); const marks = column.querySelectorAll('.mark-dot');
const labels = column.querySelectorAll('.label-item'); const labels = column.querySelectorAll('.label-item');
marks.forEach((m, i) => { marks.forEach((m, i) => { if (i * 25 <= val) m.classList.add('active'); else m.classList.remove('active'); });
if (i * 25 <= val) m.classList.add('active'); labels.forEach((l, i) => { if (Math.abs(i * 25 - val) < 5) l.classList.add('active'); else l.classList.remove('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) { function setSlider(side, val) {
@ -703,7 +513,10 @@ if ($user_id) {
} }
['buy', 'sell'].forEach(side => { ['buy', 'sell'].forEach(side => {
document.getElementById(side + '-price').addEventListener('input', () => updateFromInputs(side)); document.getElementById(side + '-price').addEventListener('input', (e) => {
e.target.setAttribute('data-auto', 'false');
updateFromInputs(side);
});
document.getElementById(side + '-amount').addEventListener('input', () => updateFromInputs(side)); document.getElementById(side + '-amount').addEventListener('input', () => updateFromInputs(side));
}); });
@ -726,6 +539,7 @@ if ($user_id) {
const res = await response.json(); const res = await response.json();
if (res.success) { if (res.success) {
alert('下单成功'); alert('下单成功');
fetchAssets();
fetchOrders(); fetchOrders();
} else { } else {
alert('失败: ' + res.error); alert('失败: ' + res.error);
@ -733,12 +547,32 @@ if ($user_id) {
} }
async function fetchOrders() { async function fetchOrders() {
const response = await fetch('api/get_orders.php?type=spot&status=open'); if (activeTab === 'assets') return fetchAssetsList();
const response = await fetch(`api/get_orders.php?type=spot&status=${activeTab}`);
const res = await response.json(); const res = await response.json();
const tbody = document.getElementById('orders-tbody'); const tbody = document.getElementById('orders-tbody');
const thead = document.getElementById('table-header');
thead.innerHTML = `
<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>
`;
if (res.success && res.data.length > 0) { if (res.success && res.data.length > 0) {
let html = ''; let html = '';
res.data.forEach(o => { res.data.forEach(o => {
let statusText = '';
if (o.status === 'open') statusText = '进行中';
else if (o.status === 'closed') statusText = '已成交';
else statusText = '已取消';
html += ` html += `
<tr style="border-bottom: 1px solid var(--border-color);"> <tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 10px 8px;">${o.created_at}</td> <td style="padding: 10px 8px;">${o.created_at}</td>
@ -747,9 +581,9 @@ if ($user_id) {
<td style="padding: 10px 8px; color: ${o.side === 'buy' ? 'var(--up-color)' : 'var(--down-color)'}">${o.side === 'buy' ? '买入' : '卖出'}</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.price).toLocaleString()}</td>
<td style="padding: 10px 8px;">${parseFloat(o.amount).toFixed(6)}</td> <td style="padding: 10px 8px;">${parseFloat(o.amount).toFixed(6)}</td>
<td style="padding: 10px 8px;">待成交</td> <td style="padding: 10px 8px;">${statusText}</td>
<td style="padding: 10px 8px; text-align: right;"> <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> ${o.status === 'open' ? `<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> </td>
</tr> </tr>
`; `;
@ -760,9 +594,46 @@ if ($user_id) {
} }
} }
async function fetchAssetsList() {
await fetchAssets();
const tbody = document.getElementById('orders-tbody');
const thead = document.getElementById('table-header');
thead.innerHTML = `
<th style="padding: 8px;">资产</th>
<th style="padding: 8px;">可用</th>
<th style="padding: 8px;">冻结</th>
<th style="padding: 8px;">折合(USDT)</th>
<th style="padding: 8px; text-align: right;">操作</th>
`;
let html = '';
const assets = Object.entries(userAssets).filter(([sym, amt]) => amt > 0 || sym === 'USDT');
assets.forEach(([sym, amt]) => {
let price = 1;
if (sym !== 'USDT') {
const pair = sym + 'USDT';
price = marketData[pair] ? parseFloat(marketData[pair].c) : 0;
}
html += `
<tr style="border-bottom: 1px solid var(--border-color);">
<td style="padding: 10px 8px; font-weight: bold;">${sym}</td>
<td style="padding: 10px 8px;">${amt.toFixed(sym === 'USDT' ? 2 : 6)}</td>
<td style="padding: 10px 8px;">0.00</td>
<td style="padding: 10px 8px;">${(amt * price).toFixed(2)}</td>
<td style="padding: 10px 8px; text-align: right;">
<button onclick="switchPair('${sym}USDT')" style="background: none; border: 1px solid var(--accent-color); color: var(--accent-color); padding: 2px 8px; border-radius: 4px; cursor: pointer;">去交易</button>
</td>
</tr>
`;
});
tbody.innerHTML = html || '<tr><td colspan="5" style="text-align: center; padding: 40px; color: var(--text-secondary);">暂无资产</td></tr>';
}
function switchTab(btn, tab) { function switchTab(btn, tab) {
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active'); btn.classList.add('active');
activeTab = tab;
fetchOrders(); fetchOrders();
} }
@ -773,12 +644,19 @@ if ($user_id) {
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify({order_id: id}) body: JSON.stringify({order_id: id})
}); });
if ((await resp.json()).success) fetchOrders(); if ((await resp.json()).success) {
fetchAssets();
fetchOrders();
}
} }
document.getElementById('market-search').addEventListener('input', renderPairs); document.getElementById('market-search').addEventListener('input', renderPairs);
fetchAssets();
fetchOrders(); fetchOrders();
setInterval(fetchOrders, 5000); setInterval(() => {
if (activeTab === 'assets') fetchAssetsList();
else fetchOrders();
}, 5000);
</script> </script>
<?php include 'footer.php'; ?> <?php include 'footer.php'; ?>