diff --git a/backend/mindmap.db b/backend/mindmap.db index 1495a0e..75608e1 100644 Binary files a/backend/mindmap.db and b/backend/mindmap.db differ diff --git a/backend/mindmap/views_doc.py b/backend/mindmap/views_doc.py index 1ce1015..b7a1991 100644 --- a/backend/mindmap/views_doc.py +++ b/backend/mindmap/views_doc.py @@ -54,8 +54,8 @@ def convert_to_mindelixir_format(mindmap, nodes): # 添加HTML内容(如果存在) if node.html_content: node_data["dangerouslySetInnerHTML"] = node.html_content - # 如果有HTML内容,清空topic以避免冲突 - node_data["topic"] = "" + # 保留topic内容,因为可能需要markdown原始内容 + # node_data["topic"] = "" # 添加图片信息 if node.image_url: diff --git a/frontend/src/components/MindMap.vue b/frontend/src/components/MindMap.vue index f40948a..91aed48 100644 --- a/frontend/src/components/MindMap.vue +++ b/frontend/src/components/MindMap.vue @@ -168,44 +168,73 @@
- {{ editorTitle }} + + {{ editorTitle }} + +
-
- - -
-

支持 JPG、PNG、GIF 格式,最大 5MB

-
-
- -
-
预览:
-
+ +
+
+ +
+
+ + +
+

支持 JPG、PNG、GIF 格式,最大 5MB

+
+ +
+
+ +
+ 点击查看 +
+ +
+
+
@@ -216,6 +245,23 @@
+ +
+
+
+

确认删除

+
+
+

确定要删除这张图片吗?

+

删除后无法恢复

