feat: improve summary review layout
This commit is contained in:
+117
-12
@@ -31,6 +31,18 @@ const reviewRows = document.querySelector("#reviewRows");
|
||||
const summaryReviewStatus = document.querySelector("#summaryReviewStatus");
|
||||
const summaryReviewMeta = document.querySelector("#summaryReviewMeta");
|
||||
const summaryReviewRows = document.querySelector("#summaryReviewRows");
|
||||
const summaryReviewDrawerBackdrop = document.querySelector("#summaryReviewDrawerBackdrop");
|
||||
const summaryReviewDrawerClose = document.querySelector("#summaryReviewDrawerClose");
|
||||
const summaryReviewDrawerTitle = document.querySelector("#summaryReviewDrawerTitle");
|
||||
const summaryReviewDrawerMeta = document.querySelector("#summaryReviewDrawerMeta");
|
||||
const summaryReviewDrawerStatus = document.querySelector("#summaryReviewDrawerStatus");
|
||||
const summaryReviewDrawerCourse = document.querySelector("#summaryReviewDrawerCourse");
|
||||
const summaryReviewDrawerLine = document.querySelector("#summaryReviewDrawerLine");
|
||||
const summaryReviewDrawerReasons = document.querySelector("#summaryReviewDrawerReasons");
|
||||
const summaryReviewDrawerBody = document.querySelector("#summaryReviewDrawerBody");
|
||||
const summaryReviewDrawerSource = document.querySelector("#summaryReviewDrawerSource");
|
||||
const summaryReviewDrawerApprove = document.querySelector("#summaryReviewDrawerApprove");
|
||||
const summaryReviewDrawerReject = document.querySelector("#summaryReviewDrawerReject");
|
||||
const summarySearchForm = document.querySelector("#summarySearchForm");
|
||||
const summarySearchQuery = document.querySelector("#summarySearchQuery");
|
||||
const summarySearchStudent = document.querySelector("#summarySearchStudent");
|
||||
@@ -55,6 +67,8 @@ const paymentRegisterStatus = document.querySelector("#paymentRegisterStatus");
|
||||
|
||||
let currentAccounts = [];
|
||||
let editingAccountId = "";
|
||||
let currentSummaryReviews = [];
|
||||
let activeSummaryReview = null;
|
||||
|
||||
function fmtHours(value) {
|
||||
return Number(value || 0).toLocaleString("zh-CN", { maximumFractionDigits: 2 });
|
||||
@@ -301,7 +315,21 @@ async function loadReviews() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderSummaryInfo(summary) {
|
||||
function summaryReviewKind(item) {
|
||||
const sourceId = String(item.source_id || "");
|
||||
const reasons = Array.isArray(item.reasons) ? item.reasons.join(" ") : "";
|
||||
if (sourceId.startsWith("history-missing:") || reasons.includes("历史 classnotes缺失")) {
|
||||
return "历史缺失";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function renderReviewTag(label) {
|
||||
if (!label) return "";
|
||||
return `<span class="review-tag history">${escapeHtml(label)}</span>`;
|
||||
}
|
||||
|
||||
function renderSummaryInfo(summary, item = {}) {
|
||||
const parts = [
|
||||
summary.student,
|
||||
summary.date_iso,
|
||||
@@ -309,13 +337,65 @@ function renderSummaryInfo(summary) {
|
||||
summary.teacher,
|
||||
summary.subject,
|
||||
].filter(Boolean);
|
||||
return `${parts.map(escapeHtml).join("<br>")}<br><small>${escapeHtml(summary.group || "")}</small>`;
|
||||
return `${renderReviewTag(summaryReviewKind(item))}${parts.map(escapeHtml).join("<br>")}<br><small>${escapeHtml(summary.group || "")}</small>`;
|
||||
}
|
||||
|
||||
function renderSummaryBody(summary) {
|
||||
function renderSummaryPreview(summary) {
|
||||
const body = String(summary.body || "");
|
||||
const preview = body.length > 260 ? `${body.slice(0, 260)}...` : body;
|
||||
return `<div class="summary-body">${escapeHtml(preview)}</div>`;
|
||||
const preview = body.length > 140 ? `${body.slice(0, 140)}...` : body;
|
||||
return `<div class="summary-body summary-preview">${escapeHtml(preview || "暂无原文")}</div>`;
|
||||
}
|
||||
|
||||
function findSummaryReview(taskId) {
|
||||
return currentSummaryReviews.find((item) => String(item.id) === String(taskId));
|
||||
}
|
||||
|
||||
function canReviewSummary(item) {
|
||||
return item && (item.status === "pending" || item.status === "conflict");
|
||||
}
|
||||
|
||||
function courseInfoLines(summary) {
|
||||
return [
|
||||
summary.student ? `学生:${summary.student}` : "",
|
||||
summary.date_iso ? `日期:${summary.date_iso}` : "",
|
||||
summary.time_range ? `时间:${summary.time_range}` : "",
|
||||
summary.teacher || summary.subject ? `老师/科目:${summary.teacher || ""} ${summary.subject || ""}`.trim() : "",
|
||||
summary.group ? `班级/分组:${summary.group}` : "",
|
||||
].filter(Boolean);
|
||||
}
|
||||
|
||||
function openSummaryReviewDrawer(taskId) {
|
||||
const item = findSummaryReview(taskId);
|
||||
if (!item) return;
|
||||
const summary = item.summary || {};
|
||||
const reasons = Array.isArray(item.reasons) ? item.reasons : [];
|
||||
const kind = summaryReviewKind(item);
|
||||
const canReview = canReviewSummary(item);
|
||||
activeSummaryReview = item;
|
||||
|
||||
summaryReviewDrawerTitle.textContent = `课程小结详情 #${item.id}`;
|
||||
summaryReviewDrawerMeta.innerHTML = [
|
||||
item.created_at ? `提交时间:${escapeHtml(item.created_at)}` : "",
|
||||
kind ? renderReviewTag(kind) : "",
|
||||
].filter(Boolean).join(" ");
|
||||
summaryReviewDrawerStatus.innerHTML = `<span class="status ${taskStatusClass(item.status)}">${escapeHtml(item.status || "")}</span>${item.message ? ` <small>${escapeHtml(item.message)}</small>` : ""}`;
|
||||
summaryReviewDrawerCourse.innerHTML = courseInfoLines(summary).map(escapeHtml).join("<br>") || "暂无课程信息";
|
||||
summaryReviewDrawerLine.innerHTML = item.proposed_line ? renderReviewLine(item.proposed_line) : "<span class=\"muted\">暂无</span>";
|
||||
summaryReviewDrawerReasons.innerHTML = reasons.length ? reasons.map(escapeHtml).join("<br>") : "待人工复核";
|
||||
summaryReviewDrawerBody.textContent = summary.body || "暂无原文";
|
||||
summaryReviewDrawerSource.innerHTML = [
|
||||
item.source_id ? `来源ID:${escapeHtml(item.source_id)}` : "",
|
||||
item.saved_path ? `保存文件:${escapeHtml(item.saved_path)}` : "",
|
||||
item.batch_id ? `批次:${escapeHtml(item.batch_id)}` : "",
|
||||
].filter(Boolean).join("<br>") || "暂无来源信息";
|
||||
summaryReviewDrawerApprove.disabled = !canReview;
|
||||
summaryReviewDrawerReject.disabled = !canReview;
|
||||
summaryReviewDrawerBackdrop.hidden = false;
|
||||
}
|
||||
|
||||
function closeSummaryReviewDrawer() {
|
||||
activeSummaryReview = null;
|
||||
summaryReviewDrawerBackdrop.hidden = true;
|
||||
}
|
||||
|
||||
async function loadSummaryReviews() {
|
||||
@@ -324,21 +404,23 @@ async function loadSummaryReviews() {
|
||||
if (summaryReviewStatus.value) params.set("status", summaryReviewStatus.value);
|
||||
try {
|
||||
const data = await fetchJson(`/api/admin/tasks?${params.toString()}`);
|
||||
currentSummaryReviews = data.items || [];
|
||||
summaryReviewMeta.innerHTML = [metric("当前结果", `${data.count} 条`)].join("");
|
||||
summaryReviewRows.innerHTML = data.items
|
||||
summaryReviewRows.innerHTML = currentSummaryReviews
|
||||
.map((item) => {
|
||||
const canReview = item.status === "pending" || item.status === "conflict";
|
||||
const canReview = canReviewSummary(item);
|
||||
const summary = item.summary || {};
|
||||
const reasons = Array.isArray(item.reasons) ? item.reasons : [];
|
||||
return `<tr>
|
||||
<td>#${escapeHtml(item.id)}</td>
|
||||
<td><span class="status ${taskStatusClass(item.status)}">${escapeHtml(item.status)}</span>${item.message ? `<br><small>${escapeHtml(item.message)}</small>` : ""}</td>
|
||||
<td>${renderSummaryInfo(summary)}</td>
|
||||
<td>${renderSummaryInfo(summary, item)}</td>
|
||||
<td>${item.proposed_line ? renderReviewLine(item.proposed_line) : "<span class=\"muted\">暂无</span>"}</td>
|
||||
<td>${renderSummaryBody(summary)}</td>
|
||||
<td>${renderSummaryPreview(summary)}</td>
|
||||
<td>${reasons.map(escapeHtml).join("<br>") || "待人工复核"}</td>
|
||||
<td class="record-action-cell">
|
||||
<div class="record-actions">
|
||||
<button class="small-button summary-detail" type="button" data-task-id="${escapeHtml(item.id)}">查看详情</button>
|
||||
<button class="small-button summary-approve" type="button" data-task-id="${escapeHtml(item.id)}" ${canReview ? "" : "disabled"}>批准</button>
|
||||
<button class="small-button summary-reject" type="button" data-task-id="${escapeHtml(item.id)}" ${canReview ? "" : "disabled"}>驳回</button>
|
||||
</div>
|
||||
@@ -346,10 +428,11 @@ async function loadSummaryReviews() {
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
if (!data.items.length) {
|
||||
if (!currentSummaryReviews.length) {
|
||||
summaryReviewRows.innerHTML = `<tr><td colspan="7" class="empty">没有符合条件的课程小结审核项</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
currentSummaryReviews = [];
|
||||
summaryReviewRows.innerHTML = `<tr><td colspan="7" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
|
||||
}
|
||||
}
|
||||
@@ -457,7 +540,8 @@ async function reviewTask(taskId, action) {
|
||||
}
|
||||
|
||||
async function summaryReviewTask(taskId, action) {
|
||||
runTaskAction(taskId, action, loadSummaryReviews);
|
||||
await runTaskAction(taskId, action, loadSummaryReviews);
|
||||
closeSummaryReviewDrawer();
|
||||
}
|
||||
|
||||
function linesFromTextarea(textarea) {
|
||||
@@ -513,11 +597,32 @@ reviewRows.addEventListener("click", (event) => {
|
||||
});
|
||||
summaryReviewStatus.addEventListener("change", loadSummaryReviews);
|
||||
summaryReviewRows.addEventListener("click", (event) => {
|
||||
const detail = event.target.closest(".summary-detail");
|
||||
const approve = event.target.closest(".summary-approve");
|
||||
const reject = event.target.closest(".summary-reject");
|
||||
if (approve) summaryReviewTask(approve.dataset.taskId, "approve");
|
||||
if (detail) {
|
||||
openSummaryReviewDrawer(detail.dataset.taskId);
|
||||
return;
|
||||
}
|
||||
if (approve) {
|
||||
summaryReviewTask(approve.dataset.taskId, "approve");
|
||||
return;
|
||||
}
|
||||
if (reject) summaryReviewTask(reject.dataset.taskId, "reject");
|
||||
});
|
||||
summaryReviewDrawerClose.addEventListener("click", closeSummaryReviewDrawer);
|
||||
summaryReviewDrawerBackdrop.addEventListener("click", (event) => {
|
||||
if (event.target === summaryReviewDrawerBackdrop) closeSummaryReviewDrawer();
|
||||
});
|
||||
summaryReviewDrawerApprove.addEventListener("click", () => {
|
||||
if (activeSummaryReview) summaryReviewTask(activeSummaryReview.id, "approve");
|
||||
});
|
||||
summaryReviewDrawerReject.addEventListener("click", () => {
|
||||
if (activeSummaryReview) summaryReviewTask(activeSummaryReview.id, "reject");
|
||||
});
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && !summaryReviewDrawerBackdrop.hidden) closeSummaryReviewDrawer();
|
||||
});
|
||||
summarySearchForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
loadSummarySearch();
|
||||
|
||||
Reference in New Issue
Block a user