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:
@@ -389,12 +389,49 @@ const problemList = {{ problem_list | tojson }};
|
||||
const severityLevels = {{ severity_levels | tojson }};
|
||||
const practiceTimeOptions = {{ practice_time_options | tojson }};
|
||||
|
||||
// 学员列表筛选状态管理
|
||||
const STUDENT_FILTER_KEY = 'index_student_filters';
|
||||
|
||||
function saveStudentFilterState() {
|
||||
const state = {
|
||||
classId: document.getElementById('classFilter').value,
|
||||
name: document.getElementById('nameFilter').value,
|
||||
mineActive: document.getElementById('mineStudentFilterBtn').classList.contains('active')
|
||||
};
|
||||
sessionStorage.setItem(STUDENT_FILTER_KEY, JSON.stringify(state));
|
||||
}
|
||||
|
||||
function restoreStudentFilterState() {
|
||||
const saved = sessionStorage.getItem(STUDENT_FILTER_KEY);
|
||||
if (!saved) return;
|
||||
try {
|
||||
const state = JSON.parse(saved);
|
||||
if (state.classId) document.getElementById('classFilter').value = state.classId;
|
||||
if (state.name) document.getElementById('nameFilter').value = state.name;
|
||||
const btn = document.getElementById('mineStudentFilterBtn');
|
||||
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');
|
||||
}
|
||||
}
|
||||
saveStudentFilterState();
|
||||
} catch (e) {
|
||||
console.error('恢复学员筛选状态失败', e);
|
||||
}
|
||||
}
|
||||
|
||||
// 页面初始化(base.html 统一登录检查后调用)
|
||||
window.pageInit = function(data) {
|
||||
loadAiTemplates();
|
||||
loadReportTemplates();
|
||||
loadClassFilter();
|
||||
loadStudents();
|
||||
loadClassFilter().then(() => {
|
||||
restoreStudentFilterState();
|
||||
loadStudents();
|
||||
});
|
||||
initProblemCheckboxes();
|
||||
|
||||
// 检查 URL 参数,自动打开学员详情
|
||||
@@ -481,6 +518,8 @@ function importStudents(input) {
|
||||
|
||||
// 加载学员列表
|
||||
async function loadStudents() {
|
||||
saveStudentFilterState();
|
||||
|
||||
const classId = document.getElementById('classFilter').value;
|
||||
const name = document.getElementById('nameFilter').value;
|
||||
const mineFilter = document.getElementById('mineStudentFilterBtn').classList.contains('active');
|
||||
@@ -552,10 +591,17 @@ function renderStudentList(students) {
|
||||
} else {
|
||||
problemText = s.problem_names.join('、');
|
||||
}
|
||||
} else {
|
||||
} else if (s.problem_count > 0) {
|
||||
problemText = `${s.problem_count} 个问题`;
|
||||
} else {
|
||||
problemText = '<span class="text-muted">暂无问题</span>';
|
||||
}
|
||||
|
||||
// 构建方案数量显示(样式与问题一致)
|
||||
const planCount = s.plan_count > 0;
|
||||
const planBadgeText = planCount ? `${s.plan_count} 个方案` : '暂无方案';
|
||||
const planBadgeClass = planCount ? 'bg-primary' : 'bg-light text-muted';
|
||||
|
||||
html += `
|
||||
<div class="col-md-4 col-sm-6 mb-3">
|
||||
<div class="card">
|
||||
@@ -564,8 +610,8 @@ function renderStudentList(students) {
|
||||
<h5 class="card-title">${s.name}</h5>
|
||||
<p class="card-text text-muted small">${s.wechat_nickname || ''} ${s.phone ? '| ' + s.phone : ''}</p>
|
||||
<span class="badge bg-info">${s.practice_time}</span>
|
||||
<span class="badge bg-secondary">${problemText}</span>
|
||||
<span class="badge bg-primary">${s.plan_count} 个方案</span>
|
||||
<span class="badge ${s.problem_count > 0 ? 'bg-secondary' : 'bg-light text-muted'}">${problemText}</span>
|
||||
<span class="badge ${planBadgeClass}">${planBadgeText}</span>
|
||||
${s.goal_count > 0 ? `<span class="badge bg-success">${s.goal_count}个目标(${s.completed_goal_count}已达成)</span>` : ''}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
Reference in New Issue
Block a user