Update teacher profiles and record summaries
This commit is contained in:
+215
-5
@@ -2,6 +2,7 @@ const adminHealthText = document.querySelector("#adminHealthText");
|
||||
const refreshBtn = document.querySelector("#refreshBtn");
|
||||
const panels = {
|
||||
accounts: document.querySelector("#accountsPanel"),
|
||||
teachers: document.querySelector("#teachersPanel"),
|
||||
reviews: document.querySelector("#reviewsPanel"),
|
||||
summaries: document.querySelector("#summariesPanel"),
|
||||
summarySearch: document.querySelector("#summarySearchPanel"),
|
||||
@@ -19,6 +20,23 @@ const accountEditForm = document.querySelector("#accountEditForm");
|
||||
const accountEditError = document.querySelector("#accountEditError");
|
||||
const cancelAccountEditBtn = document.querySelector("#cancelAccountEditBtn");
|
||||
const newAccountBtn = document.querySelector("#newAccountBtn");
|
||||
const teacherForm = document.querySelector("#teacherForm");
|
||||
const teacherQuery = document.querySelector("#teacherQuery");
|
||||
const teacherStatus = document.querySelector("#teacherStatus");
|
||||
const teacherMeta = document.querySelector("#teacherMeta");
|
||||
const teacherRows = document.querySelector("#teacherRows");
|
||||
const teacherEditor = document.querySelector("#teacherEditor");
|
||||
const teacherEditorTitle = document.querySelector("#teacherEditorTitle");
|
||||
const teacherEditForm = document.querySelector("#teacherEditForm");
|
||||
const teacherEditError = document.querySelector("#teacherEditError");
|
||||
const cancelTeacherEditBtn = document.querySelector("#cancelTeacherEditBtn");
|
||||
const newTeacherBtn = document.querySelector("#newTeacherBtn");
|
||||
const editTeacherId = document.querySelector("#editTeacherId");
|
||||
const editTeacherName = document.querySelector("#editTeacherName");
|
||||
const editTeacherAlias = document.querySelector("#editTeacherAlias");
|
||||
const editTeacherStatus = document.querySelector("#editTeacherStatus");
|
||||
const editTeacherSubjects = document.querySelector("#editTeacherSubjects");
|
||||
const editTeacherNote = document.querySelector("#editTeacherNote");
|
||||
const editStudentId = document.querySelector("#editStudentId");
|
||||
const editStudent = document.querySelector("#editStudent");
|
||||
const editRemaining = document.querySelector("#editRemaining");
|
||||
@@ -50,6 +68,7 @@ const summarySearchTeacher = document.querySelector("#summarySearchTeacher");
|
||||
const summarySearchSubject = document.querySelector("#summarySearchSubject");
|
||||
const summarySearchDateFrom = document.querySelector("#summarySearchDateFrom");
|
||||
const summarySearchDateTo = document.querySelector("#summarySearchDateTo");
|
||||
const summarySearchMissingTime = document.querySelector("#summarySearchMissingTime");
|
||||
const summarySearchMeta = document.querySelector("#summarySearchMeta");
|
||||
const summarySearchRows = document.querySelector("#summarySearchRows");
|
||||
const logOperation = document.querySelector("#logOperation");
|
||||
@@ -71,6 +90,8 @@ const summaryRegisterStatus = document.querySelector("#summaryRegisterStatus");
|
||||
|
||||
let currentAccounts = [];
|
||||
let editingAccountId = "";
|
||||
let currentTeachers = [];
|
||||
let editingTeacherId = "";
|
||||
let currentSummaryReviews = [];
|
||||
let activeSummaryReview = null;
|
||||
let summaryRegisterItemSeq = 0;
|
||||
@@ -147,6 +168,7 @@ function setActiveTab(tabName) {
|
||||
panel.hidden = name !== tabName;
|
||||
});
|
||||
if (tabName === "accounts") loadAccounts();
|
||||
if (tabName === "teachers") loadTeachers();
|
||||
if (tabName === "reviews") loadReviews();
|
||||
if (tabName === "summaries") loadSummaryReviews();
|
||||
if (tabName === "summarySearch") loadSummarySearch();
|
||||
@@ -156,7 +178,8 @@ function setActiveTab(tabName) {
|
||||
async function loadAdminHealth() {
|
||||
try {
|
||||
const data = await fetchJson("/api/account-health");
|
||||
adminHealthText.textContent = `账户 ${data.accounts_count} 人;数据更新时间 ${fmtTime(data.accounts.mtime)}`;
|
||||
const teacherText = data.teachers_count === undefined ? "" : `;老师 ${data.teachers_count} 位`;
|
||||
adminHealthText.textContent = `账户 ${data.accounts_count} 人${teacherText};数据更新时间 ${fmtTime(data.accounts.mtime)}`;
|
||||
} catch (error) {
|
||||
adminHealthText.textContent = `读取失败:${error.message}`;
|
||||
}
|
||||
@@ -291,6 +314,130 @@ async function saveAccount(event) {
|
||||
}
|
||||
}
|
||||
|
||||
function renderTeacherSummary(rows) {
|
||||
const activeCount = rows.filter((item) => item.status === "在岗").length;
|
||||
const aliasCount = rows.filter((item) => String(item.alias || "").trim()).length;
|
||||
teacherMeta.innerHTML = [
|
||||
metric("当前结果", `${rows.length} 位`),
|
||||
metric("在岗", activeCount),
|
||||
metric("已配置别名", aliasCount),
|
||||
].join("");
|
||||
}
|
||||
|
||||
function teacherMatches(row) {
|
||||
const keyword = teacherQuery.value.trim();
|
||||
if (teacherStatus.value && row.status !== teacherStatus.value) return false;
|
||||
if (!keyword) return true;
|
||||
const haystack = [
|
||||
row.teacher_id,
|
||||
row.name,
|
||||
row.alias,
|
||||
row.display_name,
|
||||
...(row.subjects || []),
|
||||
row.status,
|
||||
row.note,
|
||||
].join("\n");
|
||||
return haystack.includes(keyword);
|
||||
}
|
||||
|
||||
async function loadTeachers() {
|
||||
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">正在读取</td></tr>`;
|
||||
try {
|
||||
const data = await fetchJson("/api/admin/teachers");
|
||||
currentTeachers = data.teachers || [];
|
||||
const rows = currentTeachers.filter(teacherMatches);
|
||||
renderTeacherSummary(rows);
|
||||
teacherRows.innerHTML = rows
|
||||
.map(
|
||||
(row) => `<tr>
|
||||
<td>${escapeHtml(row.name)}<br><small>${escapeHtml(row.teacher_id)}</small></td>
|
||||
<td>${escapeHtml(row.alias || "")}</td>
|
||||
<td>${escapeHtml((row.subjects || []).join("、"))}</td>
|
||||
<td><span class="status ${row.status === "在岗" ? "normal" : "closed"}">${escapeHtml(row.status)}</span></td>
|
||||
<td class="note-cell">${escapeHtml(row.note || "")}</td>
|
||||
<td><button class="small-button teacher-edit" type="button" data-teacher-id="${escapeHtml(row.teacher_id)}">编辑</button></td>
|
||||
</tr>`,
|
||||
)
|
||||
.join("");
|
||||
if (!rows.length) {
|
||||
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">没有符合条件的老师档案</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
teacherRows.innerHTML = `<tr><td colspan="6" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
function openNewTeacherEditor() {
|
||||
editingTeacherId = "";
|
||||
teacherEditorTitle.textContent = "新增老师";
|
||||
editTeacherId.value = "";
|
||||
editTeacherId.placeholder = "保存时自动生成";
|
||||
editTeacherName.value = "";
|
||||
editTeacherAlias.value = "";
|
||||
editTeacherSubjects.value = "";
|
||||
editTeacherStatus.value = "在岗";
|
||||
editTeacherNote.value = "";
|
||||
teacherEditError.hidden = true;
|
||||
teacherEditor.hidden = false;
|
||||
editTeacherName.focus();
|
||||
}
|
||||
|
||||
function openEditTeacherEditor(teacherId) {
|
||||
const teacher = currentTeachers.find((item) => item.teacher_id === teacherId);
|
||||
if (!teacher) return;
|
||||
editingTeacherId = teacher.teacher_id;
|
||||
teacherEditorTitle.textContent = `编辑老师:${teacher.name}`;
|
||||
editTeacherId.value = teacher.teacher_id;
|
||||
editTeacherId.placeholder = "";
|
||||
editTeacherName.value = teacher.name;
|
||||
editTeacherAlias.value = teacher.alias || "";
|
||||
editTeacherSubjects.value = (teacher.subjects || []).join("、");
|
||||
editTeacherStatus.value = teacher.status;
|
||||
editTeacherNote.value = teacher.note || "";
|
||||
teacherEditError.hidden = true;
|
||||
teacherEditor.hidden = false;
|
||||
editTeacherName.focus();
|
||||
}
|
||||
|
||||
function parseTeacherSubjects(value) {
|
||||
return value
|
||||
.split(/[、,,\s]+/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function buildTeacherPayload() {
|
||||
return {
|
||||
teacher_id: editTeacherId.value.trim(),
|
||||
name: editTeacherName.value.trim(),
|
||||
alias: editTeacherAlias.value.trim(),
|
||||
subjects: parseTeacherSubjects(editTeacherSubjects.value),
|
||||
status: editTeacherStatus.value,
|
||||
note: editTeacherNote.value.trim(),
|
||||
};
|
||||
}
|
||||
|
||||
async function saveTeacher(event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const payload = buildTeacherPayload();
|
||||
const url = editingTeacherId
|
||||
? `/api/admin/teachers/${encodeURIComponent(editingTeacherId)}`
|
||||
: "/api/admin/teachers";
|
||||
const method = editingTeacherId ? "PUT" : "POST";
|
||||
await fetchJson(url, {
|
||||
method,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
teacherEditor.hidden = true;
|
||||
await loadTeachers();
|
||||
} catch (error) {
|
||||
teacherEditError.textContent = error.message;
|
||||
teacherEditError.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
function renderReviewLine(line) {
|
||||
return `<code class="line-code">${escapeHtml(line)}</code>`;
|
||||
}
|
||||
@@ -474,6 +621,7 @@ function summarySearchParams() {
|
||||
if (summarySearchSubject.value.trim()) params.set("subject", summarySearchSubject.value.trim());
|
||||
if (summarySearchDateFrom.value) params.set("date_from", summarySearchDateFrom.value);
|
||||
if (summarySearchDateTo.value) params.set("date_to", summarySearchDateTo.value);
|
||||
if (summarySearchMissingTime.value) params.set("missing_time", "true");
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -485,13 +633,25 @@ function renderSummarySearchBody(item) {
|
||||
<button class="small-button summary-toggle" type="button" data-target="${escapeHtml(fullId)}" ${hasFull ? "" : "hidden"}>展开</button>`;
|
||||
}
|
||||
|
||||
function renderSummaryActions(item) {
|
||||
if (item.time_range) {
|
||||
return `<span class="muted">已完整</span>`;
|
||||
}
|
||||
const inputId = `summary-time-${item.id}`;
|
||||
return `<div class="summary-time-actions">
|
||||
<input id="${escapeHtml(inputId)}" class="summary-time-input" data-summary-time-input="${escapeHtml(item.id)}" autocomplete="off" placeholder="08:00-10:00" />
|
||||
<button class="small-button summary-time-save" type="button" data-summary-id="${escapeHtml(item.id)}">补齐时间</button>
|
||||
<button class="small-button summary-delete" type="button" data-summary-id="${escapeHtml(item.id)}">删除</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderMatchedFields(item) {
|
||||
const fields = Array.isArray(item.matched_fields) ? item.matched_fields : [];
|
||||
return fields.length ? `<br><small>命中:${fields.map(escapeHtml).join("、")}</small>` : "";
|
||||
}
|
||||
|
||||
async function loadSummarySearch() {
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">正在读取</td></tr>`;
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">正在读取</td></tr>`;
|
||||
try {
|
||||
const data = await fetchJson(`/api/admin/course-summaries?${summarySearchParams().toString()}`);
|
||||
summarySearchMeta.innerHTML = [
|
||||
@@ -501,21 +661,38 @@ async function loadSummarySearch() {
|
||||
summarySearchRows.innerHTML = data.items
|
||||
.map((item) => `<tr>
|
||||
<td>${escapeHtml(item.date_iso || "未识别")}</td>
|
||||
<td>${escapeHtml(item.time_range || "缺少时间")}</td>
|
||||
<td>${escapeHtml(item.student || "")}<br><small>${escapeHtml(item.group || "")}</small></td>
|
||||
<td>${escapeHtml(item.teacher || "待核对老师")}<br><small>${escapeHtml(item.subject || "待核对科目")}</small></td>
|
||||
<td>${escapeHtml(item.title || "")}${renderMatchedFields(item)}</td>
|
||||
<td>${renderSummarySearchBody(item)}</td>
|
||||
<td><code class="line-code">${escapeHtml(item.relative_path || "")}</code></td>
|
||||
<td>${renderSummaryActions(item)}</td>
|
||||
</tr>`)
|
||||
.join("");
|
||||
if (!data.items.length) {
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">没有符合条件的课程小结</td></tr>`;
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">没有符合条件的课程小结</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="7" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCourseSummaryTime(summaryId, timeRange) {
|
||||
await fetchJson(`/api/admin/course-summaries/${encodeURIComponent(summaryId)}/time`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ time_range: timeRange }),
|
||||
});
|
||||
await loadSummarySearch();
|
||||
}
|
||||
|
||||
async function deleteCourseSummary(summaryId) {
|
||||
await fetchJson(`/api/admin/course-summaries/${encodeURIComponent(summaryId)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
await loadSummarySearch();
|
||||
}
|
||||
|
||||
function renderLogDetail(item) {
|
||||
const details = [];
|
||||
if (item.source_id) details.push(`来源:${item.source_id}`);
|
||||
@@ -711,6 +888,21 @@ accountRows.addEventListener("click", (event) => {
|
||||
if (!button) return;
|
||||
openEditAccountEditor(button.dataset.studentId);
|
||||
});
|
||||
teacherForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
loadTeachers();
|
||||
});
|
||||
teacherStatus.addEventListener("change", loadTeachers);
|
||||
newTeacherBtn.addEventListener("click", openNewTeacherEditor);
|
||||
cancelTeacherEditBtn.addEventListener("click", () => {
|
||||
teacherEditor.hidden = true;
|
||||
});
|
||||
teacherEditForm.addEventListener("submit", saveTeacher);
|
||||
teacherRows.addEventListener("click", (event) => {
|
||||
const button = event.target.closest(".teacher-edit");
|
||||
if (!button) return;
|
||||
openEditTeacherEditor(button.dataset.teacherId);
|
||||
});
|
||||
reviewStatus.addEventListener("change", loadReviews);
|
||||
reviewRows.addEventListener("click", (event) => {
|
||||
const approve = event.target.closest(".review-approve");
|
||||
@@ -750,8 +942,26 @@ summarySearchForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
loadSummarySearch();
|
||||
});
|
||||
summarySearchMissingTime.addEventListener("change", loadSummarySearch);
|
||||
summarySearchRows.addEventListener("click", (event) => {
|
||||
const button = event.target.closest(".summary-toggle");
|
||||
const saveTime = event.target.closest(".summary-time-save");
|
||||
const deleteSummary = event.target.closest(".summary-delete");
|
||||
if (saveTime) {
|
||||
const input = summarySearchRows.querySelector(`[data-summary-time-input='${saveTime.dataset.summaryId}']`);
|
||||
const timeRange = input ? input.value.trim() : "";
|
||||
if (!timeRange) {
|
||||
alert("请输入时间段");
|
||||
return;
|
||||
}
|
||||
updateCourseSummaryTime(saveTime.dataset.summaryId, timeRange).catch((error) => alert(error.message));
|
||||
return;
|
||||
}
|
||||
if (deleteSummary) {
|
||||
if (!confirm("确认删除这条课程小结?")) return;
|
||||
deleteCourseSummary(deleteSummary.dataset.summaryId).catch((error) => alert(error.message));
|
||||
return;
|
||||
}
|
||||
if (!button) return;
|
||||
const target = document.getElementById(button.dataset.target);
|
||||
if (!target) return;
|
||||
|
||||
Reference in New Issue
Block a user