feat(lesson-preparation): add anchor canvas design, new blocks, and textbook content node

- Add anchor injector for canvas-based anchor positioning

- Add new block components: blackboard, homework, import, key-point, new-teaching, objective, summary

- Add textbook content node for React Flow canvas

- Update actions (kp, publish, main), data-access (templates, versions, main)

- Update editor, node-editor, block-renderer, and picker components

- Update schema, types, hooks, and lib utilities (document-migration, node-summary, rf-mappers)
This commit is contained in:
SpecialX
2026-06-23 17:37:19 +08:00
parent 1fcef5c3aa
commit 2197e68069
34 changed files with 3190 additions and 402 deletions

View File

@@ -4,10 +4,11 @@ import { revalidatePath } from "next/cache";
import { getTranslations } from "next-intl/server";
import {
requirePermission,
PermissionDeniedError,
} from "@/shared/lib/auth-guard";
import { Permissions } from "@/shared/types/permissions";
import { handleActionError, safeParseDate } from "@/shared/lib/action-utils";
import { publishLessonPlanHomework, PublishServiceError } from "./publish-service";
import { publishLessonPlanHomeworkSchema } from "./schema";
import type { ActionState } from "./types";
export async function publishLessonPlanHomeworkAction(input: {
@@ -19,19 +20,26 @@ export async function publishLessonPlanHomeworkAction(input: {
}): Promise<ActionState<{ examId: string; assignmentId: string }>> {
const t = await getTranslations("lessonPreparation");
try {
const parsed = publishLessonPlanHomeworkSchema.safeParse(input);
if (!parsed.success) {
return { success: false, errors: parsed.error.flatten().fieldErrors };
}
const ctx = await requirePermission(
Permissions.LESSON_PLAN_PUBLISH,
);
await requirePermission(Permissions.HOMEWORK_CREATE);
const result = await publishLessonPlanHomework({
planId: input.planId,
blockId: input.blockId,
planId: parsed.data.planId,
blockId: parsed.data.blockId,
userId: ctx.userId,
classIds: input.classIds,
availableAt: input.availableAt
? new Date(input.availableAt)
classIds: parsed.data.classIds,
availableAt: parsed.data.availableAt
? safeParseDate(parsed.data.availableAt, "可用时间")
: undefined,
dueAt: parsed.data.dueAt
? safeParseDate(parsed.data.dueAt, "截止时间")
: undefined,
dueAt: input.dueAt ? new Date(input.dueAt) : undefined,
});
revalidatePath("/teacher/lesson-plans");
revalidatePath("/teacher/homework");
@@ -43,17 +51,12 @@ export async function publishLessonPlanHomeworkAction(input: {
},
};
} catch (e) {
if (e instanceof PermissionDeniedError)
return { success: false, message: e.message };
// publish-service 抛出 PublishServiceError含 code翻译为 i18n 消息
if (e instanceof PublishServiceError) {
const messageKey = PUBLISH_ERROR_KEY_MAP[e.code] ?? "error.publish";
return { success: false, message: t(messageKey) };
}
return {
success: false,
message: e instanceof Error ? e.message : t("error.publish"),
};
return handleActionError(e);
}
}