From 7d325517b3ffc2eb9a348a67203c2a2c1262e39f Mon Sep 17 00:00:00 2001 From: Yang Han <23377286+HansYeoh@users.noreply.github.com> Date: Thu, 17 Jul 2025 01:27:09 +0800 Subject: [PATCH 1/2] Update MarkdownViewer.tsx Add an export mind map button to support exporting HTML and PNG. --- .../src/pages/HomePage/components/MarkdownViewer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx index 9c1e1f0..ab55589 100644 --- a/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx +++ b/BillNote_frontend/src/pages/HomePage/components/MarkdownViewer.tsx @@ -208,6 +208,7 @@ const MarkdownViewer: FC = ({ status }) => { value={selectedContent} onChange={() => {}} height="100%" // 根据需求可以设定百分比或固定高度 + title={currentTask?.audioMeta?.title || '思维导图'} /> From b1b0e87d85ed28ce8d0b72029d82c45a37bc481d Mon Sep 17 00:00:00 2001 From: Yang Han <23377286+HansYeoh@users.noreply.github.com> Date: Thu, 17 Jul 2025 01:27:33 +0800 Subject: [PATCH 2/2] Update MarkmapComponent.tsx Add an export mind map button to support exporting HTML and PNG. --- .../HomePage/components/MarkmapComponent.tsx | 183 +++++++++++++++++- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/BillNote_frontend/src/pages/HomePage/components/MarkmapComponent.tsx b/BillNote_frontend/src/pages/HomePage/components/MarkmapComponent.tsx index fbd5dd8..0db8136 100644 --- a/BillNote_frontend/src/pages/HomePage/components/MarkmapComponent.tsx +++ b/BillNote_frontend/src/pages/HomePage/components/MarkmapComponent.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { Markmap } from 'markmap-view' import { transformer } from '@/lib/markmap.ts' -import { Toolbar, ToolbarButton } from 'markmap-toolbar' +import { Toolbar } from 'markmap-toolbar' import 'markmap-toolbar/dist/style.css' export interface MarkmapEditorProps { @@ -12,9 +12,11 @@ export interface MarkmapEditorProps { /** Toolbar 上要展示的 item id 列表,默认使用 Toolbar.defaultItems */ toolbarItems?: string[] /** 自定义按钮列表,会依次注册 */ - customButtons?: ToolbarButton[] + customButtons?: any[] /** 容器 SVG 的高度,默认为 600px */ height?: string + /** 文档标题,用于导出HTML时的文件名 */ + title?: string } export default function MarkmapEditor({ @@ -23,9 +25,10 @@ export default function MarkmapEditor({ toolbarItems, customButtons = [], height = '600px', + title = 'mindmap', }: MarkmapEditorProps) { const svgRef = useRef(null) - const mmRef = useRef() + const mmRef = useRef() const toolbarRef = useRef(null) // 用于跟踪是否处于全屏状态 @@ -56,6 +59,158 @@ export default function MarkmapEditor({ document.exitFullscreen() } } + + // 导出HTML思维导图 + const exportHtml = () => { + try { + const { root } = transformer.transform(value) + const data = JSON.stringify(root) + + // 创建HTML内容 + const html = ` + + + + + ${title || 'BiliNote思维导图'} + + + + + + + + +`; + + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${title || 'mindmap'}.html`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } catch (error) { + console.error('导出HTML失败:', error); + } + }; + + // 导出PNG思维导图 + const exportPng = () => { + try { + if (!svgRef.current) return; + + const svgEl = svgRef.current; + + // 获取SVG实际尺寸 + const svgWidth = svgEl.width.baseVal.value || svgEl.clientWidth || 800; + const svgHeight = svgEl.height.baseVal.value || svgEl.clientHeight || 600; + + // 设置足够大的缩放比例以确保高清输出 + const scale = 3; + + // 克隆SVG以避免修改原始SVG + const clonedSvg = svgEl.cloneNode(true) as SVGSVGElement; + + // 设置SVG的背景为白色 + const style = document.createElementNS('http://www.w3.org/2000/svg', 'style'); + style.textContent = 'svg { background-color: white; }'; + clonedSvg.insertBefore(style, clonedSvg.firstChild); + + // 确保SVG有正确的命名空间 + clonedSvg.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + clonedSvg.setAttribute('width', svgWidth.toString()); + clonedSvg.setAttribute('height', svgHeight.toString()); + + // 将SVG转换为Data URI (避免使用Blob URL来解决跨域问题) + const svgData = new XMLSerializer().serializeToString(clonedSvg); + const svgBase64 = btoa(unescape(encodeURIComponent(svgData))); + const dataUri = `data:image/svg+xml;base64,${svgBase64}`; + + // 创建Canvas + const canvas = document.createElement('canvas'); + canvas.width = svgWidth * scale; + canvas.height = svgHeight * scale; + + // 获取上下文并设置白色背景 + const ctx = canvas.getContext('2d'); + if (!ctx) { + throw new Error('无法获取Canvas上下文'); + } + + // 设置白色背景 + ctx.fillStyle = '#FFFFFF'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // 创建Image对象 + const img = new Image(); + + // 当图片加载完成后,在Canvas上绘制并导出 + img.onload = () => { + try { + // 应用缩放 + ctx.setTransform(scale, 0, 0, scale, 0, 0); + + // 绘制SVG + ctx.drawImage(img, 0, 0); + + // 重置变换 + ctx.setTransform(1, 0, 0, 1, 0, 0); + + // 将Canvas转换为PNG Blob + canvas.toBlob((blob) => { + if (blob) { + // 创建下载链接 + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `${title || 'mindmap'}.png`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } else { + console.error('无法创建Blob对象'); + } + }, 'image/png'); + } catch (err) { + console.error('Canvas处理失败:', err); + } + }; + + // 设置图片加载错误处理 + img.onerror = (error) => { + console.error('导出PNG失败(图片加载错误):', error); + }; + + // 开始加载SVG图像 (使用Data URI而不是Blob URL) + img.src = dataUri; + + } catch (error) { + console.error('导出PNG失败:', error); + } + }; // 初始化 Markmap 实例 + Toolbar useEffect(() => { @@ -82,14 +237,28 @@ export default function MarkmapEditor({ }, [value]) // 文本输入变化回调(如果你自行添加 textarea 编辑区) - const handleChange = (e: React.ChangeEvent) => { - onChange(e.target.value) - } + // const handleChange = (e: React.ChangeEvent) => { + // onChange(e.target.value) + // } return (
{/* 全屏/退出全屏 按钮 */}
+ + {isFullscreen ? (