38451-vm/admin/layout.php
2026-02-21 04:55:51 +00:00

468 lines
22 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;
}
.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-info 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-info 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-info 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-warning 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;
// 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
}
?>