4 Commits

Author SHA1 Message Date
Leonmmcoset
ab69ca55ea feat(developer): 添加应用版本控制功能
新增版本控制页面,允许开发者上传和管理应用的不同版本。包含版本号、更新日志和文件上传功能,并显示版本历史记录。同时更新了dashboard.php添加版本控制入口。
2025-07-09 15:22:00 +08:00
Leonmmcoset
7071f02cef feat(搜索): 为开发者应用页面添加搜索功能
实现应用卡片搜索功能,支持按标题和描述过滤
添加无结果提示,使用 SweetAlert 显示通知
2025-07-09 14:56:36 +08:00
Leonmmcoset
379a72ecd3 feat: 添加应用审核状态检查和使用SweetAlert弹窗
- 在app.php中添加应用审核状态检查,未通过审核时显示提示弹窗
- 引入SweetAlert2库用于统一弹窗样式
- 修改登录逻辑支持邮箱/用户名两种登录方式
- 修复profile.php中的表单标签错误
- 在数据库中添加is_approved字段标记应用审核状态
- 添加项目规则文档规定统一使用SweetAlert弹窗
2025-07-09 14:38:04 +08:00
Leonmmcoset
4a77edc087 fix(admin): 修复开发者删除功能并添加错误处理
添加会话cookie路径设置以确保跨目录访问
改进删除开发者用户时的错误处理,包括数据库操作失败日志和重定向
检查受影响行数以确认删除操作是否成功

开发者留言:没修复成功(
2025-07-09 13:58:36 +08:00
10 changed files with 337 additions and 18 deletions

View File

@@ -0,0 +1,7 @@
弹窗都用Sweet Alert弹窗
说中文!!!
不要用通用顶栏方式搞顶栏
不要出现EndOfFile错误

View File

@@ -1,6 +1,8 @@
<?php <?php
require_once '../config.php'; require_once '../config.php';
// 设置会话cookie路径为根目录以确保跨目录访问
session_set_cookie_params(0, '/');
session_start(); session_start();
// 检查管理员登录状态 // 检查管理员登录状态
if (!isset($_SESSION['admin'])) { if (!isset($_SESSION['admin'])) {
@@ -99,10 +101,25 @@ if (isset($_GET['logout'])) {
if (isset($_POST['delete_user'])) { if (isset($_POST['delete_user'])) {
$userId = $_POST['user_id']; $userId = $_POST['user_id'];
$stmt = $conn->prepare("DELETE FROM users WHERE id = ? AND role = 'developer'"); $stmt = $conn->prepare("DELETE FROM users WHERE id = ? AND role = 'developer'");
if (!$stmt) {
error_log('Database prepare failed: ' . $conn->error);
header('Location: manage_developers.php?error=delete');
exit;
}
$stmt->bind_param("i", $userId); $stmt->bind_param("i", $userId);
$stmt->execute(); if (!$stmt->execute()) {
error_log('Delete query execution failed: ' . $stmt->error);
header('Location: manage_developers.php?error=delete');
exit;
}
$affected_rows = $stmt->affected_rows;
$stmt->close(); $stmt->close();
header("Location: manage_developers.php?deleted=true"); if ($affected_rows > 0) {
header("Location: manage_developers.php?deleted=true");
} else {
error_log('No user deleted with ID: ' . $userId);
header('Location: manage_developers.php?error=delete&user_id=' . $userId);
}
exit; exit;
} }

21
app.php
View File

@@ -28,6 +28,24 @@ if (!$app) {
die("<h1>错误:应用不存在</h1><p>找不到ID为 $appId 的应用。请检查ID是否正确。</p>"); die("<h1>错误:应用不存在</h1><p>找不到ID为 $appId 的应用。请检查ID是否正确。</p>");
} }
// 检查应用审核状态
if ($app['status'] != 'approved') {
echo '<script>
document.addEventListener("DOMContentLoaded", function() {
Swal.fire({
title: "应用审核中",
text: "该应用正在审核中,暂时无法访问。",
icon: "info",
confirmButtonText: "确定"
}).then((result) => {
if (result.isConfirmed) {
window.history.back();
}
});
});
</script>';
}
// 处理评价加载请求 // 处理评价加载请求
if (isset($_GET['action']) && $_GET['action'] === 'load_reviews') { if (isset($_GET['action']) && $_GET['action'] === 'load_reviews') {
header('Content-Type: text/html; charset=UTF-8'); header('Content-Type: text/html; charset=UTF-8');
@@ -118,6 +136,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['rating'])) {
<link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome --> <!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- SweetAlert2 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.all.min.js"></script>
<!-- 本地 Chart.js --> <!-- 本地 Chart.js -->
<script src="js/charts.js"></script> <script src="js/charts.js"></script>
<!-- 自定义CSS --> <!-- 自定义CSS -->

View File

@@ -14,6 +14,7 @@ CREATE TABLE IF NOT EXISTS apps (
changelog TEXT NOT NULL, changelog TEXT NOT NULL,
file_path VARCHAR(255) NOT NULL, file_path VARCHAR(255) NOT NULL,
status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending', status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
is_approved TINYINT(1) DEFAULT 0 COMMENT '应用是否已审核',
developer_email VARCHAR(255) NOT NULL developer_email VARCHAR(255) NOT NULL
); );

View File

@@ -200,7 +200,8 @@ if (!($conn instanceof mysqli)) {
<?php endif; ?> <?php endif; ?>
</p> </p>
<div class="action-buttons"> <div class="action-buttons">
<a href="edit_app.php?id=<?php echo $app['id']; ?>" class="btn btn-primary">编辑</a> <a href="edit_app.php?id=<?php echo $app['id']; ?>", class="btn btn-primary">编辑</a>
<a href="version_control.php?id=<?php echo $app['id']; ?>", class="btn btn-secondary">版本控制</a>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -41,23 +41,23 @@ if (isset($_GET['register_success']) && $_GET['register_success'] == 1) {
} }
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = trim($_POST['email']); $loginId = trim($_POST['login_id']);
$password = $_POST['password']; $password = $_POST['password'];
if (empty($email) || empty($password)) { if (empty($loginId) || empty($password)) {
$error = '邮箱和密码不能为空'; $error = '邮箱/用户名和密码不能为空';
} else { } else {
// 检查数据库连接是否为 MySQLi 对象 // 检查数据库连接是否为 MySQLi 对象
if (!($conn instanceof mysqli)) { if (!($conn instanceof mysqli)) {
log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__); log_error('数据库连接错误: 连接不是MySQLi实例', __FILE__, __LINE__);
$error = '数据库连接错误,请检查配置'; $error = '数据库连接错误,请检查配置';
} else { } else {
$stmt = $conn->prepare('SELECT id, username, password FROM developers WHERE email = ?'); $stmt = $conn->prepare('SELECT id, username, password FROM developers WHERE email = ? OR username = ?');
if (!$stmt) { if (!$stmt) {
log_error('登录查询准备失败: ' . $conn->error, __FILE__, __LINE__); log_error('登录查询准备失败: ' . $conn->error, __FILE__, __LINE__);
$error = '登录时发生错误,请稍后再试'; $error = '登录时发生错误,请稍后再试';
} else { } else {
$stmt->bind_param('s', $email); $stmt->bind_param('ss', $loginId, $loginId);
if (!$stmt->execute()) { if (!$stmt->execute()) {
log_error('登录查询执行失败: ' . $stmt->error, __FILE__, __LINE__); log_error('登录查询执行失败: ' . $stmt->error, __FILE__, __LINE__);
$error = '登录时发生错误,请稍后再试'; $error = '登录时发生错误,请稍后再试';
@@ -70,7 +70,7 @@ if (!($conn instanceof mysqli)) {
header('Location: dashboard.php'); header('Location: dashboard.php');
exit; exit;
} else { } else {
$error = '邮箱或密码错误'; $error = '邮箱/用户名或密码错误';
} }
} }
} }
@@ -104,8 +104,8 @@ if (!($conn instanceof mysqli)) {
<?php endif; ?> <?php endif; ?>
<form method="post"> <form method="post">
<div class="mb-3"> <div class="mb-3">
<label for="email" class="form-label">邮箱</label> <label for="login_id" class="form-label">邮箱/用户名</label>
<input type="email" id="email" name="email" class="form-control" required> <input type="text" id="login_id" name="login_id" class="form-control" placeholder="请输入邮箱或用户名" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password" class="form-label">密码</label> <label for="password" class="form-label">密码</label>

View File

@@ -167,9 +167,7 @@ if (!($conn instanceof mysqli)) {
<label for="username">用户名</label> <label for="username">用户名</label>
<input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($developer['username']); ?>" placeholder="请输入用户名"> <input type="text" class="form-control" id="username" name="username" value="<?php echo htmlspecialchars($developer['username']); ?>" placeholder="请输入用户名">
</div> </div>
<button type="submit" class="btn btn-primary">保存更改</button>
?>" required>
</div>
<div class="form-group"> <div class="form-group">
<label for="email">邮箱</label> <label for="email">邮箱</label>
<input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($developer['email']); ?>" required> <input type="email" class="form-control" id="email" name="email" value="<?php echo htmlspecialchars($developer['email']); ?>" required>

View File

@@ -0,0 +1,232 @@
<?php
require_once '../config.php';
require_once '../includes/logger.php';
session_start();
// 检查开发者登录状态
if (!isset($_SESSION['developer_id'])) {
header('Location: login.php');
exit;
}
$developerId = $_SESSION['developer_id'];
// 验证App ID
if (!isset($_GET['id']) || !is_numeric($_GET['id'])) {
header('Location: dashboard.php?error=无效的App ID');
exit;
}
$appId = $_GET['id'];
// 获取App信息并验证所有权
$app = null;
$getAppSql = "SELECT * FROM apps WHERE id = ? AND developer_id = ?";
$stmt = $conn->prepare($getAppSql);
if (!$stmt) {
log_error("应用所有权验证查询准备失败: " . $conn->error, __FILE__, __LINE__);
header('Location: dashboard.php?error=验证应用所有权失败');
exit;
}
$stmt->bind_param("ii", $appId, $developerId);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
header('Location: dashboard.php?error=App不存在或无权访问');
exit;
}
$app = $result->fetch_assoc();
$platforms = json_decode($app['platforms'], true);
$success = '';
$error = '';
// 处理版本上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
// 验证版本信息
if (empty($_POST['version']) || empty($_FILES['app_file']['name'])) {
$error = '版本号和安装包不能为空';
} else {
// 处理App文件上传
$uploadDir = '../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$fileName = basename($_FILES['app_file']['name']);
$targetPath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['app_file']['tmp_name'], $targetPath)) {
$version = $_POST['version'];
$changelog = $_POST['changelog'] ?? '';
// 插入新版本记录
$insertVersionSql = "INSERT INTO app_versions (app_id, version, changelog, file_path) VALUES (?, ?, ?, ?)";
$verStmt = $conn->prepare($insertVersionSql);
if (!$verStmt) {
log_error("版本插入准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本保存失败,请稍后再试';
unlink($targetPath); // 清理已上传文件
} else {
$verStmt->bind_param("isss", $appId, $version, $changelog, $targetPath);
if ($verStmt->execute()) {
// 更新应用表中的最新版本
// 更新应用表中的最新版本
$updateAppSql = "UPDATE apps SET version = ? WHERE id = ?";
$updStmt = $conn->prepare($updateAppSql);
if (!$updStmt) {
log_error("应用版本更新准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '更新应用版本失败,请稍后再试';
unlink($targetPath); // 数据库更新失败,删除文件
} else {
$updStmt->bind_param("si", $version, $appId);
$updStmt->execute();
$success = '版本上传成功';
}
} else {
$error = '版本保存失败: '. $conn->error;
unlink($targetPath); // 数据库更新失败,删除文件
}
}
} else {
$error = '文件上传失败';
}
}
}
// 获取现有版本列表
$versions = [];
$getVersionsSql = "SELECT * FROM app_versions WHERE app_id = ? ORDER BY id DESC";
$verStmt = $conn->prepare($getVersionsSql);
if (!$verStmt) {
log_error("版本查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '获取版本列表失败,请稍后再试';
} else {
$verStmt->bind_param("i", $appId);
$verStmt->execute();
$versionsResult = $verStmt->get_result();
while ($ver = $versionsResult->fetch_assoc()) {
$versions[] = $ver;
}
}
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>版本控制 - <?php echo htmlspecialchars($app['name']); ?></title>
<link href="../css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="../styles.css">
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
.blur-bg {
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.5);
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light blur-bg">
<div class="container">
<a class="navbar-brand" href="../index.php"><?php echo APP_STORE_NAME; ?></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="dashboard.php">我的应用</a>
</li>
<li class="nav-item">
<a class="nav-link" href="upload_app.php">上传新应用</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="version_control.php?id=<?php echo $appId; ?>">版本控制</a>
</li>
<li class="nav-item">
<a class="nav-link" href="profile.php">个人资料</a>
</li>
<li class="nav-item">
<a class="nav-link" href="logout.php">退出登录</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container mt-4">
<?php if (!empty($success)): ?>
<script>Swal.fire('成功', '<?php echo addslashes($success); ?>', 'success');</script>
<?php endif; ?>
<?php if (!empty($error)): ?>
<script>Swal.fire('错误', '<?php echo addslashes($error); ?>', 'error');</script>
<?php endif; ?>
<div class="card blur-bg mb-4">
<div class="card-header">
<h2>应用版本控制: <?php echo htmlspecialchars($app['name']); ?></h2>
</div>
<div class="card-body">
<h4>上传新版本</h4>
<form method="post" enctype="multipart/form-data" class="mb-4">
<div class="row g-3">
<div class="col-md-6">
<div class="form-floating">
<input type="text" class="form-control" id="version" name="version" placeholder="版本号" required>
<label for="version">版本号 (如: 1.0.0)</label>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="app_file" class="form-label">安装包文件</label>
<input class="form-control" type="file" id="app_file" name="app_file" required>
</div>
</div>
</div>
<div class="form-floating mb-3">
<textarea class="form-control" id="changelog" name="changelog" rows="3" placeholder="更新日志"></textarea>
<label for="changelog">更新日志</label>
</div>
<button type="submit" class="btn btn-primary" name="upload_version">上传新版本</button>
<a href="dashboard.php" class="btn btn-secondary ms-2">返回</a>
</form>
<hr>
<h4>版本历史</h4>
<?php if (empty($versions)): ?>
<div class="alert alert-info">暂无版本记录</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>版本号</th>
<th>上传时间</th>
<th>更新日志</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<?php foreach ($versions as $ver): ?>
<tr>
<td><?php echo htmlspecialchars($ver['version']); ?></td>
<td><?php echo htmlspecialchars($ver['upload_time']); ?></td>
<td><?php echo nl2br(htmlspecialchars($ver['changelog'] ?: '无')); ?></td>
<td>
<a href="../download.php?id=<?php echo $ver['id']; ?>&type=version" class="btn btn-sm btn-outline-primary">下载</a>
<?php if ($ver['is_current'] == 1): ?>
<span class="badge bg-success">当前版本</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<script src="../js/bootstrap.bundle.js"></script>
</body>
</html>

View File

@@ -90,10 +90,19 @@ $resultApps = $conn->query($sqlApps);
</nav> </nav>
<div class="container mt-4"> <div class="container mt-4">
<div class="mb-3 form-floating"> <form id="searchForm" class="mb-4">
<input type="text" class="form-control" id="searchApp" placeholder="搜索应用"> <div class="row g-3">
<label for="searchApp">搜索应用</label> <div class="col-md-10">
</div> <div class="form-floating">
<input type="text" class="form-control" id="searchApp" placeholder="搜索应用" value="">
<label for="searchApp">搜索应用</label>
</div>
</div>
<div class="col-md-2">
<button class="btn btn-primary w-100" style="width: calc(3.5rem + calc(var(--bs-border-width) * 2)); height: calc(3.5rem + calc(var(--bs-border-width) * 2))" type="submit" id="searchButton">搜索</button>
</div>
</div>
</form>
<h1><?php echo $developerName; ?> 的应用</h1> <h1><?php echo $developerName; ?> 的应用</h1>
<hr> <hr>
<?php if (isset($developer['social_links']) && !empty($developer['social_links'])): ?> <?php if (isset($developer['social_links']) && !empty($developer['social_links'])): ?>
@@ -157,6 +166,39 @@ $resultApps = $conn->query($sqlApps);
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('page-transition'); document.body.classList.add('page-transition');
// 搜索功能实现
function performSearch(event) {
event.preventDefault();
const searchTerm = document.getElementById('searchApp').value.toLowerCase();
const appCards = document.querySelectorAll('.col-md-4.mb-4');
let hasResults = false;
appCards.forEach(card => {
const title = card.querySelector('.card-title').textContent.toLowerCase();
const description = card.querySelector('.card-text').textContent.toLowerCase();
if (title.includes(searchTerm) || description.includes(searchTerm)) {
card.style.display = '';
hasResults = true;
} else {
card.style.display = 'none';
}
});
// 如果没有搜索结果,显示提示
if (!hasResults) {
Swal.fire({
title: '未找到结果',
text: '没有找到与 "' + searchTerm + '" 匹配的应用',
icon: 'info',
confirmButtonText: '确定'
});
}
}
// 添加表单提交事件监听
document.getElementById('searchForm').addEventListener('submit', performSearch);
}); });
</script> </script>
</body> </body>

Binary file not shown.