feat: 问题数据迁移到数据库;学员详情页URL导航改造;侧边栏统一

- 问题从文件系统迁移到数据库 problems 表
- 移除 PROBLEMS_DIR 配置和文件读取逻辑
- student.html 完整重写:编辑/添加/删除问题,生成方案进度显示
- 学员详情页支持独立URL访问 (/student/<id>)
- 统一侧边栏到 base.html
- 更新文档:DEPLOYMENT_SOP, MODELS, STRUCTURE, FRONTEND_ARCH
- 部署到生产环境 v1.2.0
This commit is contained in:
hmo
2026-04-23 06:35:32 +08:00
parent fd593bddf4
commit 18351212e8
18 changed files with 857 additions and 488 deletions
+97 -1
View File
@@ -8,6 +8,8 @@
<!-- 公共CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/tabulator-tables@5/dist/css/tabulator.min.css" rel="stylesheet">
{% block extra_css %}{% endblock %}
<style>
@@ -84,7 +86,33 @@
<small id="currentUserDisplay" class="text-light"></small>
</div>
<nav class="nav flex-column">
{% block sidebar_nav %}{% endblock %}
<a class="nav-link {% if active_nav == 'students' %}active{% endif %}" href="/students">
<i class="bi bi-people"></i> 学员管理
</a>
<a class="nav-link {% if active_nav == 'plans' %}active{% endif %}" href="/plans">
<i class="bi bi-clipboard-check"></i> 方案管理
</a>
<a class="nav-link {% if active_nav == 'settings' %}active{% endif %}" href="/settings">
<i class="bi bi-gear"></i> 问题配置
</a>
<a class="nav-link {% if active_nav == 'classes' %}active{% endif %}" href="/classes">
<i class="bi bi-collection"></i> 班级管理
</a>
<a class="nav-link {% if active_nav == 'api-settings' %}active{% endif %}" href="/api-settings" id="apiSettingsNav" style="display:none;">
<i class="bi bi-key"></i> API设置
</a>
<a class="nav-link {% if active_nav == 'templates' %}active{% endif %}" href="/templates" id="templatesNav" style="display:none;">
<i class="bi bi-file-earmark-text"></i> 模板管理
</a>
{% block sidebar_extra %}
<hr>
<a class="nav-link" href="#" onclick="showChangePasswordModal(); return false;">
<i class="bi bi-key"></i> 修改密码
</a>
<a class="nav-link" href="#" onclick="logout(); return false;">
<i class="bi bi-box-arrow-right"></i> 退出登录
</a>
{% endblock %}
</nav>
</div>
@@ -97,7 +125,51 @@
<!-- 公共JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tabulator-tables@5/dist/js/tabulator.min.js"></script>
<script src="/static/js/plan_common.js"></script>
<script>
// 统一登录检查和权限处理
window.addEventListener('DOMContentLoaded', function() {
fetch('/api/check-login').then(r => r.json()).then(data => {
if (!data.logged_in) {
window.location.href = '/login';
return;
}
// 显示用户名
const ROLE_LABELS = { 'admin': '管理员', 'user': '普通用户' };
const userDisplay = data.username + ' (' + (ROLE_LABELS[data.role] || data.role) + ')';
const currentUserEl = document.getElementById('currentUserDisplay');
if (currentUserEl) currentUserEl.textContent = userDisplay;
const mobileUserEl = document.getElementById('mobileUserDisplay');
if (mobileUserEl) mobileUserEl.textContent = userDisplay;
// 侧边栏权限控制
const setDisplay = (id, val) => {
const el = document.getElementById(id);
if (el) el.style.display = val;
};
if (data.role === 'admin') {
setDisplay('apiSettingsNav', '');
setDisplay('templatesNav', '');
setDisplay('usersNav', '');
setDisplay('classesNav', '');
setDisplay('settingsNav', '');
} else {
setDisplay('settingsNav', '');
setDisplay('classesNav', '');
}
// 调用页面初始化函数(如果定义了)
if (typeof window.pageInit === 'function') {
window.pageInit(data);
}
}).catch(() => {
window.location.href = '/login';
});
});
// 移动端导航切换
function toggleMobileNav() {
const sidebar = document.getElementById('sidebar');
@@ -131,6 +203,30 @@
</script>
{% block extra_js %}{% endblock %}
<!-- 方案详情弹窗 -->
<div class="modal fade" id="planDetailModal" tabindex="-1">
<div class="modal-dialog modal-fullscreen modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">练习方案</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="planDetailContent">
<!-- 方案内容将通过JS动态生成 -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-warning" onclick="editPlanContent()">
<i class="bi bi-edit"></i> 编辑内容
</button>
<button type="button" class="btn btn-primary" onclick="downloadPDF()">
<i class="bi bi-download"></i> 下载PDF
</button>
</div>
</div>
</div>
</div>
<!-- 修改密码弹窗 -->
<div class="modal fade" id="changePwdModal" tabindex="-1">
<div class="modal-dialog">