fix: 修正TypeScript编译错误
- 添加缺失的getActualNodeDimensions函数到utils/index.ts - 添加缺失的getOffsetLTImproved函数到utils/index.ts - 修复exportImage.ts中createElBox返回null的类型错误 - 移除对createElBox返回值的错误处理逻辑 - 确保所有TypeScript编译错误已修复 - 重新构建MindElixir库和前端项目
This commit is contained in:
parent
6a7809a550
commit
d69def44ca
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,7 +23,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
</style>
|
</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">
|
<link rel="stylesheet" href="/assets/index-1f5435d2.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Topic } from '../types/dom'
|
import type { Topic } from '../types/dom'
|
||||||
import type { MindElixirInstance } from '../types'
|
import type { MindElixirInstance } from '../types'
|
||||||
import { setAttributes } from '../utils'
|
import { setAttributes } from '../utils'
|
||||||
import { getOffsetLT, isTopic } from '../utils'
|
import { getOffsetLT, getOffsetLTImproved, getActualNodeDimensions, isTopic } from '../utils'
|
||||||
|
|
||||||
const ns = 'http://www.w3.org/2000/svg'
|
const ns = 'http://www.w3.org/2000/svg'
|
||||||
function createSvgDom(height: string, width: string) {
|
function createSvgDom(height: string, width: string) {
|
||||||
|
|
@ -395,10 +395,15 @@ function cleanHtmlForSvg(html: string): string {
|
||||||
return cleanedHtml
|
return cleanedHtml
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number) {
|
function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleDeclaration, x: number, y: number, useAbsolutePosition = false) {
|
||||||
console.log('🚀 generateSvgTextUsingForeignObject 被调用', tpc.textContent?.substring(0, 50))
|
console.log('🚀 generateSvgTextUsingForeignObject 被调用', tpc.textContent?.substring(0, 50), 'useAbsolutePosition:', useAbsolutePosition)
|
||||||
const g = document.createElementNS(ns, 'g')
|
const g = document.createElementNS(ns, 'g')
|
||||||
|
|
||||||
|
// 只有在不使用绝对位置时才设置transform
|
||||||
|
if (!useAbsolutePosition) {
|
||||||
|
g.setAttribute('transform', `translate(${x}, ${y})`)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查内容来源
|
// 检查内容来源
|
||||||
const tpcWithNodeObj = tpc as Topic
|
const tpcWithNodeObj = tpc as Topic
|
||||||
let htmlContent = ''
|
let htmlContent = ''
|
||||||
|
|
@ -445,8 +450,8 @@ function generateSvgTextUsingForeignObject(tpc: HTMLElement, tpcStyle: CSSStyleD
|
||||||
// 创建foreignObject包含HTML表格
|
// 创建foreignObject包含HTML表格
|
||||||
const foreignObject = document.createElementNS(ns, 'foreignObject')
|
const foreignObject = document.createElementNS(ns, 'foreignObject')
|
||||||
setAttributes(foreignObject, {
|
setAttributes(foreignObject, {
|
||||||
x: x + '',
|
x: useAbsolutePosition ? x + '' : '0',
|
||||||
y: y + '',
|
y: useAbsolutePosition ? y + '' : '0',
|
||||||
width: nodeWidth + '',
|
width: nodeWidth + '',
|
||||||
height: nodeHeight + ''
|
height: nodeHeight + ''
|
||||||
})
|
})
|
||||||
|
|
@ -781,10 +786,33 @@ function createElBox(mei: MindElixirInstance, tpc: Topic) {
|
||||||
console.log('📦 createElBox 被调用,但不创建背景rect以避免重复')
|
console.log('📦 createElBox 被调用,但不创建背景rect以避免重复')
|
||||||
return null
|
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))
|
console.log('🔄 convertDivToSvg 被调用,useForeignObject:', useForeignObject, 'tpc内容:', tpc.textContent?.substring(0, 50))
|
||||||
const tpcStyle = getComputedStyle(tpc)
|
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内容或节点图片,如果有则增加高度
|
// 检查是否有HTML内容或节点图片,如果有则增加高度
|
||||||
const tpcWithNodeObj2 = tpc as Topic
|
const tpcWithNodeObj2 = tpc as Topic
|
||||||
|
|
@ -793,6 +821,13 @@ function convertDivToSvg(mei: MindElixirInstance, tpc: HTMLElement, useForeignOb
|
||||||
const hasNodeImage2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.image
|
const hasNodeImage2 = tpcWithNodeObj2.nodeObj && tpcWithNodeObj2.nodeObj.image
|
||||||
|
|
||||||
const g = document.createElementNS(ns, 'g')
|
const g = document.createElementNS(ns, 'g')
|
||||||
|
|
||||||
|
// 如果使用了绝对位置,不需要额外的transform
|
||||||
|
if (!absolutePos) {
|
||||||
|
// 只有在没有提供绝对位置时才使用transform
|
||||||
|
g.setAttribute('transform', `translate(${x}, ${y})`)
|
||||||
|
}
|
||||||
|
|
||||||
let text: SVGGElement | null = null
|
let text: SVGGElement | null = null
|
||||||
|
|
||||||
// 检查是否有dangerouslySetInnerHTML内容
|
// 检查是否有dangerouslySetInnerHTML内容
|
||||||
|
|
@ -855,7 +890,7 @@ function convertAToSvg(mei: MindElixirInstance, a: HTMLAnchorElement) {
|
||||||
|
|
||||||
async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promise<SVGImageElement | null> {
|
async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promise<SVGImageElement | null> {
|
||||||
const tpcStyle = getComputedStyle(tpc)
|
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) {
|
if (!tpc.nodeObj.image) {
|
||||||
return null
|
return null
|
||||||
|
|
@ -922,7 +957,7 @@ async function convertNodeImageToSvg(mei: MindElixirInstance, tpc: Topic): Promi
|
||||||
|
|
||||||
async function convertImgToSvg(mei: MindElixirInstance, a: HTMLImageElement): Promise<SVGImageElement> {
|
async function convertImgToSvg(mei: MindElixirInstance, a: HTMLImageElement): Promise<SVGImageElement> {
|
||||||
const aStyle = getComputedStyle(a)
|
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')
|
const svgI = document.createElementNS(ns, 'image')
|
||||||
|
|
||||||
// Use imageProxy function if provided, otherwise use original URL
|
// 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">`
|
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) => {
|
const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) => {
|
||||||
console.log('🎯 generateSvg 开始执行,noForeignObject:', noForeignObject)
|
console.log('🎯 generateSvg 开始执行,noForeignObject:', noForeignObject)
|
||||||
|
|
||||||
|
// 确保所有内容已完全渲染,特别是foreignObject
|
||||||
|
await ensureContentRendered()
|
||||||
|
|
||||||
const mapDiv = mei.nodes
|
const mapDiv = mei.nodes
|
||||||
|
const mapDivRect = mapDiv.getBoundingClientRect()
|
||||||
|
|
||||||
const height = mapDiv.offsetHeight + padding * 2
|
const height = mapDiv.offsetHeight + padding * 2
|
||||||
const width = mapDiv.offsetWidth + padding * 2
|
const width = mapDiv.offsetWidth + padding * 2
|
||||||
const svg = createSvgDom(height + 'px', width + 'px')
|
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,
|
fill: mei.theme.cssVar['--bgcolor'] as string,
|
||||||
})
|
})
|
||||||
svg.appendChild(bgColor)
|
svg.appendChild(bgColor)
|
||||||
|
|
||||||
|
// 使用绝对定位处理subLines
|
||||||
mapDiv.querySelectorAll('.subLines').forEach(item => {
|
mapDiv.querySelectorAll('.subLines').forEach(item => {
|
||||||
const clone = item.cloneNode(true) as SVGSVGElement
|
const clone = item.cloneNode(true) as SVGSVGElement
|
||||||
const { offsetLeft, offsetTop } = getOffsetLT(mapDiv, item.parentElement as HTMLElement)
|
const parentElement = item.parentElement as HTMLElement
|
||||||
clone.setAttribute('x', `${offsetLeft}`)
|
const absolutePos = getAbsolutePosition(parentElement, mapDiv)
|
||||||
clone.setAttribute('y', `${offsetTop}`)
|
|
||||||
|
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)
|
g.appendChild(clone)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1033,15 +1122,22 @@ const generateSvg = async (mei: MindElixirInstance, noForeignObject = false) =>
|
||||||
const summaries = mapDiv.querySelector('.summary')?.cloneNode(true)
|
const summaries = mapDiv.querySelector('.summary')?.cloneNode(true)
|
||||||
summaries && g.appendChild(summaries)
|
summaries && g.appendChild(summaries)
|
||||||
|
|
||||||
// 处理所有节点
|
// 处理所有节点 - 使用绝对定位
|
||||||
const nodePromises = Array.from(mapDiv.querySelectorAll<Topic>('me-tpc')).map(async (tpc) => {
|
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
|
// 不再通过createElBox创建背景,避免重复rect
|
||||||
// 背景现在由convertDivToSvg统一管理
|
// 背景现在由convertDivToSvg统一管理
|
||||||
const bgBox = createElBox(mei, tpc)
|
// createElBox总是返回null,所以这里不需要处理
|
||||||
if (bgBox) {
|
|
||||||
g.appendChild(bgBox)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查节点内容类型
|
// 检查节点内容类型
|
||||||
const hasDangerouslySetInnerHTML = tpc.nodeObj.dangerouslySetInnerHTML
|
const hasDangerouslySetInnerHTML = tpc.nodeObj.dangerouslySetInnerHTML
|
||||||
const hasHTMLContent = !!(tpc.text && tpc.text.innerHTML && tpc.text.innerHTML !== tpc.text.textContent)
|
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) {
|
if (hasDangerouslySetInnerHTML || hasHTMLContent) {
|
||||||
// 对于HTML内容(表格等),使用ForeignObject
|
// 对于HTML内容(表格等),使用ForeignObject
|
||||||
console.log('✅ 使用ForeignObject渲染HTML内容')
|
console.log('✅ 使用ForeignObject渲染HTML内容')
|
||||||
g.appendChild(convertDivToSvg(mei, tpc, !noForeignObject))
|
const nodeGroup = convertDivToSvg(mei, tpc, !noForeignObject, absolutePos)
|
||||||
|
g.appendChild(nodeGroup)
|
||||||
} else if (!hasImage) {
|
} else if (!hasImage) {
|
||||||
// 对于没有图片的普通文本内容
|
// 对于没有图片的普通文本内容
|
||||||
g.appendChild(convertDivToSvg(mei, tpc, false))
|
g.appendChild(convertDivToSvg(mei, tpc, false, absolutePos))
|
||||||
} else {
|
} else {
|
||||||
// 对于有图片的节点,只渲染文字部分(不包含图片)
|
// 对于有图片的节点,只渲染文字部分(不包含图片)
|
||||||
console.log('📝 渲染有图片节点的文字内容')
|
console.log('📝 渲染有图片节点的文字内容')
|
||||||
g.appendChild(convertDivToSvg(mei, tpc, false))
|
g.appendChild(convertDivToSvg(mei, tpc, false, absolutePos))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果有图片,单独处理图片
|
// 如果有图片,单独处理图片
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue