feat(版本管理): 添加版本修改和删除功能

- 在version_control.php中实现版本修改功能,支持更新版本号、更新日志和文件
- 添加版本删除功能,包括文件删除和数据库记录删除
- 使用模态框和确认对话框提升用户体验
- 在review_apps.php中显示应用下载链接和标签信息
- 移除index.php中的分页逻辑,改为懒加载方式
This commit is contained in:
2025-07-10 23:17:22 +08:00
parent 93128a648a
commit e94ecf5deb
3 changed files with 275 additions and 36 deletions

View File

@@ -1,6 +1,7 @@
<?php
require_once '../config.php';
require_once '../vendor/autoload.php';
require_once '../includes/logger.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
@@ -198,6 +199,46 @@ if (!($conn instanceof mysqli)) {
<h5 class="card-title mb-0"><?php echo htmlspecialchars($app['name']); ?></h5>
</div>
<div class="card-body">
<p class="card-text"><?php echo htmlspecialchars($app['description']); ?></p>
<?php
$appId = $app['id'];
// 获取下载链接,假设在 app_versions 表中
$getDownloadLinkStmt = $conn->prepare("SELECT file_path FROM app_versions WHERE app_id = ? ORDER BY created_at DESC LIMIT 1");
if ($getDownloadLinkStmt) {
$getDownloadLinkStmt->bind_param("i", $appId);
$getDownloadLinkStmt->execute();
$downloadLinkResult = $getDownloadLinkStmt->get_result();
$downloadLinkInfo = $downloadLinkResult->fetch_assoc();
$downloadLink = $downloadLinkInfo ? $downloadLinkInfo['file_path'] : '';
$getDownloadLinkStmt->close();
} else {
$downloadLink = '';
log_error('数据库准备语句错误: ' . $conn->error, __FILE__, __LINE__);
}
// 获取应用标签
$getTagsStmt = $conn->prepare("SELECT t.name FROM tags t JOIN app_tags at ON t.id = at.tag_id WHERE at.app_id = ?");
if ($getTagsStmt) {
$getTagsStmt->bind_param("i", $appId);
$getTagsStmt->execute();
$tagsResult = $getTagsStmt->get_result();
$tags = [];
while ($tag = $tagsResult->fetch_assoc()) {
$tags[] = $tag['name'];
}
$tagString = implode(', ', $tags);
$getTagsStmt->close();
} else {
$tagString = '';
log_error('数据库准备语句错误: ' . $conn->error, __FILE__, __LINE__);
}
?>
<?php if (!empty($downloadLink)): ?>
<p class="card-text"><strong>下载链接:</strong> <a href="<?php echo htmlspecialchars('../' . $downloadLink); ?>" target="_blank">点击下载</a></p>
<?php endif; ?>
<?php if (!empty($tagString)): ?>
<p class="card-text"><strong>标签:</strong> <?php echo htmlspecialchars($tagString); ?></p>
<?php endif; ?>
<p class="card-text"><strong>开发者:</strong> <?php echo htmlspecialchars($app['username']); ?></p>
<p class="card-text"><strong>提交时间:</strong> <?php echo htmlspecialchars($app['created_at']); ?></p>
<p class="card-text"><strong>描述:</strong> <?php echo nl2br(htmlspecialchars($app['description'])); ?></p>

View File

@@ -91,6 +91,108 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
}
}
// 处理版本修改请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
$versionId = $_POST['version_id'];
$version = $_POST['version'];
$changelog = $_POST['changelog'] ?? '';
// 检查是否有新文件上传
if (!empty($_FILES['new_app_file']['name'])) {
$uploadDir = '../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$fileName = basename($_FILES['new_app_file']['name']);
$newFilePath = $uploadDir . $fileName;
if (move_uploaded_file($_FILES['new_app_file']['tmp_name'], $newFilePath)) {
// 获取旧文件路径并删除
$getOldPathSql = "SELECT file_path FROM app_versions WHERE id = ?";
$getOldPathStmt = $conn->prepare($getOldPathSql);
if (!$getOldPathStmt) {
log_error("获取旧文件路径查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
unlink($newFilePath);
} else {
$getOldPathStmt->bind_param("i", $versionId);
$getOldPathStmt->execute();
$oldPathResult = $getOldPathStmt->get_result();
if ($oldPathResult->num_rows > 0) {
$oldPathRow = $oldPathResult->fetch_assoc();
$oldFilePath = $oldPathRow['file_path'];
if (file_exists($oldFilePath)) {
unlink($oldFilePath);
}
}
// 更新版本信息
$updateVersionSql = "UPDATE app_versions SET version = ?, changelog = ?, file_path = ? WHERE id = ?";
$updateVersionStmt = $conn->prepare($updateVersionSql);
if (!$updateVersionStmt) {
log_error("版本更新查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
unlink($newFilePath);
} else {
$updateVersionStmt->bind_param("sssi", $version, $changelog, $newFilePath, $versionId);
if ($updateVersionStmt->execute()) {
$success = '版本修改成功';
} else {
$error = '版本修改失败: ' . $conn->error;
unlink($newFilePath);
}
}
}
} else {
$error = '文件上传失败';
}
} else {
// 仅更新版本号和更新日志
$updateVersionSql = "UPDATE app_versions SET version = ?, changelog = ? WHERE id = ?";
$updateVersionStmt = $conn->prepare($updateVersionSql);
if (!$updateVersionStmt) {
log_error("版本更新查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本修改失败,请稍后再试';
} else {
$updateVersionStmt->bind_param("ssi", $version, $changelog, $versionId);
if ($updateVersionStmt->execute()) {
$success = '版本修改成功';
} else {
$error = '版本修改失败: ' . $conn->error;
}
}
}
}
// 处理版本删除请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_version'])) {
$versionId = $_POST['version_id'];
$filePath = $_POST['file_path'];
// 删除文件
if (file_exists($filePath)) {
if (!unlink($filePath)) {
log_error("文件删除失败: " . $filePath, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
}
}
// 从数据库删除版本记录
$deleteVersionSql = "DELETE FROM app_versions WHERE id = ?";
$deleteVersionStmt = $conn->prepare($deleteVersionSql);
if (!$deleteVersionStmt) {
log_error("版本删除查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
} else {
$deleteVersionStmt->bind_param("i", $versionId);
if ($deleteVersionStmt->execute()) {
$success = '版本删除成功';
} else {
$error = '版本删除失败: ' . $conn->error;
}
}
}
// 获取现有版本列表
$versions = [];
$getVersionsSql = "SELECT * FROM app_versions WHERE app_id = ? ORDER BY id DESC";
@@ -213,6 +315,8 @@ if (!$verStmt) {
<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>
<a href="#" class="btn btn-sm btn-outline-warning ms-2" onclick="openEditModal(<?php echo $ver['id']; ?>, '<?php echo htmlspecialchars($ver['version']); ?>', '<?php echo htmlspecialchars($ver['changelog']); ?>')">修改</a>
<a href="#" class="btn btn-sm btn-outline-danger ms-2" onclick="confirmDelete(<?php echo $ver['id']; ?>, '<?php echo htmlspecialchars($ver['file_path']); ?>')">删除</a>
<?php if ($ver['is_current'] == 1): ?>
<span class="badge bg-success">当前版本</span>
<?php endif; ?>
@@ -228,5 +332,111 @@ if (!$verStmt) {
</div>
<script src="../js/bootstrap.bundle.js"></script>
<script>
function openEditModal(versionId, version, changelog) {
const modal = `
<div class="modal fade" id="editVersionModal" tabindex="-1" aria-labelledby="editVersionModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editVersionModalLabel">修改版本</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="editVersionForm" method="post" enctype="multipart/form-data">
<div class="modal-body">
<input type="hidden" name="version_id" value="${versionId}">
<div class="form-floating mb-3">
<input type="text" class="form-control" id="editVersion" name="version" value="${version}" required>
<label for="editVersion">版本号</label>
</div>
<div class="form-floating mb-3">
<textarea class="form-control" id="editChangelog" name="changelog" rows="3">${changelog}</textarea>
<label for="editChangelog">更新日志</label>
</div>
<div class="mb-3">
<label for="new_app_file" class="form-label">更新App文件 (可选)</label>
<input class="form-control" type="file" id="new_app_file" name="new_app_file">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="submit" class="btn btn-primary">保存修改</button>
</div>
</form>
</div>
</div>
</div>`;
document.body.insertAdjacentHTML('beforeend', modal);
const editModal = new bootstrap.Modal(document.getElementById('editVersionModal'));
editModal.show();
document.getElementById('editVersionForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('version_control.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
editModal.hide();
Swal.fire({
title: '成功',
text: '版本修改成功',
icon: 'success'
}).then(() => window.location.reload());
})
.catch(error => {
editModal.hide();
Swal.fire({
title: '错误',
text: '版本修改失败: ' + error.message,
icon: 'error'
});
});
});
}
function confirmDelete(versionId, filePath) {
Swal.fire({
title: '确认删除',
text: '确定要删除这个版本吗?删除后无法恢复!',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: '确定删除'
}).then((result) => {
if (result.isConfirmed) {
const formData = new FormData();
formData.append('delete_version', 'true');
formData.append('version_id', versionId);
formData.append('file_path', filePath);
fetch('version_control.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => {
Swal.fire({
title: '成功',
text: '版本删除成功',
icon: 'success'
}).then(() => window.location.reload());
})
.catch(error => {
Swal.fire({
title: '错误',
text: '版本删除失败: ' + error.message,
icon: 'error'
});
});
}
});
}
</script>
</body>
</html>

View File

@@ -58,7 +58,6 @@ if (!isset($conn) || !$conn instanceof mysqli) {
<a class="nav-link" href="/admin/">管理</a>
</li>
<?php endif; ?>
</li>
<li class="nav-item">
<a class="nav-link" href="tags.php">标签</a>
</li>
@@ -232,8 +231,6 @@ $announcement = $announcementResult && $announcementResult->num_rows > 0 ? $anno
<!-- 这里将通过PHP动态加载应用列表 -->
<?php
$search = isset($_GET['search']) ? $_GET['search'] : '';
$limit = 10; // 默认每页显示10个应用
$offset = isset($_GET['offset']) ? intval($_GET['offset']) : 0;
$sql = "SELECT apps.id, apps.name, apps.description, apps.age_rating, AVG(reviews.rating) as avg_rating
FROM apps
LEFT JOIN reviews ON apps.id = reviews.app_id ";
@@ -287,15 +284,8 @@ $announcement = $announcementResult && $announcementResult->num_rows > 0 ? $anno
}
$sql .= "GROUP BY apps.id
ORDER BY apps.created_at DESC
LIMIT ? OFFSET ?";
$limitVal = $limit;
$offsetVal = $offset;
$params[] = &$limitVal;
$params[] = &$offsetVal;
// 添加分页参数类型
$paramTypes .= 'ii';
ORDER BY apps.created_at DESC";
// 执行查询
if (!empty($params)) {
$stmt = $conn->prepare($sql);
@@ -316,7 +306,7 @@ $announcement = $announcementResult && $announcementResult->num_rows > 0 ? $anno
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo '<div class="col-md-3 mb-4">';
echo '<div class="col-md-3 mb-4 lazy-load" data-src="app.php?id='. $row['id'] . '">';
echo '<div class="card blur-bg">';
echo '<div class="card-body">';
@@ -329,30 +319,28 @@ $announcement = $announcementResult && $announcementResult->num_rows > 0 ? $anno
echo '</div>';
}
}
?>
<script>
document.addEventListener("DOMContentLoaded", function() {
const lazyLoadItems = document.querySelectorAll(".lazy-load");
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 这里可以添加加载动画或其他操作
observer.unobserve(entry.target);
}
});
});
lazyLoadItems.forEach((item) => {
observer.observe(item);
});
});
</script>
<?php
?>
<div class="text-center mt-4">
<button id="load-more" class="btn btn-primary" onclick="loadMoreApps()">加载更多</button>
</div>
<script>
function loadMoreApps() {
const currentOffset = parseInt(new URLSearchParams(window.location.search).get('offset')) || 0;
const newOffset = currentOffset + <?php echo $limit; ?>;
fetch(`index.php?search=<?php echo urlencode($search); ?>&tag=<?php echo urlencode($_GET['tag'] ?? ''); ?>&age_rating=<?php echo urlencode($_GET['age_rating'] ?? ''); ?>&offset=${newOffset}`)
.then(response => response.text())
.then(data => {
const parser = new DOMParser();
const doc = parser.parseFromString(data, 'text/html');
const newApps = doc.querySelectorAll('#app-list .col-md-3');
const appList = document.querySelector('#app-list');
newApps.forEach(app => {
appList.appendChild(app.cloneNode(true));
});
document.getElementById('load-more').style.display = 'none';
});
}
</script>
</div>
</div>