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; flex-direction: column;
} }
</style> </style>
<script type="module" crossorigin src="/assets/index-b0cb73ba.js"></script> <script type="module" crossorigin src="/assets/index-93385e5a.js"></script>
<link rel="stylesheet" href="/assets/index-f016b388.css"> <link rel="stylesheet" href="/assets/index-1b564f4b.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

View File

@ -604,7 +604,6 @@ const convertMarkdownToHTML = (markdown) => {
return markdown; // 退Markdown return markdown; // 退Markdown
} }
}; };
/** /**
* 改进的HTML转Markdown转换函数 * 改进的HTML转Markdown转换函数
* 支持表格图片等复杂内容的转换 * 支持表格图片等复杂内容的转换
@ -1020,8 +1019,22 @@ const saveRichTextChanges = async () => {
} }
// 使VditorAPI // 使VditorAPI
const htmlContent = vditorInstance ? vditorInstance.getValue() : editorContent.value; // Markdown HTML
const markdownContent = vditorInstance ? vditorInstance.getMarkdown() : ''; 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('📝 获取到的HTML内容:', htmlContent.substring(0, 100) + '...');
console.log('📝 获取到的Markdown内容:', markdownContent.substring(0, 100) + '...'); console.log('📝 获取到的Markdown内容:', markdownContent.substring(0, 100) + '...');
@ -1247,7 +1260,6 @@ const createNewMindmap = async () => {
// //
} }
}; };
// //
const saveCurrentPosition = () => { const saveCurrentPosition = () => {
if (!mindElixir.value || !mindmapEl.value) return null; if (!mindElixir.value || !mindmapEl.value) return null;
@ -1887,7 +1899,6 @@ const removeConnectionLineFixes = () => {
// console.log('线'); // console.log('线');
}; };
// //
const handleNodeDragStart = (event, nodeId) => { const handleNodeDragStart = (event, nodeId) => {
isDragging.value = true; isDragging.value = true;
@ -2516,7 +2527,6 @@ const submitAIQuestion = async () => {
isAIProcessing.value = false; isAIProcessing.value = false;
} }
}; };
// AI // AI
// Markdown // Markdown
const formatMarkdownToText = (markdown) => { const formatMarkdownToText = (markdown) => {
@ -3139,7 +3149,6 @@ const handleNodeDragOperation = async (operation) => {
console.error('❌ 处理节点拖拽操作失败:', error); console.error('❌ 处理节点拖拽操作失败:', error);
} }
}; };
// //
const processingEditOperations = new Set(); const processingEditOperations = new Set();
@ -3786,7 +3795,6 @@ const findNodeById = (node, targetId) => {
return null; return null;
}; };
// //
const openCustomEditModal = (nodeObj, nodeElement) => { const openCustomEditModal = (nodeObj, nodeElement) => {
console.log('🎯 打开自定义编辑模态框:', nodeObj); console.log('🎯 打开自定义编辑模态框:', nodeObj);
@ -3828,12 +3836,44 @@ const openCustomEditModal = (nodeObj, nodeElement) => {
markdownContent = nodeObj.topic; markdownContent = nodeObj.topic;
console.log('✅ 从node.topic获取内容:', markdownContent.substring(0, 100) + '...'); console.log('✅ 从node.topic获取内容:', markdownContent.substring(0, 100) + '...');
} else if (nodeObj.dangerouslySetInnerHTML) { } else if (nodeObj.dangerouslySetInnerHTML) {
// VditorHTMLMarkdown // VditorHTMLMarkdown GFM
console.log('⚠️ 没有找到Markdown字段转换HTML为Markdown:', nodeObj.dangerouslySetInnerHTML.substring(0, 100) + '...'); console.log('⚠️ 没有找到Markdown字段转换HTML为Markdown:', nodeObj.dangerouslySetInnerHTML.substring(0, 100) + '...');
try { try {
const rawHtml = nodeObj?.dangerouslySetInnerHTML || ''; 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); markdownContent = Vditor.html2md(rawHtml);
console.log('✅ 使用 Vditor.html2md 转换成功:', markdownContent.substring(0, 100) + '...'); console.log('✅ 使用 Vditor.html2md 转换成功:', markdownContent.substring(0, 100) + '...');
} else { } else {
@ -4428,7 +4468,6 @@ const refreshMindMap = async () => {
console.error("❌ 刷新思维导图失败:", error); console.error("❌ 刷新思维导图失败:", error);
} }
}; };
// //
const savePreviewToDatabase = async (data, title) => { const savePreviewToDatabase = async (data, title) => {
try { try {
@ -4964,7 +5003,6 @@ defineExpose({
showMindMapPage, showMindMapPage,
cleanupIntervals cleanupIntervals
}); });
// //
const updateMindMapRealtime = async (data, title, eventDetail = null) => { const updateMindMapRealtime = async (data, title, eventDetail = null) => {
try { try {
@ -5616,7 +5654,6 @@ const findNodeInData = (nodeData, targetId) => {
color: #333 !important; color: #333 !important;
position: relative !important; position: relative !important;
} }
:deep(.map-container .topic:hover) { :deep(.map-container .topic:hover) {
border-color: #007bff !important; border-color: #007bff !important;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2) !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); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
animation: slideIn 0.3s ease; animation: slideIn 0.3s ease;
} }
@keyframes slideIn { @keyframes slideIn {
from { from {
transform: translateY(-20px); transform: translateY(-20px);
@ -6905,7 +6941,6 @@ const findNodeInData = (nodeData, targetId) => {
display: block !important; display: block !important;
visibility: visible !important; visibility: visible !important;
} }
/* 确保Vditor样式正确显示 - 强制上下布局 */ /* 确保Vditor样式正确显示 - 强制上下布局 */
.rich-editor-body .vditor { .rich-editor-body .vditor {
border: none !important; border: none !important;
@ -7237,6 +7272,4 @@ const findNodeInData = (nodeData, targetId) => {
} }
} }
</style> </style>