From 05e68a3dad16e4ac51faa45e389ac194346345fb Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:43:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(actions):=20=E7=BB=84=20A=20=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E8=BF=81=E7=A7=BB=E8=87=B3=20invalidateFor=EF=BC=88us?= =?UTF-8?q?ers/school/textbooks/questions/course-plans/standards/schedulin?= =?UTF-8?q?g/audit/onboarding/invitation-codes/proctoring/i18n=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/i18n/actions.ts | 6 ++--- src/modules/audit/actions.ts | 7 +++-- src/modules/course-plans/actions.ts | 35 ++++++++----------------- src/modules/invitation-codes/actions.ts | 7 +++-- src/modules/onboarding/actions.ts | 4 +-- src/modules/proctoring/actions.ts | 4 +-- src/modules/questions/actions.ts | 17 +++++------- src/modules/scheduling/actions.ts | 17 +++++------- src/modules/school/actions.ts | 29 ++++++++++---------- src/modules/standards/actions.ts | 12 ++++----- src/modules/textbooks/actions.ts | 26 +++++++++--------- src/modules/users/actions.ts | 9 +++---- 12 files changed, 73 insertions(+), 100 deletions(-) diff --git a/src/i18n/actions.ts b/src/i18n/actions.ts index 481d386..e9bea47 100644 --- a/src/i18n/actions.ts +++ b/src/i18n/actions.ts @@ -1,7 +1,6 @@ "use server" import { cookies } from "next/headers" -import { revalidatePath } from "next/cache" import { LOCALE_COOKIE, @@ -9,6 +8,7 @@ import { type Locale, isLocale, } from "@/shared/i18n/locale" +import { invalidateFor } from "@/shared/lib/cache" /** * 切换语言:写入 cookie + 触发页面刷新。 @@ -16,7 +16,7 @@ import { * 设计说明: * - 不使用 URL 路由段,避免破坏现有路由组结构 * - cookie 持久化用户偏好,SSR 时由 i18n/request.ts 读取 - * - revalidatePath 确保所有页面用新 locale 重新渲染 + * - invalidateFor 确保所有页面用新 locale 重新渲染 */ export async function setLocaleAction(locale: string): Promise<{ success: boolean }> { if (!isLocale(locale)) { @@ -27,6 +27,6 @@ export async function setLocaleAction(locale: string): Promise<{ success: boolea cookieStore.set(LOCALE_COOKIE, locale as Locale, LOCALE_COOKIE_OPTIONS) // 刷新所有页面,让 SSR 用新 locale 重新渲染 - revalidatePath("/", "layout") + await invalidateFor("i18n.setLocale") return { success: true } } diff --git a/src/modules/audit/actions.ts b/src/modules/audit/actions.ts index 9956572..c1fb1ff 100644 --- a/src/modules/audit/actions.ts +++ b/src/modules/audit/actions.ts @@ -1,9 +1,9 @@ "use server" -import { revalidatePath } from "next/cache" import { PermissionDeniedError, requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import type { ActionState } from "@/shared/types/action-state" +import { invalidateFor } from "@/shared/lib/cache" import { trackEvent } from "@/shared/lib/track-event" import { getSession } from "@/shared/lib/session" @@ -180,7 +180,7 @@ export async function saveAuditRetentionConfigAction( }, }) - revalidatePath("/admin/audit-logs/overview") + await invalidateFor("audit.saveRetentionConfig") return { success: true, data: config } } catch (e) { if (e instanceof PermissionDeniedError) return { success: false, message: e.message } @@ -215,8 +215,7 @@ export async function purgeAuditLogsAction( }, }) - revalidatePath("/admin/audit-logs/overview") - revalidatePath("/admin/audit-logs") + await invalidateFor("audit.purge") return { success: true, data: result } } catch (e) { if (e instanceof PermissionDeniedError) return { success: false, message: e.message } diff --git a/src/modules/course-plans/actions.ts b/src/modules/course-plans/actions.ts index ada8b05..451876f 100644 --- a/src/modules/course-plans/actions.ts +++ b/src/modules/course-plans/actions.ts @@ -1,9 +1,9 @@ "use server" -import { revalidatePath } from "next/cache" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import type { ActionState } from "@/shared/types/action-state" +import { invalidateFor } from "@/shared/lib/cache" import { handleActionError } from "@/shared/lib/action-utils" import type { DataScope } from "@/shared/types/permissions" @@ -90,19 +90,6 @@ function resolveScope( return { userId: ctx.userId, isAdmin: false } } -const revalidatePlanPaths = (id?: string): void => { - revalidatePath("/admin/course-plans") - revalidatePath("/teacher/course-plans") - revalidatePath("/parent/course-plans") - revalidatePath("/student/course-plans") - if (id) { - revalidatePath(`/admin/course-plans/${id}`) - revalidatePath(`/teacher/course-plans/${id}`) - revalidatePath(`/parent/course-plans/${id}`) - revalidatePath(`/student/course-plans/${id}`) - } -} - export async function createCoursePlanAction( prevState: ActionState | null, formData: FormData @@ -134,7 +121,7 @@ export async function createCoursePlanAction( } const id = await createCoursePlan(parsed.data, ctx.userId) - revalidatePlanPaths(id) + await invalidateFor("course-plans.create", { id }) return { success: true, message: "Course plan created", data: id } } catch (e) { return handleActionError(e) @@ -179,7 +166,7 @@ export async function updateCoursePlanAction( } await updateCoursePlan(id, parsed.data) - revalidatePlanPaths(id) + await invalidateFor("course-plans.update", { id }) return { success: true, message: "Course plan updated", data: id } } catch (e) { return handleActionError(e) @@ -198,7 +185,7 @@ export async function deleteCoursePlanAction( if (!existing) return { success: false, message: "Course plan not found" } await deleteCoursePlan(id) - revalidatePlanPaths() + await invalidateFor("course-plans.delete") return { success: true, message: "Course plan deleted" } } catch (e) { return handleActionError(e) @@ -279,7 +266,7 @@ export async function createCoursePlanItemAction( if (!existing) return { success: false, message: "Course plan not found" } const itemId = await createCoursePlanItem(parsed.data) - revalidatePlanPaths(parsed.data.planId) + await invalidateFor("course-plans.item.create", { id: parsed.data.planId }) return { success: true, message: "Week plan added", data: itemId } } catch (e) { return handleActionError(e) @@ -319,7 +306,7 @@ export async function updateCoursePlanItemAction( } await updateCoursePlanItem(id, parsed.data) - revalidatePlanPaths() + await invalidateFor("course-plans.item.update") return { success: true, message: "Week plan updated", data: id } } catch (e) { return handleActionError(e) @@ -332,7 +319,7 @@ export async function deleteCoursePlanItemAction( try { await requirePermission(Permissions.COURSE_PLAN_MANAGE) await deleteCoursePlanItem(id) - revalidatePlanPaths() + await invalidateFor("course-plans.item.delete") return { success: true, message: "Week plan deleted" } } catch (e) { return handleActionError(e) @@ -349,7 +336,7 @@ export async function toggleCoursePlanItemCompletedAction( isCompleted: completed, completedAt: completed ? new Date().toISOString().slice(0, 10) : null, }) - revalidatePlanPaths() + await invalidateFor("course-plans.item.toggleCompleted") return { success: true, message: completed ? "Marked as completed" : "Marked as incomplete", @@ -379,7 +366,7 @@ export async function reorderCoursePlanItemsAction( if (!existing) return { success: false, message: "Course plan not found" } await reorderCoursePlanItems(parsed.data.planId, parsed.data.items) - revalidatePlanPaths(parsed.data.planId) + await invalidateFor("course-plans.item.reorder", { id: parsed.data.planId }) return { success: true, message: "Order saved", data: parsed.data.planId } } catch (e) { return handleActionError(e) @@ -402,7 +389,7 @@ export async function bulkToggleItemsAction( } const count = await bulkUpdateItemCompleted(parsed.data.itemIds, parsed.data.completed) - revalidatePlanPaths() + await invalidateFor("course-plans.item.bulkToggle") return { success: true, message: "Bulk updated", data: count } } catch (e) { return handleActionError(e) @@ -430,7 +417,7 @@ export async function copyCoursePlanAction( if (!existing) return { success: false, message: "Course plan not found" } const ids = await copyCoursePlanToClasses(parsed.data.sourcePlanId, parsed.data.targetClassIds) - revalidatePlanPaths() + await invalidateFor("course-plans.copy") return { success: true, message: "Copied", data: ids } } catch (e) { return handleActionError(e) diff --git a/src/modules/invitation-codes/actions.ts b/src/modules/invitation-codes/actions.ts index 5b0013b..194ff2c 100644 --- a/src/modules/invitation-codes/actions.ts +++ b/src/modules/invitation-codes/actions.ts @@ -1,10 +1,9 @@ "use server" -import { revalidatePath } from "next/cache" - import type { ActionState } from "@/shared/types/action-state" import { requirePermission } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" +import { invalidateFor } from "@/shared/lib/cache" import { trackEvent } from "@/shared/lib/track-event" import { @@ -63,7 +62,7 @@ export async function generateInvitationCodesAction( }, }) - revalidatePath("/admin/invitation-codes") + await invalidateFor("invitation-codes.generate") return { success: true, data: result } } catch (error) { @@ -121,7 +120,7 @@ export async function deleteInvitationCodeAction( targetId: codeId, }) - revalidatePath("/admin/invitation-codes") + await invalidateFor("invitation-codes.delete") return { success: true, data: null } } catch (error) { const message = diff --git a/src/modules/onboarding/actions.ts b/src/modules/onboarding/actions.ts index 50d3637..d48bd94 100644 --- a/src/modules/onboarding/actions.ts +++ b/src/modules/onboarding/actions.ts @@ -1,10 +1,10 @@ "use server" -import { revalidatePath } from "next/cache" import { eq } from "drizzle-orm" import type { ActionState } from "@/shared/types/action-state" import { requireAuth } from "@/shared/lib/auth-guard" +import { invalidateFor } from "@/shared/lib/cache" import { logAudit } from "@/shared/lib/audit-logger" import { resolveDefaultPath } from "@/shared/lib/route-resolver" import { @@ -243,7 +243,7 @@ export async function completeOnboardingAction( status: failures.length === 0 ? "success" : "failure", }) - revalidatePath("/onboarding") + await invalidateFor("onboarding.complete") // 若有局部失败,返回成功但附带失败列表(P1-2) if (failures.length > 0) { diff --git a/src/modules/proctoring/actions.ts b/src/modules/proctoring/actions.ts index 1f165e5..06047a6 100644 --- a/src/modules/proctoring/actions.ts +++ b/src/modules/proctoring/actions.ts @@ -1,8 +1,8 @@ "use server" -import { revalidatePath } from "next/cache" import type { ActionState } from "@/shared/types/action-state" import { requirePermission } from "@/shared/lib/auth-guard" +import { invalidateFor } from "@/shared/lib/cache" import { handleActionError } from "@/shared/lib/action-utils" import { Permissions } from "@/shared/types/permissions" import { z } from "zod" @@ -85,7 +85,7 @@ export async function recordProctoringEventAction( eventDetail: parsed.data.eventDetail, }) - revalidatePath(`/teacher/exams/${parsed.data.examId}/proctoring`) + await invalidateFor("proctoring.recordEvent", { examId: parsed.data.examId }) return successState({ id: event.id }, "Event recorded") } catch (error) { diff --git a/src/modules/questions/actions.ts b/src/modules/questions/actions.ts index bda4bb4..20ff336 100644 --- a/src/modules/questions/actions.ts +++ b/src/modules/questions/actions.ts @@ -5,7 +5,7 @@ import { Permissions } from "@/shared/types/permissions" import { CreateQuestionSchema } from "./schema" import type { CreateQuestionInput } from "./schema" import type { ActionState } from "@/shared/types/action-state" -import { revalidatePath } from "next/cache" +import { invalidateFor } from "@/shared/lib/cache" import { z } from "zod" import { createQuestionWithRelations, @@ -68,8 +68,7 @@ export async function createQuestionAction( trackQuestionCreated(questionId, input.type, input.difficulty) - revalidatePath("/teacher/questions") - revalidatePath("/admin/questions") + await invalidateFor("questions.create") return { success: true, @@ -117,8 +116,7 @@ export async function updateQuestionAction( trackQuestionUpdated(id, updateData.type, updateData.difficulty) - revalidatePath("/teacher/questions") - revalidatePath("/admin/questions") + await invalidateFor("questions.update") return { success: true, message: "Question updated successfully", data: id } } catch (e) { @@ -143,8 +141,7 @@ export async function deleteQuestionAction( trackQuestionDeleted(questionId) - revalidatePath("/teacher/questions") - revalidatePath("/admin/questions") + await invalidateFor("questions.delete") return { success: true, message: "Question deleted successfully", data: questionId } } catch (e) { @@ -190,8 +187,7 @@ export async function deleteQuestionsBatchAction( trackQuestionDeleted(id) } - revalidatePath("/teacher/questions") - revalidatePath("/admin/questions") + await invalidateFor("questions.deleteBatch") return { success: true, message: "Batch delete completed", data: { deleted: deletedCount } } } catch (e) { @@ -369,8 +365,7 @@ export async function importQuestionsAction( trackQuestionCreated(createdIds[i], item.type, item.difficulty) } - revalidatePath("/teacher/questions") - revalidatePath("/admin/questions") + await invalidateFor("questions.import") return { success: true, message: "Import completed", data: { imported: createdIds.length } } } catch (e) { diff --git a/src/modules/scheduling/actions.ts b/src/modules/scheduling/actions.ts index aaa2454..edf143d 100644 --- a/src/modules/scheduling/actions.ts +++ b/src/modules/scheduling/actions.ts @@ -1,10 +1,9 @@ "use server" -import { revalidatePath } from "next/cache" - import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard" import { Permissions } from "@/shared/types/permissions" import type { ActionState } from "@/shared/types/action-state" +import { invalidateFor } from "@/shared/lib/cache" import { getUserNamesByIds } from "@/modules/users/data-access" @@ -61,7 +60,7 @@ export async function saveSchedulingRulesAction( } const id = await upsertSchedulingRules(parsed.data) - revalidatePath("/admin/scheduling/rules") + await invalidateFor("scheduling.saveRules") return { success: true, message: "Scheduling rules saved", data: id } } catch (e) { if (e instanceof PermissionDeniedError) return { success: false, message: e.message } @@ -167,8 +166,7 @@ export async function applyAutoScheduleAction( })) ) - revalidatePath("/admin/scheduling/auto") - revalidatePath("/teacher/classes/schedule") + await invalidateFor("scheduling.autoSchedule") return { success: true, message: `Applied ${schedules.length} schedule items`, data: schedules.length } } catch (e) { if (e instanceof PermissionDeniedError) return { success: false, message: e.message } @@ -205,8 +203,7 @@ export async function requestScheduleChangeAction( } const id = await createScheduleChange(parsed.data, ctx.userId) - revalidatePath("/teacher/schedule-changes") - revalidatePath("/admin/scheduling/changes") + await invalidateFor("scheduling.requestChange") return { success: true, message: "Schedule change request submitted", data: id } } catch (e) { if (e instanceof PermissionDeniedError) return { success: false, message: e.message } @@ -221,8 +218,7 @@ export async function approveScheduleChangeAction(changeId: string): Promise