修复SVG导出重复rect标签问题并恢复表格项内部高度设置
This commit is contained in:
parent
cd9b1f5a38
commit
4af977e33a
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
File diff suppressed because one or more lines are too long
|
|
@ -23,8 +23,8 @@
|
|||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/index-df04209b.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-bc28fb44.css">
|
||||
<script type="module" crossorigin src="/assets/index-3e4825f7.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index-0f0d7625.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -235,16 +235,11 @@
|
|||
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;
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
justify-content: flex-start !important;
|
||||
|
||||
.text {
|
||||
text-align: left !important;
|
||||
|
|
@ -264,32 +259,6 @@
|
|||
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;
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class TableToSVGConverter {
|
|||
const totalWidth = columnWidths.reduce((sum, width) => sum + width, 0)
|
||||
|
||||
// 计算行高,考虑多行文本
|
||||
this.cellHeight = Math.max(35, this.fontSize * 2) // 增加行高
|
||||
this.cellHeight = Math.max(35, this.fontSize * 2) // 恢复原来的行高
|
||||
|
||||
// 为每行计算实际高度(考虑多行文本)
|
||||
const rowHeights: number[] = new Array(maxRows).fill(this.cellHeight)
|
||||
|
|
@ -198,6 +198,17 @@ class TableToSVGConverter {
|
|||
|
||||
const totalHeight = rowHeights.reduce((sum, height) => sum + height, 0)
|
||||
|
||||
console.log('📊 TableToSVGConverter calculateLayout 计算结果:', {
|
||||
maxCols,
|
||||
maxRows,
|
||||
totalWidth,
|
||||
totalHeight,
|
||||
rowHeights,
|
||||
columnWidths,
|
||||
cellHeight: this.cellHeight,
|
||||
fontSize: this.fontSize
|
||||
})
|
||||
|
||||
return {
|
||||
totalWidth,
|
||||
totalHeight,
|
||||
|
|
@ -224,6 +235,9 @@ class TableToSVGConverter {
|
|||
'stroke-width': '1'
|
||||
})
|
||||
svgGroup.appendChild(tableRect)
|
||||
console.log('📊 TableToSVGConverter 创建表格背景rect:', {
|
||||
x, y, width: layout.totalWidth, height: layout.totalHeight
|
||||
})
|
||||
|
||||
// 绘制垂直网格线(使用动态列宽)
|
||||
let currentX = x
|
||||
|
|
@ -380,6 +394,7 @@ function cleanHtmlForSvg(html: string): string {
|
|||
}
|
||||
|
||||
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number) {
|
||||
console.log('🚀 generateSvgTextUsingForeignObject 被调用', tpc.textContent?.substring(0, 50))
|
||||
const g = document.createElementNS(ns, 'g')
|
||||
|
||||
// 检查内容来源
|
||||
|
|
@ -426,7 +441,7 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
}
|
||||
}
|
||||
|
||||
if (hasHTMLContent || hasTableContent) {
|
||||
if (hasHTMLContent && !hasTableContent) {
|
||||
// 清理HTML内容,修复SVG解析错误
|
||||
const cleanedHtml = cleanHtmlForSvg(htmlContent)
|
||||
console.log('🔍 清理后的HTML内容:', cleanedHtml.substring(0, 200))
|
||||
|
|
@ -441,13 +456,17 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
const hasNodeImage = tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.image
|
||||
const hasAnyImage = hasImages || hasNodeImage
|
||||
|
||||
console.log('🔍 使用getBoundingClientRect获取尺寸:', {
|
||||
console.log('🔍 图片检测详情:', {
|
||||
nodeWidth,
|
||||
nodeHeight,
|
||||
offsetWidth: tpc.offsetWidth,
|
||||
offsetHeight: tpc.offsetHeight,
|
||||
rect: rect,
|
||||
hasAnyImage: hasAnyImage
|
||||
hasImages: hasImages,
|
||||
hasNodeImage: hasNodeImage,
|
||||
hasAnyImage: hasAnyImage,
|
||||
cleanedHtml: cleanedHtml.substring(0, 100),
|
||||
nodeObj: tpcWithNodeObj.nodeObj
|
||||
})
|
||||
|
||||
// 尝试使用原生SVG文本渲染
|
||||
|
|
@ -482,7 +501,7 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
const contentHeight = (lines.length - 1) * lineHeight + fontSize + paddingTop + paddingBottom
|
||||
const actualHeight = Math.min(contentHeight, nodeHeight) // 不超过原始节点高度
|
||||
|
||||
// 检查是否包含图片
|
||||
// 根据是否有图片决定对齐方式
|
||||
const hasImages = cleanedHtml.includes('<img')
|
||||
const textX = hasImages ? x + nodeWidth / 2 : x + paddingLeft
|
||||
const startY = y + fontSize + paddingTop
|
||||
|
|
@ -561,12 +580,12 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
padding: ${paddingTop}px ${paddingRight}px ${paddingBottom}px ${paddingLeft}px;
|
||||
box-sizing: border-box;
|
||||
overflow: visible;
|
||||
text-align: ${hasAnyImage ? 'center' : 'left'} !important;
|
||||
text-align: center !important;
|
||||
line-height: 1.4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: ${hasAnyImage ? 'center' : 'flex-start'};
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
|
|
@ -604,9 +623,9 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
const htmlList = list as HTMLElement
|
||||
htmlList.style.cssText = `
|
||||
margin: 2px 0 4px 0 !important;
|
||||
padding-left: ${hasAnyImage ? '0' : '20px'} !important;
|
||||
text-align: ${hasAnyImage ? 'center' : 'left'} !important;
|
||||
list-style-position: ${hasAnyImage ? 'inside' : 'outside'} !important;
|
||||
padding-left: 0 !important;
|
||||
text-align: center !important;
|
||||
list-style-position: inside !important;
|
||||
`
|
||||
})
|
||||
|
||||
|
|
@ -617,8 +636,8 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
margin: 1px 0 !important;
|
||||
line-height: 1.2 !important;
|
||||
padding: 0 !important;
|
||||
text-align: ${hasAnyImage ? 'center' : 'left'} !important;
|
||||
list-style-position: ${hasAnyImage ? 'inside' : 'outside'} !important;
|
||||
text-align: center !important;
|
||||
list-style-position: inside !important;
|
||||
`
|
||||
})
|
||||
|
||||
|
|
@ -630,7 +649,7 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
margin: 2px 0 !important;
|
||||
line-height: 1.4 !important;
|
||||
padding: 0 !important;
|
||||
text-align: ${hasAnyImage ? 'center' : 'left'} !important;
|
||||
text-align: center !important;
|
||||
`
|
||||
})
|
||||
|
||||
|
|
@ -638,9 +657,25 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
const textElements = div.querySelectorAll('span, div, strong, em')
|
||||
textElements.forEach(element => {
|
||||
const htmlElement = element as HTMLElement
|
||||
htmlElement.style.textAlign = `${hasAnyImage ? 'center' : 'left'} !important`
|
||||
htmlElement.style.cssText = `
|
||||
text-align: center !important;
|
||||
display: block !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
`
|
||||
})
|
||||
|
||||
// 添加全局样式覆盖,确保所有元素都应用正确的对齐方式
|
||||
const globalStyle = document.createElement('style')
|
||||
globalStyle.textContent = `
|
||||
* {
|
||||
text-align: center !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
`
|
||||
div.appendChild(globalStyle)
|
||||
|
||||
|
||||
// 为图片元素添加特殊样式处理,彻底解决图片压线问题
|
||||
const images = div.querySelectorAll('img')
|
||||
|
|
@ -681,8 +716,24 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
})
|
||||
|
||||
foreignObject.appendChild(div)
|
||||
|
||||
// 为ForeignObject创建背景rect
|
||||
const bg = document.createElementNS(ns, 'rect')
|
||||
setAttributes(bg, {
|
||||
x: x + '',
|
||||
y: y + '',
|
||||
rx: tpcStyle.borderRadius || '8',
|
||||
ry: tpcStyle.borderRadius || '8',
|
||||
width: nodeWidth + 'px',
|
||||
height: nodeHeight + 'px',
|
||||
fill: tpcStyle.backgroundColor || 'white',
|
||||
stroke: tpcStyle.borderColor || '#ccc',
|
||||
'stroke-width': tpcStyle.borderWidth || '1',
|
||||
})
|
||||
g.insertBefore(bg, g.firstChild) // 将背景放在最前面
|
||||
|
||||
g.appendChild(foreignObject)
|
||||
console.log('✅ 使用foreignObject渲染HTML内容')
|
||||
console.log('✅ 使用foreignObject渲染HTML内容,hasAnyImage:', hasAnyImage, 'text-align:', hasAnyImage ? 'center' : 'left')
|
||||
} else {
|
||||
// 对于纯文本内容,使用原生SVG文本渲染
|
||||
return generateSvgText(tpc, tpcStyle, x, y)
|
||||
|
|
@ -692,32 +743,13 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
|||
}
|
||||
|
||||
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('<img')
|
||||
const hasNodeImage = tpcWithNodeObj.nodeObj && tpcWithNodeObj.nodeObj.image
|
||||
const hasAnyImage = hasImages || hasNodeImage // 判断是否包含任何图片
|
||||
|
||||
const bg = document.createElementNS(ns, 'rect')
|
||||
const rect = tpc.getBoundingClientRect()
|
||||
setAttributes(bg, {
|
||||
x: x + '',
|
||||
y: y + '',
|
||||
rx: tpcStyle.borderRadius,
|
||||
ry: tpcStyle.borderRadius,
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px',
|
||||
fill: tpcStyle.backgroundColor,
|
||||
stroke: tpcStyle.borderColor,
|
||||
'stroke-width': tpcStyle.borderWidth,
|
||||
})
|
||||
return bg
|
||||
// 不再创建背景rect,避免与convertDivToSvg重复创建
|
||||
// 背景rect现在由convertDivToSvg统一管理
|
||||
console.log('📦 createElBox 被调用,但不创建背景rect以避免重复')
|
||||
return null
|
||||
}
|
||||
function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignObject = false) {
|
||||
console.log('🔄 convertDivToSvg 被调用,useForeignObject:', useForeignObject, 'tpc内容:', tpc.textContent?.substring(0, 50))
|
||||
const tpcStyle = getComputedStyle(tpc)
|
||||
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc)
|
||||
|
||||
|
|
@ -727,35 +759,50 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
|
|||
const hasImages2 = hasHTMLContent2 && tpcWithNodeObj2.nodeObj.dangerouslySetInnerHTML?.includes('<img')
|
||||
const hasNodeImage2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.image
|
||||
|
||||
const bg = document.createElementNS(ns, 'rect')
|
||||
const rect = tpc.getBoundingClientRect()
|
||||
setAttributes(bg, {
|
||||
x: x + '',
|
||||
y: y + '',
|
||||
rx: tpcStyle.borderRadius,
|
||||
ry: tpcStyle.borderRadius,
|
||||
width: rect.width + 'px',
|
||||
height: rect.height + 'px',
|
||||
fill: tpcStyle.backgroundColor,
|
||||
stroke: tpcStyle.borderColor,
|
||||
'stroke-width': tpcStyle.borderWidth,
|
||||
})
|
||||
const g = document.createElementNS(ns, 'g')
|
||||
g.appendChild(bg)
|
||||
|
||||
let text: SVGGElement | null = null
|
||||
|
||||
// 检查是否有dangerouslySetInnerHTML内容
|
||||
const tpcWithNodeObj3 = tpc as Topic
|
||||
const hasTableContent3 = tpcWithNodeObj3.nodeObj && tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML && tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML.includes('<table')
|
||||
|
||||
console.log('🔍 convertDivToSvg 表格检测:', {
|
||||
hasNodeObj: !!tpcWithNodeObj3.nodeObj,
|
||||
hasDangerouslySetInnerHTML: !!(tpcWithNodeObj3.nodeObj && tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML),
|
||||
hasTableContent3: hasTableContent3,
|
||||
content: tpcWithNodeObj3.nodeObj?.dangerouslySetInnerHTML?.substring(0, 100)
|
||||
})
|
||||
|
||||
if (tpcWithNodeObj3.nodeObj && tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML) {
|
||||
console.log('🔍 处理dangerouslySetInnerHTML内容:', tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML.substring(0, 200))
|
||||
// 对于dangerouslySetInnerHTML,使用ForeignObject
|
||||
|
||||
if (hasTableContent3) {
|
||||
console.log('✅ 检测到表格内容,使用TableToSVGConverter')
|
||||
// 对于表格内容,直接使用TableToSVGConverter,避免重复的背景rect
|
||||
const tempDiv = document.createElement('div')
|
||||
tempDiv.innerHTML = tpcWithNodeObj3.nodeObj.dangerouslySetInnerHTML
|
||||
const table = tempDiv.querySelector('table') as HTMLTableElement
|
||||
|
||||
if (table) {
|
||||
const fontSize = parseFloat(tpcStyle.fontSize) || 14
|
||||
const fontFamily = tpcStyle.fontFamily || 'Arial, sans-serif'
|
||||
|
||||
const converter = new TableToSVGConverter(table, fontSize, fontFamily)
|
||||
const tableSVG = converter.convert(x, y)
|
||||
|
||||
// 直接返回表格SVG,不创建额外的背景rect
|
||||
return tableSVG
|
||||
}
|
||||
}
|
||||
|
||||
// 对于非表格的dangerouslySetInnerHTML,使用ForeignObject
|
||||
text = generateSvgTextUsingForeignObject(tpc, tpcStyle, x, y)
|
||||
} else if (useForeignObject) {
|
||||
// 对于其他HTML内容,也使用ForeignObject
|
||||
text = generateSvgTextUsingForeignObject(tpc, tpcStyle, x, y)
|
||||
} else {
|
||||
// 对于普通文本内容
|
||||
// 对于普通文本内容,generateSvgText已经创建了背景rect,这里不需要再创建
|
||||
console.log('📝 处理普通文本内容,generateSvgText将创建背景rect')
|
||||
text = generateSvgText(tpc, tpcStyle, x, y)
|
||||
}
|
||||
|
||||
|
|
@ -936,6 +983,7 @@ const padding = 100
|
|||
const head = `<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">`
|
||||
|
||||
const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) => {
|
||||
console.log('🎯 generateSvg 开始执行,noForeignObject:', noForeignObject)
|
||||
const mapDiv = mei.nodes
|
||||
const height = mapDiv.offsetHeight + padding * 2
|
||||
const width = mapDiv.offsetWidth + padding * 2
|
||||
|
|
@ -967,8 +1015,12 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
|
|||
|
||||
// 处理所有节点
|
||||
const nodePromises = Array.from(mapDiv.querySelectorAll<Topic>('me-tpc')).map(async (tpc) => {
|
||||
// 首先创建节点背景
|
||||
g.appendChild(createElBox(mei, tpc))
|
||||
// 不再通过createElBox创建背景,避免重复rect
|
||||
// 背景现在由convertDivToSvg统一管理
|
||||
const bgBox = createElBox(mei, tpc)
|
||||
if (bgBox) {
|
||||
g.appendChild(bgBox)
|
||||
}
|
||||
|
||||
// 检查节点内容类型
|
||||
const hasDangerouslySetInnerHTML = tpc.nodeObj.dangerouslySetInnerHTML
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const downloadExport = (type: 'svg') => {
|
|||
}
|
||||
`
|
||||
|
||||
const blob = await mind.exportSvg(false, style)
|
||||
const blob = await mind.exportSvg(true, style)
|
||||
|
||||
if (!blob) {
|
||||
console.error('导出失败:无法生成文件')
|
||||
|
|
|
|||
|
|
@ -44,14 +44,9 @@ export const shapeTpc = function (this: MindElixirInstance, tpc: Topic, nodeObj:
|
|||
const hasNodeImage = !!nodeObj.image
|
||||
const hasAnyImage = hasImages || hasNodeImage
|
||||
|
||||
// 应用智能文本对齐样式
|
||||
if (hasAnyImage) {
|
||||
tpc.classList.add('has-image')
|
||||
tpc.classList.remove('no-image')
|
||||
} else {
|
||||
// 统一使用左对齐样式,简化逻辑
|
||||
tpc.classList.add('no-image')
|
||||
tpc.classList.remove('has-image')
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -102,15 +97,10 @@ export const shapeTpc = function (this: MindElixirInstance, tpc: Topic, nodeObj:
|
|||
const hasImageInText = content.includes('<img')
|
||||
const hasAnyImage = hasNodeImage || hasImageInText
|
||||
|
||||
// 应用智能文本对齐样式
|
||||
if (hasAnyImage) {
|
||||
tpc.classList.add('has-image')
|
||||
tpc.classList.remove('no-image')
|
||||
} else {
|
||||
// 统一使用左对齐样式,简化逻辑
|
||||
tpc.classList.add('no-image')
|
||||
tpc.classList.remove('has-image')
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeObj.hyperLink) {
|
||||
const linkEl = $d.createElement('a')
|
||||
|
|
|
|||
Loading…
Reference in New Issue