diff --git a/frontend/src/lib/mind-elixir/src/index.less b/frontend/src/lib/mind-elixir/src/index.less index fc78e70..4b68f81 100644 --- a/frontend/src/lib/mind-elixir/src/index.less +++ b/frontend/src/lib/mind-elixir/src/index.less @@ -235,11 +235,61 @@ max-width: 35em; white-space: pre-wrap; pointer-events: all; - // 确保整个节点内容居中对齐 + // 默认居中对齐,但会根据是否有图片动态调整 text-align: center !important; flex-direction: column !important; align-items: center !important; justify-content: center !important; + + // 没有图片的节点使用左对齐 + &.no-image { + text-align: left !important; + align-items: flex-start !important; + + .text { + text-align: left !important; + } + + ul, ol { + text-align: left !important; + padding-left: 20px !important; + list-style-position: outside !important; + } + + li { + text-align: left !important; + list-style-position: outside !important; + } + + p, span, div, strong, em { + text-align: left !important; + } + } + + // 有图片的节点保持居中对齐 + &.has-image { + text-align: center !important; + align-items: center !important; + + .text { + text-align: center !important; + } + + ul, ol { + text-align: center !important; + padding-left: 0 !important; + list-style-position: inside !important; + } + + li { + text-align: center !important; + list-style-position: inside !important; + } + + p, span, div, strong, em { + text-align: center !important; + } + } box-sizing: border-box !important; // 确保节点有固定的最小高度,避免内容变化导致尺寸不稳定 min-height: 2em !important; @@ -260,7 +310,6 @@ } & > .text { display: block !important; - text-align: center !important; // 确保文本居中对齐,避免与连线错位 width: 100% !important; // 确保文本容器占满节点宽度 margin: 0 auto !important; // 强制居中 // 确保文本不会导致节点尺寸不稳定 diff --git a/frontend/src/lib/mind-elixir/src/plugin/exportImage.ts b/frontend/src/lib/mind-elixir/src/plugin/exportImage.ts index a0e1702..1b775b6 100644 --- a/frontend/src/lib/mind-elixir/src/plugin/exportImage.ts +++ b/frontend/src/lib/mind-elixir/src/plugin/exportImage.ts @@ -35,16 +35,36 @@ function generateSvgText(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: num const paddingTop = parseInt(tpcStyle.paddingTop) || 8 const paddingLeft = parseInt(tpcStyle.paddingLeft) || 8 - // 计算节点宽度用于居中 + // 计算节点宽度和文本起始位置 const nodeWidth = tpc.offsetWidth || 200 - const centerX = x + nodeWidth / 2 + const textX = x + paddingLeft // 纯文本节点使用左对齐 + + // 计算实际需要的内容高度 + const contentHeight = (lines.length - 1) * lineHeight + fontSize + paddingTop + (parseInt(tpcStyle.paddingBottom) || 8) + const nodeHeight = tpc.offsetHeight || 100 + const actualHeight = Math.min(contentHeight, nodeHeight) // 不超过原始节点高度 + + // 创建背景矩形,使用计算出的实际高度 + const bg = document.createElementNS(ns, 'rect') + setAttributes(bg, { + x: x + '', + y: y + '', + rx: tpcStyle.borderRadius || '8', + ry: tpcStyle.borderRadius || '8', + width: nodeWidth + 'px', + height: actualHeight + 'px', + fill: tpcStyle.backgroundColor || 'white', + stroke: tpcStyle.borderColor || '#ccc', + 'stroke-width': tpcStyle.borderWidth || '1', + }) + g.appendChild(bg) lines.forEach((line, index) => { const text = document.createElementNS(ns, 'text') setAttributes(text, { - x: centerX + '', + x: textX + '', y: y + paddingTop + fontSize + (lineHeight * index) + '', - 'text-anchor': 'middle', + 'text-anchor': 'start', 'font-family': tpcStyle.fontFamily, 'font-size': `${tpcStyle.fontSize}`, 'font-weight': `${tpcStyle.fontWeight}`, @@ -53,6 +73,13 @@ function generateSvgText(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: num text.innerHTML = line g.appendChild(text) }) + + console.log('✅ generateSvgText优化了底部间距:', { + originalHeight: nodeHeight, + actualHeight: actualHeight, + linesCount: lines.length + }) + return g } @@ -331,6 +358,12 @@ function cleanHtmlForSvg(html: string): string { // 移除其他可能导致SVG解析错误的标签 cleanedHtml = cleanedHtml.replace(//gi, ' ') + // 清理重复的格式标记 + cleanedHtml = cleanedHtml.replace(/•\s*【/g, '【') + cleanedHtml = cleanedHtml.replace(/•\s*\[/g, '[') + cleanedHtml = cleanedHtml.replace(/•\s*(/g, '(') + cleanedHtml = cleanedHtml.replace(/•\s*\(/g, '(') + // 确保所有标签都正确闭合 cleanedHtml = cleanedHtml.replace(/<([^>]+)>/g, (match, tagContent) => { // 检查是否是自闭合标签 @@ -403,12 +436,18 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD const nodeWidth = rect.width const nodeHeight = rect.height + // 检查是否包含任何图片 + const hasImages = cleanedHtml.includes(' 0) { const fontSize = parseFloat(tpcStyle.fontSize) || 14 const lineHeight = fontSize * 1.4 - const centerX = x + nodeWidth / 2 - const startY = y + fontSize + (parseInt(tpcStyle.paddingTop) || 8) + const paddingTop = parseInt(tpcStyle.paddingTop) || 8 + const paddingBottom = parseInt(tpcStyle.paddingBottom) || 8 + const paddingLeft = parseInt(tpcStyle.paddingLeft) || 8 + + // 计算实际需要的内容高度 + const contentHeight = (lines.length - 1) * lineHeight + fontSize + paddingTop + paddingBottom + const actualHeight = Math.min(contentHeight, nodeHeight) // 不超过原始节点高度 + + // 检查是否包含图片 + const hasImages = cleanedHtml.includes(' { const text = document.createElementNS(ns, 'text') setAttributes(text, { - x: centerX + '', + x: textX + '', y: startY + (lineHeight * index) + '', - 'text-anchor': 'middle', + 'text-anchor': hasImages ? 'middle' : 'start', 'font-family': tpcStyle.fontFamily, 'font-size': tpcStyle.fontSize, 'font-weight': tpcStyle.fontWeight, @@ -453,8 +502,27 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD g.appendChild(text) }) + // 创建背景矩形,使用计算出的实际高度 + const bg = document.createElementNS(ns, 'rect') + setAttributes(bg, { + x: x + '', + y: y + '', + rx: tpcStyle.borderRadius || '8', + ry: tpcStyle.borderRadius || '8', + width: nodeWidth + 'px', + height: actualHeight + 'px', + fill: tpcStyle.backgroundColor || 'white', + stroke: tpcStyle.borderColor || '#ccc', + 'stroke-width': tpcStyle.borderWidth || '1', + }) + g.insertBefore(bg, g.firstChild) // 将背景放在最前面 + document.body.removeChild(tempDiv) - console.log('✅ 使用原生SVG文本渲染成功') + console.log('✅ 使用原生SVG文本渲染成功,优化了底部间距:', { + originalHeight: nodeHeight, + actualHeight: actualHeight, + linesCount: lines.length + }) return g } @@ -493,12 +561,12 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD padding: ${paddingTop}px ${paddingRight}px ${paddingBottom}px ${paddingLeft}px; box-sizing: border-box; overflow: visible; - text-align: center !important; + text-align: ${hasAnyImage ? 'center' : 'left'} !important; line-height: 1.4; display: flex; flex-direction: column; justify-content: flex-start; - align-items: center; + align-items: ${hasAnyImage ? 'center' : 'flex-start'}; position: relative; ` @@ -536,9 +604,9 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD const htmlList = list as HTMLElement htmlList.style.cssText = ` margin: 2px 0 4px 0 !important; - padding-left: 0 !important; - text-align: center !important; - list-style-position: inside !important; + padding-left: ${hasAnyImage ? '0' : '20px'} !important; + text-align: ${hasAnyImage ? 'center' : 'left'} !important; + list-style-position: ${hasAnyImage ? 'inside' : 'outside'} !important; ` }) @@ -549,12 +617,12 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD margin: 1px 0 !important; line-height: 1.2 !important; padding: 0 !important; - text-align: center !important; - list-style-position: inside !important; + text-align: ${hasAnyImage ? 'center' : 'left'} !important; + list-style-position: ${hasAnyImage ? 'inside' : 'outside'} !important; ` }) - // 为段落文本添加居中样式 + // 为段落文本添加样式 const paragraphs = div.querySelectorAll('p') paragraphs.forEach(p => { const htmlP = p as HTMLElement @@ -562,27 +630,17 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD margin: 2px 0 !important; line-height: 1.4 !important; padding: 0 !important; - text-align: center !important; + text-align: ${hasAnyImage ? 'center' : 'left'} !important; ` }) - // 为所有文本元素添加居中样式 + // 为所有文本元素添加样式 const textElements = div.querySelectorAll('span, div, strong, em') textElements.forEach(element => { const htmlElement = element as HTMLElement - htmlElement.style.textAlign = 'center !important' + htmlElement.style.textAlign = `${hasAnyImage ? 'center' : 'left'} !important` }) - // 添加全局CSS样式确保所有内容居中 - const style = document.createElement('style') - style.textContent = ` - * { - text-align: center !important; - margin-left: auto !important; - margin-right: auto !important; - } - ` - div.appendChild(style) // 为图片元素添加特殊样式处理,彻底解决图片压线问题 const images = div.querySelectorAll('img') @@ -637,11 +695,12 @@ function createElBox(mei: MindElixirInstance, tpc: Topic) { const tpcStyle = getComputedStyle(tpc) const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc) - // 检查是否有HTML内容或节点图片,如果有则增加高度 - const tpcWithNodeObj = tpc as Topic - const hasHTMLContent = tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML - const hasImages = hasHTMLContent && tpcWithNodeObj.nodeObj.dangerouslySetInnerHTML?.includes('