Files
NextEdu/src/modules/grades/actions-draft.ts
SpecialX e85a5f05dd feat(grades): add appeals, drafts, import, report card, and growth archive
- Add actions-appeal, actions-draft, actions-import, actions-lock for grade workflow

- Add data-access-appeals, data-access-drafts, data-access-exam-entry for data layer

- Add batch-grade-entry-dialog, batch-grade-entry-stats, batch-grade-entry-table

- Add draft-lock-banner, excel-import-dialog for import and draft management

- Add growth-archive-chart, knowledge-point-mastery-chart for analytics

- Add report-card-view, report-card-print-action, report-card-print-button

- Add import-export, lib/notify, lib/report-card, scope-check test, stats-service test

- Add hooks directory
2026-07-03 10:25:01 +08:00

123 lines
3.2 KiB
TypeScript

"use server"
import { revalidatePath } from "next/cache"
import { getTranslations } from "next-intl/server"
import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state"
import { handleActionError } from "@/shared/lib/action-utils"
import {
saveGradeDraft,
getGradeDraft,
deleteGradeDraft,
type GradeDraftData,
} from "./data-access-drafts"
import { assertClassInScope } from "./lib/scope-check"
/**
* P1-1 拆分:从 actions.ts 抽取的成绩录入草稿 Server Actions。
* 对应 data-access 的 saveGradeDraft / getGradeDraft / deleteGradeDraft。
*/
/**
* 保存成绩录入草稿到服务端(跨设备同步)。
*/
export async function saveGradeDraftAction(params: {
classId: string
subjectId: string
type: string
scores: Record<string, string>
}): Promise<ActionState<{ saved: true }>> {
try {
const ctx = await requirePermission(Permissions.GRADE_RECORD_MANAGE)
const t = await getTranslations("grades")
// 校验班级权限
const scopeError = assertClassInScope(ctx.dataScope, params.classId)
if (scopeError) {
return { success: false, message: t(`action.scopeError.${scopeError}`) }
}
const data: GradeDraftData = {
scores: params.scores,
timestamp: Date.now(),
}
await saveGradeDraft({
userId: ctx.userId,
classId: params.classId,
subjectId: params.subjectId,
type: params.type,
data,
})
return { success: true, data: { saved: true } }
} catch (e) {
return handleActionError(e)
}
}
/**
* 获取成绩录入草稿(跨设备恢复)。
*/
export async function getGradeDraftAction(params: {
classId: string
subjectId: string
type: string
}): Promise<ActionState<GradeDraftData | null>> {
try {
const ctx = await requirePermission(Permissions.GRADE_RECORD_READ)
const t = await getTranslations("grades")
// 校验班级权限
const scopeError = assertClassInScope(ctx.dataScope, params.classId)
if (scopeError) {
return { success: false, message: t(`action.scopeError.${scopeError}`) }
}
const draft = await getGradeDraft({
userId: ctx.userId,
classId: params.classId,
subjectId: params.subjectId,
type: params.type,
})
return { success: true, data: draft }
} catch (e) {
return handleActionError(e)
}
}
/**
* 删除成绩录入草稿(提交成功后调用)。
*/
export async function deleteGradeDraftAction(params: {
classId: string
subjectId: string
type: string
}): Promise<ActionState<{ deleted: true }>> {
try {
const ctx = await requirePermission(Permissions.GRADE_RECORD_MANAGE)
const t = await getTranslations("grades")
// 校验班级权限
const scopeError = assertClassInScope(ctx.dataScope, params.classId)
if (scopeError) {
return { success: false, message: t(`action.scopeError.${scopeError}`) }
}
await deleteGradeDraft({
userId: ctx.userId,
classId: params.classId,
subjectId: params.subjectId,
type: params.type,
})
revalidatePath("/teacher/grades")
return { success: true, data: { deleted: true } }
} catch (e) {
return handleActionError(e)
}
}