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,