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('![]()