imetting_frontend/src/components/MindMap.jsx

242 lines
6.1 KiB
React
Raw Normal View History

2025-09-16 09:00:09 +00:00
import React, { useState, useEffect, useRef } from 'react';
import { Transformer } from 'markmap-lib';
import { Markmap } from 'markmap-view';
import apiClient from '../utils/apiClient';
import { API_ENDPOINTS } from '../config/api';
import { Brain, Download, Loader } from 'lucide-react';
const MindMap = ({ meetingId, meetingTitle }) => {
const [markdown, setMarkdown] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [hasSummary, setHasSummary] = useState(false);
const svgRef = useRef(null);
const markmapRef = useRef(null);
useEffect(() => {
const fetchSummary = async () => {
try {
setLoading(true);
const endpoint = API_ENDPOINTS.MEETINGS.DETAIL(meetingId);
const response = await apiClient.get(endpoint);
const summary = response.data?.summary;
if (summary) {
setMarkdown(summary);
setHasSummary(true);
} else {
setMarkdown('# 暂无会议总结\n\n请先生成AI总结才能查看思维导图。');
setHasSummary(false);
}
} catch (err) {
console.error('Failed to fetch summary for mind map:', err);
setError('无法加载会议总结内容。');
setMarkdown('# 加载失败\n\n无法加载会议总结内容请稍后重试。');
setHasSummary(false);
} finally {
setLoading(false);
}
};
if (meetingId) {
fetchSummary();
}
}, [meetingId]);
useEffect(() => {
if (loading || !markdown || !svgRef.current) return;
const transformer = new Transformer();
const { root } = transformer.transform(markdown);
if (markmapRef.current) {
markmapRef.current.setData(root);
} else {
markmapRef.current = Markmap.create(svgRef.current, null, root);
}
markmapRef.current.fit();
}, [markdown, loading]);
const handleExportPDF = async () => {
if (!svgRef.current) {
alert('思维导图尚未渲染,无法导出。');
return;
}
try {
// 获取SVG元素
const svgElement = svgRef.current;
const svgHTML = svgElement.outerHTML;
if (!svgHTML.trim()) {
alert('思维导图内容为空,无法导出。');
return;
}
// 获取SVG的尺寸
const svgRect = svgElement.getBoundingClientRect();
const svgWidth = svgRect.width || 800;
const svgHeight = svgRect.height || 600;
// 创建一个隐藏的iframe用于打印
const printFrame = document.createElement('iframe');
printFrame.style.position = 'fixed';
printFrame.style.width = '0';
printFrame.style.height = '0';
printFrame.style.border = 'none';
printFrame.style.left = '-9999px';
document.body.appendChild(printFrame);
// 创建HTML内容
const htmlContent = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${meetingTitle || '会议思维导图'}</title>
<style>
@charset "UTF-8";
@page {
size: A4 landscape;
margin: 15mm;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif;
margin: 0;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 90vh;
}
h1 {
color: #2563eb;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.mindmap-container {
width: 100%;
height: 500px;
border: 1px solid #e5e7eb;
border-radius: 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background: white;
}
svg {
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
}
/* 脑图节点样式 */
.markmap-node circle {
fill: #3b82f6;
stroke: #1d4ed8;
stroke-width: 2;
}
.markmap-node text {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
font-size: 14px;
fill: #1e293b;
}
.markmap-link {
stroke: #6b7280;
stroke-width: 2;
fill: none;
}
.footer-section {
margin-top: 30px;
padding-top: 15px;
border-top: 1px solid #e5e7eb;
font-size: 12px;
color: #6b7280;
text-align: center;
width: 100%;
}
@media print {
body { padding: 0; }
h1 { page-break-before: avoid; }
}
</style>
</head>
<body>
<h1>${meetingTitle || '会议思维导图'}</h1>
<div class="mindmap-container">
${svgHTML}
</div>
<div class="footer-section">
<p>导出时间${new Date().toLocaleString('zh-CN')}</p>
</div>
</body>
</html>`;
// 使用Blob创建URL以确保正确的编码
const blob = new Blob([htmlContent], { type: 'text/html; charset=UTF-8' });
const url = URL.createObjectURL(blob);
// 设置iframe的src为blob URL
printFrame.src = url;
// 等待iframe加载完成
printFrame.onload = () => {
setTimeout(() => {
try {
// 执行打印
printFrame.contentWindow.focus();
printFrame.contentWindow.print();
} catch(e) {
console.error("Print failed:", e);
alert("导出PDF失败您的浏览器可能阻止了打印操作。");
}
// 清理资源
setTimeout(() => {
URL.revokeObjectURL(url);
document.body.removeChild(printFrame);
}, 2000);
}, 500);
};
} catch (error) {
console.error('PDF导出失败:', error);
alert('PDF导出失败请重试。');
}
};
if (loading) {
return (
<div className="mindmap-loading">
<Loader className="animate-spin" />
<p>正在加载思维导图...</p>
</div>
);
}
return (
<div className="mindmap-container">
<div className="mindmap-header">
<h3><Brain size={18} /> 思维导图</h3>
{hasSummary && (
<button onClick={handleExportPDF} className="export-pdf-btn-main" disabled={loading || !!error}>
<Download size={16} />
<span>导出PDF</span>
</button>
)}
</div>
<div className="markmap-render-area">
<svg ref={svgRef} style={{ width: '100%', height: '100%' }} />
</div>
</div>
);
};
export default MindMap;