feat: v1.4.0 - 典型方案采纳、推荐方案列表、审计字段、导航优化
- 添加典型方案采纳功能 (POST /api/plans/<id>/adopt) - 添加推荐方案列表 (GET /api/students/<id>/recommended-plans) - PracticePlan 新增 created_by/updated_by/updated_at 审计字段 - 方案编辑/详情页导航优化 (bfcache 处理、pageshow 事件) - 方案列表支持删除功能 - 学员列表'暂无方案/问题'样式统一 - 更新文档:问题文件已废弃(迁移到数据库) - 更新部署脚本和验证清单
This commit is contained in:
@@ -75,18 +75,86 @@
|
||||
<script>
|
||||
// 防抖定时器
|
||||
let debounceTimer = null;
|
||||
const STORAGE_KEY = 'plans_filters';
|
||||
|
||||
function debounceLoad() {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(loadPlans, 300);
|
||||
}
|
||||
|
||||
// 保存筛选状态到 sessionStorage
|
||||
function saveFilterState() {
|
||||
const state = {
|
||||
classId: document.getElementById('filterClass').value,
|
||||
templateId: document.getElementById('filterTemplate').value,
|
||||
isTypical: document.getElementById('filterTypical').value,
|
||||
studentName: document.getElementById('filterStudentName').value,
|
||||
problemId: document.getElementById('filterProblem').value,
|
||||
mineActive: document.getElementById('minePlansBtn')?.classList.contains('active')
|
||||
};
|
||||
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
// 恢复筛选状态
|
||||
function restoreFilterState() {
|
||||
const saved = sessionStorage.getItem(STORAGE_KEY);
|
||||
if (!saved) return;
|
||||
try {
|
||||
const state = JSON.parse(saved);
|
||||
if (state.classId) document.getElementById('filterClass').value = state.classId;
|
||||
if (state.templateId) document.getElementById('filterTemplate').value = state.templateId;
|
||||
if (state.isTypical) document.getElementById('filterTypical').value = state.isTypical;
|
||||
if (state.studentName) document.getElementById('filterStudentName').value = state.studentName;
|
||||
if (state.problemId) document.getElementById('filterProblem').value = state.problemId;
|
||||
|
||||
const btn = document.getElementById('minePlansBtn');
|
||||
if (btn) {
|
||||
if (state.mineActive) {
|
||||
btn.classList.add('active', 'btn-primary');
|
||||
btn.classList.remove('btn-outline-secondary');
|
||||
} else {
|
||||
btn.classList.remove('active', 'btn-primary');
|
||||
btn.classList.add('btn-outline-secondary');
|
||||
}
|
||||
}
|
||||
|
||||
// 确保状态同步保存
|
||||
saveFilterState();
|
||||
} catch (e) {
|
||||
console.error('恢复筛选状态失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面初始化
|
||||
window.pageInit = function() {
|
||||
loadFilters();
|
||||
loadPlans();
|
||||
checkAndRefresh();
|
||||
};
|
||||
|
||||
// 检查是否需要刷新(从详情页返回)
|
||||
function checkAndRefresh() {
|
||||
const needsRefresh = sessionStorage.getItem('plans_needs_refresh') === 'true';
|
||||
if (needsRefresh) {
|
||||
sessionStorage.removeItem('plans_needs_refresh');
|
||||
loadFilters().then(() => {
|
||||
restoreFilterState();
|
||||
loadPlans(true);
|
||||
});
|
||||
} else {
|
||||
loadFilters().then(() => {
|
||||
restoreFilterState();
|
||||
loadPlans(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// pageshow 事件处理 bfcache 恢复的情况
|
||||
window.addEventListener('pageshow', function(event) {
|
||||
if (event.persisted) {
|
||||
// 页面从 bfcache 恢复,需要重新检查刷新标记
|
||||
checkAndRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
// 加载筛选器选项
|
||||
async function loadFilters() {
|
||||
// 加载班级
|
||||
@@ -107,7 +175,7 @@ async function loadFilters() {
|
||||
const problems = await resp.json();
|
||||
const problemSelect = document.getElementById('filterProblem');
|
||||
problems.forEach(p => {
|
||||
problemSelect.innerHTML += `<option value="${p.id}">${p.name}</option>`;
|
||||
problemSelect.innerHTML += `<option value="${p.id}">${p.no} - ${p.name}</option>`;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('加载问题失败', e);
|
||||
@@ -128,6 +196,9 @@ async function loadFilters() {
|
||||
|
||||
// 加载方案列表
|
||||
async function loadPlans() {
|
||||
// 保存当前筛选状态
|
||||
saveFilterState();
|
||||
|
||||
const container = document.getElementById('plansContainer');
|
||||
container.innerHTML = '<div class="text-center text-muted py-5"><i class="bi bi-hourglass fs-4"></i><p class="mt-2">加载中...</p></div>';
|
||||
|
||||
@@ -199,6 +270,7 @@ async function loadPlans() {
|
||||
<td class="text-muted small">${p.created_at || ''}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" onclick="viewPlan(${p.id})">查看</button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deletePlan(${p.id})">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@@ -224,6 +296,7 @@ function clearFilters() {
|
||||
mineBtn.classList.remove('active', 'btn-primary');
|
||||
mineBtn.classList.add('btn-outline-secondary');
|
||||
}
|
||||
sessionStorage.removeItem(STORAGE_KEY);
|
||||
loadPlans();
|
||||
}
|
||||
|
||||
@@ -239,6 +312,7 @@ function toggleMinePlans() {
|
||||
btn.classList.remove('btn-primary');
|
||||
btn.classList.add('btn-outline-secondary');
|
||||
}
|
||||
saveFilterState();
|
||||
loadPlans();
|
||||
}
|
||||
|
||||
@@ -246,5 +320,21 @@ function toggleMinePlans() {
|
||||
function viewPlan(planId) {
|
||||
window.location.href = `/plan/${planId}`;
|
||||
}
|
||||
|
||||
// 删除方案
|
||||
async function deletePlan(planId) {
|
||||
if (!confirm('确定删除该方案?删除后无法恢复。')) return;
|
||||
try {
|
||||
const resp = await fetch(`/api/plans/${planId}`, { method: 'DELETE' });
|
||||
if (resp.ok) {
|
||||
loadPlans(); // 刷新列表
|
||||
} else {
|
||||
const err = await resp.json();
|
||||
alert('删除失败: ' + (err.error || '未知错误'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('删除失败: ' + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user