Files
NextEdu/src/modules/lesson-preparation/components/structure-tree/structure-tree.tsx

127 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { useLessonPlanEditor } from "../../hooks/use-lesson-plan-editor";
import { TreeNodeRow } from "./tree-node-row";
import type { Block, InteractionBlockData, LessonPlanNode, TextbookContentNode } from "../../types";
export function StructureTree() {
const t = useTranslations("lessonPreparation");
const doc = useLessonPlanEditor((s) => s.doc);
const expandedNodeIds = useLessonPlanEditor((s) => s.expandedNodeIds);
const addNode = useLessonPlanEditor((s) => s.addNode);
const [expandedTreeNodes, setExpandedTreeNodes] = useState<Set<string>>(new Set());
// V1默认添加 rich_text 节点V2 接入节点类型选择对话框)
const onAddNode = () => {
const id = addNode("rich_text");
// 自动选中新节点addNode 内部已设置 selectedNodeId
void id;
};
const textbookNode = doc.nodes.find((n): n is TextbookContentNode => n.type === "textbook_content");
const teachingNodes = useMemo(
() =>
doc.nodes
.filter((n): n is LessonPlanNode => n.type !== "textbook_content")
.sort((a, b) => a.order - b.order),
[doc.nodes],
);
const toggleTreeNode = (id: string) => {
setExpandedTreeNodes((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};
return (
<aside
style={{
borderRight: "1px solid var(--border)",
background: "var(--background)",
padding: "16px 12px",
overflowY: "auto",
}}
>
<div
style={{
fontSize: 10,
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.08em",
color: "var(--muted-foreground)",
padding: "0 8px 12px",
display: "flex",
justifyContent: "space-between",
}}
>
<span>{t("v4.tree.title")}</span>
<span style={{ fontWeight: 500, letterSpacing: 0 }}>
{teachingNodes.length} {t("v4.tree.countSuffix")}
</span>
</div>
<div style={{ fontSize: 13 }}>
{/* 正文节点(始终在最上) */}
{textbookNode && (
<TreeNodeRow
node={{ ...textbookNode, type: "textbook_content" } as unknown as Block}
isExpanded={false}
/>
)}
{/* 教学节点 */}
{teachingNodes.map((node) => {
const isExpanded = expandedNodeIds.includes(node.id);
const isTreeNodeExpanded = expandedTreeNodes.has(node.id);
// 师生交互节点:子节点为对话轮次
let childItems: { id: string; label: string; kind: string }[] = [];
let hasChildren = false;
if (node.type === "interaction") {
const data = node.data as InteractionBlockData;
hasChildren = (data.turns?.length ?? 0) > 0;
childItems = (data.turns ?? []).map((turn, idx) => ({
id: turn.id,
label: `${t("v4.interaction.round", { n: idx + 1 })} · ${turn.role === "teacher" ? t("v4.interaction.roleTeacher") : t("v4.interaction.roleStudent")}`,
kind: turn.role === "teacher" ? t("v4.interaction.roleTeacher") : t("v4.interaction.roleStudent"),
}));
}
return (
<TreeNodeRow
key={node.id}
node={node}
isExpanded={isExpanded}
hasChildren={hasChildren}
isChildrenExpanded={isTreeNodeExpanded}
onToggleChildren={() => toggleTreeNode(node.id)}
childItems={childItems}
/>
);
})}
</div>
<div
style={{
marginTop: 10,
padding: "7px 8px",
border: "1px dashed var(--border)",
borderRadius: 5,
textAlign: "center",
fontSize: 12,
color: "var(--muted-foreground)",
cursor: "pointer",
}}
onClick={onAddNode}
>
+ {t("v4.tree.addNode")}
</div>
</aside>
);
}