完善课程小结审核操作
This commit is contained in:
@@ -1585,6 +1585,8 @@ OPERATION_LABELS = {
|
|||||||
"admin_course_summary_delete": "课程小结删除",
|
"admin_course_summary_delete": "课程小结删除",
|
||||||
"admin_course_summary_duplicate_scan": "重复小结扫描",
|
"admin_course_summary_duplicate_scan": "重复小结扫描",
|
||||||
"admin_course_summary_duplicate_delete": "重复小结删除",
|
"admin_course_summary_duplicate_delete": "重复小结删除",
|
||||||
|
"admin_course_summary_review_update": "课程小结审核修正",
|
||||||
|
"admin_course_summary_link_existing": "课程小结关联已有记录",
|
||||||
"register-class-records": "登记上课记录",
|
"register-class-records": "登记上课记录",
|
||||||
"register-payments": "登记缴费记录",
|
"register-payments": "登记缴费记录",
|
||||||
"admin-create-account": "新增课时账户",
|
"admin-create-account": "新增课时账户",
|
||||||
@@ -2553,6 +2555,80 @@ def create_incomplete_course_summary_review_task(
|
|||||||
return create_course_summary_review_task(tasks_path, summary, "", reasons)
|
return create_course_summary_review_task(tasks_path, summary, "", reasons)
|
||||||
|
|
||||||
|
|
||||||
|
def editable_course_summary_payload(summary: dict, updates: dict) -> dict:
|
||||||
|
merged = dict(summary)
|
||||||
|
for key in ("student", "date_iso", "time_range", "teacher", "subject"):
|
||||||
|
if key in updates:
|
||||||
|
merged[key] = str(updates.get(key) or "").strip()
|
||||||
|
normalized = normalize_course_summary(merged)
|
||||||
|
normalized["source_id"] = str(summary.get("source_id") or normalized.get("source_id") or "")
|
||||||
|
normalized["group"] = str(summary.get("group") or "")
|
||||||
|
normalized["sender"] = str(summary.get("sender") or "")
|
||||||
|
normalized["sender_id"] = str(summary.get("sender_id") or "")
|
||||||
|
normalized["message_time"] = str(summary.get("message_time") or "")
|
||||||
|
normalized["message_date"] = str(summary.get("message_date") or normalized.get("message_date") or "")
|
||||||
|
normalized["db"] = str(summary.get("db") or "")
|
||||||
|
normalized["local_id"] = str(summary.get("local_id") or "")
|
||||||
|
normalized["title"] = str(summary.get("title") or "")
|
||||||
|
normalized["body"] = str(summary.get("body") or normalized.get("body") or "")
|
||||||
|
normalized["recognition_source"] = str(summary.get("recognition_source") or "")
|
||||||
|
normalized["confidence"] = str(summary.get("confidence") or "")
|
||||||
|
normalized["teacher_trusted"] = True
|
||||||
|
normalized["remark"] = str(summary.get("remark") or "")
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def update_course_summary_review_task(
|
||||||
|
tasks_path: Path,
|
||||||
|
classnotes_path: Path,
|
||||||
|
accounts_path: Path,
|
||||||
|
task_id: int,
|
||||||
|
updates: dict,
|
||||||
|
) -> dict:
|
||||||
|
tasks = read_admin_tasks(tasks_path)
|
||||||
|
task = find_admin_task(tasks, task_id)
|
||||||
|
if task.get("type") != "course_summary_review":
|
||||||
|
raise ValueError("该任务不是课程小结审核")
|
||||||
|
if task.get("status") not in {"pending", "conflict"}:
|
||||||
|
raise ValueError("该任务已处理,不能修改")
|
||||||
|
summary = task.get("summary") or {}
|
||||||
|
updated_summary = editable_course_summary_payload(summary, updates)
|
||||||
|
reasons, proposed_line = auto_register_reasons(updated_summary, classnotes_path, accounts_path)
|
||||||
|
now = datetime.now().isoformat(timespec="seconds")
|
||||||
|
task["status"] = "pending"
|
||||||
|
task["updated_at"] = now
|
||||||
|
task.pop("message", None)
|
||||||
|
task["student"] = updated_summary["student"]
|
||||||
|
task["summary"] = updated_summary
|
||||||
|
task["proposed_line"] = proposed_line
|
||||||
|
task["reasons"] = reasons
|
||||||
|
write_admin_tasks(tasks_path, tasks)
|
||||||
|
return {"task": task_to_dict(task)}
|
||||||
|
|
||||||
|
|
||||||
|
def link_existing_course_summary_task(tasks_path: Path, classnotes_path: Path, task_id: int) -> dict:
|
||||||
|
tasks = read_admin_tasks(tasks_path)
|
||||||
|
task = find_admin_task(tasks, task_id)
|
||||||
|
if task.get("type") != "course_summary_review":
|
||||||
|
raise ValueError("该任务不是课程小结审核")
|
||||||
|
if task.get("status") not in {"pending", "conflict"}:
|
||||||
|
raise ValueError("该任务已处理,不能重复操作")
|
||||||
|
proposed_line = str(task.get("proposed_line") or "").strip()
|
||||||
|
if not proposed_line:
|
||||||
|
raise ValueError("课程小结缺少候选登记行,不能关联已有记录")
|
||||||
|
existing_lines = {raw.strip() for raw in classnotes_path.read_text(encoding="utf-8").splitlines()}
|
||||||
|
if proposed_line not in existing_lines:
|
||||||
|
raise ValueError("候选登记行在上课记录中不存在,不能关联已有记录")
|
||||||
|
now = datetime.now().isoformat(timespec="seconds")
|
||||||
|
task["status"] = "approved"
|
||||||
|
task["updated_at"] = now
|
||||||
|
task["reviewed_at"] = now
|
||||||
|
task["linked_record_line"] = proposed_line
|
||||||
|
task["message"] = "已关联已有上课记录,未重复扣课时"
|
||||||
|
write_admin_tasks(tasks_path, tasks)
|
||||||
|
return {"task": task_to_dict(task), "linked_record_line": proposed_line}
|
||||||
|
|
||||||
|
|
||||||
def approve_course_summary_task(
|
def approve_course_summary_task(
|
||||||
tasks_path: Path,
|
tasks_path: Path,
|
||||||
classnotes_path: Path,
|
classnotes_path: Path,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from ..data import (
|
|||||||
approve_admin_task,
|
approve_admin_task,
|
||||||
create_course_summary_duplicate_review_tasks,
|
create_course_summary_duplicate_review_tasks,
|
||||||
delete_course_summary,
|
delete_course_summary,
|
||||||
|
link_existing_course_summary_task,
|
||||||
list_admin_tasks,
|
list_admin_tasks,
|
||||||
list_operation_logs,
|
list_operation_logs,
|
||||||
migrate_operation_log_labels,
|
migrate_operation_log_labels,
|
||||||
@@ -25,6 +26,7 @@ from ..data import (
|
|||||||
reject_admin_task,
|
reject_admin_task,
|
||||||
resolve_duplicate_course_summary_task,
|
resolve_duplicate_course_summary_task,
|
||||||
rollback_operation_log,
|
rollback_operation_log,
|
||||||
|
update_course_summary_review_task,
|
||||||
update_course_summary_time,
|
update_course_summary_time,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,6 +170,54 @@ def admin_resolve_duplicate_summary(task_id: int, payload: dict, _user: str = De
|
|||||||
return {"ok": True, **result}
|
return {"ok": True, **result}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/admin/tasks/{task_id}/course-summary-review")
|
||||||
|
def admin_update_course_summary_review(task_id: int, payload: dict, _user: str = Depends(verify_admin_auth)):
|
||||||
|
try:
|
||||||
|
with write_lock:
|
||||||
|
result = update_course_summary_review_task(
|
||||||
|
ADMIN_TASKS_PATH,
|
||||||
|
CLASSNOTES_PATH,
|
||||||
|
ACCOUNTS_PATH,
|
||||||
|
task_id,
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
task = result.get("task", {})
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
"课程小结审核修正",
|
||||||
|
"已更新",
|
||||||
|
task_id=task_id,
|
||||||
|
task_type=str(task.get("type") or ""),
|
||||||
|
student=str(task.get("student") or ""),
|
||||||
|
source_id=str(task.get("source_id") or ""),
|
||||||
|
proposed_line=str(task.get("proposed_line") or ""),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||||
|
return {"ok": True, **result}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/admin/tasks/{task_id}/link-existing-course-summary")
|
||||||
|
def admin_link_existing_course_summary(task_id: int, _user: str = Depends(verify_admin_auth)):
|
||||||
|
try:
|
||||||
|
with write_lock:
|
||||||
|
result = link_existing_course_summary_task(ADMIN_TASKS_PATH, CLASSNOTES_PATH, task_id)
|
||||||
|
task = result.get("task", {})
|
||||||
|
append_operation_log(
|
||||||
|
OPERATION_LOGS_PATH,
|
||||||
|
"课程小结关联已有记录",
|
||||||
|
"已批准",
|
||||||
|
task_id=task_id,
|
||||||
|
task_type=str(task.get("type") or ""),
|
||||||
|
student=str(task.get("student") or ""),
|
||||||
|
source_id=str(task.get("source_id") or ""),
|
||||||
|
proposed_line=str(result.get("linked_record_line") or ""),
|
||||||
|
)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||||
|
return {"ok": True, **result}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/api/admin/tasks/{task_id}/reject")
|
@router.post("/api/admin/tasks/{task_id}/reject")
|
||||||
def admin_reject_task(task_id: int, _user: str = Depends(verify_admin_auth)):
|
def admin_reject_task(task_id: int, _user: str = Depends(verify_admin_auth)):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -267,6 +267,31 @@
|
|||||||
<div class="drawer-label">课程信息</div>
|
<div class="drawer-label">课程信息</div>
|
||||||
<div id="summaryReviewDrawerCourse" class="drawer-text"></div>
|
<div id="summaryReviewDrawerCourse" class="drawer-text"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="summaryReviewEditSection" class="drawer-section" hidden>
|
||||||
|
<div class="drawer-label">修正信息</div>
|
||||||
|
<div class="form-grid compact-grid">
|
||||||
|
<label>
|
||||||
|
学生
|
||||||
|
<input id="summaryReviewEditStudent" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
日期
|
||||||
|
<input id="summaryReviewEditDate" type="date" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
时间
|
||||||
|
<input id="summaryReviewEditTime" autocomplete="off" placeholder="08:00-10:00" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
老师
|
||||||
|
<input id="summaryReviewEditTeacher" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
科目
|
||||||
|
<input id="summaryReviewEditSubject" autocomplete="off" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="drawer-section">
|
<div class="drawer-section">
|
||||||
<div class="drawer-label">建议登记行</div>
|
<div class="drawer-label">建议登记行</div>
|
||||||
<div id="summaryReviewDrawerLine"></div>
|
<div id="summaryReviewDrawerLine"></div>
|
||||||
@@ -285,6 +310,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="drawer-actions">
|
<div class="drawer-actions">
|
||||||
|
<button id="summaryReviewDrawerSave" class="secondary-button" type="button">保存修正</button>
|
||||||
|
<button id="summaryReviewDrawerLink" class="secondary-button" type="button">关联已有记录</button>
|
||||||
<button id="summaryReviewDrawerReject" class="secondary-button" type="button">驳回</button>
|
<button id="summaryReviewDrawerReject" class="secondary-button" type="button">驳回</button>
|
||||||
<button id="summaryReviewDrawerApprove" type="button">批准</button>
|
<button id="summaryReviewDrawerApprove" type="button">批准</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -341,6 +368,8 @@
|
|||||||
<option value="修改老师档案">修改老师档案</option>
|
<option value="修改老师档案">修改老师档案</option>
|
||||||
<option value="课程小结接收">课程小结接收</option>
|
<option value="课程小结接收">课程小结接收</option>
|
||||||
<option value="课程小结登记">课程小结登记</option>
|
<option value="课程小结登记">课程小结登记</option>
|
||||||
|
<option value="课程小结审核修正">课程小结审核修正</option>
|
||||||
|
<option value="课程小结关联已有记录">课程小结关联已有记录</option>
|
||||||
<option value="重复小结扫描">重复小结扫描</option>
|
<option value="重复小结扫描">重复小结扫描</option>
|
||||||
<option value="重复小结删除">重复小结删除</option>
|
<option value="重复小结删除">重复小结删除</option>
|
||||||
<option value="审核批准">审核批准</option>
|
<option value="审核批准">审核批准</option>
|
||||||
|
|||||||
@@ -56,10 +56,18 @@ const summaryReviewDrawerTitle = document.querySelector("#summaryReviewDrawerTit
|
|||||||
const summaryReviewDrawerMeta = document.querySelector("#summaryReviewDrawerMeta");
|
const summaryReviewDrawerMeta = document.querySelector("#summaryReviewDrawerMeta");
|
||||||
const summaryReviewDrawerStatus = document.querySelector("#summaryReviewDrawerStatus");
|
const summaryReviewDrawerStatus = document.querySelector("#summaryReviewDrawerStatus");
|
||||||
const summaryReviewDrawerCourse = document.querySelector("#summaryReviewDrawerCourse");
|
const summaryReviewDrawerCourse = document.querySelector("#summaryReviewDrawerCourse");
|
||||||
|
const summaryReviewEditSection = document.querySelector("#summaryReviewEditSection");
|
||||||
|
const summaryReviewEditStudent = document.querySelector("#summaryReviewEditStudent");
|
||||||
|
const summaryReviewEditDate = document.querySelector("#summaryReviewEditDate");
|
||||||
|
const summaryReviewEditTime = document.querySelector("#summaryReviewEditTime");
|
||||||
|
const summaryReviewEditTeacher = document.querySelector("#summaryReviewEditTeacher");
|
||||||
|
const summaryReviewEditSubject = document.querySelector("#summaryReviewEditSubject");
|
||||||
const summaryReviewDrawerLine = document.querySelector("#summaryReviewDrawerLine");
|
const summaryReviewDrawerLine = document.querySelector("#summaryReviewDrawerLine");
|
||||||
const summaryReviewDrawerReasons = document.querySelector("#summaryReviewDrawerReasons");
|
const summaryReviewDrawerReasons = document.querySelector("#summaryReviewDrawerReasons");
|
||||||
const summaryReviewDrawerBody = document.querySelector("#summaryReviewDrawerBody");
|
const summaryReviewDrawerBody = document.querySelector("#summaryReviewDrawerBody");
|
||||||
const summaryReviewDrawerSource = document.querySelector("#summaryReviewDrawerSource");
|
const summaryReviewDrawerSource = document.querySelector("#summaryReviewDrawerSource");
|
||||||
|
const summaryReviewDrawerSave = document.querySelector("#summaryReviewDrawerSave");
|
||||||
|
const summaryReviewDrawerLink = document.querySelector("#summaryReviewDrawerLink");
|
||||||
const summaryReviewDrawerApprove = document.querySelector("#summaryReviewDrawerApprove");
|
const summaryReviewDrawerApprove = document.querySelector("#summaryReviewDrawerApprove");
|
||||||
const summaryReviewDrawerReject = document.querySelector("#summaryReviewDrawerReject");
|
const summaryReviewDrawerReject = document.querySelector("#summaryReviewDrawerReject");
|
||||||
const summarySearchForm = document.querySelector("#summarySearchForm");
|
const summarySearchForm = document.querySelector("#summarySearchForm");
|
||||||
@@ -574,6 +582,11 @@ function canReviewSummary(item) {
|
|||||||
return item && (item.status === "pending" || item.status === "conflict");
|
return item && (item.status === "pending" || item.status === "conflict");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canLinkExistingSummary(item) {
|
||||||
|
const reasons = Array.isArray(item && item.reasons) ? item.reasons : [];
|
||||||
|
return Boolean(item && item.proposed_line && reasons.includes("classnotes 已存在同一条上课记录"));
|
||||||
|
}
|
||||||
|
|
||||||
function courseInfoLines(summary) {
|
function courseInfoLines(summary) {
|
||||||
return [
|
return [
|
||||||
summary.student ? `学生:${summary.student}` : "",
|
summary.student ? `学生:${summary.student}` : "",
|
||||||
@@ -584,6 +597,24 @@ function courseInfoLines(summary) {
|
|||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fillSummaryReviewEditor(summary) {
|
||||||
|
summaryReviewEditStudent.value = summary.student || "";
|
||||||
|
summaryReviewEditDate.value = summary.date_iso || "";
|
||||||
|
summaryReviewEditTime.value = summary.time_range || "";
|
||||||
|
summaryReviewEditTeacher.value = summary.teacher || "";
|
||||||
|
summaryReviewEditSubject.value = summary.subject || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function summaryReviewEditorPayload() {
|
||||||
|
return {
|
||||||
|
student: summaryReviewEditStudent.value.trim(),
|
||||||
|
date_iso: summaryReviewEditDate.value,
|
||||||
|
time_range: summaryReviewEditTime.value.trim(),
|
||||||
|
teacher: summaryReviewEditTeacher.value.trim(),
|
||||||
|
subject: summaryReviewEditSubject.value.trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function openSummaryReviewDrawer(taskId) {
|
function openSummaryReviewDrawer(taskId) {
|
||||||
const item = findSummaryReview(taskId);
|
const item = findSummaryReview(taskId);
|
||||||
if (!item) return;
|
if (!item) return;
|
||||||
@@ -601,6 +632,8 @@ function openSummaryReviewDrawer(taskId) {
|
|||||||
].filter(Boolean).join(" ");
|
].filter(Boolean).join(" ");
|
||||||
summaryReviewDrawerStatus.innerHTML = `<span class="status ${taskStatusClass(item.status)}">${escapeHtml(item.status || "")}</span>${item.message ? ` <small>${escapeHtml(item.message)}</small>` : ""}`;
|
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>") || "暂无课程信息";
|
summaryReviewDrawerCourse.innerHTML = courseInfoLines(summary).map(escapeHtml).join("<br>") || "暂无课程信息";
|
||||||
|
summaryReviewEditSection.hidden = duplicateReview;
|
||||||
|
if (!duplicateReview) fillSummaryReviewEditor(summary);
|
||||||
summaryReviewDrawerLine.innerHTML = duplicateReview
|
summaryReviewDrawerLine.innerHTML = duplicateReview
|
||||||
? "<span class=\"muted\">请选择下方重复小结删除</span>"
|
? "<span class=\"muted\">请选择下方重复小结删除</span>"
|
||||||
: item.proposed_line ? renderReviewLine(item.proposed_line) : "<span class=\"muted\">暂无</span>";
|
: item.proposed_line ? renderReviewLine(item.proposed_line) : "<span class=\"muted\">暂无</span>";
|
||||||
@@ -615,6 +648,10 @@ function openSummaryReviewDrawer(taskId) {
|
|||||||
item.saved_path ? `保存文件:${escapeHtml(item.saved_path)}` : "",
|
item.saved_path ? `保存文件:${escapeHtml(item.saved_path)}` : "",
|
||||||
item.batch_id ? `批次:${escapeHtml(item.batch_id)}` : "",
|
item.batch_id ? `批次:${escapeHtml(item.batch_id)}` : "",
|
||||||
].filter(Boolean).join("<br>") || "暂无来源信息";
|
].filter(Boolean).join("<br>") || "暂无来源信息";
|
||||||
|
summaryReviewDrawerSave.hidden = duplicateReview;
|
||||||
|
summaryReviewDrawerSave.disabled = !canReview || duplicateReview;
|
||||||
|
summaryReviewDrawerLink.hidden = duplicateReview || !canLinkExistingSummary(item);
|
||||||
|
summaryReviewDrawerLink.disabled = !canReview || duplicateReview || !canLinkExistingSummary(item);
|
||||||
summaryReviewDrawerApprove.hidden = duplicateReview;
|
summaryReviewDrawerApprove.hidden = duplicateReview;
|
||||||
summaryReviewDrawerApprove.disabled = !canReview || duplicateReview;
|
summaryReviewDrawerApprove.disabled = !canReview || duplicateReview;
|
||||||
summaryReviewDrawerReject.disabled = !canReview;
|
summaryReviewDrawerReject.disabled = !canReview;
|
||||||
@@ -623,6 +660,9 @@ function openSummaryReviewDrawer(taskId) {
|
|||||||
|
|
||||||
function closeSummaryReviewDrawer() {
|
function closeSummaryReviewDrawer() {
|
||||||
activeSummaryReview = null;
|
activeSummaryReview = null;
|
||||||
|
summaryReviewEditSection.hidden = true;
|
||||||
|
summaryReviewDrawerSave.hidden = false;
|
||||||
|
summaryReviewDrawerLink.hidden = true;
|
||||||
summaryReviewDrawerApprove.hidden = false;
|
summaryReviewDrawerApprove.hidden = false;
|
||||||
summaryReviewDrawerBackdrop.hidden = true;
|
summaryReviewDrawerBackdrop.hidden = true;
|
||||||
}
|
}
|
||||||
@@ -754,6 +794,38 @@ async function deleteCourseSummary(summaryId) {
|
|||||||
await loadSummarySearch();
|
await loadSummarySearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveSummaryReviewEdits() {
|
||||||
|
if (!activeSummaryReview) return;
|
||||||
|
try {
|
||||||
|
const result = await fetchJson(`/api/admin/tasks/${encodeURIComponent(activeSummaryReview.id)}/course-summary-review`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(summaryReviewEditorPayload()),
|
||||||
|
});
|
||||||
|
activeSummaryReview = result.task;
|
||||||
|
await loadSummaryReviews();
|
||||||
|
openSummaryReviewDrawer(activeSummaryReview.id);
|
||||||
|
await loadOperationLogs();
|
||||||
|
} catch (error) {
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function linkExistingSummaryReview() {
|
||||||
|
if (!activeSummaryReview) return;
|
||||||
|
if (!confirm("确认关联已有上课记录?系统不会重复扣课时。")) return;
|
||||||
|
try {
|
||||||
|
await fetchJson(`/api/admin/tasks/${encodeURIComponent(activeSummaryReview.id)}/link-existing-course-summary`, {
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
closeSummaryReviewDrawer();
|
||||||
|
await loadSummaryReviews();
|
||||||
|
await loadOperationLogs();
|
||||||
|
} catch (error) {
|
||||||
|
alert(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveDuplicateSummary(taskId, summaryId) {
|
async function resolveDuplicateSummary(taskId, summaryId) {
|
||||||
if (!confirm("确认删除这条重复课程小结?系统会先备份来源文件。")) return;
|
if (!confirm("确认删除这条重复课程小结?系统会先备份来源文件。")) return;
|
||||||
await fetchJson(`/api/admin/tasks/${encodeURIComponent(taskId)}/resolve-duplicate-summary`, {
|
await fetchJson(`/api/admin/tasks/${encodeURIComponent(taskId)}/resolve-duplicate-summary`, {
|
||||||
@@ -1067,6 +1139,8 @@ summaryReviewDrawerClose.addEventListener("click", closeSummaryReviewDrawer);
|
|||||||
summaryReviewDrawerBackdrop.addEventListener("click", (event) => {
|
summaryReviewDrawerBackdrop.addEventListener("click", (event) => {
|
||||||
if (event.target === summaryReviewDrawerBackdrop) closeSummaryReviewDrawer();
|
if (event.target === summaryReviewDrawerBackdrop) closeSummaryReviewDrawer();
|
||||||
});
|
});
|
||||||
|
summaryReviewDrawerSave.addEventListener("click", saveSummaryReviewEdits);
|
||||||
|
summaryReviewDrawerLink.addEventListener("click", linkExistingSummaryReview);
|
||||||
summaryReviewDrawerApprove.addEventListener("click", () => {
|
summaryReviewDrawerApprove.addEventListener("click", () => {
|
||||||
if (activeSummaryReview) summaryReviewTask(activeSummaryReview.id, "approve");
|
if (activeSummaryReview) summaryReviewTask(activeSummaryReview.id, "approve");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -369,6 +369,10 @@ select:focus {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compact-grid {
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.span-2 {
|
.span-2 {
|
||||||
grid-column: span 2;
|
grid-column: span 2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user