fix: show full student summaries
This commit is contained in:
@@ -12,7 +12,7 @@ from ..api_utils import (
|
|||||||
payload_to_teacher,
|
payload_to_teacher,
|
||||||
read_register_payload,
|
read_register_payload,
|
||||||
)
|
)
|
||||||
from ..auth import verify_admin_auth, verify_student_profiles_auth
|
from ..auth import verify_admin_auth, verify_any_auth, verify_student_profiles_auth
|
||||||
from ..config import (
|
from ..config import (
|
||||||
ACCOUNTS_PATH,
|
ACCOUNTS_PATH,
|
||||||
ADMIN_TASKS_PATH,
|
ADMIN_TASKS_PATH,
|
||||||
@@ -176,7 +176,7 @@ def students(
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/students/{student}")
|
@router.get("/api/students/{student}")
|
||||||
def student_detail(student: str, _user: str = Depends(verify_student_profiles_auth)):
|
def student_detail(student: str, _user: str = Depends(verify_any_auth)):
|
||||||
for student_profile in load_student_profiles():
|
for student_profile in load_student_profiles():
|
||||||
if student_profile.student == student or student_profile.student_id == student:
|
if student_profile.student == student or student_profile.student_id == student:
|
||||||
return student_profile_to_dict(student_profile)
|
return student_profile_to_dict(student_profile)
|
||||||
|
|||||||
+3
-33
@@ -50,7 +50,6 @@ let currentRecordOrder = [];
|
|||||||
let correctedRecords = new Map();
|
let correctedRecords = new Map();
|
||||||
let correctedRecordOrder = [];
|
let correctedRecordOrder = [];
|
||||||
let expandedSummaryRecords = new Set();
|
let expandedSummaryRecords = new Set();
|
||||||
let expandedSummaryBodies = new Set();
|
|
||||||
let collapsedTeacherGroups = new Set();
|
let collapsedTeacherGroups = new Set();
|
||||||
let currentRecordPage = { query: "", offset: 0, limit: 0, total: 0, shown: 0, hasMore: false };
|
let currentRecordPage = { query: "", offset: 0, limit: 0, total: 0, shown: 0, hasMore: false };
|
||||||
let activeCorrectionKey = "";
|
let activeCorrectionKey = "";
|
||||||
@@ -244,16 +243,7 @@ function renderRecordRow(row, rowNumber) {
|
|||||||
</tr>`;
|
</tr>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function summaryBodyKey(recordKey, index) {
|
function renderSummaryEntry(summary) {
|
||||||
return `${recordKey}:${index}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function summaryPreviewText(value) {
|
|
||||||
const text = String(value || "暂无正文");
|
|
||||||
return text.length > 120 ? `${text.slice(0, 120)}...` : text;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSummaryEntry(summary, recordKey, index) {
|
|
||||||
const title = summary.title || "课程小结";
|
const title = summary.title || "课程小结";
|
||||||
const time = summary.time_range ? ` · ${summary.time_range}` : "";
|
const time = summary.time_range ? ` · ${summary.time_range}` : "";
|
||||||
const meta = [
|
const meta = [
|
||||||
@@ -261,15 +251,11 @@ function renderSummaryEntry(summary, recordKey, index) {
|
|||||||
summary.teacher || "",
|
summary.teacher || "",
|
||||||
summary.subject || "",
|
summary.subject || "",
|
||||||
].filter(Boolean).join(" · ");
|
].filter(Boolean).join(" · ");
|
||||||
const bodyKey = summaryBodyKey(recordKey, index);
|
|
||||||
const body = summary.body || "暂无正文";
|
const body = summary.body || "暂无正文";
|
||||||
const expanded = expandedSummaryBodies.has(bodyKey);
|
|
||||||
const canToggle = body.length > 120;
|
|
||||||
return `<div class="record-summary-item">
|
return `<div class="record-summary-item">
|
||||||
<div class="record-summary-title">${escapeHtml(title)}${escapeHtml(time)}</div>
|
<div class="record-summary-title">${escapeHtml(title)}${escapeHtml(time)}</div>
|
||||||
${meta ? `<div class="record-summary-meta">${escapeHtml(meta)}</div>` : ""}
|
${meta ? `<div class="record-summary-meta">${escapeHtml(meta)}</div>` : ""}
|
||||||
<div class="summary-body${expanded ? " summary-full" : " summary-preview"}">${escapeHtml(expanded ? body : summaryPreviewText(body))}</div>
|
<div class="summary-body summary-full">${escapeHtml(body)}</div>
|
||||||
${canToggle ? `<button class="summary-toggle" type="button" data-summary-body-key="${escapeHtml(bodyKey)}">${expanded ? "收起全文" : "展开全文"}</button>` : ""}
|
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,7 +286,7 @@ function renderGroupedRecords(records) {
|
|||||||
const summaryCount = Number(row.summary_count || 0);
|
const summaryCount = Number(row.summary_count || 0);
|
||||||
const summaryRow = summaryCount
|
const summaryRow = summaryCount
|
||||||
? `<tr class="summary-collapse-row" data-summary-row="${escapeHtml(row._recordKey)}" ${expandedSummaryRecords.has(row._recordKey) ? "" : "hidden"}>
|
? `<tr class="summary-collapse-row" data-summary-row="${escapeHtml(row._recordKey)}" ${expandedSummaryRecords.has(row._recordKey) ? "" : "hidden"}>
|
||||||
<td colspan="${RECORD_TABLE_COLUMN_COUNT}">${(row.summaries || []).map((summary, index) => renderSummaryEntry(summary, row._recordKey, index)).join("")}</td>
|
<td colspan="${RECORD_TABLE_COLUMN_COUNT}">${(row.summaries || []).map((summary) => renderSummaryEntry(summary)).join("")}</td>
|
||||||
</tr>`
|
</tr>`
|
||||||
: renderSummaryMissingRow(row._recordKey, expandedSummaryRecords.has(row._recordKey));
|
: renderSummaryMissingRow(row._recordKey, expandedSummaryRecords.has(row._recordKey));
|
||||||
return `${renderRecordRow(row, displayIndex)}${summaryRow}`;
|
return `${renderRecordRow(row, displayIndex)}${summaryRow}`;
|
||||||
@@ -341,15 +327,6 @@ function toggleTeacherGroup(teacher) {
|
|||||||
renderCurrentRecords();
|
renderCurrentRecords();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSummaryBody(key) {
|
|
||||||
if (expandedSummaryBodies.has(key)) {
|
|
||||||
expandedSummaryBodies.delete(key);
|
|
||||||
} else {
|
|
||||||
expandedSummaryBodies.add(key);
|
|
||||||
}
|
|
||||||
renderCurrentRecords();
|
|
||||||
}
|
|
||||||
|
|
||||||
function statusClass(status) {
|
function statusClass(status) {
|
||||||
if (status === "欠费") return "debt";
|
if (status === "欠费") return "debt";
|
||||||
if (status === "预警") return "warning";
|
if (status === "预警") return "warning";
|
||||||
@@ -414,7 +391,6 @@ function resetCorrections() {
|
|||||||
correctedRecordOrder = [];
|
correctedRecordOrder = [];
|
||||||
currentRecordOrder = [];
|
currentRecordOrder = [];
|
||||||
expandedSummaryRecords = new Set();
|
expandedSummaryRecords = new Set();
|
||||||
expandedSummaryBodies = new Set();
|
|
||||||
collapsedTeacherGroups = new Set();
|
collapsedTeacherGroups = new Set();
|
||||||
activeCorrectionKey = "";
|
activeCorrectionKey = "";
|
||||||
updateCorrectionToolbar();
|
updateCorrectionToolbar();
|
||||||
@@ -782,7 +758,6 @@ async function queryRecords(query, options = {}) {
|
|||||||
} else {
|
} else {
|
||||||
currentRecordOrder = [];
|
currentRecordOrder = [];
|
||||||
expandedSummaryRecords = new Set();
|
expandedSummaryRecords = new Set();
|
||||||
expandedSummaryBodies = new Set();
|
|
||||||
collapsedTeacherGroups = new Set();
|
collapsedTeacherGroups = new Set();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -853,7 +828,6 @@ recordRows.addEventListener("click", (event) => {
|
|||||||
const editButton = event.target.closest(".correction-edit");
|
const editButton = event.target.closest(".correction-edit");
|
||||||
const deleteButton = event.target.closest(".record-delete");
|
const deleteButton = event.target.closest(".record-delete");
|
||||||
const teacherToggle = event.target.closest(".teacher-group-toggle");
|
const teacherToggle = event.target.closest(".teacher-group-toggle");
|
||||||
const summaryToggle = event.target.closest(".summary-toggle");
|
|
||||||
if (summarySupplementButton) {
|
if (summarySupplementButton) {
|
||||||
openSummarySupplementDialog(summarySupplementButton.dataset.recordKey);
|
openSummarySupplementDialog(summarySupplementButton.dataset.recordKey);
|
||||||
return;
|
return;
|
||||||
@@ -870,10 +844,6 @@ recordRows.addEventListener("click", (event) => {
|
|||||||
toggleTeacherGroup(teacherToggle.dataset.teacher);
|
toggleTeacherGroup(teacherToggle.dataset.teacher);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (summaryToggle) {
|
|
||||||
toggleSummaryBody(summaryToggle.dataset.summaryBodyKey);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const row = event.target.closest(".record-row");
|
const row = event.target.closest(".record-row");
|
||||||
if (row && !event.target.closest(".record-action-cell")) toggleRecordSummary(row.dataset.recordKey);
|
if (row && !event.target.closest(".record-action-cell")) toggleRecordSummary(row.dataset.recordKey);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>新时空教务管理系统</title>
|
<title>新时空教务管理系统</title>
|
||||||
<link rel="stylesheet" href="/static/styles.css?v=20260626-record-index" />
|
<link rel="stylesheet" href="/static/styles.css?v=20260704-summary-full" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
@@ -167,6 +167,6 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/app.js?v=20260626-record-index"></script>
|
<script src="/static/app.js?v=20260704-summary-full"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -933,27 +933,6 @@ textarea:focus {
|
|||||||
border-top: 1px solid var(--line);
|
border-top: 1px solid var(--line);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-body + .summary-toggle,
|
|
||||||
.summary-full + .summary-toggle {
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-toggle {
|
|
||||||
min-height: 30px;
|
|
||||||
padding: 0 10px;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: #fff;
|
|
||||||
color: var(--accent-strong);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-toggle:hover {
|
|
||||||
border-color: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-conflict-list {
|
.summary-conflict-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
@@ -119,6 +119,38 @@ assertEqual("纠错后排序仍使用显示记录", value("currentRecordOrder.jo
|
|||||||
vm.runInContext("updateCorrectionToolbar('测试提交按钮')", context);
|
vm.runInContext("updateCorrectionToolbar('测试提交按钮')", context);
|
||||||
assertEqual("提交审核按钮文案", value("submitCorrectionsBtn.textContent"), "提交审核 1 条");
|
assertEqual("提交审核按钮文案", value("submitCorrectionsBtn.textContent"), "提交审核 1 条");
|
||||||
|
|
||||||
|
vm.runInContext(
|
||||||
|
`
|
||||||
|
const longSummaryBody = "完整课程小结内容".repeat(20);
|
||||||
|
currentRecords = [
|
||||||
|
{
|
||||||
|
date: "2026.06.14",
|
||||||
|
weekday: "星期日",
|
||||||
|
time: "10:00-11:00",
|
||||||
|
student: "甲",
|
||||||
|
duration: "1小时0分",
|
||||||
|
duration_hours: 1,
|
||||||
|
teacher: "王老师",
|
||||||
|
subject: "英语",
|
||||||
|
_recordIndex: 0,
|
||||||
|
_recordKey: "summary-test",
|
||||||
|
summary_count: 1,
|
||||||
|
summaries: [{ title: "课程小结", body: longSummaryBody, teacher: "王老师", subject: "英语" }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expandedSummaryRecords = new Set(["summary-test"]);
|
||||||
|
collapsedTeacherGroups = new Set();
|
||||||
|
const summaryHtml = renderGroupedRecords(currentRecords);
|
||||||
|
if (!summaryHtml.includes(longSummaryBody)) {
|
||||||
|
throw new Error("课程小结展开后应直接显示完整正文");
|
||||||
|
}
|
||||||
|
if (summaryHtml.includes("展开全文") || summaryHtml.includes("summary-toggle") || summaryHtml.includes("summary-preview")) {
|
||||||
|
throw new Error("课程小结展开后不应再渲染展开全文按钮或预览裁切");
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
|
||||||
const adminElements = new Map();
|
const adminElements = new Map();
|
||||||
function adminElement(id) {
|
function adminElement(id) {
|
||||||
if (!adminElements.has(id)) {
|
if (!adminElements.has(id)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user