38451-vm/admin/layout.php
Flatlogic Bot bb14c2ece7 123456
2026-02-22 08:19:34 +00:00

583 lines
27 KiB
PHP

<?php
require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/lang.php';
if (session_status() === PHP_SESSION_NONE) session_start();
// Force simplified Chinese for admin
$lang = 'zh';
// $_SESSION['lang'] = 'zh'; // Do not persist to session to avoid affecting front-end default language
// Admin check
$admin = null;
if (isset($_SESSION['admin_id'])) {
$stmt = db()->prepare("SELECT * FROM admins WHERE id = ?");
$stmt->execute([$_SESSION['admin_id']]);
$admin = $stmt->fetch();
}
if (!$admin) {
header('Location: /admin/login.php');
exit;
}
// Helper to check permissions
if (!function_exists('hasPermission')) {
function hasPermission($p) {
global $admin;
if (!$admin['is_agent']) return true; // Super admin has all permissions
$perms = json_decode($admin['permissions'] ?? '[]', true);
return in_array($p, $perms);
}
}
function renderAdminPage($content, $title = '后台管理') {
global $admin, $lang;
$current_page = basename($_SERVER['PHP_SELF']);
$site_logo = getSetting('site_logo', '/assets/images/logo.png');
$site_favicon = getSetting('site_favicon', $site_logo);
$site_name = getSetting('site_name', 'Byro');
?>
<!DOCTYPE html>
<html lang="<?= $lang ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $title ?> - <?= $site_name ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<?php if ($site_favicon): ?>
<link rel="icon" href="<?= $site_favicon ?>">
<?php endif; ?>
<style>
:root {
--sidebar-width: 240px;
--header-height: 60px;
--bg-color: #f8f9fa;
--text-color: #333;
--border-color: #dee2e6;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
.admin-sidebar {
width: var(--sidebar-width);
height: 100vh;
position: fixed;
left: 0;
top: 0;
background: #fff;
border-right: 1px solid var(--border-color);
z-index: 1000;
overflow-y: auto;
}
.admin-header {
height: var(--header-height);
background: #fff;
border-bottom: 1px solid var(--border-color);
position: fixed;
left: var(--sidebar-width);
right: 0;
top: 0;
z-index: 999;
display: flex;
align-items: center;
padding: 0 24px;
justify-content: space-between;
}
.admin-main {
margin-left: var(--sidebar-width);
margin-top: var(--header-height);
padding: 24px;
}
.sidebar-logo {
height: var(--header-height);
display: flex;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid var(--border-color);
font-weight: bold;
font-size: 20px;
color: #0062ff;
background: #fff;
}
.admin-nav {
padding-bottom: 40px;
}
.nav-link {
padding: 12px 20px;
color: #555;
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
border-bottom: 1px solid #f1f1f1;
}
.nav-link:hover, .nav-link.active {
background: #f0f7ff;
color: #0062ff;
}
.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;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.table-container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
</style>
</head>
<body>
<div class="admin-sidebar">
<div class="sidebar-logo">
<?php if ($site_logo): ?>
<img src="<?= $site_logo ?>?v=<?= time() ?>" height="30" class="me-2">
<?php else: ?>
<i class="bi bi-shield-lock-fill me-2"></i>
<?php endif; ?>
<?= $admin['is_agent'] ? __('agent_panel') : __('admin_panel') ?>
</div>
<div class="admin-nav">
<!-- 1. 平台首页 -->
<a href="/admin/index.php" class="nav-link <?= $current_page == 'index.php' ? 'active' : '' ?>">
<i class="bi bi-speedometer2"></i> <?= __('platform_home') ?>
</a>
<!-- 2. 用户管理 -->
<?php if (hasPermission('manage_users')): ?>
<a href="/admin/users.php" class="nav-link <?= $current_page == 'users.php' ? 'active' : '' ?>">
<i class="bi bi-people"></i> <?= __('users') ?>
<span class="badge bg-success rounded-pill ms-auto d-none" id="users-badge">0</span>
</a>
<?php endif; ?>
<!-- 3. 代理管理 -->
<?php if (!$admin['is_agent']): ?>
<a href="/admin/agents.php" class="nav-link <?= $current_page == 'agents.php' ? 'active' : '' ?>">
<i class="bi bi-person-badge"></i> <?= __('agents') ?>
</a>
<?php endif; ?>
<!-- 4. 实名认证 -->
<?php if (hasPermission('manage_kyc')): ?>
<a href="/admin/kyc.php" class="nav-link <?= $current_page == 'kyc.php' ? 'active' : '' ?>">
<i class="bi bi-person-vcard"></i> <?= __('real_name') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="kyc-badge">0</span>
</a>
<?php endif; ?>
<!-- 5. 充提管理 -->
<?php if (hasPermission('audit_finance')): ?>
<a href="/admin/finance.php" class="nav-link <?= $current_page == 'finance.php' ? 'active' : '' ?>">
<i class="bi bi-wallet2"></i> <?= __('finance_management') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="finance-badge">0</span>
</a>
<?php endif; ?>
<!-- 6. 财务明细 -->
<?php if (hasPermission('view_orders')): ?>
<a href="/admin/transactions.php" class="nav-link <?= $current_page == 'transactions.php' ? 'active' : '' ?>">
<i class="bi bi-list-ul"></i> <?= __('finance_details') ?>
</a>
<!-- 7. 秒合约管理 -->
<a href="/admin/binary.php" class="nav-link <?= $current_page == 'binary.php' ? 'active' : '' ?>">
<i class="bi bi-clock"></i> <?= __('sec_contract_management') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="binary-badge">0</span>
</a>
<!-- 8. 现货管理 -->
<a href="/admin/spot.php" class="nav-link <?= $current_page == 'spot.php' ? 'active' : '' ?>">
<i class="bi bi-currency-exchange"></i> <?= __('spot_trading') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="spot-badge">0</span>
</a>
<!-- 9. 合约管理 -->
<a href="/admin/contract.php" class="nav-link <?= $current_page == 'contract.php' ? 'active' : '' ?>">
<i class="bi bi-layers"></i> <?= __('contract_trading') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="contract-badge">0</span>
</a>
<?php endif; ?>
<!-- 10. 兑换管理 -->
<?php if (!$admin['is_agent']): ?>
<a href="/admin/exchange.php" class="nav-link <?= $current_page == 'exchange.php' ? 'active' : '' ?>">
<i class="bi bi-arrow-left-right"></i> <?= __('exchange_management') ?>
</a>
<!-- 11. 挖矿管理 -->
<a href="/admin/mining.php" class="nav-link <?= $current_page == 'mining.php' ? 'active' : '' ?>">
<i class="bi bi-cpu"></i> <?= __('mining_management') ?>
</a>
<!-- 12. AI控盘 -->
<a href="/admin/ai_control.php" class="nav-link <?= $current_page == 'ai_control.php' ? 'active' : '' ?>">
<i class="bi bi-robot"></i> <?= __('ai_control') ?>
</a>
<!-- 13. 在线客服 -->
<a href="/admin/customer_service.php" class="nav-link <?= $current_page == 'customer_service.php' ? 'active' : '' ?>">
<i class="bi bi-headset"></i> <?= __('online_support') ?>
<span class="badge bg-danger rounded-pill ms-auto d-none" id="messages-badge">0</span>
</a>
<!-- 14. 后台设置 -->
<a href="/admin/backend_settings.php" class="nav-link <?= $current_page == 'backend_settings.php' ? 'active' : '' ?>">
<i class="bi bi-gear"></i> <?= __('backend_settings') ?>
</a>
<?php endif; ?>
<!-- 15. 个人设置 -->
<a href="/admin/profile.php" class="nav-link <?= $current_page == 'profile.php' ? 'active' : '' ?>">
<i class="bi bi-person-gear"></i> <?= __('personal_settings') ?>
</a>
</div>
</div>
<div class="admin-header">
<div class="d-flex align-items-center gap-2">
<button onclick="location.reload()" class="btn btn-sm btn-light border">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
<h5 class="mb-0"><?= $title ?></h5>
</div>
<div class="d-flex align-items-center gap-3">
<div class="text-muted small">欢迎您, <?= htmlspecialchars($admin['username']) ?></div>
<div class="position-relative me-2">
<i class="bi bi-bell fs-5"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger d-none" id="total-badge">
0
</span>
</div>
<a href="/" class="btn btn-sm btn-outline-primary">返回首页</a>
<a href="/auth/logout.php" class="btn btn-sm btn-outline-danger">登出</a>
</div>
</div>
<div class="admin-main">
<?= $content ?>
</div>
<script>
// Handle dismissible cards and badge clearing
document.addEventListener('DOMContentLoaded', function() {
const dismissedCards = JSON.parse(localStorage.getItem('dismissed_admin_cards') || '[]');
const visitedPages = JSON.parse(localStorage.getItem('visited_admin_pages') || '[]');
const currentPage = window.location.pathname;
// Detect base path for API calls
const apiPath = (window.location.origin + window.location.pathname).split('/admin/')[0] + '/api/';
// Clear badges based on current page
if (currentPage.includes('finance.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=finance&v=' + Date.now());
} else if (currentPage.includes('kyc.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=kyc&v=' + Date.now());
} else if (currentPage.includes('binary.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=binary&v=' + Date.now());
} else if (currentPage.includes('contract.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=contract&v=' + Date.now());
} else if (currentPage.includes('spot.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=spot&v=' + Date.now());
} else if (currentPage.includes('customer_service.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=messages&v=' + Date.now());
} else if (currentPage.includes('users.php')) {
fetch(apiPath + 'admin_notifications.php?action=clear&type=users&v=' + Date.now());
}
document.querySelectorAll('.card-dismissible').forEach(card => {
const cardId = card.getAttribute('data-card-id') || currentPage;
// Auto-hide if already dismissed OR if page was visited once and it's marked as auto-dismiss
if (dismissedCards.includes(cardId) || (visitedPages.includes(currentPage) && card.classList.contains('card-auto-dismiss'))) {
card.style.display = 'none';
return;
}
const closeBtn = document.createElement('button');
closeBtn.className = 'btn-close position-absolute top-0 end-0 m-2';
closeBtn.style.zIndex = '10';
closeBtn.onclick = function() {
card.style.display = 'none';
if (!dismissedCards.includes(cardId)) {
dismissedCards.push(cardId);
localStorage.setItem('dismissed_admin_cards', JSON.stringify(dismissedCards));
}
};
card.style.position = 'relative';
card.appendChild(closeBtn);
});
// Mark current page as visited
if (!visitedPages.includes(currentPage)) {
visitedPages.push(currentPage);
localStorage.setItem('visited_admin_pages', JSON.stringify(visitedPages));
}
});
let lastTotal = 0;
let lastSoundTotal = parseInt(sessionStorage.getItem('last_sound_total') || '-1');
function speak(text) {
if ('speechSynthesis' in window) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'zh-CN';
window.speechSynthesis.speak(utterance);
}
// Also try native notification
if (Notification.permission === "granted") {
new Notification("新消息提醒", { body: text, icon: '/assets/images/logo.png' });
}
}
// Request notification permission
if (Notification.permission !== "granted" && Notification.permission !== "denied") {
Notification.requestPermission();
}
function checkNotifications() {
const currentPage = window.location.pathname;
const isDashboard = currentPage.includes('index.php') || currentPage.endsWith('/admin/');
const apiPath = (window.location.origin + window.location.pathname).split('/admin/')[0] + '/api/';
const url = apiPath + 'admin_notifications.php?v=' + Date.now() + (isDashboard ? '&stats=1' : '');
fetch(url)
.then(r => r.json())
.then(data => {
if (data.success) {
const counts = data.counts;
// Update dashboard stats if available
if (data.stats) {
const s = data.stats;
if (document.getElementById('stat-total-users')) document.getElementById('stat-total-users').innerText = parseInt(s.total_users).toLocaleString();
if (document.getElementById('stat-total-recharge')) document.getElementById('stat-total-recharge').innerText = parseFloat(s.total_recharge).toLocaleString(undefined, {minimumFractionDigits: 2});
if (document.getElementById('stat-total-withdrawal')) document.getElementById('stat-total-withdrawal').innerText = parseFloat(s.total_withdrawal).toLocaleString(undefined, {minimumFractionDigits: 2});
if (document.getElementById('stat-pending-tasks')) document.getElementById('stat-pending-tasks').innerText = s.pending_tasks;
}
// Auto-clear current page types
if (currentPage.includes('finance.php')) {
fetch('../api/admin_notifications.php?action=clear&type=finance');
counts.recharge = 0;
counts.withdrawal = 0;
} else if (currentPage.includes('customer_service.php')) {
fetch('../api/admin_notifications.php?action=clear&type=messages');
counts.messages = 0;
} else if (currentPage.includes('users.php')) {
fetch('../api/admin_notifications.php?action=clear&type=users');
counts.users = 0;
}
// ... other pages can be added here
const total = counts.recharge + counts.withdrawal + counts.kyc + counts.binary + counts.contract + counts.spot + counts.messages + counts.users;
const soundTotal = counts.sound_total || 0;
// Finance badge
const financeBadge = document.getElementById('finance-badge');
if (financeBadge) {
const fCount = (counts.recharge || 0) + (counts.withdrawal || 0);
if (fCount > 0) {
financeBadge.innerText = fCount;
financeBadge.classList.remove('d-none');
} else {
financeBadge.classList.add('d-none');
}
}
// KYC badge
const kycBadge = document.getElementById('kyc-badge');
if (kycBadge) {
if (counts.kyc > 0) {
kycBadge.innerText = counts.kyc;
kycBadge.classList.remove('d-none');
} else {
kycBadge.classList.add('d-none');
}
}
// Binary badge
const binaryBadge = document.getElementById('binary-badge');
if (binaryBadge) {
if (counts.binary > 0) {
binaryBadge.innerText = counts.binary;
binaryBadge.classList.remove('d-none');
} else {
binaryBadge.classList.add('d-none');
}
}
// Contract badge
const contractBadge = document.getElementById('contract-badge');
if (contractBadge) {
if (counts.contract > 0) {
contractBadge.innerText = counts.contract;
contractBadge.classList.remove('d-none');
} else {
contractBadge.classList.add('d-none');
}
}
// Spot badge
const spotBadge = document.getElementById('spot-badge');
if (spotBadge) {
if (counts.spot > 0) {
spotBadge.innerText = counts.spot;
spotBadge.classList.remove('d-none');
} else {
spotBadge.classList.add('d-none');
}
}
// Users badge
const usersBadge = document.getElementById('users-badge');
if (usersBadge) {
if (counts.users > 0) {
usersBadge.innerText = counts.users;
usersBadge.classList.remove('d-none');
} else {
usersBadge.classList.add('d-none');
}
}
// Messages badge
const messagesBadge = document.getElementById('messages-badge');
if (messagesBadge) {
if (counts.messages > 0) {
messagesBadge.innerText = counts.messages;
messagesBadge.classList.remove('d-none');
} else {
messagesBadge.classList.add('d-none');
}
}
// Total badge
const totalBadge = document.getElementById('total-badge');
if (totalBadge) {
if (total > 0) {
totalBadge.innerText = total;
totalBadge.classList.remove('d-none');
} else {
totalBadge.classList.add('d-none');
}
}
if (lastSoundTotal !== -1 && soundTotal > lastSoundTotal) {
speak("你有新的消息,请注意查收");
// Show a Toast notification
if (window.Swal) {
Swal.fire({
title: '新提醒',
text: '您有新的充提申请或客服消息',
icon: 'info',
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 5000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
toast.onclick = () => {
if (counts.recharge > 0 || counts.withdrawal > 0) location.href = 'finance.php';
else if (counts.messages > 0) location.href = 'customer_service.php';
};
}
});
}
}
lastTotal = total;
lastSoundTotal = soundTotal;
sessionStorage.setItem('last_sound_total', lastSoundTotal);
}
})
.catch(e => console.error('Notification check failed:', e));
}
// Check every 2 seconds
setInterval(checkNotifications, 2000);
checkNotifications();
</script>
<!-- Lightbox for Chat Images -->
<div id="chat-lightbox" class="chat-lightbox" onclick="this.classList.remove('active')">
<img id="lightbox-img" src="" alt="Preview">
</div>
<style>
.chat-lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
z-index: 10001;
display: none;
align-items: center;
justify-content: center;
cursor: zoom-out;
opacity: 0;
transition: opacity 0.3s ease;
backdrop-filter: blur(5px);
}
.chat-lightbox.active {
display: flex;
opacity: 1;
}
.chat-lightbox img {
max-width: 95%;
max-height: 95%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 0 30px rgba(0,0,0,0.5);
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.chat-lightbox.active img {
transform: scale(1);
}
.chat-img-preview {
cursor: zoom-in !important;
transition: all 0.2s;
}
.chat-img-preview:hover {
transform: scale(1.02);
filter: brightness(1.1);
}
</style>
<script>
window.showLightbox = function(src) {
const lightbox = document.getElementById('chat-lightbox');
const img = document.getElementById('lightbox-img');
if (lightbox && img) {
img.src = src;
lightbox.classList.add('active');
}
};
document.addEventListener('click', function(e) {
if (e.target.classList.contains('chat-img-preview')) {
window.showLightbox(e.target.src);
}
});
</script>
</body>
</html>
<?php
}
?>