feat: add course summary search
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
<button class="admin-tab is-active" data-admin-tab="accounts" type="button">课时账户</button>
|
||||
<button class="admin-tab" data-admin-tab="reviews" type="button">纠错审核</button>
|
||||
<button class="admin-tab" data-admin-tab="summaries" type="button">课程小结审核</button>
|
||||
<button class="admin-tab" data-admin-tab="summarySearch" type="button">课程小结查询</button>
|
||||
<button class="admin-tab" data-admin-tab="logs" type="button">操作记录</button>
|
||||
<button class="admin-tab" data-admin-tab="register" type="button">登记</button>
|
||||
</nav>
|
||||
@@ -168,6 +169,37 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="summarySearchPanel" class="panel admin-panel" hidden>
|
||||
<div class="section-head">
|
||||
<h2>课程小结查询</h2>
|
||||
</div>
|
||||
<form id="summarySearchForm" class="search-row summary-search-row">
|
||||
<input id="summarySearchQuery" autocomplete="off" placeholder="关键词:课堂内容、作业、问题等" />
|
||||
<input id="summarySearchStudent" autocomplete="off" placeholder="学生" />
|
||||
<input id="summarySearchTeacher" autocomplete="off" placeholder="老师" />
|
||||
<input id="summarySearchSubject" autocomplete="off" placeholder="科目" />
|
||||
<input id="summarySearchDateFrom" type="date" aria-label="开始日期" />
|
||||
<input id="summarySearchDateTo" type="date" aria-label="结束日期" />
|
||||
<button type="submit">查询</button>
|
||||
</form>
|
||||
<div id="summarySearchMeta" class="summary-grid"></div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>日期</th>
|
||||
<th>学生</th>
|
||||
<th>老师/科目</th>
|
||||
<th>标题</th>
|
||||
<th>小结正文</th>
|
||||
<th>来源</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="summarySearchRows"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="logsPanel" class="panel admin-panel" hidden>
|
||||
<div class="section-head">
|
||||
<h2>操作记录</h2>
|
||||
|
||||
@@ -4,6 +4,7 @@ const panels = {
|
||||
accounts: document.querySelector("#accountsPanel"),
|
||||
reviews: document.querySelector("#reviewsPanel"),
|
||||
summaries: document.querySelector("#summariesPanel"),
|
||||
summarySearch: document.querySelector("#summarySearchPanel"),
|
||||
logs: document.querySelector("#logsPanel"),
|
||||
register: document.querySelector("#registerPanel"),
|
||||
};
|
||||
@@ -30,6 +31,15 @@ const reviewRows = document.querySelector("#reviewRows");
|
||||
const summaryReviewStatus = document.querySelector("#summaryReviewStatus");
|
||||
const summaryReviewMeta = document.querySelector("#summaryReviewMeta");
|
||||
const summaryReviewRows = document.querySelector("#summaryReviewRows");
|
||||
const summarySearchForm = document.querySelector("#summarySearchForm");
|
||||
const summarySearchQuery = document.querySelector("#summarySearchQuery");
|
||||
const summarySearchStudent = document.querySelector("#summarySearchStudent");
|
||||
const summarySearchTeacher = document.querySelector("#summarySearchTeacher");
|
||||
const summarySearchSubject = document.querySelector("#summarySearchSubject");
|
||||
const summarySearchDateFrom = document.querySelector("#summarySearchDateFrom");
|
||||
const summarySearchDateTo = document.querySelector("#summarySearchDateTo");
|
||||
const summarySearchMeta = document.querySelector("#summarySearchMeta");
|
||||
const summarySearchRows = document.querySelector("#summarySearchRows");
|
||||
const logOperation = document.querySelector("#logOperation");
|
||||
const logStatus = document.querySelector("#logStatus");
|
||||
const logFilterForm = document.querySelector("#logFilterForm");
|
||||
@@ -112,6 +122,7 @@ function setActiveTab(tabName) {
|
||||
if (tabName === "accounts") loadAccounts();
|
||||
if (tabName === "reviews") loadReviews();
|
||||
if (tabName === "summaries") loadSummaryReviews();
|
||||
if (tabName === "summarySearch") loadSummarySearch();
|
||||
if (tabName === "logs") loadOperationLogs();
|
||||
}
|
||||
|
||||
@@ -343,6 +354,56 @@ async function loadSummaryReviews() {
|
||||
}
|
||||
}
|
||||
|
||||
function summarySearchParams() {
|
||||
const params = new URLSearchParams({ limit: "200" });
|
||||
if (summarySearchQuery.value.trim()) params.set("q", summarySearchQuery.value.trim());
|
||||
if (summarySearchStudent.value.trim()) params.set("student", summarySearchStudent.value.trim());
|
||||
if (summarySearchTeacher.value.trim()) params.set("teacher", summarySearchTeacher.value.trim());
|
||||
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);
|
||||
return params;
|
||||
}
|
||||
|
||||
function renderSummarySearchBody(item) {
|
||||
const fullId = `summary-full-${item.id}`;
|
||||
const hasFull = String(item.body || "") !== String(item.body_preview || "");
|
||||
return `<div class="summary-body">${escapeHtml(item.body_preview || "")}</div>
|
||||
<div id="${escapeHtml(fullId)}" class="summary-body summary-full" hidden>${escapeHtml(item.body || "")}</div>
|
||||
<button class="small-button summary-toggle" type="button" data-target="${escapeHtml(fullId)}" ${hasFull ? "" : "hidden"}>展开</button>`;
|
||||
}
|
||||
|
||||
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>`;
|
||||
try {
|
||||
const data = await fetchJson(`/api/admin/course-summaries?${summarySearchParams().toString()}`);
|
||||
summarySearchMeta.innerHTML = [
|
||||
metric("命中小结", `${data.count} 条`),
|
||||
metric("当前显示", `${data.returned} 条`),
|
||||
].join("");
|
||||
summarySearchRows.innerHTML = data.items
|
||||
.map((item) => `<tr>
|
||||
<td>${escapeHtml(item.date_iso || "未识别")}</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>
|
||||
</tr>`)
|
||||
.join("");
|
||||
if (!data.items.length) {
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">没有符合条件的课程小结</td></tr>`;
|
||||
}
|
||||
} catch (error) {
|
||||
summarySearchRows.innerHTML = `<tr><td colspan="6" class="empty">读取失败:${escapeHtml(error.message)}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderLogDetail(item) {
|
||||
const details = [];
|
||||
if (item.source_id) details.push(`来源:${item.source_id}`);
|
||||
@@ -457,6 +518,18 @@ summaryReviewRows.addEventListener("click", (event) => {
|
||||
if (approve) summaryReviewTask(approve.dataset.taskId, "approve");
|
||||
if (reject) summaryReviewTask(reject.dataset.taskId, "reject");
|
||||
});
|
||||
summarySearchForm.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
loadSummarySearch();
|
||||
});
|
||||
summarySearchRows.addEventListener("click", (event) => {
|
||||
const button = event.target.closest(".summary-toggle");
|
||||
if (!button) return;
|
||||
const target = document.getElementById(button.dataset.target);
|
||||
if (!target) return;
|
||||
target.hidden = !target.hidden;
|
||||
button.textContent = target.hidden ? "展开" : "收起";
|
||||
});
|
||||
logOperation.addEventListener("change", loadOperationLogs);
|
||||
logStatus.addEventListener("change", loadOperationLogs);
|
||||
logFilterForm.addEventListener("submit", (event) => {
|
||||
@@ -474,6 +547,7 @@ refreshBtn.addEventListener("click", () => {
|
||||
if (!panels.accounts.hidden) loadAccounts();
|
||||
if (!panels.reviews.hidden) loadReviews();
|
||||
if (!panels.summaries.hidden) loadSummaryReviews();
|
||||
if (!panels.summarySearch.hidden) loadSummarySearch();
|
||||
if (!panels.logs.hidden) loadOperationLogs();
|
||||
});
|
||||
|
||||
|
||||
+17
-1
@@ -210,6 +210,10 @@ h2 {
|
||||
grid-template-columns: minmax(0, 1fr) 76px;
|
||||
}
|
||||
|
||||
.summary-search-row {
|
||||
grid-template-columns: minmax(180px, 1.4fr) repeat(5, minmax(110px, 0.8fr)) 88px;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
min-width: 0;
|
||||
@@ -389,6 +393,17 @@ textarea:focus {
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.summary-full {
|
||||
max-width: min(720px, 72vw);
|
||||
margin-top: 8px;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.summary-toggle {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
@@ -789,7 +804,8 @@ td {
|
||||
|
||||
.search-row,
|
||||
.search-row.compact,
|
||||
.account-search-row {
|
||||
.account-search-row,
|
||||
.summary-search-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user