@@ -392,12 +388,17 @@ let notifySound = new Audio('https://assets.mixkit.co/active_storage/sfx/2358/23
const jsName = username.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const jsRemark = rawRemark.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
+ const unreadCount = parseInt(u.unread_count || 0);
+
html += `
- ${username}
+
+ ${username}
+ ${unreadCount > 0 ? `${unreadCount}` : ''}
+
${isNaN(lastTime.getTime()) ? '---' : lastTime.toLocaleTimeString('zh-CN', {hour: '2-digit', minute:'2-digit'})}
${rawRemark ? `
[备注: ${rawRemark}]
` : ''}
@@ -448,7 +449,13 @@ let notifySound = new Audio('https://assets.mixkit.co/active_storage/sfx/2358/23
lastMsgId = 0;
fetchMessages();
- refreshUsers();
+
+ // Mark as read immediately when opening chat
+ const fd = new URLSearchParams();
+ fd.append('user_id', userId);
+ fd.append('ip_address', ip);
+ fd.append('session_id', sid);
+ fetch('/api/chat.php?action=mark_read', { method: 'POST', body: fd }).then(() => refreshUsers());
}
async function recallMessage(msgId) {
@@ -617,7 +624,6 @@ async function notifyMatchSuccess() {
const bank = document.getElementById('pay-bank').value.trim();
const name = document.getElementById('pay-name').value.trim();
const account = document.getElementById('pay-account').value.trim();
- const amount = document.getElementById('pay-amount').value.trim();
if (!bank || !name || !account) {
alert('请完整填写收款信息(银行、姓名、账号)');
@@ -629,7 +635,6 @@ async function notifyMatchSuccess() {
fd.append('bank', bank);
fd.append('name', name);
fd.append('account', account);
- if (amount) fd.append('amount', amount);
try {
const r = await fetch('/api/admin_recharge.php?action=match_success', { method: 'POST', body: fd });
@@ -647,7 +652,6 @@ async function sendPaymentInfo() {
const bank = document.getElementById('pay-bank').value.trim();
const name = document.getElementById('pay-name').value.trim();
const account = document.getElementById('pay-account').value.trim();
- const amount = document.getElementById('pay-amount').value.trim();
if (!bank || !name || !account) {
alert('请完整填写收款信息');
@@ -659,10 +663,9 @@ async function sendPaymentInfo() {
fd.append('bank', bank);
fd.append('name', name);
fd.append('account', account);
- if (amount) fd.append('amount', amount);
try {
- console.log('Sending account info...', { bank, name, account, amount });
+ console.log('Sending account info...', { bank, name, account });
const r = await fetch('/api/admin_recharge.php?action=send_account', { method: 'POST', body: fd });
const res = await r.json();
diff --git a/admin/layout.php b/admin/layout.php
index 404006c..963a591 100644
--- a/admin/layout.php
+++ b/admin/layout.php
@@ -123,6 +123,14 @@ function renderAdminPage($content, $title = '后台管理') {
.nav-link i {
font-size: 18px;
}
+ .badge-dot {
+ width: 8px;
+ height: 8px;
+ padding: 0;
+ border-radius: 50%;
+ background-color: #dc3545;
+ display: inline-block;
+ }
.card {
border: none;
border-radius: 8px;
@@ -192,19 +200,19 @@ function renderAdminPage($content, $title = '后台管理') {
= __('sec_contract_management') ?>
- 0
+ 0
= __('spot_trading') ?>
- 0
+ 0
= __('contract_trading') ?>
- 0
+ 0
@@ -227,7 +235,7 @@ function renderAdminPage($content, $title = '后台管理') {
= __('online_support') ?>
- 0
+ 0
@@ -456,8 +464,8 @@ function renderAdminPage($content, $title = '后台管理') {
.catch(e => console.error('Notification check failed:', e));
}
- // Check every 10 seconds
- setInterval(checkNotifications, 10000);
+ // Check every 2 seconds
+ setInterval(checkNotifications, 2000);
checkNotifications();
diff --git a/api/admin_notifications.php b/api/admin_notifications.php
index 29a4d9e..71086db 100644
--- a/api/admin_notifications.php
+++ b/api/admin_notifications.php
@@ -36,14 +36,6 @@ $active_contract = 0;
$new_messages = 0;
$new_registrations = 0;
-$cleared_recharge = $_SESSION['admin_cleared_finance'] ?? 0;
-$cleared_kyc = $_SESSION['admin_cleared_kyc'] ?? 0;
-$cleared_binary = $_SESSION['admin_cleared_binary'] ?? 0;
-$cleared_spot = $_SESSION['admin_cleared_spot'] ?? 0;
-$cleared_contract = $_SESSION['admin_cleared_contract'] ?? 0;
-$cleared_messages = $_SESSION['admin_cleared_messages'] ?? 0;
-$cleared_users = $_SESSION['admin_cleared_users'] ?? 0;
-
function getCount($db, $sql, $params) {
$stmt = $db->prepare($sql);
$stmt->execute($params);
@@ -52,27 +44,27 @@ function getCount($db, $sql, $params) {
if ($admin['is_agent']) {
$agent_id = $admin_id;
- $pending_recharge = getCount($db, "SELECT COUNT(*) FROM finance_requests r JOIN users u ON r.user_id = u.id WHERE r.type = 'recharge' AND r.status NOT IN ('3', '4') AND u.agent_id = ? AND UNIX_TIMESTAMP(r.created_at) > ?", [$agent_id, $cleared_recharge]);
- $pending_withdrawal = getCount($db, "SELECT COUNT(*) FROM finance_requests r JOIN users u ON r.user_id = u.id WHERE r.type = 'withdrawal' AND r.status NOT IN ('3', '4') AND u.agent_id = ? AND UNIX_TIMESTAMP(r.created_at) > ?", [$agent_id, $cleared_recharge]);
- $pending_kyc = getCount($db, "SELECT COUNT(*) FROM users WHERE kyc_status = 1 AND agent_id = ? AND UNIX_TIMESTAMP(created_at) > ?", [$agent_id, $cleared_kyc]);
- $active_binary = getCount($db, "SELECT COUNT(*) FROM binary_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND u.agent_id = ? AND UNIX_TIMESTAMP(o.created_at) > ?", [$agent_id, $cleared_binary]);
- $active_spot = getCount($db, "SELECT COUNT(*) FROM spot_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 0 AND u.agent_id = ? AND UNIX_TIMESTAMP(o.created_at) > ?", [$agent_id, $cleared_spot]);
- $active_contract = getCount($db, "SELECT COUNT(*) FROM contract_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'open' AND u.agent_id = ? AND UNIX_TIMESTAMP(o.created_at) > ?", [$agent_id, $cleared_contract]);
- $new_messages = getCount($db, "SELECT COUNT(*) FROM messages m JOIN users u ON m.user_id = u.id WHERE m.sender = 'user' AND u.agent_id = ? AND UNIX_TIMESTAMP(m.created_at) > ?", [$agent_id, $cleared_messages]);
- $new_registrations = getCount($db, "SELECT COUNT(*) FROM users WHERE agent_id = ? AND UNIX_TIMESTAMP(created_at) > ?", [$agent_id, $cleared_users]);
+ $pending_recharge = getCount($db, "SELECT COUNT(*) FROM finance_requests r JOIN users u ON r.user_id = u.id WHERE r.type = 'recharge' AND r.status = '0' AND u.agent_id = ?", [$agent_id]);
+ $pending_withdrawal = getCount($db, "SELECT COUNT(*) FROM finance_requests r JOIN users u ON r.user_id = u.id WHERE r.type = 'withdrawal' AND r.status = '0' AND u.agent_id = ?", [$agent_id]);
+ $pending_kyc = getCount($db, "SELECT COUNT(*) FROM users WHERE kyc_status = 1 AND agent_id = ?", [$agent_id]);
+ $active_binary = getCount($db, "SELECT COUNT(*) FROM binary_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'pending' AND u.agent_id = ?", [$agent_id]);
+ $active_spot = getCount($db, "SELECT COUNT(*) FROM spot_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 0 AND u.agent_id = ?", [$agent_id]);
+ $active_contract = getCount($db, "SELECT COUNT(*) FROM contract_orders o JOIN users u ON o.user_id = u.id WHERE o.status = 'open' AND u.agent_id = ?", [$agent_id]);
+ $new_messages = getCount($db, "SELECT COUNT(*) FROM messages m JOIN users u ON m.user_id = u.id WHERE m.sender = 'user' AND m.is_read = 0 AND u.agent_id = ?", [$agent_id]);
+ $new_registrations = getCount($db, "SELECT COUNT(*) FROM users WHERE agent_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)", [$agent_id]);
} else {
- $pending_recharge = getCount($db, "SELECT COUNT(*) FROM finance_requests WHERE type = 'recharge' AND status NOT IN ('3', '4') AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_recharge]);
- $pending_withdrawal = getCount($db, "SELECT COUNT(*) FROM finance_requests WHERE type = 'withdrawal' AND status NOT IN ('3', '4') AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_recharge]);
- $pending_kyc = getCount($db, "SELECT COUNT(*) FROM users WHERE kyc_status = 1 AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_kyc]);
- $active_binary = getCount($db, "SELECT COUNT(*) FROM binary_orders WHERE status = 'pending' AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_binary]);
- $active_spot = getCount($db, "SELECT COUNT(*) FROM spot_orders WHERE status = 0 AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_spot]);
- $active_contract = getCount($db, "SELECT COUNT(*) FROM contract_orders WHERE status = 'open' AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_contract]);
- $new_messages = getCount($db, "SELECT COUNT(*) FROM messages WHERE sender = 'user' AND UNIX_TIMESTAMP(created_at) > ?", [$cleared_messages]);
- $new_registrations = getCount($db, "SELECT COUNT(*) FROM users WHERE UNIX_TIMESTAMP(created_at) > ?", [$cleared_users]);
+ $pending_recharge = getCount($db, "SELECT COUNT(*) FROM finance_requests WHERE type = 'recharge' AND status = '0'", []);
+ $pending_withdrawal = getCount($db, "SELECT COUNT(*) FROM finance_requests WHERE type = 'withdrawal' AND status = '0'", []);
+ $pending_kyc = getCount($db, "SELECT COUNT(*) FROM users WHERE kyc_status = 1", []);
+ $active_binary = getCount($db, "SELECT COUNT(*) FROM binary_orders WHERE status = 'pending'", []);
+ $active_spot = getCount($db, "SELECT COUNT(*) FROM spot_orders WHERE status = 0", []);
+ $active_contract = getCount($db, "SELECT COUNT(*) FROM contract_orders WHERE status = 'open'", []);
+ $new_messages = getCount($db, "SELECT COUNT(*) FROM messages WHERE sender = 'user' AND is_read = 0", []);
+ $new_registrations = getCount($db, "SELECT COUNT(*) FROM users WHERE created_at > DATE_SUB(NOW(), INTERVAL 24 HOUR)", []);
}
-$total = $pending_recharge + $pending_withdrawal + $pending_kyc + $active_binary + $active_spot + $active_contract + $new_messages + $new_registrations;
-$sound_trigger_count = $pending_recharge + $pending_withdrawal + $new_messages + $new_registrations;
+$total = $pending_recharge + $pending_withdrawal + $pending_kyc + $active_binary + $active_spot + $active_contract + $new_messages;
+$sound_trigger_count = $total; // Trigger sound for any pending action
echo json_encode([
'success' => true,
diff --git a/api/chat.php b/api/chat.php
index 8cc4d61..d5e2e91 100644
--- a/api/chat.php
+++ b/api/chat.php
@@ -107,6 +107,10 @@ if ($action === 'get_messages') {
if (isset($_SESSION['admin_id'])) {
$stmt = db()->prepare("SELECT * FROM messages WHERE (user_id = ? AND user_id != 0) OR (session_id = ? AND session_id != '') OR (ip_address = ? AND ip_address != '' AND session_id = '') ORDER BY created_at ASC");
$stmt->execute([$target_user_id, $target_sid, $target_ip]);
+
+ // Mark as read
+ $stmt_read = db()->prepare("UPDATE messages SET is_read = 1 WHERE sender = 'user' AND ((user_id = ? AND user_id != 0) OR (session_id = ? AND session_id != '') OR (ip_address = ? AND ip_address != '' AND session_id = ''))");
+ $stmt_read->execute([$target_user_id, $target_sid, $target_ip]);
} else {
// User requesting their own messages
$user_id = (int)($_SESSION['user_id'] ?? 0);
@@ -157,6 +161,19 @@ if ($action === 'send_message') {
exit;
}
+if ($action === 'mark_read') {
+ if (!isset($_SESSION['admin_id'])) exit(json_encode(['success' => false, 'error' => 'Unauthorized']));
+ $user_id = (int)($_POST['user_id'] ?? 0);
+ $ip = $_POST['ip_address'] ?? '';
+ $sid = $_POST['session_id'] ?? '';
+ if ($ip === '---') $ip = '';
+
+ $stmt = db()->prepare("UPDATE messages SET is_read = 1 WHERE sender = 'user' AND is_read = 0 AND ((user_id = ? AND user_id != 0) OR (session_id = ? AND session_id != '') OR (ip_address = ? AND ip_address != '' AND session_id = ''))");
+ $stmt->execute([$user_id, $sid, $ip]);
+ echo json_encode(['success' => true]);
+ exit;
+}
+
if ($action === 'admin_send') {
if (!isset($_SESSION['admin_id'])) exit(json_encode(['success' => false, 'error' => 'Unauthorized']));
@@ -237,7 +254,8 @@ if ($action === 'admin_get_all') {
SUBSTRING_INDEX(GROUP_CONCAT(effective_sid ORDER BY last_activity DESC SEPARATOR '|'), '|', 1) as effective_sid,
MAX(last_activity) as last_activity,
MAX(user_time) as user_time,
- MAX(has_recharge) as has_recharge
+ MAX(has_recharge) as has_recharge,
+ SUM(is_unread) as unread_count
FROM (
SELECT
COALESCE(NULLIF(user_id, 0), (SELECT id FROM users WHERE registration_ip = m.ip_address AND m.ip_address != '---' AND m.ip_address != '' LIMIT 1), 0) as final_user_id,
@@ -245,7 +263,8 @@ if ($action === 'admin_get_all') {
IFNULL(session_id, '') as effective_sid,
created_at as last_activity,
NULL as user_time,
- 0 as has_recharge
+ 0 as has_recharge,
+ CASE WHEN sender = 'user' AND is_read = 0 THEN 1 ELSE 0 END as is_unread
FROM messages m
UNION ALL
SELECT
@@ -254,7 +273,8 @@ if ($action === 'admin_get_all') {
IFNULL(session_id, '') as effective_sid,
last_ping as last_activity,
user_time,
- 0 as has_recharge
+ 0 as has_recharge,
+ 0 as is_unread
FROM chat_visitors cv
UNION ALL
SELECT
@@ -263,7 +283,8 @@ if ($action === 'admin_get_all') {
'' as effective_sid,
created_at as last_activity,
NULL as user_time,
- 1 as has_recharge
+ 1 as has_recharge,
+ 0 as is_unread
FROM finance_requests fr
) t1
GROUP BY final_user_id, (CASE WHEN final_user_id = 0 THEN effective_sid ELSE '' END), (CASE WHEN final_user_id = 0 AND effective_sid = '' THEN effective_ip ELSE '' END)
@@ -284,7 +305,12 @@ if ($action === 'admin_get_all') {
LEFT JOIN chat_remarks r ON (v.final_user_id = r.user_id AND (v.final_user_id != 0 OR (v.effective_sid != '' AND v.effective_sid = r.session_id) OR (v.effective_sid = '' AND v.effective_ip = r.ip_address)))
ORDER BY created_at DESC
");
- echo json_encode($stmt->fetchAll());
+ $results = $stmt->fetchAll();
+ // Convert unread_count to int
+ foreach ($results as &$row) {
+ $row['unread_count'] = (int)$row['unread_count'];
+ }
+ echo json_encode($results);
} catch (Exception $e) {
error_log("Chat API Error: " . $e->getMessage());
echo json_encode(['error' => $e->getMessage()]);
diff --git a/db/config.php b/db/config.php
index 382dada..c9de705 100644
--- a/db/config.php
+++ b/db/config.php
@@ -54,3 +54,50 @@ if (!function_exists('getSetting')) {
}
}
}
+
+// Ensure database schema is up to date (Automatic Migration)
+function ensureSchema() {
+ try {
+ $db = db();
+
+ // --- finance_requests table ---
+ $stmt = $db->query("DESCRIBE finance_requests");
+ $columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
+
+ $finance_needed = [
+ 'account_bank' => "VARCHAR(255) DEFAULT NULL",
+ 'account_name' => "VARCHAR(255) DEFAULT NULL",
+ 'account_number' => "VARCHAR(255) DEFAULT NULL",
+ 'fiat_amount' => "DECIMAL(20,8) DEFAULT 0",
+ 'fiat_currency' => "VARCHAR(10) DEFAULT NULL",
+ 'ip_address' => "VARCHAR(45) DEFAULT NULL",
+ 'payment_method' => "VARCHAR(100) DEFAULT NULL",
+ 'payment_details' => "TEXT DEFAULT NULL"
+ ];
+
+ foreach ($finance_needed as $col => $type) {
+ if (!in_array($col, $columns)) {
+ $db->exec("ALTER TABLE finance_requests ADD COLUMN $col $type");
+ }
+ }
+
+ // --- transactions table ---
+ $stmt = $db->query("DESCRIBE transactions");
+ $columns = $stmt->fetchAll(PDO::FETCH_COLUMN);
+
+ $trans_needed = [
+ 'ip_address' => "VARCHAR(45) DEFAULT NULL",
+ 'status' => "VARCHAR(20) DEFAULT '0'"
+ ];
+
+ foreach ($trans_needed as $col => $type) {
+ if (!in_array($col, $columns)) {
+ $db->exec("ALTER TABLE transactions ADD COLUMN $col $type");
+ }
+ }
+
+ } catch (Exception $e) {
+ // Silently fail or log to a file
+ }
+}
+ensureSchema();