feat(app_detail_window): 新增应用详情窗口独立组件

refactor(leonapp_gui): 重构应用详情展示逻辑,直接创建窗口实例
fix(version_control.php): 修复文件路径处理和删除逻辑问题
feat(upload_app.php): 添加Markdown预览功能
style(dashboard.php): 移除冗余的padding样式
chore: 更新favicon和清理pyc缓存文件
This commit is contained in:
2025-09-21 17:02:11 +08:00
parent 8ee686b80f
commit dd85397efa
12 changed files with 649 additions and 2206 deletions

View File

@@ -63,7 +63,8 @@ if (!($conn instanceof mysqli)) {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
/* 操了原来问题出现在这里 */
/* padding: 20px; */
}
.page-transition {
animation: fadeIn 0.5s ease-in-out;

View File

@@ -198,6 +198,7 @@ if (!($conn instanceof mysqli)) {
<button type="submit" class="btn btn-primary"><i class="fas fa-save me-1"></i>保存更改</button>
</form>
</div>
<script src="../js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
document.body.classList.add('page-transition');

View File

@@ -186,7 +186,6 @@ if (!($conn instanceof mysqli)) {
// 最终修正10个参数对应10个类型字符
// 根据参数实际类型修正类型字符串整数用i字符串用s
// 移除多余的$status参数匹配SQL中9个占位符
// 修正age_rating_description类型为字符串并确保9个参数与占位符匹配
// 修复变量名错误:使用已验证的$appFilePath替换未定义的$file_path
$stmt->bind_param('ssssssssis', $appName, $appDescription, $platforms_json, $ageRating, $ageRatingDescription, $version, $changelog, $appRelativePath, $developerId, $developerEmail);
if (!$stmt->execute()) {
@@ -292,7 +291,9 @@ if (!($conn instanceof mysqli)) {
<!-- 自定义CSS -->
<link rel="stylesheet" href="../styles.css">
<script src="/js/sweetalert.js"></script>
<!-- Fluent Design 模糊效果 -->
<!-- Markdown解析库 -->
<script src="/js/marked.js"></script>
<!-- Fluent Design 模糊效果和Markdown预览样式 -->
<style>
.blur-bg {
backdrop-filter: blur(10px);
@@ -316,9 +317,61 @@ if (!($conn instanceof mysqli)) {
#ageRatingDescriptionGroup {
display: none;
}
/* Markdown预览样式 */
.markdown-content {
line-height: 1.6;
}
.markdown-content h1,
.markdown-content h2,
.markdown-content h3,
.markdown-content h4,
.markdown-content h5,
.markdown-content h6 {
margin-top: 1rem;
margin-bottom: 0.5rem;
font-weight: 600;
}
.markdown-content p {
margin-bottom: 1rem;
}
.markdown-content ul,
.markdown-content ol {
margin-left: 1.5rem;
margin-bottom: 1rem;
}
.markdown-content pre {
background-color: #f8f9fa;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
margin-bottom: 1rem;
}
.markdown-content code {
background-color: #f8f9fa;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: 'Courier New', Courier, monospace;
}
.markdown-content blockquote {
border-left: 4px solid #007BFF;
padding-left: 1rem;
margin-left: 0;
margin-bottom: 1rem;
color: #6c757d;
}
.markdown-content img {
max-width: 100%;
height: auto;
margin-bottom: 1rem;
}
.markdown-content a {
color: #007BFF;
text-decoration: none;
}
.markdown-content a:hover {
text-decoration: underline;
}
</style>
<!-- Bootstrap JS with Popper -->
<script src="/js/bootstrap.bundle.js"></script>
</head>
<!-- 导航栏 -->
@@ -346,26 +399,6 @@ if (!($conn instanceof mysqli)) {
</nav>
<div class="container mt-4">
<style>
.form-group {
margin-bottom: 1rem;
}
.btn-primary {
background-color: #007BFF;
border-color: #007BFF;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.back-link {
text-align: center;
margin-top: 1rem;
}
#ageRatingDescriptionGroup {
display: none;
}
</style>
<script>
// 年龄分级说明显示控制
document.addEventListener('DOMContentLoaded', function() {
@@ -442,6 +475,43 @@ if (!($conn instanceof mysqli)) {
document.querySelectorAll('input[name="linux_distribution"]').forEach(radio => radio.checked = false);
}
});
// Markdown预览功能
const descriptionTextarea = document.getElementById('description');
const markdownPreview = document.getElementById('markdown-preview');
const previewContent = document.getElementById('preview-content');
const togglePreviewBtn = document.getElementById('toggle-preview');
const closePreviewBtn = document.getElementById('close-preview');
function updatePreview() {
const markdownText = descriptionTextarea.value;
if (markdownText.trim() === '') {
previewContent.innerHTML = '<div class="text-muted">请输入Markdown内容以查看预览</div>';
} else {
// 使用marked.js解析Markdown
try {
const html = marked.parse(markdownText);
previewContent.innerHTML = html;
} catch (error) {
previewContent.innerHTML = '<div class="text-danger">Markdown解析错误: ' + error.message + '</div>';
}
}
}
togglePreviewBtn.addEventListener('click', function() {
markdownPreview.style.display = 'block';
updatePreview();
});
closePreviewBtn.addEventListener('click', function() {
markdownPreview.style.display = 'none';
});
descriptionTextarea.addEventListener('input', function() {
if (markdownPreview.style.display === 'block') {
updatePreview();
}
});
});
</script>
</head>
@@ -486,7 +556,19 @@ if (!($conn instanceof mysqli)) {
</div>
<div class="form-group mb-3">
<label for="description" class="form-label">应用描述支持Markdown</label>
<div class="mb-2">
<button type="button" id="toggle-preview" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-eye"></i> 预览
</button>
</div>
<textarea id="description" name="description" rows="5" class="form-control" required></textarea>
<div id="markdown-preview" class="mt-3 p-3 border rounded bg-light" style="display: none;">
<div class="preview-header d-flex justify-content-between items-center mb-2">
<h5 class="mb-0">Markdown预览</h5>
<button type="button" id="close-preview" class="btn-close"></button>
</div>
<div id="preview-content" class="markdown-content"></div>
</div>
</div>
<div class="form-group mb-3">
<label for="age_rating" class="form-label">年龄分级</label>
@@ -585,5 +667,8 @@ if (!($conn instanceof mysqli)) {
</div>
</div>
</div>
<!-- Bootstrap JS with Popper -->
<script src="/js/bootstrap.bundle.js"></script>
</body>
</html>

View File

@@ -38,6 +38,7 @@ $platforms = json_decode($app['platforms'], true);
$success = '';
$error = '';
// 处理版本上传请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
// 验证版本信息
@@ -45,7 +46,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
$error = '版本号和安装包不能为空';
} else {
// 处理App文件上传
$uploadDir = '../files/';
$uploadDir = __DIR__ . '/../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
@@ -64,10 +65,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['upload_version'])) {
$error = '版本保存失败,请稍后再试';
unlink($targetPath); // 清理已上传文件
} else {
$verStmt->bind_param("isss", $appId, $version, $changelog, $targetPath);
$verStmt->bind_param("isss", $appId, $version, $changelog, $fileName);
if ($verStmt->execute()) {
// 更新应用表中的最新版本
// 更新应用表中的最新版本
$updateAppSql = "UPDATE apps SET version = ? WHERE id = ?";
$updStmt = $conn->prepare($updateAppSql);
@@ -99,7 +99,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
// 检查是否有新文件上传
if (!empty($_FILES['new_app_file']['name'])) {
$uploadDir = '../files/';
$uploadDir = __DIR__ . '/../files/';
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
@@ -120,7 +120,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
$oldPathResult = $getOldPathStmt->get_result();
if ($oldPathResult->num_rows > 0) {
$oldPathRow = $oldPathResult->fetch_assoc();
$oldFilePath = $oldPathRow['file_path'];
$oldFileName = $oldPathRow['file_path'];
$oldFilePath = $uploadDir . $oldFileName;
if (file_exists($oldFilePath)) {
unlink($oldFilePath);
}
@@ -134,7 +135,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
$error = '版本修改失败,请稍后再试';
unlink($newFilePath);
} else {
$updateVersionStmt->bind_param("sssi", $version, $changelog, $newFilePath, $versionId);
$updateVersionStmt->bind_param("sssi", $version, $changelog, $fileName, $versionId);
if ($updateVersionStmt->execute()) {
$success = '版本修改成功';
} else {
@@ -167,13 +168,30 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['version_id'])) {
// 处理版本删除请求
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['delete_version'])) {
$versionId = $_POST['version_id'];
$filePath = $_POST['file_path'];
$uploadDir = __DIR__ . '/../files/';
// 删除文件
if (file_exists($filePath)) {
if (!unlink($filePath)) {
log_error("文件删除失败: " . $filePath, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
// 获取文件路径
$getFilePathSql = "SELECT file_path FROM app_versions WHERE id = ?";
$getFileStmt = $conn->prepare($getFilePathSql);
if (!$getFileStmt) {
log_error("获取文件路径查询准备失败: " . $conn->error, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
} else {
$getFileStmt->bind_param("i", $versionId);
$getFileStmt->execute();
$fileResult = $getFileStmt->get_result();
if ($fileResult->num_rows > 0) {
$fileRow = $fileResult->fetch_assoc();
$fileName = $fileRow['file_path'];
$filePath = $uploadDir . $fileName;
// 删除文件
if (file_exists($filePath)) {
if (!unlink($filePath)) {
log_error("文件删除失败: " . $filePath, __FILE__, __LINE__);
$error = '版本删除失败,请稍后再试';
}
}
}
}
@@ -326,7 +344,7 @@ if (!$verStmt) {
<td>
<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>
<a href="#" class="btn btn-sm btn-outline-danger ms-2" onclick="confirmDelete(<?php echo $ver['id']; ?>, '<?php echo htmlspecialchars($ver['file_path']); ?>')"><i class="fas fa-trash-alt me-1"></i>删除</a>
<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>
<?php if ($ver['is_current'] == 1): ?>
<span class="badge bg-success">当前版本</span>
<?php endif; ?>
@@ -401,11 +419,10 @@ if (!$verStmt) {
});
}
function confirmDelete(versionId, filePath) {
function confirmDelete(versionId) {
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())