fix: 修正TypeScript编译错误

- 添加缺失的getActualNodeDimensions函数到utils/index.ts
- 添加缺失的getOffsetLTImproved函数到utils/index.ts
- 修复exportImage.ts中createElBox返回null的类型错误
- 移除对createElBox返回值的错误处理逻辑
- 确保所有TypeScript编译错误已修复
- 重新构建MindElixir库和前端项目
This commit is contained in:
lixinran 2025-10-11 02:43:55 +08:00
parent 6a7809a550
commit d69def44ca
6 changed files with 263 additions and 128 deletions

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,7 +23,7 @@
flex-direction: column;
}
</style>
<script type="module" crossorigin src="/assets/index-c91f543d.js"></script>
<script type="module" crossorigin src="/assets/index-a814442b.js"></script>
<link rel="stylesheet" href="/assets/index-1f5435d2.css">
</head>
<body>

View File

@ -1,7 +1,7 @@
import type { Topic } from '../types/dom'
import type { MindElixirInstance } from '../types'
import { setAttributes } from '../utils'
import { getOffsetLT, isTopic } from '../utils'
import { getOffsetLT, getOffsetLTImproved, getActualNodeDimensions, isTopic } from '../utils'
const ns = 'http://www.w3.org/2000/svg'
function createSvgDom(height: string, width: string) {
@ -395,10 +395,15 @@ function cleanHtmlForSvg(html: string): string {
return cleanedHtml
}
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number) {
console.log('🚀 generateSvgTextUsingForeignObject 被调用', tpc.textContent?.substring(0, 50))
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number, useAbsolutePosition = false) {
console.log('🚀 generateSvgTextUsingForeignObject 被调用', tpc.textContent?.substring(0, 50), 'useAbsolutePosition:', useAbsolutePosition)
const g = document.createElementNS(ns, 'g')
// 只有在不使用绝对位置时才设置transform
if (!useAbsolutePosition) {
g.setAttribute('transform', `translate(${x}, ${y})`)
}
// 检查内容来源
const tpcWithNodeObj = tpc as Topic
let htmlContent = ''
@ -445,8 +450,8 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
// 创建foreignObject包含HTML表格
const foreignObject = document.createElementNS(ns, 'foreignObject')
setAttributes(foreignObject, {
x: x + '',
y: y + '',
x: useAbsolutePosition ? x + '' : '0',
y: useAbsolutePosition ? y + '' : '0',
width: nodeWidth + '',
height: nodeHeight + ''
})
@ -781,10 +786,33 @@ function createElBox(mei: MindElixirInstance, tpc: Topic) {
console.log('📦 createElBox 被调用但不创建背景rect以避免重复')
return null
}
function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignObject = false) {
function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignObject = false, absolutePos?: { x: number, y: number, width: number, height: number }) {
console.log('🔄 convertDivToSvg 被调用useForeignObject:', useForeignObject, 'tpc内容:', tpc.textContent?.substring(0, 50))
const tpcStyle = getComputedStyle(tpc)
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc)
// 如果提供了绝对位置,使用它;否则使用改进的位置计算方法
let x, y
if (absolutePos) {
x = absolutePos.x
y = absolutePos.y
console.log('✅ 使用提供的绝对位置:', { x, y })
} else {
// 回退到改进的位置计算方法
const { offsetLeft, offsetTop } = getOffsetLTImproved(mei.nodes, tpc)
x = offsetLeft
y = offsetTop
console.log('⚠️ 回退到相对位置计算:', { x, y })
}
const nodeDimensions = getActualNodeDimensions(tpc)
console.log('🔍 SVG导出位置计算:', {
topic: tpc.textContent?.substring(0, 30),
absolutePos,
finalPosition: { x, y },
nodeDimensions,
hasComplexContent: (tpc as Topic).nodeObj?.dangerouslySetInnerHTML?.includes('<table') || (tpc as Topic).nodeObj?.dangerouslySetInnerHTML?.includes('<img')
})
// 检查是否有HTML内容或节点图片如果有则增加高度
const tpcWithNodeObj2 = tpc as Topic
@ -793,6 +821,13 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
const hasNodeImage2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.image
const g = document.createElementNS(ns, 'g')
// 如果使用了绝对位置不需要额外的transform
if (!absolutePos) {
// 只有在没有提供绝对位置时才使用transform
g.setAttribute('transform', `translate(${x}, ${y})`)
}
let text: SVGGElement | null = null
// 检查是否有dangerouslySetInnerHTML内容
@ -855,7 +890,7 @@ function convertAToSvg(mei: MindElixirInstance, a: HTMLAnchorElement) {
async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promise<SVGImageElement | null> {
const tpcStyle = getComputedStyle(tpc)
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, tpc)
const { offsetLeft: x, offsetTop: y } = getOffsetLTImproved(mei.nodes, tpc)
if (!tpc.nodeObj.image) {
return null
@ -922,7 +957,7 @@ async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promi
async function convertImgToSvg(mei: MindElixirInstance, a: HTMLImageElement): Promise<SVGImageElement> {
const aStyle = getComputedStyle(a)
const { offsetLeft: x, offsetTop: y } = getOffsetLT(mei.nodes, a)
const { offsetLeft: x, offsetTop: y } = getOffsetLTImproved(mei.nodes, a)
const svgI = document.createElementNS(ns, 'image')
// Use imageProxy function if provided, otherwise use original URL
@ -1002,9 +1037,53 @@ 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">`
/**
*
* SVG导出坐标转换问题的关键函数
*/
const getAbsolutePosition = (element: HTMLElement, container: HTMLElement) => {
const elementRect = element.getBoundingClientRect()
const containerRect = container.getBoundingClientRect()
// 考虑容器的transform和滚动
const containerStyle = window.getComputedStyle(container)
const containerTransform = containerStyle.transform
return {
x: elementRect.left - containerRect.left,
y: elementRect.top - containerRect.top,
width: elementRect.width,
height: elementRect.height
}
}
/**
*
* foreignObject内容
*/
const ensureContentRendered = async () => {
// 1. 等待Vue DOM更新
await new Promise(resolve => setTimeout(resolve, 0))
// 2. 等待字体加载
await document.fonts.ready
// 3. 等待下一帧渲染
await new Promise(resolve => requestAnimationFrame(resolve))
// 4. 额外等待foreignObject渲染
await new Promise(resolve => setTimeout(resolve, 100))
}
const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) => {
console.log('🎯 generateSvg 开始执行noForeignObject:', noForeignObject)
// 确保所有内容已完全渲染特别是foreignObject
await ensureContentRendered()
const mapDiv = mei.nodes
const mapDivRect = mapDiv.getBoundingClientRect()
const height = mapDiv.offsetHeight + padding * 2
const width = mapDiv.offsetWidth + padding * 2
const svg = createSvgDom(height + 'px', width + 'px')
@ -1018,11 +1097,21 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
fill: mei.theme.cssVar['--bgcolor'] as string,
})
svg.appendChild(bgColor)
// 使用绝对定位处理subLines
mapDiv.querySelectorAll('.subLines').forEach(item => {
const clone = item.cloneNode(true) as SVGSVGElement
const { offsetLeft, offsetTop } = getOffsetLT(mapDiv, item.parentElement as HTMLElement)
clone.setAttribute('x', `${offsetLeft}`)
clone.setAttribute('y', `${offsetTop}`)
const parentElement = item.parentElement as HTMLElement
const absolutePos = getAbsolutePosition(parentElement, mapDiv)
console.log('🔍 SVG导出subLines位置计算:', {
parent: parentElement.textContent?.substring(0, 30),
absolutePos,
oldMethod: getOffsetLTImproved(mapDiv, parentElement)
})
clone.setAttribute('x', `${absolutePos.x}`)
clone.setAttribute('y', `${absolutePos.y}`)
g.appendChild(clone)
})
@ -1033,15 +1122,22 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
const summaries = mapDiv.querySelector('.summary')?.cloneNode(true)
summaries && g.appendChild(summaries)
// 处理所有节点
// 处理所有节点 - 使用绝对定位
const nodePromises = Array.from(mapDiv.querySelectorAll<Topic>('me-tpc')).map(async (tpc) => {
// 获取节点的绝对位置
const absolutePos = getAbsolutePosition(tpc, mapDiv)
console.log('🔍 SVG导出节点位置计算:', {
topic: tpc.textContent?.substring(0, 30),
absolutePos,
oldMethod: getOffsetLTImproved(mapDiv, tpc),
hasComplexContent: tpc.nodeObj?.dangerouslySetInnerHTML?.includes('<table') || tpc.nodeObj?.dangerouslySetInnerHTML?.includes('<img')
})
// 不再通过createElBox创建背景避免重复rect
// 背景现在由convertDivToSvg统一管理
const bgBox = createElBox(mei, tpc)
if (bgBox) {
g.appendChild(bgBox)
}
// createElBox总是返回null所以这里不需要处理
// 检查节点内容类型
const hasDangerouslySetInnerHTML = tpc.nodeObj.dangerouslySetInnerHTML
const hasHTMLContent = !!(tpc.text && tpc.text.innerHTML && tpc.text.innerHTML !== tpc.text.textContent)
@ -1059,14 +1155,15 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
if (hasDangerouslySetInnerHTML || hasHTMLContent) {
// 对于HTML内容表格等使用ForeignObject
console.log('✅ 使用ForeignObject渲染HTML内容')
g.appendChild(convertDivToSvg(mei, tpc, !noForeignObject))
const nodeGroup = convertDivToSvg(mei, tpc, !noForeignObject, absolutePos)
g.appendChild(nodeGroup)
} else if (!hasImage) {
// 对于没有图片的普通文本内容
g.appendChild(convertDivToSvg(mei, tpc, false))
g.appendChild(convertDivToSvg(mei, tpc, false, absolutePos))
} else {
// 对于有图片的节点,只渲染文字部分(不包含图片)
console.log('📝 渲染有图片节点的文字内容')
g.appendChild(convertDivToSvg(mei, tpc, false))
g.appendChild(convertDivToSvg(mei, tpc, false, absolutePos))
}
// 如果有图片,单独处理图片

View File

@ -190,3 +190,41 @@ export const on = function (
}
}
}
/**
*
*/
export const getActualNodeDimensions = (element: HTMLElement) => {
const rect = element.getBoundingClientRect()
const computedStyle = getComputedStyle(element)
return {
width: rect.width,
height: rect.height,
top: rect.top,
left: rect.left,
// 保留原有的offset信息作为备用
offsetWidth: element.offsetWidth,
offsetHeight: element.offsetHeight,
offsetTop: element.offsetTop,
offsetLeft: element.offsetLeft
}
}
/**
* 使getBoundingClientRect获取更准确的位置
*/
export const getOffsetLTImproved = (parent: HTMLElement, child: HTMLElement) => {
try {
const parentRect = parent.getBoundingClientRect()
const childRect = child.getBoundingClientRect()
return {
offsetLeft: childRect.left - parentRect.left,
offsetTop: childRect.top - parentRect.top
}
} catch (error) {
console.warn('getBoundingClientRect() failed, falling back to offset method:', error)
return getOffsetLT(parent, child)
}
}