38320-vm/admin.php
Flatlogic Bot f09a03c89c 最新
2026-02-10 11:39:11 +00:00

470 lines
22 KiB
PHP

<?php
session_start();
require_once __DIR__ . '/db/config.php';
$pdo = db();
if (!isset($_SESSION['user_id'])) {
header('Location: index.php');
exit;
}
$stmt = $pdo->prepare("SELECT role FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$user = $stmt->fetch();
if ($user['role'] !== 'admin') {
die('Access Denied');
}
$action = $_GET['action'] ?? 'dashboard';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if ($action === 'confirm_recharge') {
$id = $_POST['id'];
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT * FROM recharges WHERE id = ? AND status = 'pending'");
$stmt->execute([$id]);
$recharge = $stmt->fetch();
if ($recharge) {
$stmt = $pdo->prepare("UPDATE recharges SET status = 'completed' WHERE id = ?");
$stmt->execute([$id]);
$stmt = $pdo->prepare("UPDATE users SET balance = balance + ? WHERE id = ?");
$stmt->execute([$recharge['amount'], $recharge['user_id']]);
}
$pdo->commit();
} elseif ($action === 'reject_recharge') {
$id = $_POST['id'];
$stmt = $pdo->prepare("UPDATE recharges SET status = 'rejected' WHERE id = ?");
$stmt->execute([$id]);
} elseif ($action === 'update_settings') {
foreach ($_POST['settings'] as $key => $value) {
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = ?");
$stmt->execute([$key, $value, $value]);
}
}
}
$stats = [
'users' => $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn(),
'pending_recharges' => $pdo->query("SELECT COUNT(*) FROM recharges WHERE status = 'pending'")->fetchColumn(),
'total_orders' => $pdo->query("SELECT COUNT(*) FROM sms_orders")->fetchColumn(),
'total_balance' => $pdo->query("SELECT SUM(balance) FROM users")->fetchColumn(),
];
$pending_recharges = $pdo->query("SELECT r.*, u.username FROM recharges r JOIN users u ON r.user_id = u.id WHERE r.status = 'pending' ORDER BY r.created_at DESC")->fetchAll();
$settings = $pdo->query("SELECT setting_key, setting_value FROM settings")->fetchAll(PDO::FETCH_KEY_PAIR);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>管理后台 - <?= htmlspecialchars($settings['site_name'] ?? '全球接码') ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--primary: #3b82f6;
--bg-body: #f8fafc;
--surface: #ffffff;
--text-main: #0f172a;
--text-muted: #64748b;
--border-color: #e2e8f0;
}
body { background-color: var(--bg-body); font-family: 'Plus Jakarta Sans', sans-serif; color: var(--text-main); font-size: 14px; }
.sidebar {
width: 260px;
height: 100vh;
position: fixed;
background: var(--surface);
border-right: 1px solid var(--border-color);
padding: 40px 24px;
box-shadow: 1px 0 0 rgba(0,0,0,0.02);
z-index: 1000;
}
.main { margin-left: 260px; padding: 50px; min-height: 100vh; }
.nav-link {
color: #475569;
font-weight: 600;
border-radius: 14px;
margin-bottom: 8px;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 12px;
padding: 14px 18px;
position: relative;
}
.nav-link:hover { background-color: #f1f5f9; color: var(--primary); }
.nav-link.active {
background-color: var(--primary);
color: white;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.25);
}
.badge-notification {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: #ef4444;
color: white;
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
display: none;
}
.stat-card {
background-color: var(--surface);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
transition: transform 0.2s;
}
.stat-card:hover { transform: translateY(-4px); }
.card {
background-color: var(--surface);
border: 1px solid var(--border-color);
border-radius: 24px;
padding: 30px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
margin-bottom: 24px;
}
.btn-primary { background-color: var(--primary); border: none; border-radius: 12px; padding: 12px 24px; font-weight: 700; }
.form-control { border: 1.5px solid var(--border-color); border-radius: 12px; padding: 12px 16px; background: #f8fafc; font-weight: 500; }
.form-control:focus { border-color: var(--primary); background: #fff; box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.08); }
/* Chat Workbench Styles */
.chat-workbench {
height: calc(100vh - 200px);
display: flex;
background: white;
border-radius: 24px;
overflow: hidden;
border: 1px solid var(--border-color);
}
.user-list {
width: 300px;
border-right: 1px solid var(--border-color);
overflow-y: auto;
}
.chat-area {
flex: 1;
display: flex;
flex-direction: column;
background: #f8fafc;
}
.user-item {
padding: 20px;
border-bottom: 1px solid #f1f5f9;
cursor: pointer;
transition: all 0.2s;
position: relative;
}
.user-item:hover { background: #f8fafc; }
.user-item.active { background: #eff6ff; border-left: 4px solid var(--primary); }
.user-badge {
position: absolute;
right: 20px;
bottom: 20px;
background: #ef4444;
color: white;
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
}
.chat-messages {
flex: 1;
padding: 30px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 15px;
}
.msg { max-width: 80%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.5; }
.msg-user { align-self: flex-start; background: white; border: 1px solid var(--border-color); border-bottom-left-radius: 4px; }
.msg-admin { align-self: flex-end; background: var(--primary); color: white; border-bottom-right-radius: 4px; }
.chat-input { padding: 20px; background: white; border-top: 1px solid var(--border-color); display: flex; gap: 10px; }
</style>
</head>
<body>
<div class="sidebar">
<div class="d-flex align-items-center gap-3 mb-5 px-1">
<div class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 44px; height: 44px;">
<i class="fas fa-user-shield"></i>
</div>
<div>
<div class="fw-bold fs-5">ADMIN</div>
<div class="small text-muted fw-bold" style="font-size: 10px;">CONTROL PANEL</div>
</div>
</div>
<nav class="nav flex-column">
<a class="nav-link <?= $action === 'dashboard' ? 'active' : '' ?>" href="?action=dashboard"><i class="fas fa-chart-line"></i> 数据概览</a>
<a class="nav-link <?= $action === 'recharges' ? 'active' : '' ?>" href="?action=recharges"><i class="fas fa-wallet"></i> 充值审核</a>
<a class="nav-link <?= $action === 'support' ? 'active' : '' ?>" href="?action=support">
<i class="fas fa-headset"></i> 客服工作台
<span class="badge-notification" id="chatBadge">0</span>
</a>
<a class="nav-link <?= $action === 'settings' ? 'active' : '' ?>" href="?action=settings"><i class="fas fa-cog"></i> 系统设置</a>
<hr class="text-muted opacity-10 my-4">
<a class="nav-link" href="dashboard.php" style="color: #64748b;"><i class="fas fa-reply"></i> 返回前台系统</a>
</nav>
</div>
<div class="main">
<div class="mb-5">
<h2 class="fw-bold"><?= [
'dashboard' => '核心数据概览',
'recharges' => '充值审核中心',
'support' => '客服工作台 (Live Chat)',
'settings' => '系统全局参数配置'
][$action] ?></h2>
</div>
<?php if ($action === 'dashboard'): ?>
<div class="row g-4">
<div class="col-md-3">
<div class="stat-card">
<div class="text-muted small fw-bold mb-1">注册用户总量</div>
<h2 class="fw-bold text-primary mb-0"><?= number_format($stats['users']) ?></h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="text-muted small fw-bold mb-1">待审核充值</div>
<h2 class="fw-bold text-warning mb-0"><?= number_format($stats['pending_recharges']) ?></h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="text-muted small fw-bold mb-1">累计接码订单</div>
<h2 class="fw-bold text-success mb-0"><?= number_format($stats['total_orders']) ?></h2>
</div>
</div>
<div class="col-md-3">
<div class="stat-card">
<div class="text-muted small fw-bold mb-1">系统用户总余额</div>
<h2 class="fw-bold text-dark mb-0">$<?= number_format($stats['total_balance'], 2) ?></h2>
</div>
</div>
</div>
<?php elseif ($action === 'recharges'): ?>
<div class="card p-0 overflow-hidden">
<div class="table-responsive">
<table class="table align-middle mb-0">
<thead>
<tr>
<th class="ps-4">用户信息 / USER</th>
<th>申请金额 / AMOUNT</th>
<th>交易 TXID / HASH</th>
<th>提交时间 / TIME</th>
<th class="text-end pe-4">管理操作 / ACTION</th>
</tr>
</thead>
<tbody>
<?php foreach ($pending_recharges as $r): ?>
<tr>
<td class="ps-4">
<div class="fw-bold text-dark"><?= htmlspecialchars($r['username']) ?></div>
<div class="small text-muted">USER_ID: <?= $r['user_id'] ?></div>
</td>
<td><span class="fw-bold text-primary fs-5">$<?= number_format($r['amount'], 2) ?></span></td>
<td><code class="small text-muted bg-light p-2 rounded"><?= htmlspecialchars($r['txid']) ?></code></td>
<td class="small text-muted fw-medium"><?= $r['created_at'] ?></td>
<td class="text-end pe-4">
<form method="POST" action="?action=confirm_recharge" class="d-inline" onsubmit="return confirm('确认审核通过?')">
<input type="hidden" name="id" value="<?= $r['id'] ?>">
<button class="btn btn-sm btn-success px-4 rounded-pill fw-bold">通过</button>
</form>
<form method="POST" action="?action=reject_recharge" class="d-inline" onsubmit="return confirm('确认驳回?')">
<input type="hidden" name="id" value="<?= $r['id'] ?>">
<button class="btn btn-sm btn-outline-danger px-4 rounded-pill fw-bold ms-2">驳回</button>
</form>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($pending_recharges)): ?>
<tr><td colspan="5" class="text-center py-5 text-muted fw-bold">目前暂无任何待处理的充值申请</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($action === 'support'): ?>
<div class="chat-workbench">
<div class="user-list" id="chatUserList">
<div class="p-4 text-center text-muted">加载中...</div>
</div>
<div class="chat-area">
<div id="chatMessages" class="chat-messages">
<div class="h-100 d-flex align-items-center justify-content-center text-muted flex-column">
<i class="fas fa-comments fa-3x mb-3 opacity-20"></i>
<p>请在左侧选择一个用户开始对话</p>
</div>
</div>
<div class="chat-input" id="chatInputArea" style="display: none;">
<input type="text" id="adminMsgInput" class="form-control" placeholder="输入回复内容...">
<button class="btn btn-primary" onclick="sendAdminMessage()"><i class="fas fa-paper-plane"></i></button>
</div>
</div>
</div>
<script>
let activeUserId = null;
let lastMessageCount = 0;
async function loadChatUsers() {
try {
const res = await fetch('ajax_handler.php?action=get_chat_users');
const data = await res.json();
if (data.code === 0) {
const list = document.getElementById('chatUserList');
list.innerHTML = '';
data.data.forEach(user => {
const item = document.createElement('div');
item.className = `user-item ${activeUserId == user.id ? 'active' : ''}`;
item.onclick = () => selectUser(user.id, user.username);
item.innerHTML = `
<div class="d-flex justify-content-between align-items-start mb-1">
<span class="fw-bold text-dark">${user.username}</span>
<span class="small text-muted" style="font-size: 10px;">${new Date(user.last_time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
</div>
<div class="small text-muted text-truncate" style="max-width: 180px;">${user.last_message}</div>
${user.unread_count > 0 ? `<span class="user-badge">${user.unread_count}</span>` : ''}
`;
list.appendChild(item);
});
}
} catch(e) {}
}
async function selectUser(userId, username) {
activeUserId = userId;
document.getElementById('chatInputArea').style.display = 'flex';
loadMessages(true); // force reload
loadChatUsers(); // Refresh to clear badge
}
async function loadMessages(force = false) {
if (!activeUserId) return;
try {
const res = await fetch(`ajax_handler.php?action=get_messages&user_id=${activeUserId}`);
const data = await res.json();
if (data.code === 0) {
if (!force && data.data.length === lastMessageCount) return;
const box = document.getElementById('chatMessages');
box.innerHTML = '';
data.data.forEach(msg => {
const div = document.createElement('div');
div.className = `msg msg-${msg.sender}`;
div.textContent = msg.message;
box.appendChild(div);
});
box.scrollTop = box.scrollHeight;
lastMessageCount = data.data.length;
// If it's the support page, we might be marking them as read, so refresh users list
loadChatUsers();
checkNotifications();
}
} catch(e) {}
}
async function sendAdminMessage() {
const input = document.getElementById('adminMsgInput');
const msg = input.value.trim();
if (!msg || !activeUserId) return;
const formData = new FormData();
formData.append('message', msg);
formData.append('user_id', activeUserId);
const res = await fetch('ajax_handler.php?action=send_message', {
method: 'POST',
body: formData
});
const data = await res.json();
if (data.code === 0) {
input.value = '';
loadMessages(true);
}
}
document.getElementById('adminMsgInput').addEventListener('keypress', (e) => {
if(e.key === 'Enter') sendAdminMessage();
});
setInterval(loadChatUsers, 5000);
setInterval(() => loadMessages(false), 3000);
loadChatUsers();
</script>
<?php elseif ($action === 'settings'): ?>
<div class="card">
<form method="POST" action="?action=update_settings">
<div class="row g-4">
<div class="col-md-6">
<label class="form-label fw-bold small text-muted">网站显示名称 / SITE NAME</label>
<input type="text" name="settings[site_name]" value="<?= htmlspecialchars($settings['site_name'] ?? '全球接码') ?>" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label fw-bold small text-muted">网站 Logo 路径 / LOGO PATH</label>
<input type="text" name="settings[site_logo]" value="<?= htmlspecialchars($settings['site_logo'] ?? 'assets/pasted-20260210-082628-83f66727.png') ?>" class="form-control">
</div>
<div class="col-12">
<label class="form-label fw-bold small text-muted">系统公告内容 (支持 HTML) / ANNOUNCEMENT</label>
<textarea name="settings[notice_text]" class="form-control" rows="4"><?= htmlspecialchars($settings['notice_text'] ?? '') ?></textarea>
</div>
<div class="col-12 my-5"><div class="border-top"></div></div>
<h5 class="fw-bold mb-3"><i class="fas fa-wallet text-primary me-2"></i> 区块链支付钱包地址配置 / CRYPTO WALLETS</h5>
<div class="col-md-6">
<label class="form-label fw-bold small text-muted">USDT TRC20 收款地址</label>
<input type="text" name="settings[usdt_trc20_address]" value="<?= htmlspecialchars($settings['usdt_trc20_address'] ?? '') ?>" class="form-control" placeholder="T...">
</div>
<div class="col-md-6">
<label class="form-label fw-bold small text-muted">USDT ERC20 收款地址</label>
<input type="text" name="settings[usdt_erc20_address]" value="<?= htmlspecialchars($settings['usdt_erc20_address'] ?? '') ?>" class="form-control" placeholder="0x...">
</div>
<div class="col-12 my-5"><div class="border-top"></div></div>
<h5 class="fw-bold mb-3"><i class="fas fa-plug text-primary me-2"></i> LUBAN SMS API 后端配置 / API GATEWAY</h5>
<div class="col-md-12">
<label class="form-label fw-bold small text-muted">LUBAN SMS API KEY</label>
<input type="password" name="settings[lubansms_apikey]" value="<?= htmlspecialchars($settings['lubansms_apikey'] ?? '') ?>" class="form-control">
</div>
</div>
<div class="text-end mt-5 pt-3">
<button class="btn btn-primary btn-lg px-5 shadow">应用所有配置项</button>
</div>
</form>
</div>
<?php endif; ?>
</div>
<script>
async function checkNotifications() {
try {
const res = await fetch(`ajax_handler.php?action=check_new_messages`);
const data = await res.json();
if (data.code === 0) {
const badge = document.getElementById('chatBadge');
if (data.unread_total > 0) {
badge.textContent = data.unread_total;
badge.style.display = 'block';
} else {
badge.style.display = 'none';
}
}
} catch(e) {}
}
setInterval(checkNotifications, 10000);
checkNotifications();
</script>
</body>
</html>