feat: 优化思维导图编辑和显示功能
- 修复编辑模式下表格渲染问题,使用新的节点结构逻辑 - 实现Markdown到HTML转换,支持表格和图片正确显示 - 添加generateTopicFromMarkdown函数,自动生成节点简短标题 - 优化保存逻辑,同时更新HTML、Markdown和topic字段 - 修复思维导图生成过程中的页面空白问题,优先使用增量更新 - 应用自定义紫色主题,统一线条和节点配色方案 - 简化控制台输出,只保留关键调试信息
This commit is contained in:
parent
68b0ba1ccf
commit
318974511c
Binary file not shown.
|
|
@ -209,6 +209,18 @@ import '../lib/mind-elixir/dist/style.css';
|
|||
import { renderMarkdownToHTML } from '../utils/markdownRenderer.js';
|
||||
import Vditor from 'vditor';
|
||||
import 'vditor/dist/index.css';
|
||||
import { marked } from 'marked';
|
||||
|
||||
// 配置marked库
|
||||
marked.setOptions({
|
||||
breaks: true, // 支持换行
|
||||
gfm: true, // 支持GitHub风格的Markdown
|
||||
tables: true, // 支持表格
|
||||
pedantic: false, // 不严格模式
|
||||
sanitize: false, // 不过滤HTML标签
|
||||
smartLists: true, // 智能列表
|
||||
smartypants: false // 不转换引号
|
||||
});
|
||||
|
||||
// 使用Vditor渲染Markdown内容
|
||||
const renderMarkdownWithVditor = async (markdown) => {
|
||||
|
|
@ -445,41 +457,233 @@ const closeImagePreview = () => {
|
|||
imagePreviewError.value = '';
|
||||
};
|
||||
|
||||
// HTML转Markdown函数
|
||||
const convertHTMLToMarkdown = (html) => {
|
||||
/**
|
||||
* 从Markdown内容生成简短标题
|
||||
* 用于节点显示
|
||||
*/
|
||||
const generateTopicFromMarkdown = (markdown) => {
|
||||
if (!markdown || typeof markdown !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
// 移除Markdown语法,提取纯文本
|
||||
let text = markdown
|
||||
// 移除标题标记
|
||||
.replace(/^#{1,6}\s+/gm, '')
|
||||
// 移除加粗和斜体
|
||||
.replace(/\*\*(.*?)\*\*/g, '$1')
|
||||
.replace(/\*(.*?)\*/g, '$1')
|
||||
// 移除代码块
|
||||
.replace(/```[\s\S]*?```/g, '')
|
||||
.replace(/`([^`]*)`/g, '$1')
|
||||
// 移除链接,保留文本
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
|
||||
// 移除图片
|
||||
.replace(/!\[([^\]]*)\]\([^)]+\)/g, '')
|
||||
// 移除表格分隔符
|
||||
.replace(/^\|.*\|$/gm, '')
|
||||
.replace(/^[-|:\s]+$/gm, '')
|
||||
// 移除列表标记
|
||||
.replace(/^[-*+]\s+/gm, '')
|
||||
.replace(/^\d+\.\s+/gm, '')
|
||||
// 移除引用标记
|
||||
.replace(/^>\s*/gm, '')
|
||||
// 移除多余空白
|
||||
.replace(/\n+/g, ' ')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
|
||||
// 限制长度,取前50个字符
|
||||
if (text.length > 50) {
|
||||
text = text.substring(0, 50) + '...';
|
||||
}
|
||||
|
||||
return text || '无标题';
|
||||
} catch (error) {
|
||||
console.error('❌ 生成标题失败:', error);
|
||||
return '无标题';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Markdown转HTML转换函数
|
||||
* 支持表格、图片等复杂内容的转换
|
||||
*/
|
||||
const convertMarkdownToHTML = (markdown) => {
|
||||
if (!markdown || typeof markdown !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
let html = markdown;
|
||||
|
||||
// 处理表格 - 简化版本,处理更灵活
|
||||
// 先尝试处理标准Markdown表格
|
||||
html = html.replace(/\|(.+)\|\n\|[-\s|]+\|\n((?:\|.+\|\n?)*)/g, (match, header, rows) => {
|
||||
const headers = header.split('|').map(h => h.trim()).filter(h => h);
|
||||
const rowLines = rows.trim().split('\n').filter(line => line.trim());
|
||||
|
||||
let tableHTML = '<table class="markdown-table" style="border-collapse: collapse; width: 100%; margin: 1em 0;">\n';
|
||||
|
||||
// 表头
|
||||
if (headers.length > 0) {
|
||||
tableHTML += '<thead><tr>';
|
||||
headers.forEach(header => {
|
||||
tableHTML += `<th style="border: 1px solid #ddd; padding: 8px; background: #f5f5f5; font-weight: bold;">${header}</th>`;
|
||||
});
|
||||
tableHTML += '</tr></thead>\n';
|
||||
}
|
||||
|
||||
// 表体
|
||||
if (rowLines.length > 0) {
|
||||
tableHTML += '<tbody>';
|
||||
rowLines.forEach(row => {
|
||||
const cells = row.split('|').map(c => c.trim()).filter(c => c);
|
||||
if (cells.length > 0) {
|
||||
tableHTML += '<tr>';
|
||||
cells.forEach(cell => {
|
||||
tableHTML += `<td style="border: 1px solid #ddd; padding: 8px;">${cell}</td>`;
|
||||
});
|
||||
tableHTML += '</tr>';
|
||||
}
|
||||
});
|
||||
tableHTML += '</tbody>';
|
||||
}
|
||||
|
||||
tableHTML += '</table>';
|
||||
return tableHTML;
|
||||
});
|
||||
|
||||
// 如果没有找到标准表格,尝试处理简单的列表格式
|
||||
if (!html.includes('<table')) {
|
||||
// 将列表项转换为简单的表格
|
||||
html = html.replace(/^([^::]+)[::]\s*(.+)$/gm, '<div style="display: flex; margin: 0.5em 0; border-bottom: 1px solid #eee; padding: 0.5em 0;"><div style="font-weight: bold; min-width: 120px;">$1:</div><div style="flex: 1;">$2</div></div>');
|
||||
}
|
||||
|
||||
// 处理图片
|
||||
html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width: 100%; height: auto; margin: 1em 0; border-radius: 4px;" />');
|
||||
|
||||
// 处理标题
|
||||
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
||||
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
||||
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
||||
|
||||
// 处理加粗
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// 处理斜体
|
||||
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>');
|
||||
|
||||
// 处理代码块
|
||||
html = html.replace(/```([^`]*)```/g, '<pre style="background: #f5f5f5; padding: 1em; border-radius: 4px; overflow-x: auto;"><code>$1</code></pre>');
|
||||
|
||||
// 处理行内代码
|
||||
html = html.replace(/`([^`]*)`/g, '<code style="background: #f5f5f5; padding: 0.2em 0.4em; border-radius: 3px; font-family: monospace;">$1</code>');
|
||||
|
||||
// 处理链接
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" style="color: #1a73e8; text-decoration: none;">$1</a>');
|
||||
|
||||
// 处理列表
|
||||
html = html.replace(/^- (.*$)/gim, '<li>$1</li>');
|
||||
html = html.replace(/(<li>.*<\/li>)/s, '<ul style="margin: 1em 0; padding-left: 2em;">$1</ul>');
|
||||
|
||||
// 处理换行
|
||||
html = html.replace(/\n\n/g, '</p><p>');
|
||||
html = html.replace(/\n/g, '<br>');
|
||||
|
||||
// 包装段落
|
||||
if (!html.startsWith('<')) {
|
||||
html = '<p>' + html + '</p>';
|
||||
}
|
||||
|
||||
return html;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Markdown转HTML转换失败:', error);
|
||||
return markdown; // 回退到原始Markdown
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 改进的HTML转Markdown转换函数
|
||||
* 支持表格、图片等复杂内容的转换
|
||||
*/
|
||||
const convertHTMLToMarkdownImproved = (html) => {
|
||||
if (!html || typeof html !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🔄 开始转换HTML到Markdown:', html.substring(0, 100) + '...');
|
||||
|
||||
// 使用Vditor的html2md功能(如果可用)
|
||||
// 优先使用Vditor的html2md功能
|
||||
if (typeof Vditor?.html2md === 'function') {
|
||||
const markdown = Vditor.html2md(html);
|
||||
console.log('✅ 使用Vditor.html2md转换成功:', markdown.substring(0, 100) + '...');
|
||||
return markdown;
|
||||
console.log('✅ 使用Vditor.html2md转换HTML');
|
||||
return Vditor.html2md(html);
|
||||
}
|
||||
|
||||
// 回退到简单转换逻辑
|
||||
console.log('⚠️ Vditor.html2md不可用,使用简单转换逻辑');
|
||||
let markdown = html
|
||||
// 处理表格 - 保持表格结构
|
||||
.replace(/<table[^>]*>/gi, '\n')
|
||||
.replace(/<\/table>/gi, '\n')
|
||||
.replace(/<thead[^>]*>/gi, '')
|
||||
.replace(/<\/thead>/gi, '')
|
||||
.replace(/<tbody[^>]*>/gi, '')
|
||||
.replace(/<\/tbody>/gi, '')
|
||||
.replace(/<tr[^>]*>/gi, '')
|
||||
.replace(/<\/tr>/gi, ' |\n')
|
||||
.replace(/<th[^>]*>/gi, '| ')
|
||||
.replace(/<\/th>/gi, ' ')
|
||||
.replace(/<td[^>]*>/gi, '| ')
|
||||
.replace(/<\/td>/gi, ' ')
|
||||
// 图片处理
|
||||
.replace(/<img[^>]*src="([^"]+)"[^>]*alt="([^"]*)"[^>]*>/gi, '')
|
||||
.replace(/<img[^>]*src="([^"]+)"[^>]*>/gi, '')
|
||||
console.log('⚠️ Vditor.html2md不可用,使用改进的转换逻辑');
|
||||
|
||||
// 创建临时DOM元素进行解析
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = html;
|
||||
|
||||
let markdown = '';
|
||||
|
||||
// 处理表格
|
||||
const tables = tempDiv.querySelectorAll('table');
|
||||
tables.forEach((table, tableIndex) => {
|
||||
console.log(`📊 处理表格 ${tableIndex + 1}`);
|
||||
|
||||
// 处理表头
|
||||
const thead = table.querySelector('thead');
|
||||
if (thead) {
|
||||
const headerRow = thead.querySelector('tr');
|
||||
if (headerRow) {
|
||||
const headers = Array.from(headerRow.querySelectorAll('th, td')).map(cell =>
|
||||
(cell.textContent || '').trim().replace(/\|/g, '\\|')
|
||||
);
|
||||
markdown += '| ' + headers.join(' | ') + ' |\n';
|
||||
markdown += '| ' + headers.map(() => '---').join(' | ') + ' |\n';
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表体
|
||||
const tbody = table.querySelector('tbody') || table;
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
rows.forEach(row => {
|
||||
// 跳过表头行(如果已经在thead中处理过)
|
||||
if (thead && thead.contains(row)) return;
|
||||
|
||||
const cells = Array.from(row.querySelectorAll('td, th')).map(cell =>
|
||||
(cell.textContent || '').trim().replace(/\|/g, '\\|')
|
||||
);
|
||||
if (cells.length > 0) {
|
||||
markdown += '| ' + cells.join(' | ') + ' |\n';
|
||||
}
|
||||
});
|
||||
|
||||
markdown += '\n';
|
||||
});
|
||||
|
||||
// 处理图片
|
||||
const images = tempDiv.querySelectorAll('img');
|
||||
images.forEach(img => {
|
||||
const src = img.src || img.getAttribute('src') || '';
|
||||
const alt = img.alt || img.getAttribute('alt') || '';
|
||||
if (src) {
|
||||
markdown += `\n\n`;
|
||||
}
|
||||
});
|
||||
|
||||
// 处理其他内容(移除已处理的表格和图片)
|
||||
const processedDiv = tempDiv.cloneNode(true);
|
||||
processedDiv.querySelectorAll('table, img').forEach(el => el.remove());
|
||||
|
||||
// 转换剩余内容
|
||||
let remainingContent = processedDiv.innerHTML;
|
||||
|
||||
// 基本HTML到Markdown转换
|
||||
remainingContent = remainingContent
|
||||
// 换行与段落
|
||||
.replace(/<br\s*\/?>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '\n\n')
|
||||
|
|
@ -494,48 +698,32 @@ const convertHTMLToMarkdown = (html) => {
|
|||
.replace(/<\/ul>/gi, '\n')
|
||||
.replace(/<li[^>]*>/gi, '- ')
|
||||
.replace(/<\/li>/gi, '\n')
|
||||
// 标题
|
||||
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n\n')
|
||||
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n\n')
|
||||
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n\n')
|
||||
.replace(/<h4[^>]*>(.*?)<\/h4>/gi, '#### $1\n\n')
|
||||
.replace(/<h5[^>]*>(.*?)<\/h5>/gi, '##### $1\n\n')
|
||||
.replace(/<h6[^>]*>(.*?)<\/h6>/gi, '###### $1\n\n')
|
||||
// 链接
|
||||
.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
||||
// 代码
|
||||
.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
||||
.replace(/<pre[^>]*>(.*?)<\/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()}`;
|
||||
// 检查是否可以增量更新
|
||||
if (mindElixir.value && mindElixir.value.data && mindElixir.value.data.nodeData) {
|
||||
console.log('✅ 使用增量更新,避免页面空白');
|
||||
|
||||
const mindElixirData = {
|
||||
nodeData: data,
|
||||
mindmapId: tempId,
|
||||
id: tempId,
|
||||
title: title || 'AI生成中...'
|
||||
};
|
||||
// 更新根节点标题
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保当前思维导图ID是临时ID
|
||||
if (!currentId || !currentId.startsWith('temp-')) {
|
||||
currentMindmapId.value = tempId;
|
||||
console.log('🆔 更新临时思维导图ID:', tempId);
|
||||
}
|
||||
// 增量更新子节点
|
||||
if (data.children && data.children.length > 0) {
|
||||
updateNodesIncremental(data.children, mindElixir.value.data.nodeData);
|
||||
}
|
||||
|
||||
// 重新初始化数据
|
||||
const result = mindElixir.value.init(mindElixirData);
|
||||
// 恢复位置,避免视图跳动
|
||||
if (currentPosition) {
|
||||
setTimeout(() => {
|
||||
restorePosition(currentPosition);
|
||||
console.log('📍 优化增量更新后恢复位置');
|
||||
}, 50);
|
||||
}
|
||||
} else {
|
||||
// 只有在确实无法增量更新时才执行完整更新
|
||||
console.log('⚠️ 无法增量更新,执行完整更新');
|
||||
|
||||
// 恢复位置
|
||||
if (currentPosition) {
|
||||
setTimeout(() => {
|
||||
restorePosition(currentPosition);
|
||||
console.log('📍 完整更新后恢复位置');
|
||||
}, 100);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue