diff --git a/admin/customer_service.php b/admin/customer_service.php index d292a26..bc5dbab 100644 --- a/admin/customer_service.php +++ b/admin/customer_service.php @@ -251,10 +251,6 @@ ob_start();
填写后点击发送,前端充值弹窗将自动切换并显示此账户。
-
- - -
@@ -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 = '后台管理') { - 0 + 0 - 0 + 0 - 0 + 0 @@ -227,7 +235,7 @@ function renderAdminPage($content, $title = '后台管理') { - 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();