diff --git a/app/app/data.py b/app/app/data.py
index ad80098..350ca06 100644
--- a/app/app/data.py
+++ b/app/app/data.py
@@ -2408,6 +2408,7 @@ def course_summary_index_for_records(root: Path) -> dict[tuple[str, str, str, st
def query_course_summaries(
root: Path,
+ classnotes_path: Path | None = None,
q: str = "",
student: str = "",
teacher: str = "",
@@ -2438,6 +2439,18 @@ def query_course_summaries(
]
for item in matched:
item["matched_fields"] = course_summary_matched_fields(item, keyword)
+ item["matched_record"] = False
+ if classnotes_path is not None and classnotes_path.exists():
+ record_keys = {
+ course_summary_record_key(record.student, record.teacher, record.subject, record.date.replace(".", "-"), record.time)
+ for record in read_classnotes(classnotes_path)
+ }
+ for item in matched:
+ try:
+ key = course_summary_duplicate_key(item)
+ except ValueError:
+ key = ("", "", "", "", "")
+ item["matched_record"] = bool(key and all(key) and key in record_keys)
matched.sort(
key=lambda item: (
str(item.get("date_iso") or "0000-00-00"),
diff --git a/app/app/routers/admin.py b/app/app/routers/admin.py
index e800110..da0c45c 100644
--- a/app/app/routers/admin.py
+++ b/app/app/routers/admin.py
@@ -114,6 +114,7 @@ def admin_course_summaries(
try:
return query_course_summaries(
COURSE_SUMMARIES_ROOT,
+ CLASSNOTES_PATH,
q=q,
student=student,
teacher=teacher,
diff --git a/app/app/static/admin.js b/app/app/static/admin.js
index 038e557..731e86c 100644
--- a/app/app/static/admin.js
+++ b/app/app/static/admin.js
@@ -785,24 +785,24 @@ function summarySearchParams() {
}
function renderSummaryActions(item) {
+ const editButton = ``;
+ const deleteButton = ``;
if (item.time_range) {
- return `时间已完整`;
+ return `
时间已完整${editButton}${deleteButton}
`;
}
const inputId = `summary-time-${item.id}`;
return `
-
+ ${editButton}
+ ${deleteButton}
`;
}
function renderSummaryBodyEditor(item) {
const body = item.body || item.body_preview || "";
if (String(item.id) !== editingSummarySearchId) {
- return `${escapeHtml(body || "暂无正文")}
-
-
-
`;
+ return `${escapeHtml(body || "暂无正文")}
`;
}
return `
@@ -822,7 +822,8 @@ function renderSummarySearchRows(items) {
return items
.map((item) => {
const expanded = String(item.id) === expandedSummarySearchId;
- const mainRow = `
+ const unmatchedClass = item.matched_record ? "" : " unmatched-summary";
+ const mainRow = `
| ${escapeHtml(item.date_iso || "未识别")} |
${escapeHtml(item.time_range || "缺少时间")} |
${escapeHtml(item.student || "")} ${escapeHtml(item.group || "")} |
@@ -831,7 +832,7 @@ function renderSummarySearchRows(items) {
const detailRow = expanded
? `
-
+
${escapeHtml(item.title || "课程小结正文")}
diff --git a/app/app/static/styles.css b/app/app/static/styles.css
index 50123f1..a58595a 100644
--- a/app/app/static/styles.css
+++ b/app/app/static/styles.css
@@ -726,16 +726,46 @@ textarea:focus {
background: #f8fafc;
}
+.summary-search-result-row.unmatched-summary td:first-child {
+ position: relative;
+ padding-left: 18px;
+}
+
+.summary-search-result-row.unmatched-summary td:first-child::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 4px;
+ background: #f59e0b;
+}
+
.summary-search-detail-row td {
padding: 0;
background: #fbfcfd;
}
.summary-search-detail-panel {
+ position: relative;
padding: 14px 16px 16px;
border-top: 1px solid var(--line);
}
+.summary-search-detail-panel.unmatched-summary {
+ padding-left: 22px;
+}
+
+.summary-search-detail-panel.unmatched-summary::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 4px;
+ background: #f59e0b;
+}
+
.summary-search-detail-head {
display: flex;
align-items: flex-start;
@@ -770,7 +800,7 @@ textarea:focus {
.summary-search-actions {
display: flex;
align-items: center;
- justify-content: flex-end;
+ justify-content: flex-start;
flex-wrap: wrap;
gap: 10px;
margin-top: 14px;
@@ -779,7 +809,7 @@ textarea:focus {
}
.summary-search-actions .summary-time-actions {
- justify-content: flex-end;
+ justify-content: flex-start;
}
.summary-action-note {
@@ -1717,7 +1747,7 @@ td {
.summary-search-actions,
.summary-search-actions .summary-time-actions {
- justify-content: stretch;
+ justify-content: flex-start;
}
.summary-search-actions .secondary-button,
|