2025-09-20 22:20:08 +08:00
|
|
|
|
<?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 = '';
|
2025-09-21 17:02:11 +08:00
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
// 处理版本上传请求
|
|
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
|
|
|
|
|
|
// 验证版本信息
|
|
|
|
|
|
if (empty($_POST['version']) || empty($_FILES['app_file']['name'])) {
|
|
|
|
|
|
$error = '版本号和安装包不能为空';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 处理App文件上传
|
2025-09-23 17:35:57 +08:00
|
|
|
|
$uploadDir = __DIR__ . '/../uploads/apps/';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
if (!is_dir($uploadDir)) {
|
|
|
|
|
|
mkdir($uploadDir, 0755, true);
|
|
|
|
|
|
}
|
2025-09-23 17:35:57 +08:00
|
|
|
|
// 验证文件大小 (500MB)
|
|
|
|
|
|
if ($_FILES['app_file']['size'] > 500 * 1024 * 1024) {
|
|
|
|
|
|
log_error('应用文件过大: ' . number_format($_FILES['app_file']['size'] / 1024 / 1024, 2) . 'MB', __FILE__, __LINE__);
|
|
|
|
|
|
$error = '应用文件大小不能超过500MB';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$appExtension = pathinfo($_FILES['app_file']['name'], PATHINFO_EXTENSION);
|
|
|
|
|
|
// 生成6位随机数
|
|
|
|
|
|
$randomNumber = str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
|
|
|
|
|
|
// 清理应用名称中的特殊字符
|
|
|
|
|
|
$cleanAppName = preg_replace('/[^a-zA-Z0-9_]/', '_', $app['name']);
|
|
|
|
|
|
$appFileName = $cleanAppName . '_' . $randomNumber . '.' . $appExtension;
|
|
|
|
|
|
$appRelativePath = 'uploads/apps/' . $appFileName;
|
|
|
|
|
|
$targetPath = $uploadDir . $appFileName;
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-23 17:35:57 +08:00
|
|
|
|
if (move_uploaded_file($_FILES['app_file']['tmp_name'], $targetPath)) {
|
|
|
|
|
|
$version = $_POST['version'];
|
|
|
|
|
|
$changelog = $_POST['changelog'] ?? '';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
|
2025-09-23 17:35:57 +08:00
|
|
|
|
// 插入新版本记录
|
|
|
|
|
|
$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, $appRelativePath);
|
|
|
|
|
|
|
|
|
|
|
|
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 = '版本上传成功';
|
|
|
|
|
|
}
|
2025-09-20 22:20:08 +08:00
|
|
|
|
} else {
|
2025-09-23 17:35:57 +08:00
|
|
|
|
$error = '版本保存失败: '. $conn->error;
|
|
|
|
|
|
unlink($targetPath); // 数据库更新失败,删除文件
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-23 17:35:57 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$error = '文件上传失败';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理版本修改请求
|
|
|
|
|
|
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'])) {
|
2025-09-23 17:35:57 +08:00
|
|
|
|
$uploadDir = __DIR__ . '/../uploads/apps/';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
if (!is_dir($uploadDir)) {
|
|
|
|
|
|
mkdir($uploadDir, 0755, true);
|
|
|
|
|
|
}
|
2025-09-23 17:35:57 +08:00
|
|
|
|
// 验证文件大小 (500MB)
|
|
|
|
|
|
if ($_FILES['new_app_file']['size'] > 500 * 1024 * 1024) {
|
|
|
|
|
|
log_error('应用文件过大: ' . number_format($_FILES['new_app_file']['size'] / 1024 / 1024, 2) . 'MB', __FILE__, __LINE__);
|
|
|
|
|
|
$error = '应用文件大小不能超过500MB';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$appExtension = pathinfo($_FILES['new_app_file']['name'], PATHINFO_EXTENSION);
|
|
|
|
|
|
// 生成6位随机数
|
|
|
|
|
|
$randomNumber = str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
|
|
|
|
|
|
// 清理应用名称中的特殊字符
|
|
|
|
|
|
$cleanAppName = preg_replace('/[^a-zA-Z0-9_]/', '_', $app['name']);
|
|
|
|
|
|
$appFileName = $cleanAppName . '_' . $randomNumber . '.' . $appExtension;
|
|
|
|
|
|
$appRelativePath = 'uploads/apps/' . $appFileName;
|
|
|
|
|
|
$newFilePath = $uploadDir . $appFileName;
|
|
|
|
|
|
|
|
|
|
|
|
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__);
|
2025-09-20 22:20:08 +08:00
|
|
|
|
$error = '版本修改失败,请稍后再试';
|
|
|
|
|
|
unlink($newFilePath);
|
|
|
|
|
|
} else {
|
2025-09-23 17:35:57 +08:00
|
|
|
|
$getOldPathStmt->bind_param("i", $versionId);
|
|
|
|
|
|
$getOldPathStmt->execute();
|
|
|
|
|
|
$oldPathResult = $getOldPathStmt->get_result();
|
|
|
|
|
|
if ($oldPathResult->num_rows > 0) {
|
|
|
|
|
|
$oldPathRow = $oldPathResult->fetch_assoc();
|
|
|
|
|
|
$oldFilePath = __DIR__ . '/../' . $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 = '版本修改失败,请稍后再试';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
unlink($newFilePath);
|
2025-09-23 17:35:57 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$updateVersionStmt->bind_param("sssi", $version, $changelog, $appRelativePath, $versionId);
|
|
|
|
|
|
if ($updateVersionStmt->execute()) {
|
|
|
|
|
|
$success = '版本修改成功';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$error = '版本修改失败: ' . $conn->error;
|
|
|
|
|
|
unlink($newFilePath);
|
|
|
|
|
|
}
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-23 17:35:57 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$error = '文件上传失败';
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} 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'])) {
|
2025-09-23 17:54:08 +08:00
|
|
|
|
// 设置内容类型为纯文本
|
|
|
|
|
|
header('Content-Type: text/plain');
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
$versionId = $_POST['version_id'];
|
|
|
|
|
|
|
2025-09-23 17:54:08 +08:00
|
|
|
|
// 初始化消息变量
|
|
|
|
|
|
$message = '';
|
|
|
|
|
|
|
2025-09-23 17:57:33 +08:00
|
|
|
|
// 开始事务
|
|
|
|
|
|
$conn->begin_transaction();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 获取文件路径
|
|
|
|
|
|
$filePath = '';
|
|
|
|
|
|
$getFilePathSql = "SELECT file_path FROM app_versions WHERE id = ?";
|
|
|
|
|
|
$getFileStmt = $conn->prepare($getFilePathSql);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$getFileStmt) {
|
|
|
|
|
|
throw new Exception("获取文件路径查询准备失败: " . $conn->error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 17:02:11 +08:00
|
|
|
|
$getFileStmt->bind_param("i", $versionId);
|
|
|
|
|
|
$getFileStmt->execute();
|
|
|
|
|
|
$fileResult = $getFileStmt->get_result();
|
2025-09-23 17:57:33 +08:00
|
|
|
|
|
2025-09-21 17:02:11 +08:00
|
|
|
|
if ($fileResult->num_rows > 0) {
|
|
|
|
|
|
$fileRow = $fileResult->fetch_assoc();
|
2025-09-23 17:35:57 +08:00
|
|
|
|
$filePath = __DIR__ . '/../' . $fileRow['file_path'];
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
2025-09-23 17:57:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 2. 从数据库删除版本记录
|
|
|
|
|
|
$deleteVersionSql = "DELETE FROM app_versions WHERE id = ?";
|
|
|
|
|
|
$deleteVersionStmt = $conn->prepare($deleteVersionSql);
|
|
|
|
|
|
|
|
|
|
|
|
if (!$deleteVersionStmt) {
|
|
|
|
|
|
throw new Exception("版本删除查询准备失败: " . $conn->error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
$deleteVersionStmt->bind_param("i", $versionId);
|
2025-09-23 17:57:33 +08:00
|
|
|
|
|
|
|
|
|
|
if (!$deleteVersionStmt->execute()) {
|
|
|
|
|
|
throw new Exception("版本删除执行失败: " . $conn->error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有记录被删除
|
|
|
|
|
|
if ($deleteVersionStmt->affected_rows === 0) {
|
|
|
|
|
|
throw new Exception("未找到要删除的版本记录");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 如果数据库删除成功,尝试删除文件(即使文件删除失败也不回滚数据库操作)
|
|
|
|
|
|
if (!empty($filePath) && file_exists($filePath)) {
|
|
|
|
|
|
if (!unlink($filePath)) {
|
|
|
|
|
|
log_error("文件删除失败: " . $filePath, __FILE__, __LINE__);
|
|
|
|
|
|
// 文件删除失败不影响数据库操作,继续处理
|
|
|
|
|
|
}
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
2025-09-23 17:57:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 提交事务
|
|
|
|
|
|
$conn->commit();
|
|
|
|
|
|
$message = '版本删除成功';
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception $e) {
|
|
|
|
|
|
// 回滚事务
|
|
|
|
|
|
$conn->rollback();
|
|
|
|
|
|
log_error("版本删除异常: " . $e->getMessage(), __FILE__, __LINE__);
|
|
|
|
|
|
$message = '版本删除失败: ' . $e->getMessage();
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-23 17:54:08 +08:00
|
|
|
|
// 只输出消息,不包含任何HTML
|
|
|
|
|
|
echo $message;
|
|
|
|
|
|
|
|
|
|
|
|
// 确保脚本终止,不执行后续代码
|
2025-09-20 22:20:08 +08:00
|
|
|
|
exit;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取现有版本列表
|
|
|
|
|
|
$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">
|
|
|
|
|
|
<!-- Font Awesome -->
|
|
|
|
|
|
<link rel="stylesheet" href="/css/all.min.css">
|
|
|
|
|
|
<link rel="stylesheet" href="../styles.css">
|
|
|
|
|
|
<script src="/js/sweetalert.js"></script>
|
2025-09-23 18:08:20 +08:00
|
|
|
|
<!-- Marked.js库用于Markdown解析 -->
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
2025-09-20 22:20:08 +08:00
|
|
|
|
<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 href="../index.php"><img src="/favicon.jpeg" alt="Logo" style="height: 30px; margin-right: 10px; border-radius: var(--border-radius);"></a>
|
|
|
|
|
|
<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>
|
2025-09-23 18:08:20 +08:00
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<div class="mb-1">
|
|
|
|
|
|
<label for="changelog" class="form-label">更新日志(支持Markdown)</label>
|
|
|
|
|
|
<div class="btn-group float-end">
|
|
|
|
|
|
<button type="button" id="writeModeBtn" class="btn btn-sm btn-primary active">编辑</button>
|
|
|
|
|
|
<button type="button" id="previewModeBtn" class="btn btn-sm btn-secondary">预览</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="writeModeDiv">
|
|
|
|
|
|
<textarea class="form-control" id="changelog" name="changelog" rows="6" placeholder="更新日志(支持Markdown语法)"></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="previewModeDiv" class="p-3 border rounded bg-light" style="min-height: 200px; display: none;"></div>
|
|
|
|
|
|
</div>
|
2025-09-20 22:20:08 +08:00
|
|
|
|
<button type="submit" class="btn btn-primary" name="upload_version"><i class="fas fa-cloud-upload-alt me-1"></i>上传新版本</button>
|
|
|
|
|
|
<a href="dashboard.php" class="btn btn-secondary ms-2"><i class="fas fa-arrow-left me-1"></i>返回</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>
|
2025-09-23 18:08:20 +08:00
|
|
|
|
<div class="changelog-content" data-markdown="<?php echo htmlspecialchars($ver['changelog'] ?: '无'); ?>">
|
|
|
|
|
|
<?php echo nl2br(htmlspecialchars($ver['changelog'] ?: '无')); ?>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
2025-09-23 18:11:33 +08:00
|
|
|
|
<td>
|
2025-09-20 22:20:08 +08:00
|
|
|
|
<a href="../download.php?id=<?php echo $ver['id']; ?>&type=version" class="btn btn-sm btn-outline-primary"><i class="fas fa-download me-1"></i>下载</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']); ?>')"><i class="fas fa-edit me-1"></i>修改</a>
|
2025-09-21 17:02:11 +08:00
|
|
|
|
<a href="#" class="btn btn-sm btn-outline-danger ms-2" onclick="confirmDelete(<?php echo $ver['id']; ?>)"><i class="fas fa-trash-alt me-1"></i>删除</a>
|
2025-09-20 22:20:08 +08:00
|
|
|
|
<?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>
|
|
|
|
|
|
<script>
|
2025-09-23 18:08:20 +08:00
|
|
|
|
// Markdown预览功能实现
|
|
|
|
|
|
function initMarkdownPreview() {
|
|
|
|
|
|
// 上传表单的Markdown预览
|
|
|
|
|
|
const writeModeBtn = document.getElementById('writeModeBtn');
|
|
|
|
|
|
const previewModeBtn = document.getElementById('previewModeBtn');
|
|
|
|
|
|
const writeModeDiv = document.getElementById('writeModeDiv');
|
|
|
|
|
|
const previewModeDiv = document.getElementById('previewModeDiv');
|
|
|
|
|
|
const changelogTextarea = document.getElementById('changelog');
|
|
|
|
|
|
|
|
|
|
|
|
writeModeBtn.addEventListener('click', function() {
|
|
|
|
|
|
writeModeBtn.classList.add('active');
|
|
|
|
|
|
writeModeBtn.classList.remove('btn-secondary');
|
|
|
|
|
|
writeModeBtn.classList.add('btn-primary');
|
|
|
|
|
|
previewModeBtn.classList.remove('active');
|
|
|
|
|
|
previewModeBtn.classList.remove('btn-primary');
|
|
|
|
|
|
previewModeBtn.classList.add('btn-secondary');
|
|
|
|
|
|
writeModeDiv.style.display = 'block';
|
|
|
|
|
|
previewModeDiv.style.display = 'none';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
previewModeBtn.addEventListener('click', function() {
|
|
|
|
|
|
previewModeBtn.classList.add('active');
|
|
|
|
|
|
previewModeBtn.classList.remove('btn-secondary');
|
|
|
|
|
|
previewModeBtn.classList.add('btn-primary');
|
|
|
|
|
|
writeModeBtn.classList.remove('active');
|
|
|
|
|
|
writeModeBtn.classList.remove('btn-primary');
|
|
|
|
|
|
writeModeBtn.classList.add('btn-secondary');
|
|
|
|
|
|
writeModeDiv.style.display = 'none';
|
|
|
|
|
|
previewModeDiv.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
// 解析Markdown并显示预览
|
|
|
|
|
|
if (changelogTextarea && marked) {
|
|
|
|
|
|
previewModeDiv.innerHTML = marked.parse(changelogTextarea.value || '无更新日志');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 实时更新预览
|
|
|
|
|
|
changelogTextarea.addEventListener('input', function() {
|
|
|
|
|
|
if (previewModeDiv.style.display !== 'none' && marked) {
|
|
|
|
|
|
previewModeDiv.innerHTML = marked.parse(changelogTextarea.value || '无更新日志');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-20 22:20:08 +08:00
|
|
|
|
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>
|
2025-09-23 18:08:20 +08:00
|
|
|
|
<div class="mb-3">
|
|
|
|
|
|
<div class="mb-1">
|
|
|
|
|
|
<label for="editChangelog" class="form-label">更新日志</label>
|
|
|
|
|
|
<div class="btn-group float-end">
|
|
|
|
|
|
<button type="button" id="editWriteModeBtn" class="btn btn-sm btn-primary active">编辑</button>
|
|
|
|
|
|
<button type="button" id="editPreviewModeBtn" class="btn btn-sm btn-secondary">预览</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="editWriteModeDiv">
|
|
|
|
|
|
<textarea class="form-control" id="editChangelog" name="changelog" rows="6">${changelog}</textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div id="editPreviewModeDiv" class="p-3 border rounded bg-light" style="min-height: 200px; display: none;"></div>
|
2025-09-20 22:20:08 +08:00
|
|
|
|
</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"><i class="fas fa-times me-1"></i>取消</button>
|
|
|
|
|
|
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i>保存修改</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();
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
|
|
|
|
|
editModal.hide();
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
2025-09-23 18:08:20 +08:00
|
|
|
|
|
|
|
|
|
|
// 编辑模态框的Markdown预览功能
|
|
|
|
|
|
const editWriteModeBtn = document.getElementById('editWriteModeBtn');
|
|
|
|
|
|
const editPreviewModeBtn = document.getElementById('editPreviewModeBtn');
|
|
|
|
|
|
const editWriteModeDiv = document.getElementById('editWriteModeDiv');
|
|
|
|
|
|
const editPreviewModeDiv = document.getElementById('editPreviewModeDiv');
|
|
|
|
|
|
const editChangelogTextarea = document.getElementById('editChangelog');
|
|
|
|
|
|
|
|
|
|
|
|
editWriteModeBtn.addEventListener('click', function() {
|
|
|
|
|
|
editWriteModeBtn.classList.add('active');
|
|
|
|
|
|
editWriteModeBtn.classList.remove('btn-secondary');
|
|
|
|
|
|
editWriteModeBtn.classList.add('btn-primary');
|
|
|
|
|
|
editPreviewModeBtn.classList.remove('active');
|
|
|
|
|
|
editPreviewModeBtn.classList.remove('btn-primary');
|
|
|
|
|
|
editPreviewModeBtn.classList.add('btn-secondary');
|
|
|
|
|
|
editWriteModeDiv.style.display = 'block';
|
|
|
|
|
|
editPreviewModeDiv.style.display = 'none';
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
editPreviewModeBtn.addEventListener('click', function() {
|
|
|
|
|
|
editPreviewModeBtn.classList.add('active');
|
|
|
|
|
|
editPreviewModeBtn.classList.remove('btn-secondary');
|
|
|
|
|
|
editPreviewModeBtn.classList.add('btn-primary');
|
|
|
|
|
|
editWriteModeBtn.classList.remove('active');
|
|
|
|
|
|
editWriteModeBtn.classList.remove('btn-primary');
|
|
|
|
|
|
editWriteModeBtn.classList.add('btn-secondary');
|
|
|
|
|
|
editWriteModeDiv.style.display = 'none';
|
|
|
|
|
|
editPreviewModeDiv.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
// 解析Markdown并显示预览
|
|
|
|
|
|
if (editChangelogTextarea && marked) {
|
|
|
|
|
|
editPreviewModeDiv.innerHTML = marked.parse(editChangelogTextarea.value || '无更新日志');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 实时更新预览
|
|
|
|
|
|
editChangelogTextarea.addEventListener('input', function() {
|
|
|
|
|
|
if (editPreviewModeDiv.style.display !== 'none' && marked) {
|
|
|
|
|
|
editPreviewModeDiv.innerHTML = marked.parse(editChangelogTextarea.value || '无更新日志');
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 模态框关闭时移除元素
|
|
|
|
|
|
document.getElementById('editVersionModal').addEventListener('hidden.bs.modal', function() {
|
|
|
|
|
|
this.remove();
|
|
|
|
|
|
});
|
2025-09-20 22:20:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-21 17:02:11 +08:00
|
|
|
|
function confirmDelete(versionId) {
|
2025-09-23 17:35:57 +08:00
|
|
|
|
// 使用Sweet Alert弹窗进行确认
|
|
|
|
|
|
Swal.fire({
|
|
|
|
|
|
title: '确定要删除这个版本吗?',
|
|
|
|
|
|
text: "此操作无法撤销,删除后将无法恢复。",
|
|
|
|
|
|
icon: 'warning',
|
|
|
|
|
|
showCancelButton: true,
|
|
|
|
|
|
confirmButtonColor: '#d33',
|
|
|
|
|
|
cancelButtonColor: '#3085d6',
|
|
|
|
|
|
confirmButtonText: '确认删除',
|
|
|
|
|
|
cancelButtonText: '取消'
|
|
|
|
|
|
}).then((result) => {
|
|
|
|
|
|
if (result.isConfirmed) {
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append('delete_version', 'true');
|
|
|
|
|
|
formData.append('version_id', versionId);
|
|
|
|
|
|
|
2025-09-23 17:54:08 +08:00
|
|
|
|
fetch('version_control.php', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
body: formData,
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'X-Requested-With': 'XMLHttpRequest' // 添加AJAX请求标识
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-09-23 17:35:57 +08:00
|
|
|
|
.then(response => response.text())
|
|
|
|
|
|
.then(data => {
|
2025-09-23 17:54:08 +08:00
|
|
|
|
// 清理返回的文本,去除任何可能的HTML标签或额外空格
|
|
|
|
|
|
const cleanMessage = data.trim();
|
|
|
|
|
|
|
2025-09-23 17:35:57 +08:00
|
|
|
|
// 显示操作结果
|
2025-09-23 17:54:08 +08:00
|
|
|
|
if (cleanMessage.includes('成功')) {
|
|
|
|
|
|
Swal.fire({
|
|
|
|
|
|
title: '操作成功',
|
|
|
|
|
|
text: '版本已成功删除',
|
|
|
|
|
|
icon: 'success'
|
|
|
|
|
|
}).then(() => {
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
} else {
|
|
|
|
|
|
Swal.fire(
|
|
|
|
|
|
'操作失败',
|
|
|
|
|
|
cleanMessage || '版本删除失败,请稍后再试',
|
|
|
|
|
|
'error'
|
|
|
|
|
|
).then(() => {
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-23 17:35:57 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch(error => {
|
2025-09-23 17:54:08 +08:00
|
|
|
|
console.error('删除请求错误:', error);
|
2025-09-23 17:35:57 +08:00
|
|
|
|
Swal.fire(
|
|
|
|
|
|
'操作失败',
|
|
|
|
|
|
'版本删除失败,请稍后再试',
|
|
|
|
|
|
'error'
|
|
|
|
|
|
).then(() => {
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-20 22:20:08 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-23 18:08:20 +08:00
|
|
|
|
// 页面加载完成后初始化Markdown预览功能
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
|
initMarkdownPreview();
|
|
|
|
|
|
|
|
|
|
|
|
// 为版本历史中的更新日志添加Markdown渲染
|
|
|
|
|
|
renderChangelogHistory();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染版本历史中的更新日志
|
|
|
|
|
|
function renderChangelogHistory() {
|
|
|
|
|
|
const changelogElements = document.querySelectorAll('.changelog-content');
|
|
|
|
|
|
|
|
|
|
|
|
changelogElements.forEach(element => {
|
|
|
|
|
|
const markdownText = element.getAttribute('data-markdown');
|
|
|
|
|
|
if (markdownText && marked) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 创建一个切换按钮
|
|
|
|
|
|
const toggleBtn = document.createElement('button');
|
|
|
|
|
|
toggleBtn.className = 'btn btn-xs btn-outline-secondary mb-1';
|
|
|
|
|
|
toggleBtn.textContent = '查看格式化';
|
|
|
|
|
|
|
|
|
|
|
|
let isFormatted = false;
|
|
|
|
|
|
const originalContent = element.innerHTML;
|
|
|
|
|
|
|
|
|
|
|
|
toggleBtn.addEventListener('click', function() {
|
|
|
|
|
|
if (isFormatted) {
|
|
|
|
|
|
element.innerHTML = originalContent;
|
|
|
|
|
|
toggleBtn.textContent = '查看格式化';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
element.innerHTML = marked.parse(markdownText);
|
|
|
|
|
|
toggleBtn.textContent = '查看原始';
|
|
|
|
|
|
}
|
|
|
|
|
|
isFormatted = !isFormatted;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 将按钮添加到元素前
|
|
|
|
|
|
element.parentNode.insertBefore(toggleBtn, element);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Markdown解析错误:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-09-20 22:20:08 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|