import React, { useState, useCallback, useEffect } from "react"; import { Tree, Segmented, Spin } from "antd"; import type { TreeDataNode } from "antd"; import { BookOutlined, FileTextOutlined, FolderOutlined, FontSizeOutlined, TranslationOutlined, } from "@ant-design/icons"; import { get } from "../../request"; import type { IPaliListResponse, IPaliParagraphResponse, ISentenceListResponse, } from "../../api/Corpus"; // 定义节点类型 type NodeType = | "book" | "chapter" | "paragraph" | "sentence" | "text" | "resources" | "translations" | "similar" | "preview"; // 定义模式类型 type ParagraphMode = "preview" | "edit"; // 定义基础节点数据接口 interface BaseNodeData { id: string; title: string; type: NodeType; isLeaf?: boolean; preview?: string; content?: React.ReactNode; children?: BaseNodeData[]; } // 定义树节点接口 interface TreeNode extends TreeDataNode { key: string; type: NodeType; isLeaf?: boolean; children?: TreeNode[]; } // 定义API响应接口 type ApiResponse = BaseNodeData; // 定义组件状态接口 interface IWidget { type?: NodeType; rootId?: string; channelsId?: string[]; } const TreeTextComponent = ({ type, rootId, channelsId }: IWidget) => { const [treeData, setTreeData] = useState([]); const [loadingKeys, setLoadingKeys] = useState([]); const [paragraphModes, setParagraphModes] = useState< Record >({}); useEffect(() => { if (type === "chapter") { const url = `/v2/palitext/${rootId}`; get(url).then((json) => { if (json.ok) { setTreeData([ { id: json.data.uid, title: json.data.text, type: "chapter", }, ]); } }); } }, [rootId, type]); // 模拟API调用 const mockApiCall = useCallback( async (type: NodeType, key: string): Promise => { console.log("Calling API:", type, key); //await new Promise((resolve) => setTimeout(resolve, 1000)); // 模拟网络延迟 // 模拟不同类型节点的响应数据 if (type === "book") { return [ { id: "chapter_1", title: "第一章", type: "chapter" }, { id: "chapter_2", title: "第二章", type: "chapter" }, { id: "chapter_3", title: "第三章", type: "chapter" }, ]; } else if (type === "chapter") { const url = `/v2/palitext?view=children&id=${key}`; const paragraphs = await get(url); return paragraphs.data.rows.map((item) => { if (item.level < 8) { return { id: item.uid, title: item.toc, type: "chapter", }; } else { return { id: `${item.book}-${item.paragraph}`, title: item.paragraph.toString(), type: "paragraph", preview: item.text, }; } }); } else if (type === "paragraph") { const [book, paragraph] = key.split("-"); const url = `/v2/sentence?view=paragraph&book=${book}¶=${paragraph}&channels=${channelsId?.join()}`; const res = await get(url); return res.data.rows.map((item) => { return { id: item.id ?? "123", title: `${item.book}-${item.paragraph}-${item.word_start}-${item.word_end}`, type: "sentence", children: [ { id: "text_node", title: item.content, type: "text", isLeaf: true, }, { id: "resources", title: "资源", type: "resources", children: [ { id: "translations", title: "参考译文", type: "translations", }, { id: "similar", title: "相似句", type: "similar", }, ], }, ], }; }); } else if (type === "similar") { return [ { id: "text_node", title: "句子文本:This is the original sentence text.", type: "text", isLeaf: true, }, { id: "resources", title: "资源", type: "resources", children: [ { id: "translations", title: "参考译文", type: "translations", }, { id: "similar", title: "相似句", type: "similar", }, ], }, ]; } return []; }, [channelsId] ); // 获取节点图标 const getNodeIcon = (type: NodeType): React.ReactNode => { switch (type) { case "book": return ; case "chapter": return ; case "paragraph": return ; case "sentence": return ; case "translations": return ; case "similar": return ; default: return null; } }; // 构建树节点 const buildTreeNode = ( node: BaseNodeData, parentKey: string = "" ): TreeNode => { const key = parentKey ? `${parentKey}_${node.id}` : `${node.type}_${node.id}`; const isLoading = loadingKeys.includes(key); let children: TreeNode[] = []; let hasChildren = false; if (node.type === "paragraph") { const mode = paragraphModes[key] || "preview"; if (mode === "preview" && node.preview) { // 预览模式:只显示预览文字节点 children = [ { title: `预览:${node.preview}`, key: `${key}_preview`, type: "preview" as NodeType, isLeaf: true, icon: , }, ]; } else if (mode === "edit" && node.children) { // 编辑模式:显示子sentence节点 children = node.children.map((child) => buildTreeNode(child, key)); } hasChildren = Boolean(node.children && node.children.length > 0); } else if (node.children) { children = node.children.map((child) => buildTreeNode(child, key)); hasChildren = true; } else if ( !node.isLeaf && ["book", "chapter", "paragraph", "sentence"].includes(node.type) ) { hasChildren = true; } const handleModeChange = (value: string | number): void => { setParagraphModes((prev) => ({ ...prev, [key]: value as ParagraphMode, })); }; const handleSegmentedClick = (e: React.MouseEvent): void => { e.stopPropagation(); }; const treeNode: TreeNode = { title: (
{node.content ?? {node.title}} {node.type === "paragraph" && ( )}
), key, type: node.type, icon: isLoading ? : getNodeIcon(node.type), isLeaf: node.isLeaf || (!hasChildren && node.type === "text"), children: children.length > 0 ? children : undefined, }; return treeNode; }; // 懒加载处理 const onLoadData = async (node: TreeNode): Promise => { const { key, type } = node; if (loadingKeys.includes(key)) return; setLoadingKeys((prev) => [...prev, key]); try { const id = key.split("_").pop() || ""; const data = await mockApiCall(type, id); // 更新树数据 const updateTreeData = ( nodes: BaseNodeData[], parentKey: string = "" ): BaseNodeData[] => { return nodes.map((node) => { const currentKey = parentKey ? `${parentKey}_${node.id}` : `${node.type}_${node.id}`; if (currentKey === key) { return { ...node, children: data.map((child) => ({ ...child, children: child.children || [], })), }; } else if (node.children) { return { ...node, children: updateTreeData(node.children, currentKey), }; } return node; }); }; setTreeData((prev) => updateTreeData(prev)); } catch (error) { console.error("加载数据失败:", error); } finally { setLoadingKeys((prev) => prev.filter((k) => k !== key)); } }; return (

树状文本展示组件 (TypeScript)

buildTreeNode(node))} style={{ fontSize: 14 }} blockNode />
{/* 使用说明 */}

功能说明:

  • 📚 点击book节点懒加载chapter数据
  • 📄 点击chapter节点懒加载paragraph数据
  • 📝 paragraph节点右侧可切换预览/编辑模式
  • 👁️ 预览模式:只显示预览文字
  • ✏️ 编辑模式:显示子sentence节点
  • 📖 sentence节点包含句子文本和资源(参考译文、相似句)

TypeScript 特性:

  • 🔒 完整的类型定义和类型安全
  • 📝 接口定义清晰,便于维护
  • ⚡ 更好的IDE支持和代码提示
  • 🛡️ 编译时错误检查
); }; export default TreeTextComponent;