"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import { useLessonPlanEditor } from "../hooks/use-lesson-plan-editor"; import { NodeEditor } from "./node-editor"; import { NodeEditPanel } from "./node-edit-panel"; import { VersionHistoryDrawer } from "./version-history-drawer"; import { updateLessonPlanAction, saveLessonPlanVersionAction, getLessonPlanByIdAction, } from "../actions"; import { useLessonPlanTrackerSafe } from "../providers/lesson-plan-provider"; import type { BlockType } from "../types"; import { Button } from "@/shared/components/ui/button"; import { Plus, Save, History } from "lucide-react"; interface Props { planId: string; initialTitle: string; initialDoc: import("../types").LessonPlanDocument; textbookId?: string; chapterId?: string; classes?: { id: string; name: string }[]; } const BLOCK_TYPES_TO_ADD: BlockType[] = [ "objective", "key_point", "import", "new_teaching", "consolidation", "summary", "homework", "blackboard", "exercise", "text_study", "rich_text", "reflection", ]; export function LessonPlanEditor({ planId, initialTitle, initialDoc, textbookId, chapterId, classes, }: Props) { const t = useTranslations("lessonPreparation"); const editor = useLessonPlanEditor(); const tracker = useLessonPlanTrackerSafe(); const [showVersions, setShowVersions] = useState(false); const [showAddMenu, setShowAddMenu] = useState(false); const autoSaveTimer = useRef | null>(null); const versionTimer = useRef | null>(null); const addMenuRef = useRef(null); // 初始化:仅在 planId 变化时 hydrate(修复 P1-3) const initKey = planId; useEffect(() => { useLessonPlanEditor.getState().hydrate(planId, initialTitle, initialDoc); // eslint-disable-next-line react-hooks/exhaustive-deps }, [initKey]); // 自动保存(debounce 3s)- 用 getState() 获取最新值(修复 P1-4) useEffect(() => { if (!editor.isDirty) return; if (autoSaveTimer.current) clearTimeout(autoSaveTimer.current); autoSaveTimer.current = setTimeout(async () => { const state = useLessonPlanEditor.getState(); state.setSaving(true); const res = await updateLessonPlanAction({ planId: state.planId, title: state.title, content: state.doc, }); state.setSaving(false); if (res.success) state.markSaved(); }, 3000); return () => { if (autoSaveTimer.current) clearTimeout(autoSaveTimer.current); }; }, [editor.isDirty, editor.doc, planId]); // 定时自动版本(30min) useEffect(() => { versionTimer.current = setInterval(async () => { const state = useLessonPlanEditor.getState(); if (!state.isDirty) return; await saveLessonPlanVersionAction({ planId: state.planId, content: state.doc, label: t("version.autoLabel"), }); }, 30 * 60 * 1000); return () => { if (versionTimer.current) clearInterval(versionTimer.current); }; }, [planId, t]); // 离开未保存提示(P3-1) useEffect(() => { function handleBeforeUnload(e: BeforeUnloadEvent) { if (useLessonPlanEditor.getState().isDirty) { e.preventDefault(); e.returnValue = ""; } } window.addEventListener("beforeunload", handleBeforeUnload); return () => window.removeEventListener("beforeunload", handleBeforeUnload); }, []); // 添加节点菜单点击外部关闭(P3-2) useEffect(() => { if (!showAddMenu) return; function handleClickOutside(e: MouseEvent) { if (addMenuRef.current && !addMenuRef.current.contains(e.target as Node)) { setShowAddMenu(false); } } document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [showAddMenu]); const handleManualSave = useCallback(async () => { const state = useLessonPlanEditor.getState(); state.setSaving(true); const res = await saveLessonPlanVersionAction({ planId: state.planId, content: state.doc, }); state.setSaving(false); if (res.success) { state.markSaved(); tracker.track("lesson_plan.save", { planId: state.planId, source: "manual" }); } }, [tracker]); // 版本回退后刷新内容(修复 P1-1) const handleReverted = useCallback(async () => { const state = useLessonPlanEditor.getState(); const res = await getLessonPlanByIdAction(state.planId); if (res.success && res.data?.plan) { state.hydrate(state.planId, res.data.plan.title, res.data.plan.content); } }, []); return (
{/* 顶部工具栏 */}
editor.setTitle(e.target.value)} className="flex-1 bg-transparent font-headline-md text-headline-md focus:outline-none" /> {editor.isSaving ? t("status.saving") : editor.isDirty ? t("status.unsaved") : t("status.saved")}
{/* 主区域:画布 + 侧边面板 */}
{/* 节点画布 */}
{/* 添加节点浮动按钮 */}
{showAddMenu && (
{BLOCK_TYPES_TO_ADD.map((blockType) => ( ))}
)}
{/* 侧边内容编辑面板:直接用 selectedNodeId 控制显示(修复 P1-2) */} {editor.selectedNodeId && (
)}
setShowVersions(false)} planId={planId} onReverted={handleReverted} />
); }