diff --git a/src/modules/lesson-preparation/components/anchor-migration-banner.tsx b/src/modules/lesson-preparation/components/anchor-migration-banner.tsx
index cd798d9..ec110d2 100644
--- a/src/modules/lesson-preparation/components/anchor-migration-banner.tsx
+++ b/src/modules/lesson-preparation/components/anchor-migration-banner.tsx
@@ -1,28 +1,44 @@
"use client";
-import { useState, useEffect } from "react";
+import { useState } from "react";
import { useTranslations } from "next-intl";
import { useLessonPlanEditor } from "../hooks/use-lesson-plan-editor";
+import type { LessonPlanDocument } from "../types";
/**
* V4:v3 锚点失效提示 banner。
* 当文档含旧锚点(invalid: true 或含 start/end 字段)且未 dismiss 时显示。
* dismiss 状态记录到 localStorage(按 planId)。
+ *
+ * 通过 key={planId} 实现 planId 变化时重新挂载内部组件,
+ * 从而使用 lazy initial state 读取 localStorage,避免在 effect 中 setState。
*/
export function AnchorMigrationBanner() {
- const t = useTranslations("lessonPreparation");
const doc = useLessonPlanEditor((s) => s.doc);
const planId = useLessonPlanEditor((s) => s.planId);
- const [dismissed, setDismissed] = useState(false);
- useEffect(() => {
- if (planId) {
- const stored = localStorage.getItem(`lp-legacy-anchor-dismissed-${planId}`);
- setDismissed(stored === "true");
- } else {
- setDismissed(false);
- }
- }, [planId]);
+ return (
+
+ );
+}
+
+function BannerContent({
+ doc,
+ planId,
+}: {
+ doc: LessonPlanDocument;
+ planId: string;
+}) {
+ const t = useTranslations("lessonPreparation");
+ const [dismissed, setDismissed] = useState(() => {
+ if (typeof window === "undefined") return false;
+ if (!planId) return false;
+ return localStorage.getItem(`lp-legacy-anchor-dismissed-${planId}`) === "true";
+ });
// 检测是否有旧锚点(invalid: true 或含 start/end 字段)
const hasLegacyAnchors = doc.anchors.some(