fix(editor): preserve table structure when editing by converting HTML tables to GFM markdown and render HTML via Vditor preview on save
This commit is contained in:
parent
327c58151b
commit
0809c0e7df
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -23,8 +23,8 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-b0cb73ba.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-f016b388.css">
|
||||
<script type="module" crossorigin src="/assets/index-93385e5a.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-1b564f4b.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -604,7 +604,6 @@ const convertMarkdownToHTML = (markdown) => {
|
|||
return markdown; // 回退到原始Markdown
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 改进的HTML转Markdown转换函数
|
||||
* 支持表格、图片等复杂内容的转换
|
||||
|
|
@ -1020,8 +1019,22 @@ const saveRichTextChanges = async () => {
|
|||
}
|
||||
|
||||
// ✅ 使用Vditor内置API获取内容
|
||||
const htmlContent = vditorInstance ? vditorInstance.getValue() : editorContent.value;
|
||||
const markdownContent = vditorInstance ? vditorInstance.getMarkdown() : '';
|
||||
// 从编辑器获取 Markdown 与 HTML,两者都保留
|
||||
const markdownContent = vditorInstance ? vditorInstance.getValue() : editorContent.value;
|
||||
// 使用 Vditor 的预览引擎将 Markdown 渲染为 HTML,确保表格在渲染时仍是 <table>
|
||||
let htmlContent = '';
|
||||
try {
|
||||
if (vditorInstance?.preview) {
|
||||
// 借用预览渲染
|
||||
const temp = document.createElement('div');
|
||||
await Vditor.preview(temp, markdownContent, { mode: 'light' });
|
||||
htmlContent = temp.innerHTML;
|
||||
} else {
|
||||
htmlContent = markdownContent;
|
||||
}
|
||||
} catch {
|
||||
htmlContent = markdownContent;
|
||||
}
|
||||
|
||||
console.log('📝 获取到的HTML内容:', htmlContent.substring(0, 100) + '...');
|
||||
console.log('📝 获取到的Markdown内容:', markdownContent.substring(0, 100) + '...');
|
||||
|
|
@ -1247,7 +1260,6 @@ const createNewMindmap = async () => {
|
|||
// 静默处理错误
|
||||
}
|
||||
};
|
||||
|
||||
// 保存当前思维导图位置和缩放
|
||||
const saveCurrentPosition = () => {
|
||||
if (!mindElixir.value || !mindmapEl.value) return null;
|
||||
|
|
@ -1887,7 +1899,6 @@ const removeConnectionLineFixes = () => {
|
|||
|
||||
// console.log('已移除连接线强制样式');
|
||||
};
|
||||
|
||||
// 节点拖拽处理函数
|
||||
const handleNodeDragStart = (event, nodeId) => {
|
||||
isDragging.value = true;
|
||||
|
|
@ -2516,7 +2527,6 @@ const submitAIQuestion = async () => {
|
|||
isAIProcessing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 创建AI回答节点
|
||||
// 格式化Markdown为结构化文本
|
||||
const formatMarkdownToText = (markdown) => {
|
||||
|
|
@ -3139,7 +3149,6 @@ const handleNodeDragOperation = async (operation) => {
|
|||
console.error('❌ 处理节点拖拽操作失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 防止重复处理的编辑操作
|
||||
const processingEditOperations = new Set();
|
||||
|
||||
|
|
@ -3786,7 +3795,6 @@ const findNodeById = (node, targetId) => {
|
|||
|
||||
return null;
|
||||
};
|
||||
|
||||
// 打开自定义编辑模态框
|
||||
const openCustomEditModal = (nodeObj, nodeElement) => {
|
||||
console.log('🎯 打开自定义编辑模态框:', nodeObj);
|
||||
|
|
@ -3828,12 +3836,44 @@ const openCustomEditModal = (nodeObj, nodeElement) => {
|
|||
markdownContent = nodeObj.topic;
|
||||
console.log('✅ 从node.topic获取内容:', markdownContent.substring(0, 100) + '...');
|
||||
} else if (nodeObj.dangerouslySetInnerHTML) {
|
||||
// ✅ 在传给Vditor之前先把HTML转成Markdown(推荐)
|
||||
// ✅ 在传给Vditor之前先把HTML转成Markdown(优先支持表格为 GFM 语法)
|
||||
console.log('⚠️ 没有找到Markdown字段,转换HTML为Markdown:', nodeObj.dangerouslySetInnerHTML.substring(0, 100) + '...');
|
||||
try {
|
||||
const rawHtml = nodeObj?.dangerouslySetInnerHTML || '';
|
||||
|
||||
if (typeof Vditor?.html2md === 'function') {
|
||||
// 先尝试把 <table> 转成 GFM 表格,避免转换成纯文本
|
||||
const tableToMarkdown = (html) => {
|
||||
if (!/<table[\s\S]*<\/table>/i.test(html)) return null;
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = html;
|
||||
const table = temp.querySelector('table');
|
||||
if (!table) return null;
|
||||
const getText = (el) => (el?.textContent || '').replace(/\n+/g, ' ').trim();
|
||||
let headers = [];
|
||||
let rows = [];
|
||||
const thead = table.querySelector('thead');
|
||||
const tbody = table.querySelector('tbody');
|
||||
if (thead) {
|
||||
const tr = thead.querySelector('tr');
|
||||
if (tr) headers = Array.from(tr.children).map(getText);
|
||||
}
|
||||
const bodyTrs = (tbody || table).querySelectorAll('tr');
|
||||
bodyTrs.forEach((tr, index) => {
|
||||
const cells = Array.from(tr.children).map(getText);
|
||||
if (index === 0 && headers.length === 0) headers = cells; else rows.push(cells);
|
||||
});
|
||||
if (headers.length === 0) return null;
|
||||
const headerLine = `| ${headers.join(' | ')} |`;
|
||||
const alignLine = `| ${headers.map(() => '---').join(' | ')} |`;
|
||||
const rowLines = rows.map(r => `| ${r.join(' | ')} |`);
|
||||
return [headerLine, alignLine, ...rowLines].join('\n');
|
||||
};
|
||||
|
||||
const mdFromTable = tableToMarkdown(rawHtml);
|
||||
if (mdFromTable) {
|
||||
markdownContent = mdFromTable;
|
||||
console.log('✅ 使用自定义表格转换为Markdown');
|
||||
} else if (typeof Vditor?.html2md === 'function') {
|
||||
markdownContent = Vditor.html2md(rawHtml);
|
||||
console.log('✅ 使用 Vditor.html2md 转换成功:', markdownContent.substring(0, 100) + '...');
|
||||
} else {
|
||||
|
|
@ -4428,7 +4468,6 @@ const refreshMindMap = async () => {
|
|||
console.error("❌ 刷新思维导图失败:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存预览数据到数据库
|
||||
const savePreviewToDatabase = async (data, title) => {
|
||||
try {
|
||||
|
|
@ -4964,7 +5003,6 @@ defineExpose({
|
|||
showMindMapPage,
|
||||
cleanupIntervals
|
||||
});
|
||||
|
||||
// 实时更新思维导图(用于流式生成)
|
||||
const updateMindMapRealtime = async (data, title, eventDetail = null) => {
|
||||
try {
|
||||
|
|
@ -5616,7 +5654,6 @@ const findNodeInData = (nodeData, targetId) => {
|
|||
color: #333 !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
:deep(.map-container .topic:hover) {
|
||||
border-color: #007bff !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2) !important;
|
||||
|
|
@ -6258,7 +6295,6 @@ const findNodeInData = (nodeData, targetId) => {
|
|||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateY(-20px);
|
||||
|
|
@ -6905,7 +6941,6 @@ const findNodeInData = (nodeData, targetId) => {
|
|||
display: block !important;
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
/* 确保Vditor样式正确显示 - 强制上下布局 */
|
||||
.rich-editor-body .vditor {
|
||||
border: none !important;
|
||||
|
|
@ -7237,6 +7272,4 @@ const findNodeInData = (nodeData, targetId) => {
|
|||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</style>
|
||||
Loading…
Reference in New Issue