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:
lixinran 2025-10-15 21:32:31 +08:00
parent 327c58151b
commit 0809c0e7df
7 changed files with 265 additions and 231 deletions

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

View File

@ -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>

View File

@ -604,7 +604,6 @@ const convertMarkdownToHTML = (markdown) => {
return markdown; // 退Markdown
}
};
/**
* 改进的HTML转Markdown转换函数
* 支持表格图片等复杂内容的转换
@ -1020,8 +1019,22 @@ const saveRichTextChanges = async () => {
}
// 使VditorAPI
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) {
// VditorHTMLMarkdown
// VditorHTMLMarkdown 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;
@ -7238,5 +7273,3 @@ const findNodeInData = (nodeData, targetId) => {
}
</style>