78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
type CountProps = {
|
|
mindmap: any;
|
|
};
|
|
|
|
export const MindmapCount = ({ mindmap }: CountProps) => {
|
|
const [counts, setCounts] = useState({ words: 0, nodes: 0 });
|
|
|
|
useEffect(() => {
|
|
if (!mindmap) return;
|
|
|
|
const calculateCounts = (data: any) => {
|
|
let nodes = 0;
|
|
let words = 0;
|
|
|
|
const stripHtmlLen = (html: string) => {
|
|
// 简易去标签统计长度:避免每次 data_change 都创建 DOM 容器造成卡顿
|
|
const plain = html
|
|
.replace(/<[^>]*>/g, "")
|
|
.replace(/ /g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
return plain.length;
|
|
};
|
|
|
|
const walk = (nodeData: any) => {
|
|
if (!nodeData) return;
|
|
nodes++;
|
|
// simple-mind-map 节点数据结构:{ data: { text: ... }, children: [...] }
|
|
const text = String(nodeData.data?.text || "");
|
|
words += stripHtmlLen(text);
|
|
|
|
if (nodeData.children && Array.isArray(nodeData.children)) {
|
|
nodeData.children.forEach(walk);
|
|
}
|
|
};
|
|
|
|
const root = data?.root ?? data;
|
|
walk(root);
|
|
|
|
setCounts((prev) => (prev.words === words && prev.nodes === nodes ? prev : { words, nodes }));
|
|
};
|
|
|
|
const onDataChange = (data: any) => {
|
|
calculateCounts(data);
|
|
};
|
|
|
|
mindmap.on("data_change", onDataChange);
|
|
|
|
// 初次渲染时尝试计算一次(mindmap.getData() 可能返回完整数据对象)
|
|
try {
|
|
const initialData = mindmap.getData();
|
|
if (initialData) calculateCounts(initialData);
|
|
} catch (e) {
|
|
console.warn("获取思维导图初始数据用于统计失败", e);
|
|
}
|
|
|
|
return () => {
|
|
mindmap.off("data_change", onDataChange);
|
|
};
|
|
}, [mindmap]);
|
|
|
|
return (
|
|
<div className="absolute bottom-4 left-4 flex items-center gap-4 rounded-md border border-gray-200 bg-white/90 px-3 py-1 text-xs text-gray-600 shadow-sm backdrop-blur-sm select-none z-10">
|
|
<div className="flex items-center gap-1">
|
|
<span>字数</span>
|
|
<span className="font-medium text-gray-900">{counts.words}</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<span>节点</span>
|
|
<span className="font-medium text-gray-900">{counts.nodes}</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|