feat: settings页面改名为problems,移除调试代码

This commit is contained in:
hmo
2026-04-23 22:29:51 +08:00
parent 22143bad78
commit 66451c2006
4 changed files with 3 additions and 4 deletions
+438
View File
@@ -0,0 +1,438 @@
{% extends "base.html" %}
{% block title %}问题配置 - 钢琴练习方案系统{% endblock %}
{% block page_css %}
<style>
.problem-card { transition: all 0.2s; }
.problem-card:hover { transform: translateY(-2px); box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.category-badge { font-size: 11px; padding: 3px 8px; }
.category-综合 { background: #f5f5f5; color: #616161; }
.category-乐理相关 { background: #e3f2fd; color: #1565c0; }
.category-演奏能力 { background: #e8f5e9; color: #2e7d32; }
.category-其他 { background: #fff3e0; color: #e65100; }
.editor-textarea { font-family: monospace; font-size: 13px; }
</style>
{% endblock %}
{% block content %}
<!-- Tab导航 -->
<ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="problems-tab" data-bs-toggle="tab" data-bs-target="#problemsPane" type="button">
<i class="bi bi-list-check"></i> 问题配置
</button>
</li>
</ul>
<div class="tab-content" id="settingsTabsContent">
<!-- 问题配置面板 -->
<div class="tab-pane fade show active" id="problemsPane" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4><i class="bi bi-list-check"></i> 问题配置</h4>
<div>
<button type="button" class="btn btn-outline-secondary btn-sm me-2" onclick="loadProblems()">
<i class="bi bi-arrow-clockwise"></i> 刷新
</button>
<button type="button" class="btn btn-primary btn-sm" onclick="showAddModal()">
<i class="bi bi-plus-lg"></i> 新增问题
</button>
</div>
</div>
<div class="mb-3">
<input type="text" class="form-control" placeholder="搜索问题..." id="searchInput" onkeyup="applyProblemFilters()">
</div>
<!-- 筛选和分组控制 -->
<div class="row g-3 align-items-center mb-3">
<div class="col-auto">
<label class="form-label mb-0">筛选:</label>
</div>
<div class="col-auto">
<select class="form-select" id="filterCategory" onchange="applyProblemFilters()">
<option value="">全部分类</option>
<option value="综合">综合</option>
<option value="乐理相关">乐理相关</option>
<option value="演奏能力">演奏能力</option>
<option value="其他">其他</option>
</select>
</div>
<div class="col-auto">
<label class="form-label mb-0">分组:</label>
</div>
<div class="col-auto">
<select class="form-select" id="groupByCategory" onchange="applyProblemFilters()">
<option value="">不分组</option>
<option value="category">按分类分组</option>
</select>
</div>
<div class="col-auto ms-auto">
<span id="problems-count" class="text-muted small"></span>
</div>
</div>
<div class="row" id="problemList"></div>
</div>
</div>
<!-- 新增问题模态框 -->
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog">
<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">
<div class="mb-3">
<label class="form-label">问题编号</label>
<input type="text" class="form-control" id="newProblemId" placeholder="如: 16">
<small class="text-muted">数字编号,将自动补齐为2位</small>
</div>
<div class="mb-3">
<label class="form-label">问题名称 *</label>
<input type="text" class="form-control" id="newProblemName" placeholder="如: 手小">
</div>
<div class="mb-3">
<label class="form-label">分类</label>
<select class="form-select" id="newProblemCategory">
<option value="综合">综合</option>
<option value="乐理相关">乐理相关</option>
<option value="演奏能力">演奏能力</option>
<option value="其他">其他</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="createProblem()">创建</button>
</div>
</div>
</div>
</div>
<!-- 编辑问题模态框 -->
<div class="modal fade" id="editModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<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">
<div class="mb-3">
<label class="form-label">问题名称 *</label>
<input type="text" class="form-control" id="editProblemName">
</div>
<div class="mb-3">
<label class="form-label">分类</label>
<select class="form-select" id="editProblemCategory">
<option value="综合">综合</option>
<option value="乐理相关">乐理相关</option>
<option value="演奏能力">演奏能力</option>
<option value="其他">其他</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">问题内容 (Markdown)</label>
<textarea class="form-control editor-textarea" id="editProblemContent" rows="20"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" id="cancelEditProblemBtn">取消</button>
<button type="button" class="btn btn-primary" onclick="saveProblem()">保存</button>
</div>
</div>
</div>
</div>
<!-- 删除确认模态框 -->
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<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">
<p>确定要删除问题 <strong id="deleteProblemName"></strong> 吗?</p>
<p class="text-muted small">删除后可在 bk 文件夹中找到备份</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-danger" onclick="confirmDelete()">删除</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let allProblems = [];
let currentEditId = null;
let currentDeleteId = null;
window.pageInit = function() {
loadProblems();
};
// ========== 问题配置相关 ==========
async function loadProblems() {
const response = await fetch('/api/problems');
allProblems = await response.json();
applyProblemFilters();
}
function applyProblemFilters() {
const search = document.getElementById('searchInput').value.toLowerCase();
const filterCategory = document.getElementById('filterCategory').value;
const groupBy = document.getElementById('groupByCategory').value;
// 筛选
let filtered = allProblems.filter(p => {
if (search && !p.name.toLowerCase().includes(search)) return false;
if (filterCategory && p.category !== filterCategory) return false;
return true;
});
// 更新计数
document.getElementById('problems-count').textContent = `${filtered.length} 个问题`;
renderProblems(filtered, groupBy);
}
function renderProblems(problems, groupBy) {
const container = document.getElementById('problemList');
if (problems.length === 0) {
container.innerHTML = '<div class="col-12 text-center text-muted py-5"><p>未找到问题配置</p></div>';
return;
}
if (groupBy) {
// 分组显示
const groups = {};
problems.forEach(p => {
const key = p.category || '其他';
if (!groups[key]) groups[key] = [];
groups[key].push(p);
});
container.innerHTML = Object.entries(groups).map(([key, items]) => `
<div class="col-12 mb-3">
<div class="card">
<div class="card-header bg-light">
<h6 class="mb-0">分类:${key}${items.length}个)</h6>
</div>
<div class="card-body p-2">
<div class="row g-2">
${items.map(p => renderProblemCard(p)).join('')}
</div>
</div>
</div>
</div>
`).join('');
} else {
// 不分组
container.innerHTML = problems.map(p => `
<div class="col-md-4 col-sm-6 mb-3">
${renderProblemCard(p)}
</div>
`).join('');
}
}
function renderProblemCard(p) {
return `
<div class="card problem-card h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-start mb-2">
<h6 class="card-title mb-0">${p.name}</h6>
<span class="badge category-badge category-${p.category}">${p.category}</span>
</div>
<p class="text-muted small mb-2">编号: ${p.id}</p>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-primary" onclick="editProblem('${p.id}')">
<i class="bi bi-pencil"></i> 编辑
</button>
<button type="button" class="btn btn-outline-danger" onclick="showDeleteConfirm('${p.id}', '${p.name}')">
<i class="bi bi-trash"></i> 删除
</button>
</div>
</div>
</div>
`;
}
// 新增
function showAddModal() {
document.getElementById('newProblemId').value = '';
document.getElementById('newProblemName').value = '';
document.getElementById('newProblemCategory').value = '综合';
new bootstrap.Modal(document.getElementById('addModal')).show();
}
async function createProblem() {
const id = document.getElementById('newProblemId').value.trim();
const name = document.getElementById('newProblemName').value.trim();
const category = document.getElementById('newProblemCategory').value;
if (!id || !name) {
alert('请填写编号和名称');
return;
}
const response = await fetch('/api/problems', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({id, name, category})
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('addModal')).hide();
loadProblems();
} else {
const err = await response.json();
alert(err.error || '创建失败');
}
}
// 编辑
let editProblemOriginalState = { name: '', content: '' }; // 记录原始状态
async function editProblem(problemId) {
currentEditId = problemId;
const response = await fetch(`/api/problems/${problemId}`);
const data = await response.json();
if (data.error) {
alert(data.error);
return;
}
const name = data.name;
document.getElementById('editProblemName').value = name;
document.getElementById('editProblemCategory').value = data.category || '综合';
document.getElementById('editProblemContent').value = data.content || '';
// 记录原始状态
editProblemOriginalState.name = name;
editProblemOriginalState.content = data.content;
if (window.problemEditor) {
window.problemEditor.toTextArea();
window.problemEditor = null;
}
if (window.EasyMDE) {
window.problemEditor = new EasyMDE({
element: document.getElementById('editProblemContent'),
spellChecker: false,
status: false,
toolbar: ['bold', 'italic', 'heading', '|', 'code', 'quote', 'unordered-list', '|', 'preview', 'side-by-side', 'fullscreen'],
initialValue: data.content
});
}
const editModal = new bootstrap.Modal(document.getElementById('editModal'), {
keyboard: false // 禁用 ESC 关闭,由我们手动处理
});
const modalEl = document.getElementById('editModal');
// 取消按钮点击处理
document.getElementById('cancelEditProblemBtn').onclick = () => {
if (isEditProblemDirty()) {
if (confirm('内容已修改,确定要关闭吗?')) {
editModal.hide();
}
} else {
editModal.hide();
}
};
// ESC 键处理
const handleEscape = (e) => {
if (e.key === 'Escape') {
e.preventDefault();
e.stopPropagation();
if (isEditProblemDirty()) {
if (confirm('内容已修改,确定要关闭吗?')) {
modalEl.removeEventListener('keydown', handleEscape);
editModal.hide();
}
} else {
modalEl.removeEventListener('keydown', handleEscape);
editModal.hide();
}
}
};
modalEl.addEventListener('keydown', handleEscape);
editModal.show();
editModal._element.addEventListener('shown.bs.modal', function() {
if (window.problemEditor && window.problemEditor.codemirror) {
window.problemEditor.codemirror.refresh();
}
});
}
// 检测编辑问题是否有修改
function isEditProblemDirty() {
const currentName = document.getElementById('editProblemName').value;
const currentContent = window.problemEditor ? window.problemEditor.value() : document.getElementById('editProblemContent').value;
return currentName !== editProblemOriginalState.name || currentContent !== editProblemOriginalState.content;
}
async function saveProblem() {
const name = document.getElementById('editProblemName').value.trim();
const category = document.getElementById('editProblemCategory').value;
const content = window.problemEditor ? window.problemEditor.value() : document.getElementById('editProblemContent').value;
if (!name) {
alert('请填写名称');
return;
}
const response = await fetch(`/api/problems/${currentEditId}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, category, content})
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
if (window.problemEditor) {
window.problemEditor.toTextArea();
window.problemEditor = null;
}
loadProblems();
} else {
const err = await response.json();
alert(err.error || '保存失败');
}
}
// 删除
function showDeleteConfirm(problemId, problemName) {
currentDeleteId = problemId;
document.getElementById('deleteProblemName').textContent = problemName;
new bootstrap.Modal(document.getElementById('deleteModal')).show();
}
async function confirmDelete() {
const response = await fetch(`/api/problems/${currentDeleteId}`, {
method: 'DELETE'
});
if (response.ok) {
bootstrap.Modal.getInstance(document.getElementById('deleteModal')).hide();
loadProblems();
} else {
const err = await response.json();
alert(err.error || '删除失败');
}
}
</script>
{% endblock %}