feat(版本管理): 添加Markdown预览功能并改进版本删除逻辑

- 在版本上传和编辑表单中添加Markdown实时预览功能
- 使用marked.js库实现Markdown解析
- 改进版本删除逻辑,使用事务处理确保数据一致性
- 为历史版本日志添加Markdown渲染切换功能
This commit is contained in:
2025-09-23 18:08:20 +08:00
parent 8cf0da2f56
commit e2d0e10cfe
3 changed files with 229 additions and 32 deletions

View File

@@ -292,6 +292,8 @@ if (!$verStmt) {
<link rel="stylesheet" href="/css/all.min.css">
<link rel="stylesheet" href="../styles.css">
<script src="/js/sweetalert.js"></script>
<!-- Marked.js库用于Markdown解析 -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
.blur-bg {
backdrop-filter: blur(10px);
@@ -358,10 +360,19 @@ if (!$verStmt) {
</div>
</div>
</div>
<div class="form-floating mb-3">
<textarea class="form-control" id="changelog" name="changelog" rows="3" placeholder="更新日志"></textarea>
<label for="changelog">更新日志支持Markdown</label>
</div>
<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>
<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>
@@ -387,8 +398,11 @@ if (!$verStmt) {
<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>
<div class="changelog-content" data-markdown="<?php echo htmlspecialchars($ver['changelog'] ?: '无'); ?>">
<?php echo nl2br(htmlspecialchars($ver['changelog'] ?: '无')); ?>
</div>
</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']; ?>)"><i class="fas fa-trash-alt me-1"></i>删除</a>
@@ -408,6 +422,50 @@ if (!$verStmt) {
<script src="../js/bootstrap.bundle.js"></script>
<script>
// 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 || '无更新日志');
}
});
}
function openEditModal(versionId, version, changelog) {
const modal = `
<div class="modal fade" id="editVersionModal" tabindex="-1" aria-labelledby="editVersionModalLabel" aria-hidden="true">
@@ -424,9 +482,18 @@ if (!$verStmt) {
<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 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>
</div>
<div class="mb-3">
<label for="new_app_file" class="form-label">更新App文件 (可选)</label>
@@ -464,6 +531,52 @@ if (!$verStmt) {
window.location.reload();
});
});
// 编辑模态框的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();
});
}
function confirmDelete(versionId) {
@@ -527,6 +640,49 @@ if (!$verStmt) {
}
});
}
// 页面加载完成后初始化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);
}
}
});
}
</script>
</body>
</html>