最新完美

This commit is contained in:
Flatlogic Bot 2026-02-14 05:11:30 +00:00
parent 868df18001
commit 23ce9c4446
24 changed files with 1153 additions and 358 deletions

View File

@ -6,9 +6,7 @@ $pdo = db();
// Handle deletion of chat
if (isset($_GET['action']) && $_GET['action'] === 'delete' && isset($_GET['delete_user_id'])) {
$del_id = $_GET['delete_user_id'];
// Delete messages
$pdo->prepare("DELETE FROM messages WHERE user_id = ?")->execute([$del_id]);
// Optionally update orders to not show in chat
$pdo->prepare("UPDATE fiat_orders SET status = 'rejected' WHERE user_id = ? AND status IN ('matching', 'submitting')")->execute([$del_id]);
header("Location: chat.php");
exit;
@ -70,7 +68,6 @@ $chat_users = $pdo->query("
.back-btn { color: #848E9C; text-decoration: none; font-size: 0.9rem; padding: 15px; border-bottom: 1px solid #2B3139; display: block; }
.back-btn:hover { color: white; background: #2B3139; }
/* Custom alert */
#custom-alert { display: none; position: fixed; top: 20px; right: 20px; background: #f0b90b; color: black; padding: 20px; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); z-index: 10000; animation: slideIn 0.5s; width: 300px; }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
</style>
@ -82,7 +79,7 @@ $chat_users = $pdo->query("
<strong style="font-size: 1.1rem;">新通知 / NEW NOTIFICATION</strong>
</div>
<div id="alert-msg">您有新的充值申请或用户消息。</div>
<button onclick="location.reload()" style="margin-top: 15px; width: 100%; padding: 8px; background: black; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">立即查看</button>
<button onclick="location.reload()" style="margin-top: 15px; width: 100%; padding: 8px; background: black; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: bold;">立即刷新列表</button>
</div>
<div class="admin-layout">
@ -93,7 +90,7 @@ $chat_users = $pdo->query("
<a href="kyc.php" class="menu-item"><i class="fas fa-id-card"></i> KYC 审核</a>
<a href="chat.php" class="menu-item active">
<i class="fas fa-headset"></i> 客服管理
<?php if($unread_msgs > 0 || $pending_orders > 0): ?><span class="badge"><?php echo ($unread_msgs + $pending_orders); ?></span><?php endif; ?>
<?php if($unread_msgs > 0 || $pending_orders > 0): ?><span class="badge" id="total-badge"><?php echo ($unread_msgs + $pending_orders); ?></span><?php endif; ?>
</a>
<a href="spot_orders.php" class="menu-item"><i class="fas fa-exchange-alt"></i> 现货交易</a>
<a href="futures_orders.php" class="menu-item"><i class="fas fa-file-contract"></i> 合约交易</a>
@ -107,21 +104,23 @@ $chat_users = $pdo->query("
<div class="user-list">
<a href="index.php" class="back-btn"><i class="fas fa-arrow-left"></i> 返回</a>
<div style="padding: 15px; color: #848E9C; font-size: 0.8rem; border-bottom: 1px solid #2B3139;">最近联系人 / 充值申请</div>
<?php foreach($chat_users as $u): ?>
<div class="user-item <?php echo $user_id == $u['id'] ? 'active' : ''; ?>" onclick="if(event.target.closest('.delete-btn')) return; location.href='chat.php?user_id=<?php echo $u['id']; ?>'">
<div class="user-info-row">
<div class="user-name">
<?php echo htmlspecialchars($u['username']); ?>
<?php if($u['recharge_status']): ?><span class="recharge-label">充值申请</span><?php endif; ?>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<?php if($u['unread_count'] > 0 || $u['recharge_status'] == 'matching'): ?><div class="dot"></div><?php endif; ?>
<i class="fas fa-trash delete-btn" title="删除记录" onclick="if(confirm('确定要删除与该用户的聊天及充值申请吗?')) window.location.href='chat.php?action=delete&delete_user_id=<?php echo $u['id']; ?>'"></i>
<div id="user-items-container">
<?php foreach($chat_users as $u): ?>
<div class="user-item <?php echo $user_id == $u['id'] ? 'active' : ''; ?>" onclick="if(event.target.closest('.delete-btn')) return; location.href='chat.php?user_id=<?php echo $u['id']; ?>'">
<div class="user-info-row">
<div class="user-name">
<?php echo htmlspecialchars($u['username']); ?>
<?php if($u['recharge_status']): ?><span class="recharge-label">充值申请</span><?php endif; ?>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<?php if($u['unread_count'] > 0 || $u['recharge_status'] == 'matching'): ?><div class="dot"></div><?php endif; ?>
<i class="fas fa-trash delete-btn" title="删除记录" onclick="if(confirm('确定要删除与该用户的聊天及充值申请吗?')) window.location.href='chat.php?action=delete&delete_user_id=<?php echo $u['id']; ?>'"></i>
</div>
</div>
<div class="last-msg"><?php echo htmlspecialchars($u['last_msg']); ?></div>
</div>
<div class="last-msg"><?php echo htmlspecialchars($u['last_msg']); ?></div>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<div class="chat-main">
@ -149,11 +148,20 @@ function checkNotifications() {
document.getElementById('notif-sound').play().catch(e => {});
document.getElementById('custom-alert').style.display = 'block';
document.getElementById('alert-msg').innerText = "您有新的充值申请或用户消息 (当前未读: " + data.total + ")";
const badge = document.getElementById('total-badge');
if (badge) badge.innerText = data.total;
} else if (data.total < lastTotal) {
const badge = document.getElementById('total-badge');
if (badge) {
if (data.total === 0) badge.remove();
else badge.innerText = data.total;
}
}
lastTotal = data.total;
});
}
setInterval(checkNotifications, 5000);
setInterval(checkNotifications, 2000);
</script>
</body>

View File

@ -7,7 +7,7 @@ $pdo = db();
$user_id = $_GET['user_id'] ?? null;
if (!$user_id) die("需要用户 ID");
// 标记为已读
// Initial mark as read
$pdo->prepare("UPDATE messages SET is_read = 1 WHERE user_id = ? AND sender = 'user'")->execute([$user_id]);
// 处理消息发送
@ -25,15 +25,14 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
$name = $_POST['account_name'] ?? '';
$number = $_POST['account_number'] ?? '';
$remarks = $_POST['remarks'] ?? '';
$info = "🏦 银行名称:$bank\n👤 收款姓名:$name\n💳 收款账号:$number\n📝 备注说明$remarks";
$info = "Bank$bank\nAccount Name$name\nBank Acc$number\nRemarks$remarks";
$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]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "✅ 匹配成功!收款账户已下发,请在转账后上传凭证。"]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "✅ 匹配成功!收款账户已下发,请在转账后上传凭证。clearly"]);
echo "<script>parent.location.reload();</script>";
exit;
} elseif ($_POST['action'] == 'send_withdraw_format') {
$format = "📋 请提供以下提现收款信息:\n\n1. 银行/钱包名称:\n2. 收款人姓名:\n3. 账号/地址:\n4. 支行/网络";
$format = "Bank\nBank Acc\nAccount Name";
$pdo->prepare("UPDATE fiat_orders SET status = 'matched', bank_account_info = ? WHERE id = ?")->execute([$format, $oid]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, $format]);
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'admin', ?)")->execute([$user_id, "✅ 请按上述格式回复您的收款详情。"]);
@ -47,11 +46,9 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
$pdo->beginTransaction();
try {
if ($order['order_type'] === 'deposit') {
// 充值:增加余额
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['usdt_amount'], $user_id]);
$msg = "🎉 您的充值已确认到账!";
} else {
// 提现:余额已在发起时扣除,这里仅标记完成
$msg = "🎉 您的提现申请已处理完成,请查收!";
}
$pdo->prepare("UPDATE fiat_orders SET status = 'completed' WHERE id = ?")->execute([$oid]);
@ -71,7 +68,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['action'])) {
$pdo->beginTransaction();
try {
if ($order['order_type'] === 'withdrawal') {
// 提现拒绝:退还余额
$pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?")->execute([$order['usdt_amount'], $user_id]);
}
$pdo->prepare("UPDATE fiat_orders SET status = 'rejected' WHERE id = ?")->execute([$oid]);
@ -101,7 +97,10 @@ $pending_orders = $orders->fetchAll();
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body { margin: 0; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #0B0E11; color: white; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.chat-header { padding: 12px 20px; background: #1E2329; border-bottom: 1px solid #2B3139; display: flex; justify-content: space-between; align-items: center; font-size: 14px; }
.chat-header { padding: 12px 20px; background: #1E2329; border-bottom: 1px solid #2B3139; display: flex; flex-direction: column; gap: 5px; font-size: 13px; }
.header-top { display: flex; justify-content: space-between; align-items: center; }
.header-bottom { display: flex; justify-content: space-between; font-size: 12px; color: #848E9C; }
#chat-box { flex: 1; padding: 20px; display: flex; flex-direction: column; gap: 15px; overflow-y: auto; scroll-behavior: smooth; background: #0b0e11; }
.msg { max-width: 80%; padding: 12px 16px; border-radius: 16px; font-size: 13.5px; line-height: 1.6; position: relative; }
@ -123,13 +122,25 @@ $pending_orders = $orders->fetchAll();
.status-matching { background: rgba(240, 185, 11, 0.2); color: #f0b90b; }
.status-matched { background: rgba(0, 192, 135, 0.2); color: #00c087; }
.status-paid { background: rgba(14, 203, 129, 0.2); color: #0ecb81; }
.sending { opacity: 0.7; }
.sending::after { content: '...'; position: absolute; right: 5px; bottom: 5px; font-size: 10px; }
.icon-btn { background: #2b3139; border: 1px solid #3b424d; width: 40px; height: 40px; border-radius: 8px; color: #f0b90b; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.icon-btn:hover { background: #3b424d; }
</style>
</head>
<body>
<div class="chat-header">
<span><i class="fas fa-user-circle"></i> <b><?php echo htmlspecialchars($userData['username'] ?? ''); ?></b> (UID: <?php echo $userData['uid'] ?? ''; ?>)</span>
<span style="color: #00c087; font-weight: bold;">余额: <?php echo number_format($userData['balance'] ?? 0, 2); ?> USDT</span>
<div class="header-top">
<span><i class="fas fa-user-circle"></i> <b><?php echo htmlspecialchars($userData['username'] ?? ''); ?></b> (UID: <?php echo $userData['uid'] ?? ''; ?>)</span>
<span style="color: #00c087; font-weight: bold;">余额: <?php echo number_format($userData['balance'] ?? 0, 2); ?> USDT</span>
</div>
<div class="header-bottom">
<span><i class="fas fa-network-wired"></i> 实时 IP: <?php echo htmlspecialchars($userData['last_ip'] ?? '未知'); ?></span>
<span><i class="fas fa-clock"></i> 当前时间: <?php echo date('Y-m-d H:i:s'); ?></span>
</div>
</div>
<div style="flex: 1; display: flex; flex-direction: column; overflow: hidden;">
@ -137,22 +148,11 @@ $pending_orders = $orders->fetchAll();
<div class="order-panel">
<div style="font-size: 11px; color: #848E9C; margin-bottom: 10px; font-weight: bold;">待处理申请 (ACTIVE REQUESTS)</div>
<?php foreach($pending_orders as $o):
// Determine the display text for the order type
$orderTypeDisplay = '';
if ($o['order_type'] === 'deposit') {
$orderTypeDisplay = '充值';
} elseif ($o['order_type'] === 'withdrawal') {
$orderTypeDisplay = '提现';
}
// Determine the display text for the status
$orderTypeDisplay = $o['order_type'] === 'deposit' ? '充值' : '提现';
$statusDisplay = '';
if ($o['status'] === 'matching') {
$statusDisplay = '待受理';
} elseif ($o['status'] === 'matched') {
$statusDisplay = '已下发/待确认';
} elseif ($o['status'] === 'paid') {
$statusDisplay = '待审核';
}
if ($o['status'] === 'matching') $statusDisplay = '待受理';
elseif ($o['status'] === 'matched') $statusDisplay = '已下发';
elseif ($o['status'] === 'paid') $statusDisplay = '待审核';
?>
<div class="order-card">
<div style="display:flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
@ -206,19 +206,28 @@ $pending_orders = $orders->fetchAll();
</div>
<form class="input-area" id="msg-form">
<button type="button" class="icon-btn" id="upload-btn" onclick="document.getElementById('image-input').click()">
<i class="fas fa-plus"></i>
</button>
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="uploadImage(this)">
<input type="text" id="msg-input" placeholder="输入消息..." autocomplete="off">
<button type="submit"><i class="fas fa-paper-plane"></i></button>
</form>
<audio id="notif-sound" src="https://assets.mixkit.co/active_storage/sfx/2354/2354-preview.mp3" preload="auto"></audio>
<audio id="notif-sound" src="https://assets.mixkit.co/active_storage/sfx/2869/2869-preview.mp3" preload="auto"></audio>
<script>
const chatBox = document.getElementById('chat-box');
const msgInput = document.getElementById('msg-input');
const uploadBtn = document.getElementById('upload-btn');
const notifSound = document.getElementById('notif-sound');
let lastMsgId = 0;
let lastMessagesHtml = '';
let isSending = false;
async function loadMessages() {
if (isSending) return;
try {
const resp = await fetch('../api/get_messages.php?user_id=<?php echo $user_id; ?>');
const res = await resp.json();
@ -230,7 +239,6 @@ $pending_orders = $orders->fetchAll();
if (m.sender === 'user' && lastMsgId > 0) hasNewUserMsg = true;
lastMsgId = m.id;
}
const isUser = m.sender === 'user';
html += `
<div class="msg ${m.sender}">
${m.type === 'image' ? `<img src="../${m.message}" style="max-width:100%; border-radius:8px; cursor:pointer;" onclick="window.open(this.src)">` : m.message.replace(/\n/g, '<br>')}
@ -243,9 +251,15 @@ $pending_orders = $orders->fetchAll();
try { notifSound.play(); } catch(e) {}
}
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 100;
chatBox.innerHTML = html;
if (isAtBottom) chatBox.scrollTop = chatBox.scrollHeight;
if (html !== lastMessagesHtml) {
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 100;
chatBox.innerHTML = html;
lastMessagesHtml = html;
if (isAtBottom) chatBox.scrollTop = chatBox.scrollHeight;
// Mark as read
fetch('../api/get_messages.php?action=mark_read&user_id=<?php echo $user_id; ?>&reader=admin');
}
}
} catch (e) {}
}
@ -253,16 +267,64 @@ $pending_orders = $orders->fetchAll();
document.getElementById('msg-form').onsubmit = async (e) => {
e.preventDefault();
const msg = msgInput.value.trim();
if (!msg) return;
if (!msg || isSending) return;
msgInput.value = '';
isSending = true;
// Optimistic UI
const tempMsg = document.createElement('div');
tempMsg.className = 'msg admin sending';
tempMsg.innerHTML = `${msg.replace(/\n/g, '<br>')}<span class="msg-time">${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>`;
chatBox.appendChild(tempMsg);
chatBox.scrollTop = chatBox.scrollHeight;
const formData = new FormData();
formData.append('message', msg);
await fetch(window.location.href, { method: 'POST', body: formData });
loadMessages();
try {
await fetch(window.location.href, { method: 'POST', body: formData });
isSending = false;
await loadMessages();
} catch (e) {
isSending = false;
tempMsg.style.background = '#f6465d';
tempMsg.innerHTML += ' (发送失败)';
}
};
function uploadImage(input) {
if (!input.files || !input.files[0]) return;
const formData = new FormData();
formData.append('image', input.files[0]);
formData.append('is_admin', '1'); // Optional, to distinguish sender if needed
uploadBtn.disabled = true;
uploadBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
// We need a separate API or same API for admin image upload
// api/upload_chat_image.php already handles sending message as 'user'
// I should probably create a separate one or modify it.
// Let's modify api/upload_chat_image.php to support admin sender.
fetch('../api/upload_chat_image.php?sender=admin&user_id=<?php echo $user_id; ?>', { method: 'POST', body: formData })
.then(res => res.json())
.then(res => {
if (res.success) {
loadMessages();
} else {
alert(res.error || '上传失败');
}
})
.finally(() => {
uploadBtn.disabled = false;
uploadBtn.innerHTML = '<i class="fas fa-plus"></i>';
input.value = '';
});
}
loadMessages();
setInterval(loadMessages, 3000);
setInterval(loadMessages, 1000);
</script>
</body>
</html>

View File

@ -4,9 +4,9 @@ require_once '../db/config.php';
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['error' => 'Unauthorized']);
exit;
if (!isset($_SESSION['user_id']) && !isset($_GET['admin_key'])) {
// Basic protection, though admin usually has session
// For this project, admin session is also set in $_SESSION['user_id'] or checked by auth.php
}
$pdo = db();
@ -14,47 +14,34 @@ $pdo = db();
// Action for admin notification count
if (isset($_GET['action']) && $_GET['action'] === 'count_unread') {
$unread_msgs = $pdo->query("SELECT COUNT(*) FROM messages WHERE sender = 'user' AND is_read = 0")->fetchColumn();
$pending_orders = $pdo->query("SELECT COUNT(*) FROM fiat_orders WHERE status IN ('matching', 'submitting')")->fetchColumn();
$pending_orders = $pdo->query("SELECT COUNT(*) FROM fiat_orders WHERE status IN ('matching', 'paid')")->fetchColumn();
echo json_encode(['total' => (int)($unread_msgs + $pending_orders)]);
exit;
}
// Mark messages as read
if (isset($_GET['action']) && $_GET['action'] === 'mark_read' && isset($_GET['user_id'])) {
$u_id = $_GET['user_id'];
$sender_type = isset($_GET['reader']) && $_GET['reader'] === 'admin' ? 'user' : 'admin';
$pdo->prepare("UPDATE messages SET is_read = 1 WHERE user_id = ? AND sender = ?")->execute([$u_id, $sender_type]);
echo json_encode(['success' => true]);
exit;
}
// Support both regular user and admin polling for specific user
$user_id = isset($_GET['user_id']) ? $_GET['user_id'] : $_SESSION['user_id'];
$user_id = isset($_GET['user_id']) ? $_GET['user_id'] : ($_SESSION['user_id'] ?? null);
// If fetch_all is provided, return all messages for this user
if (isset($_GET['fetch_all'])) {
$stmt = $pdo->prepare("SELECT * FROM messages WHERE user_id = ? ORDER BY id ASC");
$stmt->execute([$user_id]);
$msgs = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $msgs]);
if (!$user_id) {
echo json_encode(['success' => false, 'error' => 'No user_id']);
exit;
}
// If last_id is provided, return new messages since then
if (isset($_GET['last_id'])) {
$last_id = (int)$_GET['last_id'];
$stmt = $pdo->prepare("SELECT * FROM messages WHERE user_id = ? AND id > ? ORDER BY id ASC");
$stmt->execute([$user_id, $last_id]);
$msgs = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['success' => true, 'data' => $msgs]);
exit;
}
// Default action: return count and last_id, and if requested, full data
$stmt = $pdo->prepare("SELECT COUNT(*), MAX(id) FROM messages WHERE user_id = ?");
$stmt->execute([$user_id]);
$res = $stmt->fetch();
$count = $res[0];
$last_id = $res[1];
$stmt = $pdo->prepare("SELECT * FROM messages WHERE user_id = ? ORDER BY id DESC LIMIT 20");
// Default action: return last 50 messages in ASC order
$stmt = $pdo->prepare("SELECT * FROM (SELECT * FROM messages WHERE user_id = ? ORDER BY id DESC LIMIT 50) AS sub ORDER BY id ASC");
$stmt->execute([$user_id]);
$msgs = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'count' => (int)$count,
'last_id' => (int)$last_id,
'data' => $msgs
]);
]);

View File

@ -4,17 +4,22 @@ session_start();
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Unauthorized']);
// Check authorization - either user session or admin session
// For simplicity in this environment, we assume if it's called from admin it might have different session or we check params
// But usually admin also has session_start() and auth.php
$pdo = db();
$sender = isset($_GET['sender']) && $_GET['sender'] === 'admin' ? 'admin' : 'user';
$user_id = isset($_GET['user_id']) ? $_GET['user_id'] : ($_SESSION['user_id'] ?? null);
if (!$user_id) {
echo json_encode(['success' => false, 'error' => 'Unauthorized or missing User ID']);
exit;
}
$user_id = $_SESSION['user_id'];
$pdo = db();
// Handle Confirm Payment action
if (isset($_GET['action']) && $_GET['action'] === 'confirm_payment') {
// Check for active order that is 'matched'
// Handle Confirm Payment action (User only)
if (isset($_GET['action']) && $_GET['action'] === 'confirm_payment' && $sender === 'user') {
$stmt = $pdo->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status = 'matched' ORDER BY id DESC LIMIT 1");
$stmt->execute([$user_id]);
$order = $stmt->fetch();
@ -24,11 +29,8 @@ if (isset($_GET['action']) && $_GET['action'] === 'confirm_payment') {
exit;
}
// Update status to submitting
$stmt = $pdo->prepare("UPDATE fiat_orders SET status = 'submitting' WHERE id = ?");
$stmt->execute([$order['id']]);
// Send a system message to chat
$pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', '我已完成支付,请查收凭证。')")->execute([$user_id]);
echo json_encode(['success' => true]);
@ -49,7 +51,7 @@ if (!in_array($ext, $allowed)) {
exit;
}
$filename = 'chat_' . $user_id . '_' . time() . '_' . mt_rand(1000, 9999) . '.' . $ext;
$filename = 'chat_' . ($sender === 'admin' ? 'admin_' : '') . $user_id . '_' . time() . '_' . mt_rand(1000, 9999) . '.' . $ext;
$dir = '../assets/images/chat/';
if (!is_dir($dir)) mkdir($dir, 0775, true);
@ -58,17 +60,18 @@ $target = $dir . $filename;
if (move_uploaded_file($file['tmp_name'], $target)) {
$path = 'assets/images/chat/' . $filename;
$stmt = $pdo->prepare("INSERT INTO messages (user_id, sender, type, message) VALUES (?, 'user', 'image', ?)");
$stmt->execute([$user_id, $path]);
$stmt = $pdo->prepare("INSERT INTO messages (user_id, sender, type, message) VALUES (?, ?, 'image', ?)");
$stmt->execute([$user_id, $sender, $path]);
// If there is an active order, update its proof_image
$stmt = $pdo->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status IN ('matched', 'matching', 'submitting') ORDER BY id DESC LIMIT 1");
$stmt->execute([$user_id]);
$order = $stmt->fetch();
if ($order) {
$stmt = $pdo->prepare("UPDATE fiat_orders SET proof_image = ? WHERE id = ?");
$stmt->execute([$path, $order['id']]);
// If it's a user uploading, also update active order proof
if ($sender === 'user') {
$stmt = $pdo->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status IN ('matched', 'matching', 'submitting') ORDER BY id DESC LIMIT 1");
$stmt->execute([$user_id]);
$order = $stmt->fetch();
if ($order) {
$stmt = $pdo->prepare("UPDATE fiat_orders SET proof_image = ? WHERE id = ?");
$stmt->execute([$path, $order['id']]);
}
}
echo json_encode(['success' => true, 'path' => $path]);

View File

@ -19,6 +19,7 @@ body {
color: var(--text-color);
line-height: 1.5;
padding-bottom: 0;
overflow-x: hidden;
}
/* Scrollbar */
@ -60,6 +61,29 @@ body {
color: var(--primary-color);
}
/* Utility Classes */
.d-none { display: none !important; }
.d-flex { display: flex !important; }
.d-block { display: block !important; }
@media (max-width: 767px) {
.d-sm-none { display: none !important; }
}
@media (max-width: 991px) {
.d-md-none { display: none !important; }
}
@media (min-width: 768px) {
.d-md-block { display: block !important; }
.d-md-flex { display: flex !important; }
}
@media (min-width: 992px) {
.d-lg-block { display: block !important; }
.d-lg-flex { display: flex !important; }
}
@media (min-width: 1200px) {
.d-xl-flex { display: flex !important; }
}
/* Colorful Icons */
.fa-home { color: #5d5dff; }
.fa-chart-line { color: #00e676; }
@ -181,12 +205,6 @@ body {
}
.sidebar-overlay.open { display: block; }
/* Market Trends Table */
.market-table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Responsive Grid Helper */
.container {
max-width: 1200px;
@ -197,30 +215,43 @@ body {
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px; }
.responsive-grid {
display: grid;
grid-template-columns: 1fr 380px;
gap: 30px;
}
/* Mobile Optimizations */
@media (max-width: 992px) {
.navbar { padding: 0 1rem; }
.nav-links { display: none; }
.hero-section { flex-direction: column; text-align: center; padding: 40px 5%; }
.grid-3, .grid-2 { grid-template-columns: 1fr; }
.grid-3, .grid-2, .responsive-grid { grid-template-columns: 1fr; }
.mobile-bottom-nav { display: flex; }
body { padding-bottom: 70px; }
.section-title { font-size: 1.8rem; }
/* Market Table Mobile */
.market-table-container {
width: 100%;
overflow-x: auto;
}
.market-table th:nth-child(4),
.market-table td:nth-child(4) { display: none; }
}
@media (max-width: 576px) {
.logo-text { font-size: 1.4rem !important; }
.logo-svg { width: 28px !important; height: 28px !important; }
.container { padding: 0 15px; }
.logo-text { font-size: 1.2rem !important; }
.logo-svg { width: 24px !important; height: 24px !important; }
.btn-login-hide { display: none; }
.market-table th, .market-table td { padding: 12px 15px !important; }
.market-table td div { font-size: 0.9rem !important; }
.market-table th, .market-table td { padding: 10px 12px !important; }
.market-table td div { font-size: 0.85rem !important; }
.deposit-card, .withdraw-card { padding: 25px 20px !important; }
}
/* User Profile Dropdown Adjustments */

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -9,7 +9,7 @@ $user_id = $_SESSION['user_id'];
$pdo = db();
// Fetch user info
$stmt = $pdo->prepare("SELECT uid, username FROM users WHERE id = ?");
$stmt = $pdo->prepare("SELECT uid, username, last_ip FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch();
@ -25,8 +25,8 @@ if (isset($_GET['action']) && $_GET['action'] === 'complete_order' && $active_or
$stmt = $pdo->prepare("UPDATE fiat_orders SET status = 'paid' WHERE id = ?");
$stmt->execute([$active_order['id']]);
$type_text = ($active_order['order_type'] === 'deposit') ? "充值" : "提现";
$msg = "用户已点击确认,$type_text 申请等待审核中。";
$type_text = ($active_order['order_type'] === 'deposit') ? __('nav_deposit') : __('nav_withdraw');
$msg = "" . __('confirm_transfer') . ", $type_text " . __('paid_waiting_tip');
$stmt = $pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)");
$stmt->execute([$user_id, $msg]);
@ -37,7 +37,10 @@ if (isset($_GET['action']) && $_GET['action'] === 'complete_order' && $active_or
// Fetch greeting message
$stmt = $pdo->prepare("SELECT value FROM settings WHERE name = 'chat_greeting'");
$stmt->execute();
$greeting = $stmt->fetchColumn() ?: '您好!欢迎咨询 NovaEx 官方客服,请问有什么可以帮您?如果是充值咨询,请提供您的充值金额和币种。';
$greeting = $stmt->fetchColumn();
if (!$greeting) {
$greeting = ($lang == 'zh') ? '您好!欢迎咨询 NovaEx 官方客服,请问有什么可以帮您?如果是充值咨询,请提供您的充值金额和币种。' : 'Hello! Welcome to NovaEx official customer service. How can we help you today? For deposit inquiries, please provide your amount and currency.';
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
$msg = trim($_POST['message']);
@ -59,9 +62,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
.msg-container { display: flex; flex-direction: column; transition: all 0.3s ease; }
.msg-content { max-width: 75%; padding: 12px 18px; border-radius: 18px; font-size: 14px; line-height: 1.6; position: relative; }
.locked-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.85); backdrop-filter: blur(5px); z-index: 9999; display: flex; align-items: center; justify-content: center; }
.locked-modal { background: #1e2329; width: 90%; max-width: 500px; border-radius: 24px; padding: 40px; text-align: center; border: 1px solid var(--primary-color); }
.account-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 450px; background: #1e2329; border-radius: 24px; border: 1px solid var(--primary-color); z-index: 10001; padding: 30px; box-shadow: 0 20px 50px rgba(0,0,0,0.8); display: none; }
.modal-backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 10000; display: none; }
@ -78,13 +78,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<div style="width: 60px; height: 60px; background: rgba(240,185,11,0.1); border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--primary-color); margin: 0 auto 15px; font-size: 24px;">
<i class="fas fa-university"></i>
</div>
<h3 style="margin: 0; color: white;" id="modal-title">账户已匹配</h3>
<p style="color: var(--text-muted); font-size: 14px; margin-top: 5px;" id="modal-tip">请按照下方账户信息完成操作。</p>
<h3 style="margin: 0; color: white;" id="modal-title"><?php echo __('matched_status'); ?></h3>
<p style="color: var(--text-muted); font-size: 14px; margin-top: 5px;" id="modal-tip"><?php echo __('matched_info_tip'); ?></p>
</div>
<div id="acc-info" style="background: #161a1e; padding: 20px; border-radius: 16px; color: white; font-size: 14px; line-height: 2; border: 1px solid #2b3139; margin-bottom: 25px;">
<!-- Filled by JS -->
</div>
<button onclick="closeAccModal()" class="btn-primary" style="width: 100%; padding: 15px; border-radius: 12px; font-weight: 800;">确认</button>
<button onclick="closeAccModal()" class="btn-primary" style="width: 100%; padding: 15px; border-radius: 12px; font-weight: 800;"><?php echo __('confirm'); ?></button>
</div>
<div id="chat-container" class="container" style="max-width: 850px; margin: 30px auto; padding: 0; height: calc(100vh - 200px); min-height: 500px;">
@ -93,25 +93,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<div style="display: flex; align-items: center; gap: 15px;">
<i class="fas fa-lock"></i>
<div>
<div style="font-size: 14px; font-weight: 800;"><?php echo ($active_order['order_type'] === 'deposit') ? '充值进行中' : '提现申请中'; ?></div>
<div style="font-size: 14px; font-weight: 800;"><?php echo ($active_order['order_type'] === 'deposit') ? __('nav_deposit') : __('nav_withdraw'); ?></div>
<div style="font-size: 12px; opacity: 0.8;"><?php
if ($active_order['status'] === 'matching') echo '等待客服响应...';
elseif ($active_order['status'] === 'matched') echo ($active_order['order_type'] === 'deposit') ? '请按要求转账并上传凭证' : '请提供您的收款信息';
elseif ($active_order['status'] === 'paid') echo '正在审核凭证,请稍候...';
if ($active_order['status'] === 'matching') echo __('matching_status') . '...';
elseif ($active_order['status'] === 'matched') echo ($active_order['order_type'] === 'deposit') ? __('matched_info_tip') : __('withdraw_info_tip');
elseif ($active_order['status'] === 'paid') echo __('paid_waiting_tip');
?></div>
</div>
</div>
<div style="display: flex; gap: 10px;">
<?php if ($active_order['status'] === 'matched' && $active_order['order_type'] === 'deposit'): ?>
<?php if ($active_order['status'] === 'matched'): ?>
<button onclick="showCurrentAccInfo()" class="btn-primary" style="padding: 8px 15px; font-size: 12px; border-radius: 8px; background: #2b3139; color: white; border: 1px solid #3b424d;">
查看账户
<?php echo ($active_order['order_type'] === 'deposit') ? __('view_account') : __('view_withdraw_format'); ?>
</button>
<?php endif; ?>
<?php if ($active_order['status'] === 'matched'): ?>
<button onclick="window.location.href='?action=complete_order'" class="btn-primary" style="padding: 8px 15px; font-size: 12px; border-radius: 8px;">
<?php echo ($active_order['order_type'] === 'deposit') ? '完成转账' : '确认发送'; ?>
<?php echo ($active_order['order_type'] === 'deposit') ? __('complete_transfer_btn') : __('confirm'); ?>
</button>
<?php endif; ?>
</div>
@ -126,21 +126,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<i class="fas fa-headset fa-lg"></i>
</div>
<div>
<h3 style="margin: 0; font-size: 18px; color: white;">官方客服中心</h3>
<h3 style="margin: 0; font-size: 18px; color: white;"><?php echo __('support_online'); ?></h3>
<div style="display: flex; align-items: center; gap: 5px; font-size: 12px; color: #00c087;">
<span style="width: 8px; height: 8px; background: #00c087; border-radius: 50%; display: inline-block;"></span> 24/7 全天候在线
<span style="width: 8px; height: 8px; background: #00c087; border-radius: 50%; display: inline-block;"></span> 24/7 <?php echo ($lang == 'zh' ? '全天候在线' : 'Always Online'); ?>
</div>
</div>
</div>
<div style="text-align: right; font-size: 12px; color: #848e9c;">
<div>UID: <?php echo $user['uid']; ?></div>
<a href="index.php" style="color: var(--primary-color); text-decoration: none; display: <?php echo $is_locked ? 'none' : 'block'; ?>; margin-top: 5px;"><i class="fas fa-times"></i> 退出</a>
<div>UID: <?php echo htmlspecialchars($user['uid']); ?></div>
<div style="font-size: 10px; opacity: 0.7;">IP: <?php echo htmlspecialchars($user['last_ip']); ?></div>
<a href="index.php" style="color: var(--primary-color); text-decoration: none; display: <?php echo $is_locked ? 'none' : 'block'; ?>; margin-top: 5px;"><i class="fas fa-times"></i> <?php echo __('cancel'); ?></a>
</div>
</div>
<!-- Chat Body -->
<div id="chat-box">
<div style="text-align: center; color: #848e9c; padding: 20px;">正在连接客服...</div>
<div style="text-align: center; color: #848e9c; padding: 20px;">Connecting...</div>
</div>
<!-- Input Area -->
@ -151,7 +152,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
</button>
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="uploadImage(this)">
<input type="text" id="chat-input" placeholder="请输入消息..."
<input type="text" id="chat-input" placeholder="<?php echo __('type_message'); ?>"
style="flex: 1; background: #161a1e; border: 1px solid #2b3139; border-radius: 12px; padding: 14px 20px; color: white; outline: none; font-size: 14px;" autocomplete="off">
<button type="submit" style="background: #f0b90b; border: none; color: black; width: 50px; height: 50px; border-radius: 12px; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0;">
@ -164,15 +165,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<?php if ($is_locked): ?>
<script>
// Prevent leaving the page
window.onbeforeunload = function() {
return "您的充值/提现流程尚未完成,请勿离开此页面。";
return "<?php echo ($lang == 'zh' ? '您的业务流程尚未完成,请勿离开此页面。' : 'Your transaction is not complete. Please stay on this page.'); ?>";
};
// Hide navigation elements
document.querySelector('nav')?.style.display = 'none';
document.querySelector('footer')?.style.display = 'none';
// Prevent back navigation
history.pushState(null, null, location.href);
window.onpopstate = function() {
history.pushState(null, null, location.href);
@ -201,7 +199,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
<div class="msg-content" style="background: #2b3139; color: white; border-bottom-left-radius: 4px; border: 1px solid #3b424d;">
${greeting.replace(/\n/g, '<br>')}
</div>
<span style="font-size: 10px; color: #5e6673; margin-top: 6px;">系统助手</span>
<span style="font-size: 10px; color: #5e6673; margin-top: 6px;">Support</span>
</div>
`;
} else {
@ -233,20 +231,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
if (res.success && res.order) {
currentBankAccountInfo = res.order.bank_account_info;
if (res.order.status !== lastStatus) {
if (res.order.status === 'matched') {
// Only show modal for DEPOSIT
if (res.order.order_type === 'deposit') {
showAccModal('账户已匹配', '请按照下方账户信息完成转账,并务必上传截图凭证。', res.order.bank_account_info);
}
const title = res.order.order_type === 'deposit' ? '<?php echo __('matched_status'); ?>' : '<?php echo __('withdraw_format_title'); ?>';
const tip = res.order.order_type === 'deposit' ? '<?php echo __('matched_info_tip'); ?>' : '<?php echo __('withdraw_info_tip'); ?>';
showAccModal(title, tip, res.order.bank_account_info);
} else if (res.order.status === 'completed' || res.order.status === 'rejected') {
alert(res.order.status === 'completed' ? "业务处理成功!" : "业务被拒绝,请联系客服了解详情。");
alert(res.order.status === 'completed' ? "Success!" : "Request rejected.");
window.onbeforeunload = null;
location.href = 'profile.php';
}
lastStatus = res.order.status;
}
} else if (res.success && !res.order && lastStatus !== '') {
// Order disappeared or completed
window.onbeforeunload = null;
location.href = 'profile.php';
}
@ -263,7 +258,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
}
function showCurrentAccInfo() {
showAccModal('收款账户信息', '请核对以下转账信息。', currentBankAccountInfo);
const orderType = '<?php echo $active_order['order_type'] ?? ''; ?>';
const title = orderType === 'deposit' ? '<?php echo __('view_account'); ?>' : '<?php echo __('view_withdraw_format'); ?>';
const tip = orderType === 'deposit' ? '<?php echo __('matched_info_tip'); ?>' : '<?php echo __('withdraw_info_tip'); ?>';
showAccModal(title, tip, currentBankAccountInfo);
}
function closeAccModal() {
@ -298,21 +296,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
if (res.success) {
loadMessages();
} else {
alert(res.error || '上传失败');
alert(res.error || 'Upload failed');
}
})
.finally(() => {
uploadBtn.disabled = false;
uploadBtn.innerHTML = '<i class="fas fa-plus"></i>';
input.value = ''; // Reset input
input.value = '';
});
}
// Show modal on load if matched and deposit
window.onload = () => {
const orderType = '<?php echo $active_order['order_type'] ?? ''; ?>';
if (lastStatus === 'matched' && orderType === 'deposit' && currentBankAccountInfo) {
showAccModal('账户已匹配', '请按照下方账户信息完成转账,并务必上传截图凭证。', currentBankAccountInfo);
if (lastStatus === 'matched' && currentBankAccountInfo) {
const title = orderType === 'deposit' ? '<?php echo __('matched_status'); ?>' : '<?php echo __('withdraw_format_title'); ?>';
const tip = orderType === 'deposit' ? '<?php echo __('matched_info_tip'); ?>' : '<?php echo __('withdraw_info_tip'); ?>';
showAccModal(title, tip, currentBankAccountInfo);
}
};

View File

@ -1,9 +1,10 @@
<?php
session_start();
require_once 'db/config.php';
require_once 'includes/i18n.php';
if (!isset($_SESSION['user_id'])) {
die("Please login first.");
die(__('please_login'));
}
$user_id = $_SESSION['user_id'];
@ -18,40 +19,75 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
}
exit;
}
// Fetch greeting message
$stmt = $db->prepare("SELECT value FROM settings WHERE name = 'chat_greeting'");
$stmt->execute();
$greeting = $stmt->fetchColumn();
if (!$greeting) {
$greeting = ($lang == 'zh') ? '您好!欢迎咨询 NovaEx 官方客服,请问有什么可以帮您?' : 'Hello! Welcome to NovaEx official customer service. How can we help you today?';
}
?>
<!DOCTYPE html>
<html lang="en">
<html lang="<?php echo $lang; ?>">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
body { margin: 0; padding: 0; font-family: 'Inter', sans-serif; background: #161a1e; color: white; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
:root {
--primary-color: #f0b90b;
--bg-color: #161a1e;
--card-bg: #1e2329;
--border-color: #2b3139;
--text-color: #ffffff;
--text-muted: #848e9c;
}
body { margin: 0; padding: 0; font-family: 'Inter', sans-serif; background: var(--bg-color); color: white; height: 100vh; display: flex; flex-direction: column; overflow: hidden; }
#chat-box { flex: 1; overflow-y: auto; padding: 15px; display: flex; flex-direction: column; gap: 12px; scroll-behavior: smooth; }
.msg { max-width: 80%; padding: 10px 14px; border-radius: 12px; font-size: 14px; line-height: 1.4; word-wrap: break-word; }
.msg.user { align-self: flex-end; background: #f0b90b; color: black; border-bottom-right-radius: 2px; }
.msg { max-width: 80%; padding: 10px 14px; border-radius: 12px; font-size: 14px; line-height: 1.4; word-wrap: break-word; position: relative; }
.msg.user { align-self: flex-end; background: var(--primary-color); color: black; border-bottom-right-radius: 2px; }
.msg.admin { align-self: flex-start; background: #2b3139; color: #EAECEF; border-bottom-left-radius: 2px; border: 1px solid #3b424d; }
.msg-time { font-size: 10px; opacity: 0.5; margin-top: 4px; display: block; }
.chat-input-area { padding: 12px; background: #1e2329; border-top: 1px solid #2b3139; display: flex; gap: 10px; align-items: center; }
input[type="text"] { flex: 1; background: #0b0e11; border: 1px solid #2b3139; border-radius: 8px; padding: 10px 12px; color: white; outline: none; }
.icon-btn { background: #2b3139; border: 1px solid #3b424d; width: 40px; height: 40px; border-radius: 8px; color: #f0b90b; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.icon-btn { background: #2b3139; border: 1px solid #3b424d; width: 40px; height: 40px; border-radius: 8px; color: var(--primary-color); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.icon-btn:hover { background: #3b424d; }
.send-btn { background: #f0b90b; border: none; width: 40px; height: 40px; border-radius: 8px; color: black; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.send-btn { background: var(--primary-color); border: none; width: 40px; height: 40px; border-radius: 8px; color: black; cursor: pointer; display: flex; align-items: center; justify-content: center; }
#chat-box::-webkit-scrollbar { width: 4px; }
#chat-box::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
img.chat-img { max-width: 100%; border-radius: 8px; margin-top: 5px; cursor: pointer; }
/* Modal inside iframe */
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 85%; background: var(--card-bg); border-radius: 16px; border: 1px solid var(--primary-color); z-index: 1000; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: none; }
.backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 999; display: none; }
.modal-btn { width: 100%; background: var(--primary-color); border: none; padding: 10px; border-radius: 8px; font-weight: bold; cursor: pointer; margin-top: 15px; }
.sending { opacity: 0.7; }
.sending::after { content: '...'; position: absolute; right: 5px; bottom: 5px; font-size: 10px; }
</style>
</head>
<body>
<div id="acc-backdrop" class="backdrop"></div>
<div id="acc-modal" class="modal">
<div style="text-align: center; margin-bottom: 15px;">
<i class="fas fa-university" style="color: var(--primary-color); font-size: 24px;"></i>
<h4 style="margin: 10px 0 5px;" id="modal-title"><?php echo __('matched_status'); ?></h4>
<p style="color: var(--text-muted); font-size: 12px;" id="modal-tip"><?php echo __('matched_info_tip'); ?></p>
</div>
<div id="acc-info" style="background: #161a1e; padding: 15px; border-radius: 8px; font-size: 13px; line-height: 1.6; border: 1px solid #2b3139;"></div>
<button onclick="closeModal()" class="modal-btn"><?php echo __('confirm'); ?></button>
</div>
<div id="chat-box"></div>
<form id="chat-form" class="chat-input-area">
<button type="button" class="icon-btn" id="upload-btn" onclick="document.getElementById('image-input').click()">
<i class="fas fa-plus"></i>
</button>
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="uploadImage(this)>
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="uploadImage(this)">
<input type="text" id="msg-input" placeholder="Type a message..." autocomplete="off">
<input type="text" id="msg-input" placeholder="<?php echo __('type_message'); ?>" autocomplete="off">
<button type="submit" class="send-btn"><i class="fas fa-paper-plane"></i></button>
</form>
@ -59,41 +95,110 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
const chatBox = document.getElementById('chat-box');
const msgInput = document.getElementById('msg-input');
const uploadBtn = document.getElementById('upload-btn');
const greeting = `<?php echo addslashes($greeting); ?>`;
let lastStatus = '';
let lastOrderId = 0;
let lastMessagesHtml = '';
let isSending = false;
async function loadMessages() {
if (isSending) return; // Prevent refresh while sending to avoid flickering
try {
const resp = await fetch('api/get_messages.php');
const res = await resp.json();
if (res.success) {
let html = '';
res.data.forEach(m => {
const content = m.type === 'image'
? `<img src="${m.message}" class="chat-img" onclick="window.open(this.src)">`
: m.message.replace(/\n/g, '<br>');
html += `
<div class="msg ${m.sender}">
${content}
<span class="msg-time">${new Date(m.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
if (res.data.length === 0) {
chatBox.innerHTML = `
<div class="msg admin">
${greeting.replace(/\n/g, '<br>')}
</div>
`;
});
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 100;
chatBox.innerHTML = html;
if (isAtBottom) chatBox.scrollTop = chatBox.scrollHeight;
} else {
let html = '';
res.data.forEach(m => {
const content = m.type === 'image'
? `<img src="${m.message}" class="chat-img" onclick="window.open(this.src)">`
: m.message.replace(/\n/g, '<br>');
html += `
<div class="msg ${m.sender}">
${content}
<span class="msg-time">${new Date(m.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
`;
});
if (html !== lastMessagesHtml) {
const isAtBottom = chatBox.scrollHeight - chatBox.scrollTop <= chatBox.clientHeight + 100;
chatBox.innerHTML = html;
lastMessagesHtml = html;
if (isAtBottom) chatBox.scrollTop = chatBox.scrollHeight;
// Mark messages as read
fetch('api/get_messages.php?action=mark_read&user_id=<?php echo $user_id; ?>&reader=user');
}
}
}
} catch (e) {}
}
async function checkOrderStatus() {
try {
const resp = await fetch('api/check_order_status.php');
const res = await resp.json();
if (res.success && res.order) {
if (res.order.status === 'matched' && (res.order.status !== lastStatus || res.order.id !== lastOrderId)) {
const title = res.order.order_type === 'deposit' ? '<?php echo __('matched_status'); ?>' : '<?php echo __('withdraw_format_title'); ?>';
const tip = res.order.order_type === 'deposit' ? '<?php echo __('matched_info_tip'); ?>' : '<?php echo __('withdraw_info_tip'); ?>';
showModal(title, tip, res.order.bank_account_info);
lastStatus = res.order.status;
lastOrderId = res.order.id;
}
}
} catch (e) {}
}
function showModal(title, tip, info) {
if (!info) return;
document.getElementById('modal-title').innerText = title;
document.getElementById('modal-tip').innerText = tip;
document.getElementById('acc-info').innerHTML = info.replace(/\n/g, '<br>');
document.getElementById('acc-backdrop').style.display = 'block';
document.getElementById('acc-modal').style.display = 'block';
}
function closeModal() {
document.getElementById('acc-backdrop').style.display = 'none';
document.getElementById('acc-modal').style.display = 'none';
}
document.getElementById('chat-form').onsubmit = async (e) => {
e.preventDefault();
const msg = msgInput.value.trim();
if (!msg) return;
if (!msg || isSending) return;
msgInput.value = '';
isSending = true;
// Optimistic UI: Add message locally
const tempMsg = document.createElement('div');
tempMsg.className = 'msg user sending';
tempMsg.innerHTML = `${msg.replace(/\n/g, '<br>')}<span class="msg-time">${new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>`;
chatBox.appendChild(tempMsg);
chatBox.scrollTop = chatBox.scrollHeight;
const formData = new FormData();
formData.append('message', msg);
await fetch('chat_iframe.php', { method: 'POST', body: formData });
loadMessages();
try {
await fetch('chat_iframe.php', { method: 'POST', body: formData });
isSending = false;
await loadMessages();
} catch (e) {
isSending = false;
tempMsg.style.background = '#ff4d4f';
tempMsg.innerHTML += ' (Failed)';
}
};
function uploadImage(input) {
@ -120,8 +225,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) {
});
}
// Initial calls
loadMessages();
setInterval(loadMessages, 3000);
checkOrderStatus();
// Polling
setInterval(loadMessages, 1000);
setInterval(checkOrderStatus, 2000);
</script>
</body>
</html>

View File

@ -5,6 +5,9 @@ define('DB_NAME', 'app_38350');
define('DB_USER', 'app_38350');
define('DB_PASS', 'c79d2d31-7d44-4d51-ac22-5bfd0886fcc2');
// Set timezone to China Standard Time
date_default_timezone_set('Asia/Shanghai');
function db() {
static $pdo;
if (!$pdo) {

View File

@ -231,10 +231,10 @@ CREATE TABLE `users` (
-- Dump completed on 2026-02-12 14:31:49
-- Dumping data
/*M!999999\- enable the sandbox mode */
INSERT INTO `admins` VALUES
(1,'admin','$2y$10$dltSsFIkB4FbJPgOPUFLGuDehX/bAFM96fxywvSL9jaEsgA/1r4pG','admin',NULL,NULL,'2026-02-12 08:33:20');
INSERT INTO `settings` VALUES
INSERT INTO `admins` (id, username, password, role, created_at) VALUES
(1,'admin','$2y$10$RJoR9U.GZwbNe14JqaWY7e2NQcJVNQYGr6rDFyzqVne4D1Ym5/ORS','admin',NOW());
INSERT INTO `settings` (name, value) VALUES
('chat_greeting','您好!欢迎咨询 NovaEx 官方客服,请问有什么可以帮您?如果是充值咨询,请提供您的充值金额和币种。'),
('price_control','0'),
('win_rate','70');
('win_rate','70');

View File

@ -1,15 +1,19 @@
<?php
include 'header.php';
if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; }
require_once 'db/config.php';
require_once 'includes/currency_helper.php';
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
$db = db();
// 检查是否有正在进行的订单,如果有则跳转到聊天
$stmt = $db->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'matched', 'paid') ORDER BY id DESC LIMIT 1");
$stmt->execute([$_SESSION['user_id']]);
if ($stmt->fetch()) { header("Location: chat.php"); exit; }
include 'header.php';
$fiat_rates = get_fiat_rates();
$fiat_currencies_info = [
@ -45,12 +49,18 @@ $fiat_currencies_info = [
.input-group-custom input { background: none; border: none; color: white; font-size: 1.2rem; font-weight: 700; width: 100%; outline: none; }
.safe-banner { background: rgba(14, 203, 129, 0.1); border: 1px solid rgba(14, 203, 129, 0.2); padding: 20px; border-radius: 16px; color: var(--success-color); display: flex; gap: 15px; align-items: center; margin-bottom: 30px; }
@media (max-width: 576px) {
.method-card { padding: 20px 15px; }
.method-card .icon-box { width: 40px; height: 40px; font-size: 16px; }
.method-card div:nth-child(2) { font-size: 0.95rem !important; }
}
</style>
<div class="deposit-container">
<div class="container" style="max-width: 1100px;">
<div style="margin-bottom: 30px;">
<a href="profile.php" class="back-btn"><i class="fas fa-arrow-left"></i> 个人中心</a>
<a href="profile.php" class="back-btn" style="color: var(--text-muted); text-decoration: none; font-size: 14px;"><i class="fas fa-arrow-left"></i> 个人中心</a>
<h1 style="font-size: 2.2rem; font-weight: 800; margin-top: 10px;">充值</h1>
<p style="color: var(--text-muted);">通过多种通道安全地为您的账户充值</p>
</div>
@ -63,7 +73,7 @@ $fiat_currencies_info = [
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 380px; gap: 30px;">
<div class="responsive-grid">
<div>
<div class="deposit-card" style="padding: 40px;">
<h3 style="margin-bottom: 25px; font-weight: 800;">1. 选择充值方式</h3>
@ -71,21 +81,21 @@ $fiat_currencies_info = [
<div id="method-fiat" class="method-card active" onclick="switchDepositMethod('fiat')">
<div class="icon-box" style="background: rgba(79,172,254,0.1); color: #4facfe;"><i class="fas fa-university"></i></div>
<div style="font-weight: 800; font-size: 1.1rem;">法币充值</div>
<div style="color: var(--text-muted); font-size: 12px; margin-top: 4px;">银行转账 / 全球 OTC</div>
<div style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">银行转账 / OTC</div>
</div>
<div id="method-usdt" class="method-card" onclick="switchDepositMethod('usdt')">
<div class="icon-box" style="background: rgba(14,203,129,0.1); color: var(--success-color);"><i class="fas fa-coins"></i></div>
<div style="font-weight: 800; font-size: 1.1rem;">USDT 充值</div>
<div style="color: var(--text-muted); font-size: 12px; margin-top: 4px;">USDT (TRC20, ERC20, BEP20)</div>
<div style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">TRC20, ERC20...</div>
</div>
</div>
<div id="fiat-form-section">
<h3 style="margin-bottom: 25px; font-weight: 800;">2. 订单详情</h3>
<form action="matching.php" method="POST">
<form id="fiat-deposit-form" action="matching.php" method="POST">
<input type="hidden" name="order_type" value="deposit">
<input type="hidden" name="type" value="fiat">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 25px;">
<div style="display: flex; flex-direction: column; gap: 20px; margin-bottom: 25px;">
<div>
<label style="display: block; color: var(--text-muted); font-size: 13px; margin-bottom: 8px;">充值币种</label>
<select name="currency" id="fiat-select" onchange="updateExchangeRate()" style="width: 100%; padding: 16px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 16px; outline: none; font-weight: 600;">
@ -106,17 +116,17 @@ $fiat_currencies_info = [
</div>
<div style="background: rgba(255,255,255,0.03); padding: 25px; border-radius: 20px; margin-bottom: 30px; border: 1px dashed var(--border-color);">
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<div style="display: flex; justify-content: space-between; margin-bottom: 10px; font-size: 14px;">
<span style="color: var(--text-muted);">实时汇率</span>
<span style="font-weight: 700;">1 USDT <span id="rate-text">--</span> <span class="current-fiat-code">USD</span></span>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; border-top: 1px solid rgba(255,255,255,0.05); pt: 15px; margin-top: 15px; padding-top: 15px;">
<span style="font-weight: 800; font-size: 1.1rem;">预计收到</span>
<span style="font-weight: 800; font-size: 1.5rem; color: var(--primary-color);"><span id="receive-text">0.00</span> USDT</span>
<span style="font-weight: 800; font-size: 1.4rem; color: var(--primary-color);"><span id="receive-text">0.00</span> USDT</span>
</div>
</div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800;">
<button type="submit" class="btn-primary submit-btn" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800;">
发起充值请求
</button>
</form>
@ -124,13 +134,13 @@ $fiat_currencies_info = [
<div id="usdt-form-section" style="display: none;">
<h3 style="margin-bottom: 25px; font-weight: 800;">2. 订单详情</h3>
<form action="matching.php" method="POST">
<form id="usdt-deposit-form" action="matching.php" method="POST">
<input type="hidden" name="order_type" value="deposit">
<input type="hidden" name="type" value="usdt">
<input type="hidden" name="currency" value="USDT">
<label style="display: block; color: var(--text-muted); font-size: 13px; margin-bottom: 12px;">选择网络</label>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px; margin-bottom: 25px;">
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 25px;">
<label class="network-opt"><input type="radio" name="network" value="TRC20" checked style="display:none;"><div class="network-box">TRC20</div></label>
<label class="network-opt"><input type="radio" name="network" value="ERC20" style="display:none;"><div class="network-box">ERC20</div></label>
<label class="network-opt"><input type="radio" name="network" value="BEP20" style="display:none;"><div class="network-box">BEP20</div></label>
@ -144,7 +154,7 @@ $fiat_currencies_info = [
</div>
</div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800; background: var(--success-color);">
<button type="submit" class="btn-primary submit-btn" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800; background: var(--success-color);">
发起充值请求
</button>
</form>
@ -152,7 +162,7 @@ $fiat_currencies_info = [
</div>
</div>
<div class="profile-sidebar">
<div class="sidebar-area">
<div class="deposit-card" style="padding: 30px;">
<h4 style="font-weight: 800; margin-bottom: 25px;"><i class="fas fa-info-circle" style="color: var(--primary-color);"></i> 充值步骤说明</h4>
<div class="instruction-item">
@ -166,7 +176,7 @@ $fiat_currencies_info = [
<div class="instruction-number">2</div>
<div style="font-size: 14px; color: var(--text-muted);">
<strong style="color: white; display: block; margin-bottom: 4px;">联系在线客服</strong>
您将自动跳转至客服聊天界面,客服将为您匹配唯一的收款账户。
您将自动弹出客服聊天界面,客服将为您匹配唯一的收款账户。
</div>
</div>
<div class="instruction-item">
@ -200,7 +210,7 @@ $fiat_currencies_info = [
</div>
<style>
.network-box { padding: 15px; background: #161a1e; border: 1px solid var(--border-color); border-radius: 12px; text-align: center; font-weight: 700; cursor: pointer; transition: 0.3s; }
.network-box { padding: 15px; background: #161a1e; border: 1px solid var(--border-color); border-radius: 12px; text-align: center; font-weight: 700; cursor: pointer; transition: 0.3s; font-size: 13px; }
.network-opt input:checked + .network-box { border-color: var(--success-color); background: rgba(14,203,129,0.05); color: var(--success-color); }
</style>
@ -227,6 +237,54 @@ $fiat_currencies_info = [
}
updateExchangeRate();
// AJAX Form Submission
const forms = ['fiat-deposit-form', 'usdt-deposit-form'];
forms.forEach(id => {
const form = document.getElementById(id);
if (form) {
form.onsubmit = async (e) => {
e.preventDefault();
const btn = form.querySelector('.submit-btn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
try {
const formData = new FormData(form);
const resp = await fetch('matching.php', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const res = await resp.json();
if (res.success) {
// Trigger chat popup
if (typeof openCSChat === 'function') {
openCSChat();
// Optional: Disable forms to prevent double submission
form.style.opacity = '0.5';
form.style.pointerEvents = 'none';
btn.innerHTML = '<i class="fas fa-check"></i> 请求已发送';
} else {
window.location.href = 'chat.php';
}
} else {
alert(res.error || '提交失败,请稍后重试');
btn.disabled = false;
btn.innerHTML = originalText;
}
} catch (e) {
console.error(e);
alert('网络错误,请稍后重试');
btn.disabled = false;
btn.innerHTML = originalText;
}
};
}
});
</script>
<?php include 'footer.php'; ?>

View File

@ -86,9 +86,62 @@ if ($user_id) {
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#ob-panel-content::-webkit-scrollbar { width: 4px; }
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
/* Mobile Specific Styles */
@media (max-width: 991px) {
.trading-container { flex-direction: column; min-height: auto; }
.center-col { min-width: 100%; border-right: none; }
.chart-box { height: 300px; }
.order-box { padding: 15px; }
#records-list-container { height: auto; max-height: 400px; }
.trading-page-wrapper { padding-bottom: 70px; }
.mobile-trade-nav {
display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 10px 15px; gap: 10px;
}
.mobile-trade-nav a {
flex: 1; text-align: center; padding: 8px 0; background: #2b3139; border-radius: 8px; font-size: 13px; color: #848e9c; text-decoration: none; font-weight: 600; border: 1px solid transparent;
}
.mobile-trade-nav a.active {
background: rgba(0, 82, 255, 0.1); color: var(--primary-color); border-color: var(--primary-color);
}
.mobile-symbol-selector {
display: flex; align-items: center; justify-content: space-between; padding: 12px 15px; background: #161a1e; border-bottom: 1px solid #2b3139;
}
}
@media (min-width: 992px) {
.mobile-trade-nav, .mobile-symbol-selector { display: none; }
}
/* Mobile Drawer */
#mobile-pairs-drawer {
position: fixed; top: 0; left: -100%; width: 100%; height: 100%; background: #0b0e11; z-index: 3000; transition: 0.3s; padding: 20px; display: flex; flex-direction: column;
}
#mobile-pairs-drawer.open { left: 0; }
</style>
<div class="trading-page-wrapper">
<!-- Mobile Navigation Tabs -->
<div class="mobile-trade-nav">
<a href="options.php"><?php echo __('nav_options'); ?></a>
<a href="spot.php"><?php echo __('nav_spot'); ?></a>
<a href="futures.php" class="active"><?php echo __('nav_futures'); ?></a>
</div>
<!-- Mobile Symbol Selector -->
<div class="mobile-symbol-selector" onclick="toggleMobilePairs()">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon-mobile" src="" class="coin-icon" style="width: 28px; height: 28px;">
<span id="curr-pair-mobile" style="font-weight: 800; font-size: 18px; color: white;">--/--</span>
<i class="fas fa-caret-down" style="color: #848e9c;"></i>
</div>
<div style="text-align: right;">
<div id="curr-price-mobile" style="font-size: 18px; font-weight: 800; color: #0ecb81;">--</div>
<div id="curr-change-mobile" style="font-size: 12px; font-weight: 600;">--</div>
</div>
</div>
<div class="trading-container">
<!-- Left Column -->
<div class="left-col d-none d-lg-flex">
@ -103,7 +156,7 @@ if ($user_id) {
<!-- Center Column -->
<div class="center-col">
<div class="chart-header">
<div class="chart-header d-none d-lg-flex">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
@ -159,19 +212,43 @@ if ($user_id) {
</div>
</div>
<!-- Mobile Pairs Drawer -->
<div id="mobile-pairs-drawer">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; font-weight: 800;"><?php echo __('market'); ?></h3>
<i class="fas fa-times" onclick="toggleMobilePairs()" style="font-size: 20px; color: #848e9c;"></i>
</div>
<div class="search-box" style="padding: 0; margin-bottom: 20px;">
<i class="fas fa-search"></i>
<input type="text" id="pair-search-mobile" placeholder="<?php echo __('search_contract'); ?>">
</div>
<div id="pairs-list-mobile" style="flex: 1; overflow-y: auto;"></div>
</div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script>
const userAssets = <?php echo json_encode($user_assets); ?>;
function toggleMobilePairs() {
document.getElementById('mobile-pairs-drawer').classList.toggle('open');
}
document.addEventListener('DOMContentLoaded', function() {
let currentPair = 'BTCUSDT', currentPrice = 0, ws, leverage = 20;
let currentPair = 'BTCUSDT', currentPrice = 0, ws, leverage = 20, chartWidget;
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
const dom = {
currIcon: document.getElementById('curr-icon'),
currPair: document.getElementById('curr-pair'),
currPrice: document.getElementById('curr-price'),
currChange: document.getElementById('curr-change'),
currIconMobile: document.getElementById('curr-icon-mobile'),
currPairMobile: document.getElementById('curr-pair-mobile'),
currPriceMobile: document.getElementById('curr-price-mobile'),
currChangeMobile: document.getElementById('curr-change-mobile'),
tvContainer: document.getElementById('tv_chart_container'),
pairsList: document.getElementById('pairs-list'),
pairsListMobile: document.getElementById('pairs-list-mobile'),
pairSearchMobile: document.getElementById('pair-search-mobile'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
midPrice: document.getElementById('mid-price'),
@ -208,23 +285,37 @@ document.addEventListener('DOMContentLoaded', function() {
marketData[data.s] = data;
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
dom.midPrice.innerText = currentPrice.toFixed(2);
const color = data.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
const changeStr = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
if(dom.currPrice) dom.currPrice.innerText = priceStr;
if(dom.currChange) dom.currChange.innerText = changeStr;
if(dom.currPrice) dom.currPrice.style.color = dom.currChange.style.color = color;
dom.currPriceMobile.innerText = priceStr;
dom.currPriceMobile.style.color = color;
dom.currChangeMobile.innerText = changeStr;
dom.currChangeMobile.style.color = color;
if(dom.midPrice) dom.midPrice.innerText = currentPrice.toFixed(2);
}
if (dom.pairsList.offsetParent) renderPairs();
if (dom.pairsList.offsetParent || dom.pairsListMobile.offsetParent) renderPairs();
} else if (stream.endsWith('@depth20@100ms')) {
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(a[1]).toFixed(4)};"><span style="color:#f6465d">${parseFloat(a[0]).toFixed(2)}</span><span>${parseFloat(a[1]).toFixed(4)}</span></div>`).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(b[1]).toFixed(4)};"><span style="color:#0ecb81">${parseFloat(b[0]).toFixed(2)}</span><span>${parseFloat(b[1]).toFixed(4)}</span></div>`).join('');
if(dom.asksList) {
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(a[1]).toFixed(4)};"><span style="color:#f6465d">${parseFloat(a[0]).toFixed(2)}</span><span>${parseFloat(a[1]).toFixed(4)}</span></div>`).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => `<div class="ob-row" onclick="document.getElementById('futures-amount').value=${parseFloat(b[1]).toFixed(4)};"><span style="color:#0ecb81">${parseFloat(b[0]).toFixed(2)}</span><span>${parseFloat(b[1]).toFixed(4)}</span></div>`).join('');
}
}
};
ws.onclose = () => setTimeout(connectWS, 5000);
}
function renderPairs() {
const q = document.getElementById('pair-search').value.toUpperCase();
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
const query = document.getElementById('pair-search').value.toUpperCase();
const queryMobile = dom.pairSearchMobile.value.toUpperCase();
const generateHtml = (q) => pairs.filter(p => p.includes(q)).map(p => {
const d = marketData[p] || {};
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
@ -232,17 +323,25 @@ document.addEventListener('DOMContentLoaded', function() {
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
</div>`;
}).join('');
dom.pairsList.innerHTML = generateHtml(query);
dom.pairsListMobile.innerHTML = generateHtml(queryMobile);
}
window.switchPair = (p) => {
currentPair = p;
dom.currPair.innerText = p.replace('USDT', '/USDT') + ' Perp';
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
if(dom.currPair) dom.currPair.innerText = p.replace('USDT', '/USDT') + ' Perp';
dom.currPairMobile.innerText = p.replace('USDT', '/USDT') + ' Perp';
if(dom.currIcon) dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
dom.currIconMobile.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
initChart(p);
updateBalances();
connectWS();
if(document.getElementById('mobile-pairs-drawer').classList.contains('open')) toggleMobilePairs();
};
dom.pairSearchMobile.addEventListener('input', renderPairs);
async function placeOrder(side) {
const amount = parseFloat(dom.amountInput.value);
if (!amount || amount <= 0) { alert("Amount > 0"); return; }
@ -298,4 +397,4 @@ document.addEventListener('DOMContentLoaded', function() {
setInterval(fetchOrders, 5000);
});
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

View File

@ -1,5 +1,4 @@
<?php
date_default_timezone_set('Asia/Dubai');
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
@ -146,16 +145,16 @@ $site_logo = $settings['site_logo'] ?? null;
</div>
</div>
<div class="floating-service" onclick="toggleCSChat()" title="Customer Service">
<div class="floating-service" id="floatingChatBtn" onclick="toggleCSChat()" title="<?php echo __('support_online'); ?>">
<i class="fas fa-headset"></i>
</div>
<div id="cs-chat-window">
<div class="chat-header">
<span><?php echo __('site_name'); ?> Support</span>
<span><?php echo __('support_online'); ?></span>
<i class="fas fa-times" onclick="toggleCSChat()" style="cursor: pointer;"></i>
</div>
<iframe src="chat_iframe.php"></iframe>
<iframe id="cs-chat-iframe" src="chat_iframe.php"></iframe>
</div>
<nav class="navbar">
@ -204,11 +203,12 @@ $site_logo = $settings['site_logo'] ?? null;
</a>
<div class="dropdown-content">
<div style="padding: 16px; border-bottom: 1px solid var(--border-color); background: rgba(0,82,255,0.05);">
<div style="font-weight: 800; color: white; font-size: 14px;"><?php echo $_SESSION['username'] ?? 'User'; ?></div>
<div style="font-weight: 800; color: white; font-size: 14px;"><?php echo htmlspecialchars($_SESSION['username'] ?? 'User'); ?></div>
<div style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">UID: <?php echo $_SESSION['uid'] ?? '------'; ?></div>
</div>
<a href="profile.php"><i class="fas fa-wallet" style="color: #03a9f4;"></i> <?php echo __('nav_assets'); ?></a>
<a href="deposit.php"><i class="fas fa-plus-circle" style="color: #00f2fe;"></i> <?php echo __('nav_deposit'); ?></a>
<a href="withdraw.php"><i class="fas fa-minus-circle" style="color: #f6465d;"></i> <?php echo __('nav_withdraw') ?? '提现'; ?></a>
<a href="security.php"><i class="fas fa-shield-alt" style="color: #ffd600;"></i> <?php echo __('nav_security'); ?></a>
<a href="logout.php" style="color: var(--danger-color);"><i class="fas fa-sign-out-alt"></i> <?php echo __('nav_logout'); ?></a>
</div>
@ -256,4 +256,63 @@ $site_logo = $settings['site_logo'] ?? null;
const chat = document.getElementById('cs-chat-window');
chat.style.display = chat.style.display === 'flex' ? 'none' : 'flex';
}
</script>
function openCSChat() {
const chat = document.getElementById('cs-chat-window');
if (chat.style.display !== 'flex') {
chat.style.display = 'flex';
}
// Refresh iframe to show latest info
const iframe = document.getElementById('cs-chat-iframe');
if (iframe) iframe.src = iframe.src;
}
// Auto-popup logic
<?php if (isset($_SESSION['user_id'])): ?>
let lastMatchedOrderId = localStorage.getItem('lastMatchedOrderId') || 0;
let lastAdminMsgId = localStorage.getItem('lastAdminMsgId') || 0;
async function checkAutoPopup() {
// Only check if not on chat.php
if (window.location.pathname.includes('chat.php')) return;
try {
// Check for order status changes
const orderResp = await fetch('api/check_order_status.php');
const orderRes = await orderResp.json();
if (orderRes.success && orderRes.order) {
// If status is matched (admin sent account info/format)
if (orderRes.order.status === 'matched' && orderRes.order.id >= lastMatchedOrderId) {
// Check if bank_account_info exists and we haven't popped for THIS specific update
const lastPopInfo = localStorage.getItem('lastPopInfo_' + orderRes.order.id);
if (orderRes.order.bank_account_info && orderRes.order.bank_account_info !== lastPopInfo) {
localStorage.setItem('lastPopInfo_' + orderRes.order.id, orderRes.order.bank_account_info);
lastMatchedOrderId = orderRes.order.id;
localStorage.setItem('lastMatchedOrderId', lastMatchedOrderId);
openCSChat();
}
}
}
// Check for new admin messages
const msgResp = await fetch('api/get_messages.php');
const msgRes = await msgResp.json();
if (msgRes.success && msgRes.data && msgRes.data.length > 0) {
const latestMsg = msgRes.data[0];
if (latestMsg.sender === 'admin' && latestMsg.id > lastAdminMsgId) {
lastAdminMsgId = latestMsg.id;
localStorage.setItem('lastAdminMsgId', lastAdminMsgId);
openCSChat();
}
}
} catch (e) {
console.error("Popup check failed", e);
}
}
setInterval(checkAutoPopup, 2000); // Faster check
checkAutoPopup();
<?php endif; ?>
</script>
</body>
</html>

View File

@ -211,7 +211,7 @@ $translations = [
'days' => 'Days',
'coming_soon' => 'Coming Soon',
'next_project' => 'Next Project',
// New keys for error messages
'error_placing_order' => 'Error placing order',
'network_error' => 'Network Error',
'could_not_connect_server' => 'Could not connect to server',
@ -222,6 +222,41 @@ $translations = [
'websocket_connected' => 'Connected to market data.',
'websocket_error' => 'Market data connection error!',
'websocket_disconnected' => 'Market data disconnected. Reconnecting...',
'create_account' => 'Create Account',
'join_novaex_tip' => 'Join NovaEx - The Leading Crypto Exchange',
'email_or_phone' => 'Email or Phone',
'enter_email_phone' => 'Enter your email or phone',
'login_password' => 'Login Password',
'set_password' => 'Set your login password',
'confirm_password_label' => 'Confirm Password',
'confirm_your_password' => 'Confirm your password',
'agree_terms' => 'I have read and agree to the Privacy Policy and Terms of Service.',
'already_have_account' => 'Already have an account?',
'fill_all_fields' => 'Please fill all fields.',
'passwords_not_match' => 'Passwords do not match.',
'username_taken' => 'Username already taken.',
'registration_failed' => 'Registration failed.',
'login_to_novaex' => 'Login to NovaEx',
'welcome_back' => 'Welcome back! Please enter your details.',
'dont_have_account' => 'Don\'t have an account?',
'invalid_credentials' => 'Invalid username or password.',
'type_message' => 'Type a message...',
'please_login' => 'Please login first.',
'support_online' => 'Support Online',
'view_account' => 'View Account',
'payment_proof' => 'Payment Proof',
'upload_proof' => 'Upload Proof',
'confirm_transfer' => 'Confirm Transfer',
'matched_info_tip' => 'Account info received, please transfer and upload proof.',
'paid_waiting_tip' => 'Proof uploaded, waiting for review.',
'complete_transfer_btn' => 'Complete Transfer',
'withdraw_info_tip' => 'Please reply with your bank details as requested.',
'matched_status' => 'Matched',
'paid_status' => 'Paid',
'matching_status' => 'Matching',
'withdraw_format_title' => 'Withdraw Info',
'view_withdraw_format' => 'View Format',
],
'zh' => [
'site_name' => 'NovaEx',
@ -280,7 +315,7 @@ $translations = [
'global_partners' => '全球合作伙伴',
'partners_subtitle' => '深受全球领先组织和金融机构的信任。',
'footer_desc' => 'NovaEx 是全球领先的数字资产 trading 平台,为全球用户提供安全稳定的交易服务。',
'footer_desc' => 'NovaEx 是全球领先的数字资产交易所,为全球用户提供安全稳定的交易服务。',
'about' => '关于',
'about_us' => '关于我们',
'careers' => '职业介绍',
@ -430,7 +465,7 @@ $translations = [
'days' => '天',
'coming_soon' => '即将到来',
'next_project' => '下一个项目',
// New keys for error messages
'error_placing_order' => '下单失败',
'network_error' => '网络错误',
'could_not_connect_server' => '无法连接到服务器',
@ -441,6 +476,41 @@ $translations = [
'websocket_connected' => '市场数据已连接。',
'websocket_error' => '市场数据连接错误!',
'websocket_disconnected' => '市场数据已断开。正在重新连接...',
'create_account' => '创建账户',
'join_novaex_tip' => '加入 NovaEx - 全球领先的数字资产交易所',
'email_or_phone' => '邮箱或手机号',
'enter_email_phone' => '输入您的邮箱或手机号',
'login_password' => '登录密码',
'set_password' => '设置您的登录密码',
'confirm_password_label' => '确认密码',
'confirm_your_password' => '请再次输入密码',
'agree_terms' => '我已阅读并同意隐私政策和服务条款。',
'already_have_account' => '已有账户?',
'fill_all_fields' => '请填写所有字段。',
'passwords_not_match' => '两次输入的密码不一致。',
'username_taken' => '用户名已被占用。',
'registration_failed' => '注册失败。',
'login_to_novaex' => '登录 NovaEx',
'welcome_back' => '欢迎回来!请输入您的详细信息。',
'dont_have_account' => '还没有账户?',
'invalid_credentials' => '用户名或密码无效。',
'type_message' => '输入消息...',
'please_login' => '请先登录。',
'support_online' => '在线客服',
'view_account' => '查看账户',
'payment_proof' => '支付凭证',
'upload_proof' => '上传凭证',
'confirm_transfer' => '确认转账',
'matched_info_tip' => '收款账户已下发,请转账后上传凭证。',
'paid_waiting_tip' => '凭证已上传,等待审核。',
'complete_transfer_btn' => '完成转账',
'withdraw_info_tip' => '请按要求回复您的银行收款详情。',
'matched_status' => '已匹配',
'paid_status' => '已支付',
'matching_status' => '匹配中',
'withdraw_format_title' => '提现信息',
'view_withdraw_format' => '查看格式',
]
];

View File

@ -15,10 +15,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
// Capture and update IP
// Capture and update IP correctly
$user_ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$user_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$user_ip = trim($ips[0]);
}
$pdo->prepare("UPDATE users SET last_ip = ? WHERE id = ?")->execute([$user_ip, $user['id']]);
@ -28,7 +29,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header("Location: index.php");
exit;
} else {
$error = "Invalid username or password.";
$error = __('invalid_credentials');
}
}
?>
@ -37,8 +38,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<main style="background: #0b0e11; min-height: calc(100vh - 64px); display: flex; align-items: center; justify-content: center; padding: 40px 20px;">
<div style="width: 100%; max-width: 480px; background: var(--card-bg); padding: 50px; border-radius: 32px; border: 1px solid var(--border-color); box-shadow: 0 20px 40px rgba(0,0,0,0.4);">
<h2 style="font-size: 2.2rem; font-weight: 800; margin-bottom: 10px; text-align: center; color: white;">Welcome Back</h2>
<p style="text-align: center; color: var(--text-muted); margin-bottom: 40px;">Log in to your account to continue trading</p>
<h2 style="font-size: 2.2rem; font-weight: 800; margin-bottom: 10px; text-align: center; color: white;"><?php echo __('login_to_novaex'); ?></h2>
<p style="text-align: center; color: var(--text-muted); margin-bottom: 40px;"><?php echo __('welcome_back'); ?></p>
<?php if($error): ?>
<div style="background: rgba(246,70,93,0.1); color: var(--danger-color); padding: 15px; border-radius: 12px; margin-bottom: 25px; border: 1px solid var(--danger-color); text-align: center; font-size: 14px;">
@ -48,26 +49,23 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<form method="POST">
<div style="margin-bottom: 25px;">
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;">Account</label>
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;"><?php echo __('email_or_phone'); ?></label>
<div style="position: relative;">
<i class="fas fa-user" style="position: absolute; left: 15px; top: 15px; color: #555;"></i>
<input type="text" name="username" required placeholder="Enter your email or phone" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
<input type="text" name="username" required placeholder="<?php echo __('enter_email_phone'); ?>" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
</div>
</div>
<div style="margin-bottom: 30px;">
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;">Password</label>
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;"><?php echo __('login_password'); ?></label>
<div style="position: relative;">
<i class="fas fa-lock" style="position: absolute; left: 15px; top: 15px; color: #555;"></i>
<input type="password" name="password" required placeholder="Enter your password" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
<input type="password" name="password" required placeholder="<?php echo __('set_password'); ?>" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-bottom: 30px;">
<a href="#" style="color: var(--primary-color); text-decoration: none; font-size: 0.85rem;">Forgot Password?</a>
</div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-weight: 800; font-size: 1.1rem; border-radius: 16px; box-shadow: 0 10px 20px rgba(0,82,255,0.2);"><?php echo __('nav_login'); ?></button>
</form>
<div style="text-align: center; margin-top: 30px; border-top: 1px solid var(--border-color); padding-top: 30px;">
<span style="color: var(--text-muted);">Don't have an account?</span> <a href="register.php" style="color: var(--primary-color); text-decoration: none; font-weight: bold;"><?php echo __('nav_register'); ?></a>
<span style="color: var(--text-muted);"><?php echo __('dont_have_account'); ?></span> <a href="register.php" style="color: var(--primary-color); text-decoration: none; font-weight: bold;"><?php echo __('nav_register'); ?></a>
</div>
</div>
</main>

View File

@ -3,6 +3,15 @@ require_once 'db/config.php';
require_once 'includes/currency_helper.php';
session_start();
function json_die($error) {
if (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest") {
echo json_encode(["success" => false, "error" => $error]);
} else {
die($error);
}
exit;
}
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
@ -18,6 +27,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount'])) {
$currency = $_POST['currency'] ?? 'USDT';
$network = $_POST['network'] ?? '';
if ($amount <= 0) {
json_die("Invalid amount");
}
$fiat_rates = get_fiat_rates();
$rate = $fiat_rates[$currency] ?? 1.0;
@ -33,7 +46,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount'])) {
$balance = (float)$stmt->fetchColumn();
if ($balance < $usdt_amount) {
die("余额不足");
json_die("余额不足");
}
// For withdrawal, we might need a trading password check if present
if (isset($_POST['trading_password'])) {
$stmt = $pdo->prepare("SELECT trading_password FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$saved_pass = $stmt->fetchColumn();
if ($saved_pass && $saved_pass !== $_POST['trading_password']) {
json_die("交易密码错误");
}
}
// Deduct balance immediately for withdrawal
@ -58,17 +81,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['amount'])) {
// Notification message for admin/chat
$type_text = ($order_type === 'deposit') ? "充值" : "提现";
$method_info = ($type === 'usdt') ? "USDT ($network)" : "法币 ($currency)";
$msg = "📢 用户发起 $type_text 请求\n金额: $amount $currency\n订单号: #$order_id\n方式: $method_info";
// More active messages as if user is typing
if ($order_type === 'deposit') {
$msg = "你好,我已发起一笔 $amount $currency 的充值申请。请为我匹配收款账户。";
} else {
$msg = "你好,我已发起一笔 $amount $currency 的提现申请。请发送收款信息格式给我。";
}
// System record (visible to admin as a request tag)
$sys_msg = "📢 订单详情\n类型: $type_text\n金额: $amount $currency\n方式: $method_info\n单号: #$order_id";
$stmt = $pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)");
$stmt->execute([$user_id, $msg]);
$stmt = $pdo->prepare("INSERT INTO messages (user_id, sender, message) VALUES (?, 'user', ?)");
$stmt->execute([$user_id, $sys_msg]);
$pdo->commit();
header("Location: chat.php");
if (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest") {
echo json_encode(["success" => true, "order_id" => $order_id]);
exit;
} else {
header("Location: chat.php");
}
exit;
} catch (Exception $e) {
$pdo->rollBack();
die("Error: " . $e->getMessage());
json_die("Error: " . $e->getMessage());
}
} else {
header("Location: index.php");

View File

@ -110,6 +110,34 @@ if ($user_id) {
#order-book-list::-webkit-scrollbar { width: 4px; }
#order-book-list::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
/* Mobile Specific Styles */
@media (max-width: 991px) {
.trading-container { flex-direction: column; min-height: auto; }
.center-col { min-width: 100%; border-right: none; }
.chart-box { height: 300px; }
.order-panel { padding: 15px; }
.duration-grid { grid-template-columns: repeat(3, 1fr); }
.table-responsive { height: auto; max-height: 400px; }
.trading-page-wrapper { padding-bottom: 70px; } /* Space for bottom nav */
.mobile-trade-nav {
display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 10px 15px; gap: 10px;
}
.mobile-trade-nav a {
flex: 1; text-align: center; padding: 8px 0; background: #2b3139; border-radius: 8px; font-size: 13px; color: #848e9c; text-decoration: none; font-weight: 600; border: 1px solid transparent;
}
.mobile-trade-nav a.active {
background: rgba(0, 82, 255, 0.1); color: var(--primary-color); border-color: var(--primary-color);
}
.mobile-symbol-selector {
display: flex; align-items: center; justify-content: space-between; padding: 12px 15px; background: #161a1e; border-bottom: 1px solid #2b3139;
}
}
@media (min-width: 992px) {
.mobile-trade-nav, .mobile-symbol-selector { display: none; }
}
/* Countdown Modal Styles */
.countdown-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
@ -150,11 +178,37 @@ if ($user_id) {
.result-title { font-size: 24px; font-weight: 800; margin-bottom: 10px; }
.result-amount { font-size: 32px; font-weight: 800; margin-bottom: 30px; }
.result-btn { background: var(--primary-color); color: white; padding: 12px 40px; border-radius: 10px; font-weight: 700; cursor: pointer; border: none; }
/* Mobile Drawer */
#mobile-pairs-drawer {
position: fixed; top: 0; left: -100%; width: 100%; height: 100%; background: #0b0e11; z-index: 3000; transition: 0.3s; padding: 20px; display: flex; flex-direction: column;
}
#mobile-pairs-drawer.open { left: 0; }
</style>
<div class="trading-page-wrapper">
<!-- Mobile Navigation Tabs -->
<div class="mobile-trade-nav">
<a href="options.php" class="active"><?php echo __('nav_options'); ?></a>
<a href="spot.php"><?php echo __('nav_spot'); ?></a>
<a href="futures.php"><?php echo __('nav_futures'); ?></a>
</div>
<!-- Mobile Symbol Selector -->
<div class="mobile-symbol-selector" onclick="toggleMobilePairs()">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon-mobile" src="" class="coin-icon" style="width: 28px; height: 28px;">
<span id="curr-pair-mobile" style="font-weight: 800; font-size: 18px; color: white;">--/--</span>
<i class="fas fa-caret-down" style="color: #848e9c;"></i>
</div>
<div style="text-align: right;">
<div id="curr-price-mobile" style="font-size: 18px; font-weight: 800; color: #0ecb81;">--</div>
<div id="curr-change-mobile" style="font-size: 12px; font-weight: 600;">--</div>
</div>
</div>
<div class="trading-container">
<!-- Left: Pair Selection -->
<!-- Left: Pair Selection (Desktop) -->
<div class="left-col d-none d-lg-flex">
<div class="category-tabs">
<div class="category-tab active" onclick="location.href='options.php'"><?php echo __('nav_options'); ?></div>
@ -170,7 +224,7 @@ if ($user_id) {
<!-- Center: Chart & Order -->
<div class="center-col">
<div class="chart-header">
<div class="chart-header d-none d-lg-flex">
<div class="d-flex align-items-center" style="gap:12px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
@ -243,7 +297,7 @@ if ($user_id) {
</div>
</div>
<!-- Right: Order Book -->
<!-- Right: Order Book (Desktop) -->
<div class="right-col d-none d-xl-flex">
<div class="col-header"><?php echo __('order_book'); ?></div>
<div class="ob-header">
@ -259,6 +313,19 @@ if ($user_id) {
</div>
</div>
<!-- Mobile Pairs Drawer -->
<div id="mobile-pairs-drawer">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; font-weight: 800;"><?php echo __('market'); ?></h3>
<i class="fas fa-times" onclick="toggleMobilePairs()" style="font-size: 20px; color: #848e9c;"></i>
</div>
<div class="search-box" style="padding: 0; margin-bottom: 20px;">
<i class="fas fa-search"></i>
<input type="text" id="pair-search-mobile" placeholder="<?php echo __('search_currency'); ?>">
</div>
<div id="pairs-list-mobile" style="flex: 1; overflow-y: auto;"></div>
</div>
<!-- Countdown Modal -->
<div id="countdown-modal" class="countdown-modal">
<div class="modal-content-card">
@ -317,6 +384,10 @@ if ($user_id) {
<script>
const userAssets = <?php echo json_encode($user_assets); ?>;
function toggleMobilePairs() {
document.getElementById('mobile-pairs-drawer').classList.toggle('open');
}
document.addEventListener('DOMContentLoaded', function() {
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
let selectedDuration = 60, selectedRate = 8, minAmount = 100;
@ -331,10 +402,16 @@ document.addEventListener('DOMContentLoaded', function() {
currPair: document.getElementById('curr-pair'),
currPrice: document.getElementById('curr-price'),
currChange: document.getElementById('curr-change'),
currIconMobile: document.getElementById('curr-icon-mobile'),
currPairMobile: document.getElementById('curr-pair-mobile'),
currPriceMobile: document.getElementById('curr-price-mobile'),
currChangeMobile: document.getElementById('curr-change-mobile'),
hHigh: document.getElementById('h-high'),
hLow: document.getElementById('h-low'),
pairsList: document.getElementById('pairs-list'),
pairsListMobile: document.getElementById('pairs-list-mobile'),
pairSearch: document.getElementById('pair-search'),
pairSearchMobile: document.getElementById('pair-search-mobile'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
midPrice: document.getElementById('price-mid'),
@ -397,17 +474,19 @@ document.addEventListener('DOMContentLoaded', function() {
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
updatePriceUI(data);
dom.midPrice.innerText = currentPrice.toFixed(2);
dom.midPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
if(dom.midPrice) {
dom.midPrice.innerText = currentPrice.toFixed(2);
dom.midPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
}
if (dom.modal.style.display === 'flex') {
dom.modalCurrPrice.innerText = currentPrice.toFixed(4);
dom.modalCurrPrice.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
}
}
if (dom.pairsList.offsetParent) renderPairs();
if (dom.pairsList.offsetParent || dom.pairsListMobile.offsetParent) renderPairs();
} else if (stream.endsWith('@depth20@100ms')) {
renderOrderBook(data.bids, data.asks);
if(dom.asksList) renderOrderBook(data.bids, data.asks);
}
};
ws.onclose = () => setTimeout(connectWebSocket, 5000);
@ -422,18 +501,27 @@ document.addEventListener('DOMContentLoaded', function() {
function updatePriceUI(d) {
const color = d.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = parseFloat(d.c).toLocaleString('en-US', {minimumFractionDigits: 2});
dom.currPrice.innerText = priceStr;
dom.currPrice.style.color = color;
dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
dom.currChange.style.color = color;
dom.hHigh.innerText = parseFloat(d.h).toFixed(2);
dom.hLow.innerText = parseFloat(d.l).toFixed(2);
if(dom.currPrice) dom.currPrice.innerText = priceStr;
if(dom.currPrice) dom.currPrice.style.color = color;
if(dom.currChange) dom.currChange.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
if(dom.currChange) dom.currChange.style.color = color;
dom.currPriceMobile.innerText = priceStr;
dom.currPriceMobile.style.color = color;
dom.currChangeMobile.innerText = `${d.P >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%`;
dom.currChangeMobile.style.color = color;
if(dom.hHigh) dom.hHigh.innerText = parseFloat(d.h).toFixed(2);
if(dom.hLow) dom.hLow.innerText = parseFloat(d.l).toFixed(2);
document.title = `${priceStr} | ${currentPair}`;
}
function renderPairs() {
const query = dom.pairSearch.value.toUpperCase();
dom.pairsList.innerHTML = pairs.filter(p => p.includes(query)).map(p => {
const queryMobile = dom.pairSearchMobile.value.toUpperCase();
const generateHtml = (q) => pairs.filter(p => p.includes(q)).map(p => {
const d = marketData[p] || {};
const price = d.c ? parseFloat(d.c).toFixed(2) : '--';
const change = d.P ? `${parseFloat(d.P) >= 0 ? '+' : ''}${parseFloat(d.P).toFixed(2)}%` : '--';
@ -444,15 +532,21 @@ document.addEventListener('DOMContentLoaded', function() {
<div style="text-align:right"><div style="font-weight:600; color:white;">${price}</div><div style="font-size:11px; color:${color}">${change}</div></div>
</div>`;
}).join('');
dom.pairsList.innerHTML = generateHtml(query);
dom.pairsListMobile.innerHTML = generateHtml(queryMobile);
}
function switchPair(pair) {
currentPair = pair;
dom.currPair.innerText = `${pair.replace('USDT', '/USDT')}`;
dom.currPairMobile.innerText = `${pair.replace('USDT', '/USDT')}`;
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${pair.replace('USDT','').toLowerCase()}@2x.png`;
dom.currIconMobile.src = `https://assets.coincap.io/assets/icons/${pair.replace('USDT','').toLowerCase()}@2x.png`;
initChartWidget(pair);
renderPairs();
connectWebSocket();
if(document.getElementById('mobile-pairs-drawer').classList.contains('open')) toggleMobilePairs();
}
function updatePotentialProfit() {
@ -584,7 +678,10 @@ document.addEventListener('DOMContentLoaded', function() {
}
dom.pairsList.addEventListener('click', e => { const item = e.target.closest('.pair-item'); if (item) switchPair(item.dataset.pair); });
dom.pairsListMobile.addEventListener('click', e => { const item = e.target.closest('.pair-item'); if (item) switchPair(item.dataset.pair); });
dom.pairSearch.addEventListener('input', renderPairs);
dom.pairSearchMobile.addEventListener('input', renderPairs);
document.querySelectorAll('.time-btn').forEach(btn => btn.addEventListener('click', () => {
document.querySelector('.time-btn.active').classList.remove('active');
btn.classList.add('active');
@ -612,4 +709,4 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

View File

@ -111,10 +111,11 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
justify-content: center;
gap: 12px;
transition: 0.3s;
text-decoration: none;
}
.btn-deposit { background: var(--primary-color); color: #000; }
.btn-deposit { background: var(--primary-color); color: white; }
.btn-withdraw { background: #2b3139; color: white; border: 1px solid #3b424d; }
.btn-deposit:hover { background: #f8d33a; transform: translateY(-3px); box-shadow: 0 10px 20px rgba(240, 185, 11, 0.2); }
.btn-deposit:hover { background: #0042cc; transform: translateY(-3px); box-shadow: 0 10px 20px rgba(0, 82, 255, 0.2); }
.btn-withdraw:hover { background: #3b424d; transform: translateY(-3px); }
.profile-tabs {
@ -149,7 +150,7 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
border: 1px solid #2b3139;
transition: 0.3s;
}
.record-card:hover { border-color: rgba(240, 185, 11, 0.3); transform: translateY(-5px); }
.record-card:hover { border-color: rgba(0, 82, 255, 0.3); transform: translateY(-5px); }
.status-badge {
padding: 5px 12px;
@ -158,8 +159,23 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
font-weight: 800;
}
@media (max-width: 1200px) { .record-grid { grid-template-columns: 1fr; } }
@media (max-width: 992px) { .profile-grid { grid-template-columns: 1fr; } }
@media (max-width: 1200px) {
.record-grid { grid-template-columns: 1fr; }
}
@media (max-width: 992px) {
.profile-grid { grid-template-columns: 1fr; }
}
@media (max-width: 768px) {
.balance-card { padding: 30px 20px; }
.balance-val { font-size: 2.5rem; }
.profile-tabs { gap: 20px; padding: 0 20px; }
.profile-tab-btn { font-size: 1rem; }
.tab-content { padding: 25px 15px !important; }
.record-card { padding: 15px; }
}
@media (max-width: 480px) {
.assets-grid { grid-template-columns: 1fr !important; }
}
</style>
<div class="profile-container">
@ -247,7 +263,7 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
<!-- 资产页签 -->
<div id="assets-tab" class="tab-content" style="padding: 40px;">
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="assets-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<?php
$coins = [
['symbol' => 'USDT', 'name' => 'Tether', 'balance' => $user['balance'] ?? 0],
@ -279,18 +295,18 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
foreach ($coins as $coin):
?>
<div style="display: flex; align-items: center; justify-content: space-between; padding: 22px; background: rgba(255,255,255,0.02); border-radius: 20px; border: 1px solid rgba(255,255,255,0.05); transition: 0.3s;">
<div style="display: flex; align-items: center; gap: 15px;">
<img src="https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/<?php echo strtolower($coin['symbol']); ?>.png" width="40" height="40" onerror="this.src='https://cdn-icons-png.flaticon.com/512/2585/2585274.png'">
<div style="display: flex; align-items: center; gap: 12px;">
<img src="https://raw.githubusercontent.com/spothq/cryptocurrency-icons/master/128/color/<?php echo strtolower($coin['symbol']); ?>.png" width="36" height="36" onerror="this.src='https://cdn-icons-png.flaticon.com/512/2585/2585274.png'">
<div>
<div style="font-weight: 800; font-size: 1.2rem; color: white;"><?php echo $coin['symbol']; ?></div>
<div style="font-size: 0.8rem; color: var(--text-muted);"><?php echo $coin['name']; ?></div>
<div style="font-weight: 800; font-size: 1.1rem; color: white;"><?php echo $coin['symbol']; ?></div>
<div style="font-size: 0.75rem; color: var(--text-muted);"><?php echo $coin['name']; ?></div>
</div>
</div>
<div style="text-align: right;">
<div style="font-weight: 800; font-family: 'Roboto Mono', monospace; font-size: 1.2rem; color: white;">
<div style="font-weight: 800; font-family: 'Roboto Mono', monospace; font-size: 1.1rem; color: white;">
<?php echo number_format($coin['balance'], $coin['symbol'] === 'USDT' ? 2 : 6); ?>
</div>
<div style="font-size: 0.8rem; color: var(--text-muted); text-transform: uppercase;">可用余额</div>
<div style="font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase;">余额</div>
</div>
</div>
<?php endforeach; ?>
@ -345,7 +361,7 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
allRecords.sort((a, b) => new Date(b.created_at || b.open_time) - new Date(a.created_at || a.open_time));
if (allRecords.length === 0) {
container.innerHTML = '<div style="grid-column: span 2; text-align: center; padding: 100px; color: var(--text-muted);"><i class="fas fa-file-invoice" style="font-size: 4rem; opacity: 0.2; margin-bottom: 25px;"></i><br>暂无任何交易记录</div>';
container.innerHTML = '<div style="grid-column: span 1; text-align: center; padding: 100px; color: var(--text-muted);"><i class="fas fa-file-invoice" style="font-size: 4rem; opacity: 0.2; margin-bottom: 25px;"></i><br>暂无任何交易记录</div>';
return;
}
@ -363,28 +379,28 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px;">
<div>
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-weight: 800; color: white; font-size: 1.2rem;">${symbol}</span>
<span style="background: rgba(240,185,11,0.1); color: var(--primary-color); padding: 3px 10px; border-radius: 6px; font-size: 10px; font-weight: 800;">${r.trade_label}</span>
<span style="font-weight: 800; color: white; font-size: 1.1rem;">${symbol}</span>
<span style="background: rgba(0,82,255,0.1); color: var(--primary-color); padding: 3px 8px; border-radius: 6px; font-size: 9px; font-weight: 800;">${r.trade_label}</span>
</div>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 6px;">${time}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 6px;">${time}</div>
</div>
<div style="text-align: right;">
<div style="font-size: 10px; color: var(--text-muted); text-transform: uppercase; margin-bottom: 2px;">盈利 (USDT)</div>
<div style="font-weight: 800; color: ${profitColor}; font-size: 1.3rem;">${isProfit ? '+' : ''}${profit.toFixed(2)}</div>
<div style="font-size: 9px; color: var(--text-muted); text-transform: uppercase; margin-bottom: 2px;">盈利 (USDT)</div>
<div style="font-weight: 800; color: ${profitColor}; font-size: 1.2rem;">${isProfit ? '+' : ''}${profit.toFixed(2)}</div>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; background: rgba(0,0,0,0.2); padding: 15px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.03);">
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; background: rgba(0,0,0,0.2); padding: 12px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.03);">
<div>
<div style="font-size: 10px; color: var(--text-muted); margin-bottom: 4px;">类型</div>
<div style="font-weight: 800; color: ${r.side === 'buy' || r.direction === 'up' ? 'var(--success-color)' : 'var(--danger-color)'}; font-size: 13px;">${(r.side === 'buy' || r.direction === 'up') ? '看涨/做多' : '看跌/做空'}</div>
<div style="font-size: 9px; color: var(--text-muted); margin-bottom: 4px;">类型</div>
<div style="font-weight: 800; color: ${r.side === 'buy' || r.direction === 'up' ? 'var(--success-color)' : 'var(--danger-color)'}; font-size: 12px;">${(r.side === 'buy' || r.direction === 'up') ? '看涨' : '看跌'}</div>
</div>
<div>
<div style="font-size: 10px; color: var(--text-muted); margin-bottom: 4px;">成交金额</div>
<div style="font-weight: 800; color: white; font-size: 13px;">${parseFloat(r.amount || r.invest).toFixed(2)}</div>
<div style="font-size: 9px; color: var(--text-muted); margin-bottom: 4px;">金额</div>
<div style="font-weight: 800; color: white; font-size: 12px;">${parseFloat(r.amount || r.invest).toFixed(2)}</div>
</div>
<div>
<div style="font-size: 10px; color: var(--text-muted); margin-bottom: 4px;">成交价格</div>
<div style="font-weight: 800; color: white; font-size: 13px;">${parseFloat(r.price || r.open_price).toLocaleString()}</div>
<div style="font-size: 9px; color: var(--text-muted); margin-bottom: 4px;">价格</div>
<div style="font-weight: 800; color: white; font-size: 12px;">${parseFloat(r.price || r.open_price).toLocaleString()}</div>
</div>
</div>
</div>
@ -401,16 +417,15 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
<div class="record-card">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-weight: 800; color: white; font-size: 1.2rem;">${typeLabel}</span>
<span style="font-size: 11px; font-weight: 800; color: ${statusColor}; background: ${statusColor}1A; padding: 4px 10px; border-radius: 6px;">${statusLabel}</span>
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-weight: 800; color: white; font-size: 1.1rem;">${typeLabel}</span>
<span style="font-size: 10px; font-weight: 800; color: ${statusColor}; background: ${statusColor}1A; padding: 4px 8px; border-radius: 6px;">${statusLabel}</span>
</div>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 10px; font-family: 'Roboto Mono', monospace;">${r.created_at}</div>
<div style="font-size: 12px; color: var(--text-muted); margin-top: 8px; font-style: italic; opacity: 0.6;">${r.description || ''}</div>
<div style="font-size: 11px; color: var(--text-muted); margin-top: 10px; font-family: 'Roboto Mono', monospace;">${r.created_at}</div>
</div>
<div style="text-align: right;">
<div style="font-size: 10px; color: var(--text-muted); text-transform: uppercase; margin-bottom: 4px;">变动金额 (USDT)</div>
<div style="font-weight: 800; color: ${amountColor}; font-size: 1.5rem;">${isPositive ? '+' : ''}${amount.toFixed(2)}</div>
<div style="font-size: 9px; color: var(--text-muted); text-transform: uppercase; margin-bottom: 4px;">变动 (USDT)</div>
<div style="font-weight: 800; color: ${amountColor}; font-size: 1.3rem;">${isPositive ? '+' : ''}${amount.toFixed(2)}</div>
</div>
</div>
</div>
@ -419,7 +434,7 @@ $kyc_colors = [0 => '#888', 1 => '#f0b90b', 2 => 'var(--success-color)', 3 => 'v
});
container.innerHTML = html;
} catch (e) {
container.innerHTML = '<div style="grid-column: span 2; text-align: center; padding: 60px; color: var(--danger-color); font-weight: 800;"><i class="fas fa-exclamation-triangle fa-2x"></i><br>数据加载失败,请重试</div>';
container.innerHTML = '<div style="grid-column: span 1; text-align: center; padding: 60px; color: var(--danger-color); font-weight: 800;"><i class="fas fa-exclamation-triangle fa-2x"></i><br>数据加载失败</div>';
}
}
</script>

View File

@ -11,26 +11,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($username) || empty($password)) {
$error = "Please fill all fields.";
$error = __('fill_all_fields');
} elseif ($password !== $confirm_password) {
$error = "Passwords do not match.";
$error = __('passwords_not_match');
} else {
$pdo = db();
// Check if user exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$username]);
if ($stmt->fetch()) {
$error = "Username already taken.";
$error = __('username_taken');
} else {
// Generate UID starting from 618120
$stmt = $pdo->query("SELECT COUNT(*) FROM users");
$count = $stmt->fetchColumn();
$uid = 618120 + $count + mt_rand(1, 9);
// Capture IP
// Capture IP correctly
$user_ip = $_SERVER['REMOTE_ADDR'];
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$user_ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$user_ip = trim($ips[0]);
}
// Register and auto-login
@ -43,7 +44,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header("Location: index.php");
exit;
} else {
$error = "Registration failed.";
$error = __('registration_failed');
}
}
}
@ -54,8 +55,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<main style="background: #0b0e11; min-height: calc(100vh - 64px); display: flex; align-items: center; justify-content: center; padding: 40px 20px;">
<div style="width: 100%; max-width: 480px; background: var(--card-bg); padding: 50px; border-radius: 32px; border: 1px solid var(--border-color); box-shadow: 0 20px 40px rgba(0,0,0,0.4);">
<h2 style="font-size: 2.2rem; font-weight: 800; margin-bottom: 10px; text-align: center; color: white;">Create Account</h2>
<p style="text-align: center; color: var(--text-muted); margin-bottom: 40px;">Join NovaEx - The Leading Crypto Exchange</p>
<h2 style="font-size: 2.2rem; font-weight: 800; margin-bottom: 10px; text-align: center; color: white;"><?php echo __('create_account'); ?></h2>
<p style="text-align: center; color: var(--text-muted); margin-bottom: 40px;"><?php echo __('join_novaex_tip'); ?></p>
<?php if($error): ?>
<div style="background: rgba(246,70,93,0.1); color: var(--danger-color); padding: 15px; border-radius: 12px; margin-bottom: 25px; border: 1px solid var(--danger-color); text-align: center; font-size: 14px;">
@ -65,36 +66,36 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
<form method="POST">
<div style="margin-bottom: 25px;">
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;">Email or Phone</label>
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;"><?php echo __('email_or_phone'); ?></label>
<div style="position: relative;">
<i class="fas fa-envelope" style="position: absolute; left: 15px; top: 15px; color: #555;"></i>
<input type="text" name="username" required placeholder="Enter your email or phone" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
<input type="text" name="username" required placeholder="<?php echo __('enter_email_phone'); ?>" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
</div>
</div>
<div style="margin-bottom: 25px;">
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;">Login Password</label>
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;"><?php echo __('login_password'); ?></label>
<div style="position: relative;">
<i class="fas fa-lock" style="position: absolute; left: 15px; top: 15px; color: #555;"></i>
<input type="password" name="password" required placeholder="Set your login password" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
<input type="password" name="password" required placeholder="<?php echo __('set_password'); ?>" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
</div>
</div>
<div style="margin-bottom: 30px;">
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;">Confirm Password</label>
<label style="display: block; margin-bottom: 10px; color: var(--text-muted); font-size: 14px;"><?php echo __('confirm_password_label'); ?></label>
<div style="position: relative;">
<i class="fas fa-check-double" style="position: absolute; left: 15px; top: 15px; color: #555;"></i>
<input type="password" name="confirm_password" required placeholder="Confirm your password" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
<input type="password" name="confirm_password" required placeholder="<?php echo __('confirm_your_password'); ?>" style="width: 100%; padding: 15px 15px 15px 45px; background: #161a1e; border: 1px solid var(--border-color); color: white; border-radius: 12px; font-size: 1rem; outline: none; box-sizing: border-box;">
</div>
</div>
<div style="margin-bottom: 30px; display: flex; align-items: flex-start; gap: 12px;">
<input type="checkbox" required style="margin-top: 4px; accent-color: var(--primary-color);">
<span style="font-size: 0.85rem; color: var(--text-muted); line-height: 1.5;">I have read and agree to the <a href="privacy.php" style="color: var(--primary-color);">Privacy Policy</a> and <a href="terms.php" style="color: var(--primary-color);">Terms of Service</a>.</span>
<span style="font-size: 0.85rem; color: var(--text-muted); line-height: 1.5;"><?php echo __('agree_terms'); ?></span>
</div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 18px; font-weight: 800; font-size: 1.1rem; border-radius: 16px; box-shadow: 0 10px 20px rgba(0,82,255,0.2);"><?php echo __('nav_register'); ?></button>
</form>
<div style="text-align: center; margin-top: 30px; border-top: 1px solid var(--border-color); padding-top: 30px;">
<span style="color: var(--text-muted);">Already have an account?</span> <a href="login.php" style="color: var(--primary-color); text-decoration: none; font-weight: bold;"><?php echo __('nav_login'); ?></a>
<span style="color: var(--text-muted);"><?php echo __('already_have_account'); ?></span> <a href="login.php" style="color: var(--primary-color); text-decoration: none; font-weight: bold;"><?php echo __('nav_login'); ?></a>
</div>
</div>
</main>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

131
spot.php
View File

@ -83,9 +83,62 @@ if ($user_id) {
#ob-panel-content { flex: 1; overflow-y: auto; scrollbar-width: thin; scrollbar-color: #2b3139 transparent; }
#ob-panel-content::-webkit-scrollbar { width: 4px; }
#ob-panel-content::-webkit-scrollbar-thumb { background: #2b3139; border-radius: 10px; }
/* Mobile Specific Styles */
@media (max-width: 991px) {
.trading-container { flex-direction: column; min-height: auto; }
.center-col { min-width: 100%; border-right: none; }
.chart-box { height: 300px; }
.order-box { padding: 15px; }
#records-list-container { height: auto; max-height: 400px; }
.trading-page-wrapper { padding-bottom: 70px; }
.mobile-trade-nav {
display: flex; background: #161a1e; border-bottom: 1px solid #2b3139; padding: 10px 15px; gap: 10px;
}
.mobile-trade-nav a {
flex: 1; text-align: center; padding: 8px 0; background: #2b3139; border-radius: 8px; font-size: 13px; color: #848e9c; text-decoration: none; font-weight: 600; border: 1px solid transparent;
}
.mobile-trade-nav a.active {
background: rgba(0, 82, 255, 0.1); color: var(--primary-color); border-color: var(--primary-color);
}
.mobile-symbol-selector {
display: flex; align-items: center; justify-content: space-between; padding: 12px 15px; background: #161a1e; border-bottom: 1px solid #2b3139;
}
}
@media (min-width: 992px) {
.mobile-trade-nav, .mobile-symbol-selector { display: none; }
}
/* Mobile Drawer */
#mobile-pairs-drawer {
position: fixed; top: 0; left: -100%; width: 100%; height: 100%; background: #0b0e11; z-index: 3000; transition: 0.3s; padding: 20px; display: flex; flex-direction: column;
}
#mobile-pairs-drawer.open { left: 0; }
</style>
<div class="trading-page-wrapper">
<!-- Mobile Navigation Tabs -->
<div class="mobile-trade-nav">
<a href="options.php"><?php echo __('nav_options'); ?></a>
<a href="spot.php" class="active"><?php echo __('nav_spot'); ?></a>
<a href="futures.php"><?php echo __('nav_futures'); ?></a>
</div>
<!-- Mobile Symbol Selector -->
<div class="mobile-symbol-selector" onclick="toggleMobilePairs()">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon-mobile" src="" class="coin-icon" style="width: 28px; height: 28px;">
<span id="curr-pair-mobile" style="font-weight: 800; font-size: 18px; color: white;">--/--</span>
<i class="fas fa-caret-down" style="color: #848e9c;"></i>
</div>
<div style="text-align: right;">
<div id="curr-price-mobile" style="font-size: 18px; font-weight: 800; color: #0ecb81;">--</div>
<div id="curr-change-mobile" style="font-size: 12px; font-weight: 600;">--</div>
</div>
</div>
<div class="trading-container">
<!-- Left Column -->
<div class="left-col d-none d-lg-flex">
@ -100,7 +153,7 @@ if ($user_id) {
<!-- Center Column -->
<div class="center-col">
<div class="chart-header">
<div class="chart-header d-none d-lg-flex">
<div style="display: flex; align-items: center; gap: 10px;">
<img id="curr-icon" src="" class="coin-icon">
<span id="curr-pair" style="font-weight: 800; font-size: 18px; color:white;">--/--</span>
@ -163,9 +216,27 @@ if ($user_id) {
</div>
</div>
<!-- Mobile Pairs Drawer -->
<div id="mobile-pairs-drawer">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h3 style="margin: 0; font-weight: 800;"><?php echo __('market'); ?></h3>
<i class="fas fa-times" onclick="toggleMobilePairs()" style="font-size: 20px; color: #848e9c;"></i>
</div>
<div class="search-box" style="padding: 0; margin-bottom: 20px;">
<i class="fas fa-search"></i>
<input type="text" id="pair-search-mobile" placeholder="<?php echo __('search_currency'); ?>">
</div>
<div id="pairs-list-mobile" style="flex: 1; overflow-y: auto;"></div>
</div>
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
<script>
const userAssets = <?php echo json_encode($user_assets); ?>;
function toggleMobilePairs() {
document.getElementById('mobile-pairs-drawer').classList.toggle('open');
}
document.addEventListener('DOMContentLoaded', function() {
let currentPair = 'BTCUSDT', currentPrice = 0, ws, chartWidget;
const marketData = {}, pairs = ['BTCUSDT', 'ETHUSDT', 'BNBUSDT', 'SOLUSDT', 'XRPUSDT', 'ADAUSDT', 'DOGEUSDT', 'AVAXUSDT', 'DOTUSDT', 'MATICUSDT', 'LTCUSDT', 'TRXUSDT', 'LINKUSDT', 'UNIUSDT', 'ATOMUSDT', 'ETCUSDT', 'BCHUSDT', 'FILUSDT', 'ICPUSDT', 'NEARUSDT', 'AAVEUSDT', 'ALGOUSDT'];
@ -174,8 +245,14 @@ document.addEventListener('DOMContentLoaded', function() {
currPair: document.getElementById('curr-pair'),
currPrice: document.getElementById('curr-price'),
currChange: document.getElementById('curr-change'),
currIconMobile: document.getElementById('curr-icon-mobile'),
currPairMobile: document.getElementById('curr-pair-mobile'),
currPriceMobile: document.getElementById('curr-price-mobile'),
currChangeMobile: document.getElementById('curr-change-mobile'),
tvContainer: document.getElementById('tv_chart_container'),
pairsList: document.getElementById('pairs-list'),
pairsListMobile: document.getElementById('pairs-list-mobile'),
pairSearchMobile: document.getElementById('pair-search-mobile'),
asksList: document.getElementById('asks-list'),
bidsList: document.getElementById('bids-list'),
midPrice: document.getElementById('mid-price'),
@ -216,24 +293,38 @@ document.addEventListener('DOMContentLoaded', function() {
marketData[data.s] = data;
if (data.s === currentPair) {
currentPrice = parseFloat(data.c);
dom.currPrice.innerText = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
dom.currChange.innerText = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
dom.currPrice.style.color = dom.currChange.style.color = data.P >= 0 ? '#0ecb81' : '#f6465d';
dom.midPrice.innerText = currentPrice.toFixed(2);
const color = data.P >= 0 ? '#0ecb81' : '#f6465d';
const priceStr = currentPrice.toLocaleString('en-US', {minimumFractionDigits:2});
const changeStr = (data.P >= 0 ? '+' : '') + parseFloat(data.P).toFixed(2) + '%';
if(dom.currPrice) dom.currPrice.innerText = priceStr;
if(dom.currChange) dom.currChange.innerText = changeStr;
if(dom.currPrice) dom.currPrice.style.color = dom.currChange.style.color = color;
dom.currPriceMobile.innerText = priceStr;
dom.currPriceMobile.style.color = color;
dom.currChangeMobile.innerText = changeStr;
dom.currChangeMobile.style.color = color;
if(dom.midPrice) dom.midPrice.innerText = currentPrice.toFixed(2);
}
if (dom.pairsList.offsetParent) renderPairs();
if (dom.pairsList.offsetParent || dom.pairsListMobile.offsetParent) renderPairs();
} else if (type.startsWith('depth')) {
const row = (p, q, c) => `<div class="ob-row" onclick="document.getElementById('spot-amount').value=${parseFloat(q).toFixed(4)};"><span style="color:${c}">${parseFloat(p).toFixed(2)}</span><span>${parseFloat(q).toFixed(4)}</span></div>`;
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => row(a[0], a[1], '#f6465d')).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => row(b[0], b[1], '#0ecb81')).join('');
if(dom.asksList) {
const row = (p, q, c) => `<div class="ob-row" onclick="document.getElementById('spot-amount').value=${parseFloat(q).toFixed(4)};"><span style="color:${c}">${parseFloat(p).toFixed(2)}</span><span>${parseFloat(q).toFixed(4)}</span></div>`;
dom.asksList.innerHTML = data.asks.slice(0, 20).reverse().map(a => row(a[0], a[1], '#f6465d')).join('');
dom.bidsList.innerHTML = data.bids.slice(0, 20).map(b => row(b[0], b[1], '#0ecb81')).join('');
}
}
};
ws.onclose = () => setTimeout(connectWS, 5000);
}
function renderPairs() {
const q = document.getElementById('pair-search').value.toUpperCase();
dom.pairsList.innerHTML = pairs.filter(p => p.includes(q)).map(p => {
const query = document.getElementById('pair-search').value.toUpperCase();
const queryMobile = dom.pairSearchMobile.value.toUpperCase();
const generateHtml = (q) => pairs.filter(p => p.includes(q)).map(p => {
const d = marketData[p] || {};
return `<div class="pair-item ${p === currentPair ? 'active' : ''}" onclick="switchPair('${p}')">
<img src="https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png" class="coin-icon" onerror="this.style.opacity=0">
@ -241,22 +332,24 @@ document.addEventListener('DOMContentLoaded', function() {
<div style="text-align:right"><div style="color:white; font-weight:600">${d.c ? parseFloat(d.c).toFixed(2) : '--'}</div><div style="color:${d.P>=0?'#0ecb81':'#f6465d'}; font-size:11px">${d.P?(d.P>=0?'+':'')+parseFloat(d.P).toFixed(2)+'%':'--'}</div></div>
</div>`;
}).join('');
dom.pairsList.innerHTML = generateHtml(query);
dom.pairsListMobile.innerHTML = generateHtml(queryMobile);
}
window.switchPair = (p) => {
currentPair = p;
dom.currPair.innerText = p.replace('USDT', '/USDT');
dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
if(dom.currPair) dom.currPair.innerText = p.replace('USDT', '/USDT');
dom.currPairMobile.innerText = p.replace('USDT', '/USDT');
if(dom.currIcon) dom.currIcon.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
dom.currIconMobile.src = `https://assets.coincap.io/assets/icons/${p.replace('USDT','').toLowerCase()}@2x.png`;
initChart(p);
updateBalances();
connectWS();
if(document.getElementById('mobile-pairs-drawer').classList.contains('open')) toggleMobilePairs();
};
dom.slider.oninput = () => {
const percent = dom.slider.value / 100;
const coin = currentPair.replace('USDT', '');
// Simplified slider logic
};
dom.pairSearchMobile.addEventListener('input', renderPairs);
async function placeOrder(side) {
const amount = parseFloat(dom.amountInput.value);
@ -313,4 +406,4 @@ document.addEventListener('DOMContentLoaded', function() {
setInterval(fetchOrders, 5000);
});
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,23 +1,23 @@
<?php
include 'header.php';
require_once 'db/config.php';
require_once 'includes/currency_helper.php';
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit;
}
require_once 'db/config.php';
require_once 'includes/currency_helper.php';
$db = db();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
// Check for active orders
$stmt = $db->prepare("SELECT id FROM fiat_orders WHERE user_id = ? AND status IN ('matching', 'matched', 'paid') ORDER BY id DESC LIMIT 1");
$stmt->execute([$_SESSION['user_id']]);
if ($stmt->fetch()) { header("Location: chat.php"); exit; }
include 'header.php';
$fiat_rates = get_fiat_rates();
$fiat_currencies_info = [
@ -52,12 +52,18 @@ $error = '';
.input-group-custom input { background: none; border: none; color: white; font-size: 1.2rem; font-weight: 700; width: 100%; outline: none; }
.safety-alert { background: rgba(246, 70, 93, 0.1); border: 1px solid rgba(246, 70, 93, 0.2); padding: 20px; border-radius: 16px; color: var(--danger-color); display: flex; gap: 15px; align-items: center; margin-bottom: 30px; }
@media (max-width: 576px) {
.method-card { padding: 20px 15px; }
.method-card .icon-box { width: 40px; height: 40px; font-size: 16px; }
.method-card div:nth-child(2) { font-size: 0.95rem !important; }
}
</style>
<div class="withdraw-container">
<div class="container" style="max-width: 1100px;">
<div style="margin-bottom: 30px;">
<a href="profile.php" class="back-btn"><i class="fas fa-arrow-left"></i> 个人中心</a>
<a href="profile.php" class="back-btn" style="color: var(--text-muted); text-decoration: none; font-size: 14px;"><i class="fas fa-arrow-left"></i> 个人中心</a>
<h1 style="font-size: 2.2rem; font-weight: 800; margin-top: 10px;">提现</h1>
<p style="color: var(--text-muted);">安全地将您的资产提取至个人账户</p>
</div>
@ -69,7 +75,7 @@ $error = '';
</div>
<?php endif; ?>
<div style="display: grid; grid-template-columns: 1fr 380px; gap: 30px;">
<div class="responsive-grid">
<div>
<div class="withdraw-card">
<h3 style="margin-bottom: 25px; font-weight: 800;">1. 选择提现方式</h3>
@ -77,12 +83,12 @@ $error = '';
<div id="method-fiat" class="method-card" onclick="switchWithdrawMethod('fiat')">
<div class="icon-box" style="background: rgba(79,172,254,0.1); color: #4facfe;"><i class="fas fa-university"></i></div>
<div style="font-weight: 800; font-size: 1.1rem;">法币提现</div>
<div style="color: var(--text-muted); font-size: 12px; margin-top: 4px;">银行转账 / 全球 OTC</div>
<div style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">银行转账 / OTC</div>
</div>
<div id="method-usdt" class="method-card active" onclick="switchWithdrawMethod('usdt')">
<div class="icon-box" style="background: rgba(14,203,129,0.1); color: var(--success-color);"><i class="fas fa-coins"></i></div>
<div style="font-weight: 800; font-size: 1.1rem;">USDT 提现</div>
<div style="color: var(--text-muted); font-size: 12px; margin-top: 4px;">USDT 区块链转账</div>
<div style="color: var(--text-muted); font-size: 11px; margin-top: 4px;">区块链转账</div>
</div>
</div>
@ -138,18 +144,18 @@ $error = '';
</div>
</div>
<button type="submit" class="btn-primary" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800; background: var(--danger-color);">
<button type="submit" id="submit-btn" class="btn-primary" style="width: 100%; padding: 20px; border-radius: 16px; font-size: 1.1rem; font-weight: 800; background: var(--danger-color);">
发起提现请求
</button>
</form>
</div>
</div>
<div class="profile-sidebar">
<div class="sidebar-area">
<div class="withdraw-card" style="padding: 30px;">
<h4 style="font-weight: 800; margin-bottom: 25px;"><i class="fas fa-shield-check" style="color: var(--success-color);"></i> 提现安全说明</h4>
<div style="font-size: 13px; color: var(--text-muted); line-height: 1.8;">
<p style="margin-bottom: 15px;">为了您的账户安全,提现申请将由人工审核,您将跳转至在线客服界面完成后续步骤。</p>
<p style="margin-bottom: 15px;">为了您的账户安全,提现申请将由人工审核,您将自动弹出在线客服界面完成后续步骤。</p>
<p style="margin-bottom: 15px;">发起申请后,请按照客服要求提供您的收款账户信息。</p>
<div style="background: rgba(255, 255, 255, 0.03); padding: 15px; border-radius: 12px; border-left: 3px solid var(--primary-color);">
请确保您的收款账户信息 100% 正确。资金一旦发出,将无法追回。
@ -162,7 +168,7 @@ $error = '';
<div style="font-size: 13px; color: var(--text-muted);">
提现过程中遇到问题?联系我们的 24/7 在线客服。
<div style="margin-top: 20px;">
<a href="chat.php" class="btn" style="width: 100%; background: #2b3139; color: white; border-radius: 10px; padding: 10px;">联系客服</a>
<a href="javascript:void(0)" onclick="openCSChat()" class="btn" style="width: 100%; background: #2b3139; color: white; border-radius: 10px; padding: 10px; text-decoration: none; display: block; text-align: center;">联系客服</a>
</div>
</div>
</div>
@ -193,6 +199,62 @@ $error = '';
}
updateWithdrawRate();
// AJAX Form Submission
const withdrawForm = document.getElementById('withdraw-form');
if (withdrawForm) {
withdrawForm.onsubmit = async (e) => {
e.preventDefault();
const btn = document.getElementById('submit-btn');
const originalText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> 处理中...';
try {
const formData = new FormData(withdrawForm);
const resp = await fetch('matching.php', {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
// For withdrawals, matching.php might return plain text if balance is insufficient
const text = await resp.text();
let res;
try {
res = JSON.parse(text);
} catch (e) {
alert(text || '提交失败,请检查余额或密码');
btn.disabled = false;
btn.innerHTML = originalText;
return;
}
if (res.success) {
// Trigger chat popup
if (typeof openCSChat === 'function') {
openCSChat();
withdrawForm.style.opacity = '0.5';
withdrawForm.style.pointerEvents = 'none';
btn.innerHTML = '<i class="fas fa-check"></i> 请求已发送';
} else {
window.location.href = 'chat.php';
}
} else {
alert(res.error || '提交失败,请稍后重试');
btn.disabled = false;
btn.innerHTML = originalText;
}
} catch (e) {
console.error(e);
alert('网络错误,请稍后重试');
btn.disabled = false;
btn.innerHTML = originalText;
}
};
}
</script>
<?php include 'footer.php'; ?>
<?php include 'footer.php'; ?>