tougao/admin/index.php

750 lines
25 KiB
PHP
Raw Normal View History

2025-05-26 15:23:18 +08:00
<?php
session_start();
// 检查登录状态
if (!isset($_SESSION['admin_logged_in']) || !$_SESSION['admin_logged_in']) {
header('Location: login.php');
exit;
}
require_once '../config/database.php';
require_once '../includes/utils.php';
$database = new Database();
$db = $database->getConnection();
$message = '';
$message_type = '';
// 处理操作
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
if ($action === 'update_status') {
$type = $_POST['type'] ?? '';
$id = $_POST['id'] ?? '';
$status = $_POST['status'] ?? '';
$note = $_POST['note'] ?? '';
if ($type === 'website') {
$stmt = $db->prepare("UPDATE website_submissions SET status = ?, admin_note = ? WHERE id = ?");
} else {
$stmt = $db->prepare("UPDATE app_submissions SET status = ?, admin_note = ? WHERE id = ?");
}
if ($stmt->execute([$status, $note, $id])) {
$message = '状态更新成功';
$message_type = 'success';
} else {
$message = '状态更新失败';
$message_type = 'error';
}
} elseif ($action === 'update_account') {
$new_username = trim($_POST['new_username'] ?? '');
$new_password = $_POST['new_password'] ?? '';
$confirm_password = $_POST['confirm_password'] ?? '';
if (empty($new_username)) {
$message = '用户名不能为空';
$message_type = 'error';
} elseif (!empty($new_password) && $new_password !== $confirm_password) {
$message = '两次输入的密码不一致';
$message_type = 'error';
} else {
$admin_id = $_SESSION['admin_id'];
if (!empty($new_password)) {
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $db->prepare("UPDATE admins SET username = ?, password = ? WHERE id = ?");
$stmt->execute([$new_username, $hashed_password, $admin_id]);
} else {
$stmt = $db->prepare("UPDATE admins SET username = ? WHERE id = ?");
$stmt->execute([$new_username, $admin_id]);
}
$_SESSION['admin_username'] = $new_username;
$message = '账户信息更新成功';
$message_type = 'success';
}
}
}
// 获取统计数据
$stats = [
'website_pending' => 0,
'website_approved' => 0,
'website_rejected' => 0,
'app_pending' => 0,
'app_approved' => 0,
'app_rejected' => 0
];
$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM website_submissions GROUP BY status");
$stmt->execute();
while ($row = $stmt->fetch()) {
$stats['website_' . $row['status']] = $row['count'];
}
$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM app_submissions GROUP BY status");
$stmt->execute();
while ($row = $stmt->fetch()) {
$stats['app_' . $row['status']] = $row['count'];
}
// 获取列表数据
$filter = $_GET['filter'] ?? 'pending';
$type = $_GET['type'] ?? 'website';
$page = max(1, intval($_GET['page'] ?? 1));
$limit = 10;
$offset = ($page - 1) * $limit;
// 强制转换为整数
$limit = (int)$limit;
$offset = (int)$offset;
if ($type === 'website') {
$count_stmt = $db->prepare("SELECT COUNT(*) FROM website_submissions WHERE status = ?");
$count_stmt->execute([$filter]);
$total = $count_stmt->fetchColumn();
$stmt = $db->prepare("
SELECT id, url, title, description, platforms, contact, status, admin_note, created_at
FROM website_submissions
WHERE status = ?
ORDER BY created_at DESC
LIMIT ?, ?
");
$stmt->bindValue(1, $filter, PDO::PARAM_STR);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->bindValue(3, $limit, PDO::PARAM_INT);
$stmt->execute();
} else {
$count_stmt = $db->prepare("SELECT COUNT(*) FROM app_submissions WHERE status = ?");
$count_stmt->execute([$filter]);
$total = $count_stmt->fetchColumn();
$stmt = $db->prepare("
SELECT id, name, platform, version, icon_url, download_url, website_url, description, platforms, contact, status, admin_note, created_at
FROM app_submissions
WHERE status = ?
ORDER BY created_at DESC
LIMIT ?, ?
");
$stmt->bindValue(1, $filter, PDO::PARAM_STR);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->bindValue(3, $limit, PDO::PARAM_INT);
$stmt->execute();
}
$submissions = $stmt->fetchAll();
$total_pages = ceil($total / $limit);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>管理后台 - 内容投稿系统</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f8fafc;
color: #1a202c;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
font-size: 1.5rem;
font-weight: 700;
}
.header-actions {
display: flex;
gap: 15px;
align-items: center;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 6px;
}
.btn-primary {
background: rgba(255, 255, 255, 0.2);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.btn-primary:hover {
background: rgba(255, 255, 255, 0.3);
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 30px 20px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: white;
padding: 25px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
border-left: 4px solid #667eea;
}
.stat-card h3 {
font-size: 2rem;
font-weight: 700;
color: #667eea;
margin-bottom: 5px;
}
.stat-card p {
color: #64748b;
font-size: 14px;
}
.controls {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
display: flex;
gap: 15px;
align-items: center;
flex-wrap: wrap;
}
.controls select {
padding: 8px 12px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 14px;
}
.submissions-table {
background: white;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
overflow: hidden;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th,
.table td {
padding: 15px;
text-align: left;
border-bottom: 1px solid #e2e8f0;
}
.table th {
background: #f8fafc;
font-weight: 600;
color: #374151;
}
.table tr:hover {
background: #f8fafc;
}
.status-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.status-pending {
background: #fef3c7;
color: #92400e;
}
.status-approved {
background: #d1fae5;
color: #065f46;
}
.status-rejected {
background: #fee2e2;
color: #991b1b;
}
.action-buttons {
display: flex;
gap: 5px;
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
.btn-success {
background: #10b981;
color: white;
}
.btn-warning {
background: #f59e0b;
color: white;
}
.pagination {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 20px;
}
.pagination a {
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
text-decoration: none;
color: #374151;
}
.pagination a.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 30px;
border-radius: 12px;
width: 90%;
max-width: 500px;
}
.modal h3 {
margin-bottom: 20px;
color: #374151;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #374151;
}
.form-group select,
.form-group textarea,
.form-group input {
width: 100%;
padding: 8px 12px;
border: 2px solid #e2e8f0;
border-radius: 6px;
font-size: 14px;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
.message {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
font-weight: 500;
}
.message.success {
background: #d1fae5;
color: #065f46;
border: 1px solid #10b981;
}
.message.error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #ef4444;
}
.account-settings {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.account-settings.collapsed {
display: none;
}
@media (max-width: 768px) {
.header-content {
flex-direction: column;
gap: 15px;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.table {
font-size: 14px;
}
.table th,
.table td {
padding: 10px 8px;
}
}
</style>
</head>
<body>
<div class="header">
<div class="header-content">
<h1><i class="fas fa-tachometer-alt"></i> 管理后台</h1>
<div class="header-actions">
<span>欢迎,<?php echo htmlspecialchars($_SESSION['admin_username']); ?></span>
<button class="btn btn-primary" onclick="toggleAccountSettings()">
<i class="fas fa-cog"></i> 账户设置
</button>
<a href="../index.php" class="btn btn-primary" target="_blank">
<i class="fas fa-external-link-alt"></i> 前台
</a>
<a href="logout.php" class="btn btn-danger">
<i class="fas fa-sign-out-alt"></i> 退出
</a>
</div>
</div>
</div>
<div class="container">
<?php if ($message): ?>
<div class="message <?php echo $message_type; ?>">
<?php echo htmlspecialchars($message); ?>
</div>
<?php endif; ?>
<!-- 账户设置 -->
<div class="account-settings" id="accountSettings">
<h3><i class="fas fa-user-cog"></i> 账户设置</h3>
<form method="POST">
<input type="hidden" name="action" value="update_account">
<div class="form-group">
<label for="new_username">新用户名</label>
<input type="text" id="new_username" name="new_username"
value="<?php echo htmlspecialchars($_SESSION['admin_username']); ?>" required>
</div>
<div class="form-group">
<label for="new_password">新密码(留空则不修改)</label>
<input type="password" id="new_password" name="new_password">
</div>
<div class="form-group">
<label for="confirm_password">确认新密码</label>
<input type="password" id="confirm_password" name="confirm_password">
</div>
<div class="modal-actions">
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> 保存
</button>
<button type="button" class="btn btn-warning" onclick="toggleAccountSettings()">
<i class="fas fa-times"></i> 取消
</button>
</div>
</form>
</div>
<!-- 统计数据 -->
<div class="stats-grid">
<div class="stat-card">
<h3><?php echo $stats['website_pending']; ?></h3>
<p>网址待审核</p>
</div>
<div class="stat-card">
<h3><?php echo $stats['website_approved']; ?></h3>
<p>网址已通过</p>
</div>
<div class="stat-card">
<h3><?php echo $stats['app_pending']; ?></h3>
<p>应用待审核</p>
</div>
<div class="stat-card">
<h3><?php echo $stats['app_approved']; ?></h3>
<p>应用已通过</p>
</div>
</div>
<!-- 筛选控制 -->
<div class="controls">
<label>类型:</label>
<select onchange="changeType(this.value)">
<option value="website" <?php echo $type === 'website' ? 'selected' : ''; ?>>网址投稿</option>
<option value="app" <?php echo $type === 'app' ? 'selected' : ''; ?>>APP投稿</option>
</select>
<label>状态:</label>
<select onchange="changeFilter(this.value)">
<option value="pending" <?php echo $filter === 'pending' ? 'selected' : ''; ?>>待处理</option>
<option value="approved" <?php echo $filter === 'approved' ? 'selected' : ''; ?>>已通过</option>
<option value="rejected" <?php echo $filter === 'rejected' ? 'selected' : ''; ?>>已拒绝</option>
</select>
</div>
<!-- 投稿列表 -->
<div class="submissions-table">
<table class="table">
<thead>
<tr>
<th>ID</th>
<?php if ($type === 'website'): ?>
<th>网址</th>
<th>标题</th>
<?php else: ?>
<th>应用名称</th>
<th>平台</th>
<th>版本</th>
<?php endif; ?>
<th>收录平台</th>
<th>联系方式</th>
<th>状态</th>
<th>提交时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($submissions as $submission): ?>
<tr>
<td><?php echo $submission['id']; ?></td>
<?php if ($type === 'website'): ?>
<td>
<a href="<?php echo htmlspecialchars($submission['url']); ?>" target="_blank">
<?php echo htmlspecialchars(substr($submission['url'], 0, 30)) . (strlen($submission['url']) > 30 ? '...' : ''); ?>
</a>
</td>
<td><?php echo htmlspecialchars($submission['title'] ?: '未获取'); ?></td>
<?php else: ?>
<td><?php echo htmlspecialchars($submission['name']); ?></td>
<td><?php echo htmlspecialchars($submission['platform']); ?></td>
<td><?php echo htmlspecialchars($submission['version'] ?: '-'); ?></td>
<?php endif; ?>
<td><?php echo htmlspecialchars($submission['platforms'] ?: '-'); ?></td>
<td><?php echo htmlspecialchars($submission['contact'] ?: '-'); ?></td>
<td>
<span class="status-badge status-<?php echo $submission['status']; ?>">
<?php
$status_text = [
'pending' => '待处理',
'approved' => '已通过',
'rejected' => '已拒绝'
];
echo $status_text[$submission['status']];
?>
</span>
</td>
<td><?php echo date('Y-m-d H:i', strtotime($submission['created_at'])); ?></td>
<td>
<div class="action-buttons">
<button class="btn btn-sm btn-primary"
onclick="showStatusModal(<?php echo $submission['id']; ?>, '<?php echo $type; ?>', '<?php echo $submission['status']; ?>', '<?php echo htmlspecialchars($submission['admin_note'] ?? '', ENT_QUOTES); ?>')">
<i class="fas fa-edit"></i>
</button>
<?php if ($submission['status'] === 'pending'): ?>
<button class="btn btn-sm btn-success"
onclick="quickUpdate(<?php echo $submission['id']; ?>, '<?php echo $type; ?>', 'approved')">
<i class="fas fa-check"></i>
</button>
<button class="btn btn-sm btn-danger"
onclick="quickUpdate(<?php echo $submission['id']; ?>, '<?php echo $type; ?>', 'rejected')">
<i class="fas fa-times"></i>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- 分页 -->
<?php if ($total_pages > 1): ?>
<div class="pagination">
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<a href="?type=<?php echo $type; ?>&filter=<?php echo $filter; ?>&page=<?php echo $i; ?>"
class="<?php echo $i === $page ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
</div>
<?php endif; ?>
</div>
<!-- 状态更新模态框 -->
<div class="modal" id="statusModal">
<div class="modal-content">
<h3>更新状态</h3>
<form method="POST">
<input type="hidden" name="action" value="update_status">
<input type="hidden" name="type" id="modal_type">
<input type="hidden" name="id" id="modal_id">
<div class="form-group">
<label for="modal_status">状态</label>
<select name="status" id="modal_status">
<option value="pending">待处理</option>
<option value="approved">通过</option>
<option value="rejected">拒绝</option>
</select>
</div>
<div class="form-group">
<label for="modal_note">备注</label>
<textarea name="note" id="modal_note" placeholder="审核备注(可选)"></textarea>
</div>
<div class="modal-actions">
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> 保存
</button>
<button type="button" class="btn btn-warning" onclick="closeModal()">
<i class="fas fa-times"></i> 取消
</button>
</div>
</form>
</div>
</div>
<script>
function changeType(type) {
window.location.href = `?type=${type}&filter=<?php echo $filter; ?>`;
}
function changeFilter(filter) {
window.location.href = `?type=<?php echo $type; ?>&filter=${filter}`;
}
function showStatusModal(id, type, status, note) {
document.getElementById('modal_id').value = id;
document.getElementById('modal_type').value = type;
document.getElementById('modal_status').value = status;
document.getElementById('modal_note').value = note;
document.getElementById('statusModal').style.display = 'block';
}
function closeModal() {
document.getElementById('statusModal').style.display = 'none';
}
function quickUpdate(id, type, status) {
if (confirm(`确定要${status === 'approved' ? '通过' : '拒绝'}这个投稿吗?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.innerHTML = `
<input type="hidden" name="action" value="update_status">
<input type="hidden" name="type" value="${type}">
<input type="hidden" name="id" value="${id}">
<input type="hidden" name="status" value="${status}">
<input type="hidden" name="note" value="">
`;
document.body.appendChild(form);
form.submit();
}
}
function toggleAccountSettings() {
const settings = document.getElementById('accountSettings');
settings.classList.toggle('collapsed');
}
// 点击模态框外部关闭
window.onclick = function(event) {
const modal = document.getElementById('statusModal');
if (event.target === modal) {
closeModal();
}
}
// 初始化时折叠账户设置
document.getElementById('accountSettings').classList.add('collapsed');
</script>
</body>
</html>