+
+ +
+
+
@@ -223,6 +269,7 @@ import { ref, onMounted, onUnmounted, nextTick } from 'vue'; import MindElixir from '../lib/mind-elixir/dist/MindElixir.js'; import '../lib/mind-elixir/dist/style.css'; +import { renderMarkdownToHTML } from '../utils/markdownRenderer.js'; // 自定义主题 const customTheme = { @@ -273,8 +320,7 @@ const customTheme = { import { mindmapAPI } from '../api/mindmap.js'; import { smartRenderNodeContent, - hasMarkdownSyntax, - renderMarkdownToHTML + hasMarkdownSyntax } from '../utils/markdownRenderer.js'; // 响应式数据 @@ -313,6 +359,13 @@ const editorTitle = ref(''); const editorContent = ref(''); const currentNode = ref(null); const currentNodeElement = ref(null); +const existingImages = ref([]); // 已有图片列表 +const showDeleteConfirm = ref(false); // 显示删除确认弹窗 +const deleteImageIndex = ref(-1); // 要删除的图片索引 +const sectionTitle = ref(''); // 图片区域标题 +const isEditingTitle = ref(false); // 是否正在编辑标题 +const editingTitleValue = ref(''); // 编辑中的标题值 +const titleInput = ref(null); // 标题输入框引用 @@ -404,7 +457,25 @@ const openRichTextEditor = (nodeObj, nodeElement) => { currentNode.value = nodeObj; currentNodeElement.value = nodeElement; - editorTitle.value = nodeObj.topic || '编辑节点内容'; + // 优先使用节点的实际标题,如果没有则从topic中提取纯文本 + let tempTitle = nodeObj.title; + if (!tempTitle && nodeObj.topic) { + // 如果nodeObj.title为空,尝试从nodeObj.topic中获取纯文本标题 + // 移除Markdown图片语法 + tempTitle = nodeObj.topic.replace(/!\[.*?\]\(.*?\)/g, '').trim(); + } + editorTitle.value = tempTitle || '编辑节点内容'; + + // 调试:检查节点的完整数据结构 + console.log('📝 节点完整数据:', { + title: nodeObj.title, + topic: nodeObj.topic, + dangerouslySetInnerHTML: nodeObj.dangerouslySetInnerHTML, + image: nodeObj.image, + hasImage: !!nodeObj.image, + htmlContent: nodeObj.htmlContent, + allKeys: Object.keys(nodeObj) + }); // 将HTML内容转换为Markdown格式供编辑 if (nodeObj.dangerouslySetInnerHTML) { @@ -416,6 +487,18 @@ const openRichTextEditor = (nodeObj, nodeElement) => { editorContent.value = ''; } + // 检查是否有MindElixir原生图片 + if (nodeObj.image && !editorContent.value.includes('![')) { + console.log('📝 发现MindElixir原生图片,添加到编辑器中'); + const imageUrl = typeof nodeObj.image === 'string' ? nodeObj.image : nodeObj.image.url; + const imageAlt = nodeObj.image.alt || nodeObj.topic || '图片'; + const imageMarkdown = `![${imageAlt}](${imageUrl})`; + editorContent.value = editorContent.value + '\n' + imageMarkdown; + } + + // 解析已有图片 + parseExistingImages(); + showRichTextEditor.value = true; console.log('📝 富文本编辑器已打开'); }; @@ -426,6 +509,150 @@ const closeRichTextEditor = () => { editorContent.value = ''; currentNode.value = null; currentNodeElement.value = null; + existingImages.value = []; +}; + +// 解析已有图片 +const parseExistingImages = () => { + existingImages.value = []; + + if (!editorContent.value) return; + + console.log('📝 开始解析图片,内容长度:', editorContent.value.length); + console.log('📝 内容前100字符:', editorContent.value.substring(0, 100)); + + // 使用正则表达式匹配markdown图片语法 ![alt](url) + const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g; + let match; + + while ((match = imageRegex.exec(editorContent.value)) !== null) { + const alt = match[1] || '图片'; + const url = match[2]; + + console.log('📝 找到图片:', { alt, url: url.substring(0, 50) + '...' }); + + existingImages.value.push({ + alt: alt, + url: url, + markdown: match[0] // 完整的markdown语法 + }); + } + + // 如果没有找到标准markdown格式,尝试查找其他可能的图片格式 + if (existingImages.value.length === 0) { + console.log('📝 未找到标准markdown图片格式,尝试其他格式...'); + + // 查找data:image格式的base64图片 + const base64Regex = /data:image\/[^;]+;base64,[A-Za-z0-9+/=]+/g; + let base64Match; + + while ((base64Match = base64Regex.exec(editorContent.value)) !== null) { + const url = base64Match[0]; + const alt = '图片'; + + console.log('📝 找到base64图片:', url.substring(0, 50) + '...'); + + existingImages.value.push({ + alt: alt, + url: url, + markdown: `![${alt}](${url})` + }); + } + + // 查找普通URL图片 + const urlRegex = /https?:\/\/[^\s]+\.(jpg|jpeg|png|gif|webp)/gi; + let urlMatch; + + while ((urlMatch = urlRegex.exec(editorContent.value)) !== null) { + const url = urlMatch[0]; + const alt = '图片'; + + console.log('📝 找到URL图片:', url); + + existingImages.value.push({ + alt: alt, + url: url, + markdown: `![${alt}](${url})` + }); + } + } + + console.log('📝 解析到已有图片:', existingImages.value.length, '张'); +}; + +// 开始编辑标题 +const startEditTitle = () => { + editingTitleValue.value = editorTitle.value; + isEditingTitle.value = true; + + // 使用nextTick确保DOM更新后再聚焦 + nextTick(() => { + if (titleInput.value) { + titleInput.value.focus(); + titleInput.value.select(); + } + }); +}; + +// 完成编辑标题 +const finishEditTitle = () => { + if (editingTitleValue.value.trim()) { + console.log('📝 完成编辑标题:', editingTitleValue.value.trim()); + editorTitle.value = editingTitleValue.value.trim(); + } + isEditingTitle.value = false; +}; + +// 取消编辑标题 +const cancelEditTitle = () => { + isEditingTitle.value = false; + editingTitleValue.value = ''; +}; + +// 预览已有图片 +const previewExistingImage = (image) => { + console.log('📝 预览已有图片:', image.alt); + openImagePreview(image.url, image.alt); +}; + +// 删除已有图片 +const deleteExistingImage = (index) => { + showDeleteConfirm.value = true; + deleteImageIndex.value = index; +}; + +// 确认删除图片 +const confirmDeleteImage = () => { + const index = deleteImageIndex.value; + + // 从已有图片列表中删除 + existingImages.value.splice(index, 1); + + // 从编辑器内容中删除对应的markdown + if (existingImages.value.length > 0) { + // 重新构建编辑器内容 + let newContent = ''; + existingImages.value.forEach(img => { + newContent += img.markdown + '\n'; + }); + editorContent.value = newContent; + } else { + // 如果没有图片了,只保留文本内容 + const textContent = currentNode.value?.topic || ''; + editorContent.value = textContent; + } + + console.log('📝 已删除图片,剩余图片数量:', existingImages.value.length); + + // 关闭确认弹窗 + showDeleteConfirm.value = false; + deleteImageIndex.value = -1; +}; + +// 取消删除 +const cancelDeleteImage = () => { + showDeleteConfirm.value = false; + deleteImageIndex.value = -1; }; const handleImageFileSelect = (event) => { @@ -551,40 +778,98 @@ const handleFileSelect = (event) => { const imageUrl = e.target.result; const fileName = file.name || 'image'; - // 在光标位置插入图片Markdown - const textarea = document.getElementById('richTextEditor'); - const cursorPos = textarea.selectionStart; + // 直接添加到编辑器内容末尾 const imageMarkdown = `![${fileName}](${imageUrl})\n`; + editorContent.value = editorContent.value + imageMarkdown; - const newContent = editorContent.value.slice(0, cursorPos) + imageMarkdown + editorContent.value.slice(cursorPos); - editorContent.value = newContent; + // 重新解析图片列表 + parseExistingImages(); - // 设置光标位置到插入内容之后 - setTimeout(() => { - textarea.focus(); - textarea.setSelectionRange(cursorPos + imageMarkdown.length, cursorPos + imageMarkdown.length); - }, 0); + console.log('📝 图片已添加到编辑器:', fileName); }; reader.readAsDataURL(file); }; -const saveRichTextChanges = () => { - if (!currentNode.value || !editorContent.value.trim()) { - alert('请输入内容'); +const saveRichTextChanges = async () => { + console.log('🚀 开始保存富文本内容...'); + console.log('📝 当前editorTitle:', editorTitle.value); + console.log('📝 当前节点:', currentNode.value); + + if (!currentNode.value) { + alert('节点信息丢失'); return; } + // 允许保存空内容(比如删除了所有图片的情况) + const contentToSave = editorContent.value.trim() || ''; + // 使用markdown渲染引擎将Markdown转换为HTML - const { renderMarkdownToHTML } = require('../utils/markdownRenderer.js'); - const htmlContent = renderMarkdownToHTML(editorContent.value); + const htmlContent = contentToSave ? renderMarkdownToHTML(contentToSave) : ''; // 更新节点数据 - currentNode.value.topic = editorContent.value; // 保存原始Markdown + // 如果标题有变化,直接更新MindElixir使用的topic字段 + const titleChanged = editorTitle.value !== (currentNode.value.title || ''); + console.log('🔍 标题变化检查:', { + editorTitle: editorTitle.value, + currentNodeTitle: currentNode.value.title || '', + titleChanged: titleChanged + }); + + if (titleChanged) { + // 更新标题,同时保留图片的Markdown语法 + // 从原始topic中提取图片Markdown部分 + const originalTopic = currentNode.value.topic || ''; + const imageMatches = originalTopic.match(/!\[.*?\]\(.*?\)/g); + const imageMarkdown = imageMatches ? imageMatches.join('\n') : ''; + + // 构建新的topic:新标题 + 图片Markdown + const newTopic = editorTitle.value + (imageMarkdown ? '\n' + imageMarkdown : ''); + + currentNode.value.topic = newTopic; + currentNode.value.title = editorTitle.value; + console.log('📝 节点topic已更新:', newTopic); + } else { + // 如果没有标题变化,只更新内容 + currentNode.value.topic = contentToSave; // 保存原始Markdown + } + currentNode.value.dangerouslySetInnerHTML = htmlContent; // 保存渲染后的HTML // 更新DOM元素 if (currentNodeElement.value) { currentNodeElement.value.innerHTML = htmlContent; + + // 如果标题有变化,也更新节点的文本显示 + if (titleChanged) { + // 查找并更新节点文本元素 + const textElement = currentNodeElement.value.querySelector('.tpc'); + if (textElement) { + textElement.textContent = editorTitle.value; + console.log('📝 DOM文本元素已更新:', editorTitle.value); + } + } + } + + // 直接调用后端API保存数据,不依赖operation事件 + try { + const updateData = { + newTitle: editorTitle.value, // 使用更新后的标题 + newDes: currentNode.value.data?.des || "", + newParentId: currentNode.value.parentId || currentNode.value.parent?.id, + newDangerouslySetInnerHTML: htmlContent || "" // 保存富文本内容 + }; + + console.log("🔍 直接发送到后端的更新数据:", updateData); + + const response = await mindmapAPI.updateNode(currentNode.value.id, updateData); + + if (response.data && response.data.success) { + console.log("✅ 节点数据已保存到后端"); + } else { + console.warn("⚠️ 保存节点到后端失败:", response); + } + } catch (error) { + console.error("❌ 保存节点到后端失败:", error); } // 触发操作历史记录,确保数据持久化 @@ -594,18 +879,39 @@ const saveRichTextChanges = () => { origin: currentNode.value.topic || '', }); - // 重新渲染思维导图 - mindElixir.value?.render(); + // 如果标题有变化,触发节点更新事件 + if (titleChanged) { + mindElixir.value?.bus.fire('operation', { + name: 'updateNode', + obj: currentNode.value, + origin: editorTitle.value, + }); + } + + // 关闭编辑器弹窗 + closeRichTextEditor(); + + // 强制刷新思维导图显示 + if (mindElixir.value) { + // 延迟刷新,确保DOM更新完成 + nextTick(() => { + mindElixir.value.refresh(); + console.log('🔄 思维导图已刷新'); + }); + } console.log('✅ 富文本内容已保存'); - closeRichTextEditor(); }; // Markdown预览渲染函数 const renderMarkdownPreview = (markdown) => { if (!markdown) return ''; - const { renderMarkdownToHTML } = require('../utils/markdownRenderer.js'); - return renderMarkdownToHTML(markdown); + try { + return renderMarkdownToHTML(markdown); + } catch (error) { + console.error('渲染markdown失败:', error); + return markdown; // 降级处理,直接返回markdown文本 + } }; const onImageLoad = () => { @@ -5057,7 +5363,7 @@ const updateMindMapRealtime = async (data, title) => { display: flex; justify-content: center; align-items: center; - z-index: 10000; + z-index: 10002; animation: fadeIn 0.3s ease; } @@ -5283,6 +5589,14 @@ const updateMindMapRealtime = async (data, title) => { font-weight: 600; } +.add-image-horizontal { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 8px; + flex-shrink: 0; +} + .file-input-wrapper { margin-bottom: 8px; } @@ -5291,26 +5605,273 @@ const updateMindMapRealtime = async (data, title) => { display: none; } -.file-input-label { - display: inline-block; - padding: 10px 20px; - background: #660874; - color: white; - border-radius: 6px; +.add-image-box { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 15px 10px; + border: 2px dashed #660874; + border-radius: 8px; + background: #fafafa; cursor: pointer; - font-size: 14px; - font-weight: 500; - transition: background 0.2s; - border: none; + transition: all 0.3s ease; + min-height: 60px; + width: 160px; + margin: 0; + box-sizing: border-box; } -.file-input-label:hover { - background: #4d0655; +.add-image-box:hover { + border-color: #4d0655; + background: #f5f5f5; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(102, 8, 116, 0.15); } +.add-image-icon { + font-size: 18px; + color: #660874; + margin-bottom: 4px; + font-weight: 300; + line-height: 1; +} + +.add-image-text { + text-align: center; +} + +.add-image-title { + font-size: 12px; + font-weight: 600; + color: #333; + margin-bottom: 1px; +} + +.add-image-subtitle { + font-size: 10px; + color: #666; + font-weight: 400; +} + +/* 已有图片展示样式 */ +.existing-images-section { + margin-bottom: 16px; +} + +.section-title { + font-size: 14px; + font-weight: 600; + color: #333; + margin: 0 0 8px 0; + padding: 0; +} + +.editable-title { + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; + transition: all 0.2s ease; + border: 1px solid transparent; +} + +.editable-title:hover { + background-color: #f5f5f5; + border-color: #e0e0e0; +} + +.editable-title:active { + background-color: #e8e8e8; +} + +.existing-images-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-bottom: 16px; + align-items: flex-start; + justify-content: flex-start; +} + +.existing-image-container { + display: flex; + flex-direction: column; + gap: 6px; + width: 160px; + flex-shrink: 0; +} + +.existing-image-item { + position: relative; + border-radius: 6px; + overflow: hidden; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid #e0e0e0; +} + +.existing-image-item:hover { + transform: scale(1.05); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); +} + +.existing-image-preview { + width: 100%; + height: 90px; + object-fit: cover; + display: block; +} + +.existing-image-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(102, 8, 116, 0.8); + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s ease; +} + +.existing-image-item:hover .existing-image-overlay { + opacity: 1; +} + +.insert-text { + color: white; + font-size: 11px; + font-weight: 500; + text-align: center; +} + + +.delete-image-btn { + position: absolute; + top: 2px; + right: 2px; + width: 18px; + height: 18px; + border-radius: 50%; + background: #ff4757; + color: white; + border: none; + font-size: 12px; + font-weight: bold; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + transition: all 0.2s ease; + z-index: 10; + line-height: 1; +} + +.delete-image-btn:hover { + background: #ff3742; + transform: scale(1.1); +} + +/* 删除确认弹窗样式 */ +.delete-confirm-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: center; + align-items: center; + z-index: 10003; + animation: fadeIn 0.3s ease; +} + +.delete-confirm-content { + background: white; + border-radius: 12px; + width: 90%; + max-width: 400px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); + overflow: hidden; +} + +.delete-confirm-header { + background: #fff5f5; + padding: 20px 24px 16px; + border-bottom: 1px solid #ffe6e6; +} + +.delete-confirm-header h3 { + margin: 0; + font-size: 18px; + font-weight: 600; + color: #dc2626; +} + +.delete-confirm-body { + padding: 24px; +} + +.delete-confirm-body p { + margin: 0 0 8px 0; + font-size: 16px; + color: #374151; +} + +.delete-warning { + font-size: 14px; + color: #ef4444; + font-weight: 500; +} + +.delete-confirm-footer { + padding: 16px 24px 24px; + display: flex; + justify-content: flex-end; + gap: 12px; +} + +.cancel-btn { + padding: 10px 20px; + border: 1px solid #d1d5db; + background: white; + color: #374151; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.cancel-btn:hover { + background: #f9fafb; + border-color: #9ca3af; +} + +.confirm-btn { + padding: 10px 20px; + border: none; + background: #dc2626; + color: white; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.confirm-btn:hover { + background: #b91c1c; +} + + .file-hint { margin: 0; - font-size: 12px; + font-size: 10px; color: #666; } @@ -5403,7 +5964,7 @@ const updateMindMapRealtime = async (data, title) => { background: white; border-radius: 12px; width: 95%; - max-width: 1000px; + max-width: 700px; max-height: 85vh; overflow: hidden; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); @@ -5427,6 +5988,24 @@ const updateMindMapRealtime = async (data, title) => { color: #333; } +.rich-text-editor-title-input { + font-size: 16px; + font-weight: 600; + color: #333; + border: 2px solid #660874; + border-radius: 4px; + padding: 4px 8px; + background: white; + outline: none; + margin: 0; + min-width: 200px; +} + +.rich-text-editor-title-input:focus { + border-color: #4d0655; + box-shadow: 0 0 0 2px rgba(102, 8, 116, 0.2); +} + .rich-text-editor-close { background: none; border: none; diff --git a/frontend/src/lib/mind-elixir/src/mouse.ts b/frontend/src/lib/mind-elixir/src/mouse.ts index 9b609aa..f1651a9 100644 --- a/frontend/src/lib/mind-elixir/src/mouse.ts +++ b/frontend/src/lib/mind-elixir/src/mouse.ts @@ -54,10 +54,16 @@ export default function (mind: MindElixirInstance) { const imageUrl = img.src const altText = img.alt || img.title || '' - console.log('🖼️ 双击图片节点,准备预览:', { imageUrl, altText }) + console.log('🖼️ 双击图片节点,准备编辑:', { imageUrl, altText }) - // 双击图片触发预览事件 - mind.bus.fire('showImagePreview', imageUrl, altText) + // 双击图片触发富文本编辑事件(可以编辑图片) + const topicElement = target.closest('.me-tpc') as Topic + if (topicElement && topicElement.nodeObj) { + console.log('🖼️ 找到图片所在的节点:', topicElement.nodeObj) + mind.bus.fire('showRichTextEditor', topicElement.nodeObj, topicElement) + } else { + console.warn('🖼️ 未找到图片所在的节点') + } return } @@ -77,8 +83,9 @@ export default function (mind: MindElixirInstance) { // 检查节点是否有MindElixir原生图片 if (topic.nodeObj?.image) { const imageUrl = typeof topic.nodeObj.image === 'string' ? topic.nodeObj.image : topic.nodeObj.image.url - console.log('🖼️ 双击包含原生图片的节点,准备预览:', imageUrl) - mind.bus.fire('showImagePreview', imageUrl, topic.nodeObj.topic || '') + console.log('🖼️ 双击包含原生图片的节点,准备编辑:', imageUrl) + // 双击包含原生图片的节点触发富文本编辑事件 + mind.bus.fire('showRichTextEditor', topic.nodeObj, topic) return } diff --git a/frontend/src/utils/markdownRenderer.js b/frontend/src/utils/markdownRenderer.js index 1138c36..0fd8464 100644 --- a/frontend/src/utils/markdownRenderer.js +++ b/frontend/src/utils/markdownRenderer.js @@ -19,11 +19,42 @@ const renderer = new marked.Renderer(); // 自定义图片渲染器 renderer.image = function(href, title, text) { + console.log('🖼️ 图片渲染器被调用:', { href, title, text, hrefType: typeof href }); + + // 确保href是字符串类型,处理各种异常情况 + let hrefStr = ''; + + if (typeof href === 'string') { + hrefStr = href; + } else if (href === null || href === undefined) { + console.warn('图片href为null或undefined'); + return `
图片链接为空
`; + } else if (typeof href === 'object') { + // 如果是对象,尝试提取URL + if (href.href) { + hrefStr = String(href.href); + } else if (href.url) { + hrefStr = String(href.url); + } else if (href.src) { + hrefStr = String(href.src); + } else { + console.warn('图片href是对象但没有href、url或src属性:', href); + return `
图片链接格式错误
`; + } + } else if (typeof href.toString === 'function') { + hrefStr = href.toString(); + } else { + console.warn('图片href无法转换为字符串:', typeof href, href); + return `
图片链接格式错误
`; + } + + console.log('🖼️ 处理后的hrefStr:', hrefStr); + // 处理图片URL,确保能正确显示 - let processedUrl = href; - if (href.includes('cdn-mineru.openxlab.org.cn')) { + let processedUrl = hrefStr; + if (hrefStr.includes('cdn-mineru.openxlab.org.cn')) { // 将外部CDN URL转换为代理URL - const urlPath = href.replace('https://cdn-mineru.openxlab.org.cn', ''); + const urlPath = hrefStr.replace('https://cdn-mineru.openxlab.org.cn', ''); processedUrl = `/proxy-image${urlPath}`; } @@ -31,6 +62,8 @@ renderer.image = function(href, title, text) { const altText = text || '图片'; const titleAttr = title ? ` title="${title}"` : ''; + console.log('🖼️ 最终生成的图片HTML:', `${altText}`); + return `${altText}`; };