38451-vm/admin/layout.php
2026-02-18 12:34:11 +00:00

432 lines
20 KiB
PHP

<?php
require_once __DIR__ . '/../db/config.php';
if (session_status() === PHP_SESSION_NONE) session_start();
// 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;
$current_page = basename($_SERVER['PHP_SELF']);
$site_logo = '';
$site_name = 'Byro';
try {
$stmt = db()->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'site_logo'");
$stmt->execute();
$site_logo = $stmt->fetchColumn() ?: '';
$stmt = db()->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'site_name'");
$stmt->execute();
$site_name = $stmt->fetchColumn() ?: 'Byro';
} catch (Exception $e) {}
?>
<!DOCTYPE html>
<html lang="zh">
<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">
<?php if ($site_logo): ?>
<link rel="icon" href="<?= $site_logo ?>">
<?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;
}
.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 ?>" height="30" class="me-2">
<?php else: ?>
<i class="bi bi-shield-lock-fill me-2"></i>
<?php endif; ?>
<?= $admin['is_agent'] ? '代理后台' : '管理系统' ?>
</div>
<div class="admin-nav">
<a href="/admin/index.php" class="nav-link <?= $current_page == 'index.php' ? 'active' : '' ?>"><i class="bi bi-house-door"></i> 首页</a>
<?php if (hasPermission('manage_users')): ?>
<a href="/admin/users.php" class="nav-link <?= $current_page == 'users.php' ? 'active' : '' ?>">
<i class="bi bi-people"></i> 玩家管理
<span class="badge bg-success rounded-pill ms-auto d-none" id="users-badge">0</span>
</a>
<?php endif; ?>
<?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> 代理管理</a>
<?php endif; ?>
<?php if (hasPermission('audit_finance')): ?>
<a href="/admin/finance.php" class="nav-link <?= $current_page == 'finance.php' ? 'active' : '' ?>">
<i class="bi bi-wallet2"></i> 充提管理
<span class="badge bg-danger rounded-pill ms-auto d-none" id="finance-badge">0</span>
</a>
<?php endif; ?>
<?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> 财务明细
</a>
<a href="/admin/binary.php" class="nav-link <?= $current_page == 'binary.php' ? 'active' : '' ?>">
<i class="bi bi-clock"></i> 秒合约管理
<span class="badge bg-info rounded-pill ms-auto d-none" id="binary-badge">0</span>
</a>
<a href="/admin/contract.php" class="nav-link <?= $current_page == 'contract.php' ? 'active' : '' ?>">
<i class="bi bi-layers"></i> 永续合约
<span class="badge bg-info rounded-pill ms-auto d-none" id="contract-badge">0</span>
</a>
<a href="/admin/spot.php" class="nav-link <?= $current_page == 'spot.php' ? 'active' : '' ?>">
<i class="bi bi-currency-exchange"></i> 币币交易
<span class="badge bg-info rounded-pill ms-auto d-none" id="spot-badge">0</span>
</a>
<?php endif; ?>
<?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> 实名认证
<span class="badge bg-danger rounded-pill ms-auto d-none" id="kyc-badge">0</span>
</a>
<?php endif; ?>
<?php if (!$admin['is_agent']): ?>
<a href="/admin/exchange.php" class="nav-link"><i class="bi bi-arrow-left-right"></i> 兑换管理</a>
<a href="/admin/mining.php" class="nav-link"><i class="bi bi-cpu"></i> 质押挖矿</a>
<a href="/admin/ai_control.php" class="nav-link"><i class="bi bi-robot"></i> AI控盘</a>
<a href="/admin/customer_service.php" class="nav-link <?= $current_page == 'customer_service.php' ? 'active' : '' ?>">
<i class="bi bi-headset"></i> 客服管理
<span class="badge bg-warning rounded-pill ms-auto d-none" id="messages-badge">0</span>
</a>
<a href="/admin/backend_settings.php" class="nav-link"><i class="bi bi-gear"></i> 后台设置</a>
<?php endif; ?>
<a href="/admin/profile.php" class="nav-link <?= $current_page == 'profile.php' ? 'active' : '' ?>"><i class="bi bi-person-gear"></i> 个人设置</a>
</div>
</div>
<div class="admin-header">
<div class="d-flex align-items-center">
<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 src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<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;
// Clear badges based on current page
if (currentPage.includes('finance.php')) {
fetch('/api/admin_notifications.php?action=clear&type=finance');
} else if (currentPage.includes('kyc.php')) {
fetch('/api/admin_notifications.php?action=clear&type=kyc');
} else if (currentPage.includes('binary.php')) {
fetch('/api/admin_notifications.php?action=clear&type=binary');
} else if (currentPage.includes('contract.php')) {
fetch('/api/admin_notifications.php?action=clear&type=contract');
} else if (currentPage.includes('spot.php')) {
fetch('/api/admin_notifications.php?action=clear&type=spot');
} else if (currentPage.includes('customer_service.php')) {
fetch('/api/admin_notifications.php?action=clear&type=messages');
} else if (currentPage.includes('users.php')) {
fetch('/api/admin_notifications.php?action=clear&type=users');
}
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);
}
}
function checkNotifications() {
const currentPage = window.location.pathname;
fetch('/api/admin_notifications.php')
.then(r => r.json())
.then(data => {
if (data.success) {
const counts = data.counts;
// 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("你有新的消息,请注意查收");
}
lastTotal = total;
lastSoundTotal = soundTotal;
sessionStorage.setItem('last_sound_total', lastSoundTotal);
}
})
.catch(e => console.error('Notification check failed:', e));
}
// Check every 10 seconds
setInterval(checkNotifications, 10000);
checkNotifications();
</script>
</body>
</html>
<?php
}
?>