function closeDrawer() { const el = document.getElementById("drawer"); if (el) el.classList.remove("show"); } function esc(value) { return String(value ?? "").replace(/[&<>"']/g, c => ({"&":"&","<":"<",">":">",'"':""","'":"'"}[c])); } async function openStudent(aid, sid) { const res = await fetch(`/analysis/${aid}/api/student/${encodeURIComponent(sid)}`); if (!res.ok) return; const s = await res.json(); const exams = window.HB_EXAMS || []; const rows = exams.map((exam, i) => { const score = (s.scores || {})[exam]; const computed = (s.computed_scores || {})[exam]; const rank = (s.ranks || {})[exam]; const layer = (s.layers || {})[exam]; return ` ${esc(exam)} ${score == null ? "缺考(按 0 分)" : esc(score)} ${rank ?? "—"}${layer ?? "—"} ${computed ?? "—"} `; }).join(""); const box = document.getElementById("drawer"); const body = document.getElementById("drawer-body"); body.innerHTML = `

${esc(s.name)} ${esc(s.sid)}

${esc(s.class_name)}班 · ${esc(s.group)} · ${esc(s.status || "在籍")} · ${esc(s.subjects || "")}
${s.move ? `

异动/备注:${esc(s.move)}

` : ""}
${rows}
考试原始分方向名次层次计算分
`; box.classList.add("show"); } async function openClass(aid, group, target, className) { const url = `/analysis/${aid}/api/class?group=${encodeURIComponent(group)}&target=${encodeURIComponent(target)}&class_name=${encodeURIComponent(className)}`; const res = await fetch(url); if (!res.ok) return; const data = await res.json(); const lastExam = data.last_exam || (data.exams || []).slice(-1)[0]; const rows = (data.students || []).map((s) => { const rank = (s.ranks || {})[lastExam]; const layer = (s.layers || {})[lastExam]; return ` ${esc(s.name)}${esc(s.status || "")} ${rank ?? "—"}${layer ?? "—"} `; }).join(""); const box = document.getElementById("drawer"); const body = document.getElementById("drawer-body"); body.innerHTML = `

${esc(className)}班 · ${esc(lastExam || "")}

按最后一次方向名次排序,共 ${data.n} 人。

${rows}
姓名学籍名次层次
`; box.classList.add("show"); } document.addEventListener("click", (e) => { if (e.target.id === "drawer") closeDrawer(); });