]*>(.*?)<\/code>/gi, '`$1`')
+ .replace(/]*>(.*?)<\/pre>/gi, '```\n$1\n```\n\n')
// 清除剩余标签
.replace(/<[^>]+>/g, '')
// 规范换行
.replace(/\n{3,}/g, '\n\n')
.trim();
- // 处理表格格式 - 添加表头分隔线
- const lines = markdown.split('\n');
- const processedLines = [];
- let inTable = false;
+ markdown += remainingContent;
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- if (line.includes('|') && line.trim().length > 0) {
- if (!inTable) {
- inTable = true;
- processedLines.push(line);
- // 添加表头分隔线
- const headerCells = line.split('|').filter(cell => cell.trim());
- if (headerCells.length > 0) {
- const separator = '| ' + headerCells.map(() => '---').join(' | ') + ' |';
- processedLines.push(separator);
- }
- } else {
- processedLines.push(line);
- }
- } else {
- if (inTable) {
- inTable = false;
- }
- processedLines.push(line);
- }
- }
-
- markdown = processedLines.join('\n');
-
- console.log('✅ 简单转换逻辑完成:', markdown.substring(0, 100) + '...');
+ console.log('✅ HTML转Markdown转换完成:', markdown.substring(0, 200) + '...');
return markdown;
} catch (error) {
- console.error('❌ HTML转Markdown失败:', error);
- return html; // 转换失败时返回原始HTML
+ console.error('❌ HTML转Markdown转换失败:', error);
+ return html; // 回退到原始HTML
}
};
@@ -565,15 +753,55 @@ const openRichTextEditor = (nodeObj, nodeElement) => {
allKeys: Object.keys(nodeObj)
});
- // 将HTML内容转换为Markdown格式供编辑
+ // 使用新的节点结构逻辑
+ let htmlContent = '';
+
+ // 优先使用dangerouslySetInnerHTML(用于WYSIWYG编辑)
if (nodeObj.dangerouslySetInnerHTML) {
- editorContent.value = convertHTMLToMarkdown(nodeObj.dangerouslySetInnerHTML);
- console.log('📝 转换后的Markdown内容:', editorContent.value);
- } else if (nodeObj.topic) {
- editorContent.value = nodeObj.topic;
- } else {
- editorContent.value = '';
+ htmlContent = nodeObj.dangerouslySetInnerHTML;
+ console.log('📝 使用dangerouslySetInnerHTML内容:', htmlContent.substring(0, 200) + '...');
+ }
+ // 其次使用markdown字段(如果存在)
+ else if (nodeObj.markdown) {
+ console.log('📝 使用markdown内容,转换为HTML:', nodeObj.markdown.substring(0, 200) + '...');
+ try {
+ htmlContent = marked.parse(nodeObj.markdown);
+ // 更新dangerouslySetInnerHTML
+ nodeObj.dangerouslySetInnerHTML = htmlContent;
+ } catch (error) {
+ console.error('❌ Markdown转HTML失败:', error);
+ htmlContent = nodeObj.markdown;
+ }
}
+ // 最后回退到data或topic(兼容旧数据)
+ else {
+ const markdownContent = nodeObj.data || nodeObj.topic || '';
+ console.log('📝 使用兼容模式,从data/topic获取内容:', markdownContent.substring(0, 200) + '...');
+
+ // 检查是否包含Markdown格式内容
+ const hasMarkdownContent = markdownContent.includes('|') && markdownContent.includes('-') || // 表格
+ markdownContent.includes('![') || // 图片
+ markdownContent.includes('#') || // 标题
+ markdownContent.includes('**') || // 粗体
+ markdownContent.includes('`'); // 代码
+
+ if (hasMarkdownContent) {
+ try {
+ htmlContent = marked.parse(markdownContent);
+ // 更新节点结构
+ nodeObj.markdown = markdownContent;
+ nodeObj.dangerouslySetInnerHTML = htmlContent;
+ } catch (error) {
+ console.error('❌ Markdown转HTML失败:', error);
+ htmlContent = markdownContent;
+ }
+ } else {
+ htmlContent = markdownContent;
+ }
+ }
+
+ editorContent.value = htmlContent;
+ console.log('🎯 WYSIWYG模式HTML内容:', htmlContent.substring(0, 200) + '...');
// 检查是否有MindElixir原生图片
if (nodeObj.image && !editorContent.value.includes('![')) {
@@ -622,13 +850,6 @@ const initVditor = async () => {
return;
}
- console.log('🎯 开始初始化Vditor编辑器...');
- console.log('🔍 vditorContainer元素:', vditorContainer.value);
- console.log('🔍 容器可见性:', vditorContainer.value.offsetParent !== null);
- console.log('🔍 容器尺寸:', {
- width: vditorContainer.value.offsetWidth,
- height: vditorContainer.value.offsetHeight
- });
try {
// 先清理容器
@@ -638,7 +859,7 @@ const initVditor = async () => {
vditorInstance = new Vditor(vditorContainer.value, {
height: 400,
placeholder: '请输入节点内容...',
- mode: 'wysiwyg', // 所见即所得模式
+ mode: 'wysiwyg', // 所见即所得模式,使用HTML内容
theme: 'classic',
toolbarConfig: {
pin: true // 固定工具栏
@@ -691,17 +912,12 @@ const initVditor = async () => {
}
},
after: () => {
- console.log('✅ Vditor编辑器初始化完成');
-
// 等待编辑器完全渲染后再设置内容
setTimeout(() => {
// ✅ 直接设置内容,让Vditor自动处理Markdown/HTML转换
if (editorContent.value && editorContent.value.trim()) {
vditorInstance.setValue(editorContent.value);
- console.log('✅ 内容已设置到WYSIWYG编辑器:', editorContent.value.substring(0, 100) + '...');
- console.log('✅ 编辑器当前模式: wysiwyg (所见即所得模式)');
} else {
- console.warn('⚠️ editorContent.value为空,设置空内容');
vditorInstance.setValue('');
}
@@ -714,34 +930,18 @@ const initVditor = async () => {
// 获取Markdown格式的内容
const markdownContent = vditorInstance.getValue();
editorContent.value = markdownContent;
- console.log('📝 编辑器内容变化(Markdown):', markdownContent.substring(0, 50) + '...');
});
- } else {
- console.warn('⚠️ Vditor实例没有on方法');
}
// 检查工具栏是否正确渲染
setTimeout(() => {
const toolbar = document.querySelector('.vditor-toolbar');
const editor = document.querySelector('.vditor-wysiwyg');
- console.log('🔍 Vditor工具栏:', toolbar);
- console.log('🔍 Vditor编辑器区域:', editor);
-
- if (!toolbar || !editor) {
- console.error('❌ Vditor组件没有正确渲染');
- console.error('工具栏状态:', toolbar ? '存在' : '不存在');
- console.error('编辑器状态:', editor ? '存在' : '不存在');
- } else {
- console.log('✅ Vditor组件渲染成功');
- console.log('工具栏按钮数量:', toolbar.querySelectorAll('button').length);
- console.log('工具栏高度:', toolbar.offsetHeight);
- }
}, 100);
}, 100);
}
});
- console.log('✅ Vditor编辑器创建成功');
} catch (error) {
console.error('❌ Vditor编辑器初始化失败:', error);
console.error('错误详情:', error.stack);
@@ -820,14 +1020,13 @@ const saveRichTextChanges = async () => {
}
// ✅ 使用Vditor内置API获取内容
- const markdownContent = vditorInstance ? vditorInstance.getValue() : editorContent.value;
- const htmlContent = vditorInstance ? vditorInstance.getHTML() : editorContent.value;
- const contentToSave = markdownContent.trim() || '';
+ const htmlContent = vditorInstance ? vditorInstance.getValue() : editorContent.value;
+ const markdownContent = vditorInstance ? vditorInstance.getMarkdown() : '';
- console.log('📝 获取到的Markdown内容:', contentToSave.substring(0, 100) + '...');
console.log('📝 获取到的HTML内容:', htmlContent.substring(0, 100) + '...');
+ console.log('📝 获取到的Markdown内容:', markdownContent.substring(0, 100) + '...');
- // 更新节点数据
+ // 更新节点数据 - 使用新的节点结构
const titleChanged = editorTitle.value !== (currentNode.value.title || '');
console.log('🔍 标题变化检查:', {
editorTitle: editorTitle.value,
@@ -835,10 +1034,11 @@ const saveRichTextChanges = async () => {
titleChanged: titleChanged
});
- // 更新节点内容
- currentNode.value.topic = contentToSave; // 保存原始Markdown内容
+ // 更新节点内容 - 新的节点结构
+ currentNode.value.dangerouslySetInnerHTML = htmlContent; // 保存HTML内容(用于渲染)
+ currentNode.value.markdown = markdownContent || htmlContent; // 保存Markdown内容(用于导出)
+ currentNode.value.topic = generateTopicFromMarkdown(markdownContent || htmlContent); // 生成简短标题
currentNode.value.title = editorTitle.value; // 更新标题
- currentNode.value.dangerouslySetInnerHTML = htmlContent; // 保存渲染后的HTML
// 更新DOM元素
if (currentNodeElement.value) {
@@ -864,7 +1064,9 @@ const saveRichTextChanges = async () => {
newTitle: editorTitle.value, // 使用更新后的标题
newDes: currentNode.value.data?.des || "",
newParentId: currentNode.value.parentId || currentNode.value.parent?.id,
- newDangerouslySetInnerHTML: htmlContent || "" // 保存富文本内容
+ newDangerouslySetInnerHTML: htmlContent || "", // 保存HTML内容(用于渲染)
+ newMarkdown: markdownContent || htmlContent, // 保存Markdown内容(用于导出)
+ newTopic: currentNode.value.topic // 保存生成的简短标题
};
console.log("🔍 直接发送到后端的更新数据:", updateData);
@@ -3656,24 +3858,18 @@ const openCustomEditModal = (nodeObj, nodeElement) => {
// 确保模态框完全渲染后再初始化Vditor编辑器
nextTick(() => {
setTimeout(() => {
- console.log('🔄 准备初始化Vditor,容器状态:', vditorContainer.value);
-
// 检查容器是否真正可见
if (vditorContainer.value && vditorContainer.value.offsetParent !== null) {
- console.log('✅ 容器已可见,开始初始化Vditor');
initVditor();
} else {
- console.warn('⚠️ 容器未准备好,延迟初始化');
// 多次尝试,确保容器准备好
let attempts = 0;
const maxAttempts = 10;
const checkContainer = () => {
attempts++;
if (vditorContainer.value && vditorContainer.value.offsetParent !== null) {
- console.log('✅ 容器在第', attempts, '次尝试后可见');
initVditor();
} else if (attempts < maxAttempts) {
- console.log('🔄 第', attempts, '次检查,容器仍未准备好,继续等待...');
setTimeout(checkContainer, 100);
} else {
console.error('❌ 容器在', maxAttempts, '次尝试后仍未准备好');
@@ -4785,6 +4981,7 @@ const updateMindMapRealtime = async (data, title, eventDetail = null) => {
infinite: true,
maxScale: 5,
minScale: 0.1,
+ theme: customTheme, // 使用自定义紫色主题
markdown: (text, nodeObj) => {
// 检查内容是否包含markdown语法(包括图片和数学公式)
if (text.includes('|') || text.includes('**') || text.includes('`') || text.includes('#') || text.includes('$') || text.includes('![')) {
@@ -4869,36 +5066,73 @@ const updateMindMapRealtime = async (data, title, eventDetail = null) => {
}
} else {
- // 完整更新:仅在必要时重新初始化
- console.log('🔄 执行完整更新');
+ // 优化:避免不必要的完整更新,优先使用增量更新
+ console.log('🔄 尝试增量更新替代完整更新');
- const currentId = String(currentMindmapId.value || '');
- const tempId = currentId && currentId.startsWith('temp-')
- ? currentId
- : `temp-${Date.now()}`;
-
- const mindElixirData = {
- nodeData: data,
- mindmapId: tempId,
- id: tempId,
- title: title || 'AI生成中...'
- };
-
- // 确保当前思维导图ID是临时ID
- if (!currentId || !currentId.startsWith('temp-')) {
- currentMindmapId.value = tempId;
- console.log('🆔 更新临时思维导图ID:', tempId);
- }
-
- // 重新初始化数据
- const result = mindElixir.value.init(mindElixirData);
-
- // 恢复位置
- if (currentPosition) {
- setTimeout(() => {
- restorePosition(currentPosition);
- console.log('📍 完整更新后恢复位置');
- }, 100);
+ // 检查是否可以增量更新
+ if (mindElixir.value && mindElixir.value.data && mindElixir.value.data.nodeData) {
+ console.log('✅ 使用增量更新,避免页面空白');
+
+ // 更新根节点标题
+ if (mindElixir.value.data.nodeData && data.topic) {
+ const rootNode = Object.values(mindElixir.value.data.nodeData)[0];
+ if (rootNode && rootNode.topic !== data.topic) {
+ rootNode.topic = data.topic;
+ // 更新DOM中的标题显示
+ const rootElement = mindmapEl.value.querySelector(`[data-id="${rootNode.id}"]`);
+ if (rootElement) {
+ const textElement = rootElement.querySelector('.topic-text');
+ if (textElement) {
+ textElement.textContent = data.topic;
+ }
+ }
+ }
+ }
+
+ // 增量更新子节点
+ if (data.children && data.children.length > 0) {
+ updateNodesIncremental(data.children, mindElixir.value.data.nodeData);
+ }
+
+ // 恢复位置,避免视图跳动
+ if (currentPosition) {
+ setTimeout(() => {
+ restorePosition(currentPosition);
+ console.log('📍 优化增量更新后恢复位置');
+ }, 50);
+ }
+ } else {
+ // 只有在确实无法增量更新时才执行完整更新
+ console.log('⚠️ 无法增量更新,执行完整更新');
+
+ const currentId = String(currentMindmapId.value || '');
+ const tempId = currentId && currentId.startsWith('temp-')
+ ? currentId
+ : `temp-${Date.now()}`;
+
+ const mindElixirData = {
+ nodeData: data,
+ mindmapId: tempId,
+ id: tempId,
+ title: title || 'AI生成中...'
+ };
+
+ // 确保当前思维导图ID是临时ID
+ if (!currentId || !currentId.startsWith('temp-')) {
+ currentMindmapId.value = tempId;
+ console.log('🆔 更新临时思维导图ID:', tempId);
+ }
+
+ // 重新初始化数据
+ const result = mindElixir.value.init(mindElixirData);
+
+ // 恢复位置
+ if (currentPosition) {
+ setTimeout(() => {
+ restorePosition(currentPosition);
+ console.log('📍 完整更新后恢复位置');
+ }, 100);
+ }
}
}
diff --git a/frontend/src/utils/markdownRenderer.js b/frontend/src/utils/markdownRenderer.js
index ed0fff4..2831ff9 100644
--- a/frontend/src/utils/markdownRenderer.js
+++ b/frontend/src/utils/markdownRenderer.js
@@ -53,13 +53,10 @@ renderer.image = function(href, title, text) {
// 处理图片URL,确保能正确显示
let processedUrl = hrefStr;
-
- // 暂时禁用代理URL转换,直接使用原始URL
- // 这样可以避免代理服务配置问题导致的图片显示异常
if (hrefStr.includes('cdn-mineru.openxlab.org.cn')) {
- // 直接使用原始URL,不进行代理转换
- processedUrl = hrefStr;
- console.log('🖼️ 使用原始CDN URL:', processedUrl);
+ // 将外部CDN URL转换为代理URL
+ const urlPath = hrefStr.replace('https://cdn-mineru.openxlab.org.cn', '');
+ processedUrl = `/proxy-image${urlPath}`;
}
// 生成图片HTML