refactor(modules): update existing module implementations across attendance, audit, auth, classes, course-plans, exams, files, homework, layout, proctoring, questions, scheduling, textbooks, users
- Update attendance components and data-access for record management - Update audit log views, filters, and data-access - Update auth login and register forms - Update classes actions, components, and data-access (admin, schedule, stats) - Update course-plans actions, form, list, progress, and schema - Update exams actions, AI pipeline, preview components, and hooks - Update files components (icon, list, preview, upload) and data-access - Update homework assignment form, review view, auto-save hook, and stats-service - Update layout sidebar, header, and navigation config - Update proctoring actions, anti-cheat monitor, and data-access - Update questions actions, components (dialog, actions, columns, filters), and data-access - Update scheduling actions, auto-scheduler, components, and schema - Update textbooks constants and text-selection hook - Update users class-registration, import-dialog, data-access, and user-service
This commit is contained in:
@@ -70,27 +70,40 @@ export function useDebouncedAutoSave({
|
||||
savingRef.current = true
|
||||
setStatus("saving")
|
||||
|
||||
let allOk = true
|
||||
for (const [questionId, answer] of pending) {
|
||||
const fd = new FormData()
|
||||
fd.set("submissionId", submissionId)
|
||||
fd.set("questionId", questionId)
|
||||
fd.set("answerJson", JSON.stringify({ answer }))
|
||||
const res = await saveHomeworkAnswerAction(null, fd)
|
||||
if (!res.success) {
|
||||
allOk = false
|
||||
}
|
||||
}
|
||||
// 并行保存所有待保存答案,单个失败不影响其他答案
|
||||
const results = await Promise.allSettled(
|
||||
pending.map(([questionId, answer]) => {
|
||||
const fd = new FormData()
|
||||
fd.set("submissionId", submissionId)
|
||||
fd.set("questionId", questionId)
|
||||
fd.set("answerJson", JSON.stringify({ answer }))
|
||||
return saveHomeworkAnswerAction(null, fd)
|
||||
})
|
||||
)
|
||||
|
||||
savingRef.current = false
|
||||
|
||||
if (allOk) {
|
||||
// 收集失败的 questionId 以便重试
|
||||
const failedQuestionIds: string[] = []
|
||||
results.forEach((res, idx) => {
|
||||
if (res.status !== "fulfilled" || !res.value.success) {
|
||||
failedQuestionIds.push(pending[idx][0])
|
||||
}
|
||||
})
|
||||
|
||||
if (failedQuestionIds.length === 0) {
|
||||
pendingRef.current.clear()
|
||||
setStatus("saved")
|
||||
setLastSavedAt(Date.now())
|
||||
} else {
|
||||
setStatus("error")
|
||||
// Keep pending items for retry on next change or manual flush
|
||||
// 仅保留失败的项用于重试,移除已成功的项
|
||||
const newPending = new Map<string, unknown>()
|
||||
for (const qid of failedQuestionIds) {
|
||||
const ans = pendingRef.current.get(qid)
|
||||
if (ans !== undefined) newPending.set(qid, ans)
|
||||
}
|
||||
pendingRef.current = newPending
|
||||
}
|
||||
}, [submissionId])
|
||||
|
||||
@@ -135,7 +148,12 @@ export function useDebouncedAutoSave({
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current)
|
||||
}
|
||||
// Fire-and-forget final save
|
||||
// 注意:此处无法使用 navigator.sendBeacon,因为保存逻辑调用的是
|
||||
// Next.js Server Action(基于 fetch 的 RPC),而非简单的 POST 请求。
|
||||
// sendBeacon 仅支持发送原始 body,无法携带 Server Action 所需的
|
||||
// 特定 headers 和编码格式。因此采用 fire-and-forget 方式触发最后的
|
||||
// 保存,并依赖 localStorage 离线缓存作为兜底(下次进入页面会恢复)。
|
||||
// 真正的可靠 flush 由 handleSubmit 在提交前调用 autoSave.flush() 保证。
|
||||
void savePending()
|
||||
}
|
||||
}, [savePending])
|
||||
|
||||
Reference in New Issue
Block a user