Skip to content

Commit d889ef7

Browse files
authored
fix: There is a record of tool calls, and exporting a PDF displays a blank one (#4879)
1 parent 207930b commit d889ef7

1 file changed

Lines changed: 68 additions & 115 deletions

File tree

ui/src/components/pdf-export/index.vue

Lines changed: 68 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
justify-content: center;
1818
"
1919
>
20-
<div ref="cloneContainerRef" style="width: 100%"></div>
2120
<div ref="svgContainerRef"></div>
2221
</div>
2322
<template #footer>
@@ -39,17 +38,18 @@
3938
</template>
4039
</el-dialog>
4140
</template>
41+
4242
<script setup lang="ts">
4343
import * as htmlToImage from 'html-to-image'
4444
import { ref, nextTick } from 'vue'
45-
import html2Canvas from 'html2canvas'
4645
import { jsPDF } from 'jspdf'
4746
4847
const loading = ref<boolean>(false)
4948
const svgContainerRef = ref()
50-
const cloneContainerRef = ref()
5149
const dialogVisible = ref<boolean>(false)
52-
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
50+
51+
// 保存原始元素引用,用于导出
52+
const originalElement = ref<HTMLElement | null>(null)
5353
5454
const open = (element: HTMLElement | null) => {
5555
dialogVisible.value = true
@@ -58,118 +58,73 @@ const open = (element: HTMLElement | null) => {
5858
loading.value = false
5959
return
6060
}
61-
const cElement = element.cloneNode(true) as HTMLElement
62-
setTimeout(() => {
63-
nextTick(() => {
64-
cloneContainerRef.value.appendChild(cElement)
65-
htmlToImage
66-
.toSvg(cElement, {
67-
pixelRatio: 1,
68-
quality: 1,
69-
onImageErrorHandler: (
70-
event: Event | string,
71-
source?: string,
72-
lineno?: number,
73-
colno?: number,
74-
error?: Error,
75-
) => {
76-
console.log(event, source, lineno, colno, error)
77-
},
78-
})
79-
.then((dataUrl) => {
80-
if (isSafari) {
81-
// Safari: 跳过 SVG data URI,直接用 toCanvas
82-
return htmlToImage
83-
.toCanvas(cElement, {
84-
pixelRatio: 1,
85-
quality: 1,
86-
})
87-
.then((canvas) => {
88-
cloneContainerRef.value.style.display = 'none'
89-
canvas.style.width = '100%'
90-
canvas.style.height = 'auto'
91-
svgContainerRef.value.appendChild(canvas)
92-
svgContainerRef.value.style.height = canvas.height + 'px'
93-
})
94-
} else {
95-
// Chrome 等:保持原逻辑
96-
return fetch(dataUrl)
97-
.then((response) => {
98-
return response.text()
99-
})
100-
.then((text) => {
101-
const parser = new DOMParser()
102-
const svgDoc = parser.parseFromString(text, 'image/svg+xml')
103-
cloneContainerRef.value.style.display = 'none'
104-
const svgElement = svgDoc.documentElement
105-
svgContainerRef.value.appendChild(svgElement)
106-
svgContainerRef.value.style.height = svgElement.scrollHeight + 'px'
107-
})
108-
}
109-
})
110-
.finally(() => {
111-
loading.value = false
112-
})
113-
.catch((e) => {
114-
console.error(e)
115-
loading.value = false
116-
})
117-
})
118-
}, 1)
61+
62+
// 保存原始元素引用
63+
originalElement.value = element
64+
65+
nextTick(() => {
66+
htmlToImage
67+
.toCanvas(element, {
68+
pixelRatio: window.devicePixelRatio || 1,
69+
quality: 1,
70+
skipFonts: false,
71+
backgroundColor: '#ffffff',
72+
})
73+
.then((canvas) => {
74+
// 清空之前的内容
75+
svgContainerRef.value.innerHTML = ''
76+
canvas.style.width = '100%'
77+
canvas.style.height = 'auto'
78+
svgContainerRef.value.appendChild(canvas)
79+
})
80+
.finally(() => {
81+
loading.value = false
82+
})
83+
.catch((e) => {
84+
console.error(e)
85+
loading.value = false
86+
})
87+
})
11988
}
12089
12190
const exportPDF = () => {
12291
loading.value = true
12392
setTimeout(() => {
124-
nextTick(() => {
125-
if (isSafari) {
126-
// Safari: 直接取已有的 canvas
127-
const canvas = svgContainerRef.value.querySelector('canvas')
128-
if (canvas) {
129-
generatePDF(canvas)
130-
}
131-
loading.value = false
132-
} else {
133-
html2Canvas(svgContainerRef.value, {
134-
logging: false,
135-
allowTaint: true,
136-
useCORS: true,
93+
nextTick(async () => {
94+
try {
95+
const targetEl = originalElement.value
96+
if (!targetEl) return
97+
const canvas = await htmlToImage.toCanvas(targetEl, {
98+
pixelRatio: 2,
99+
quality: 1,
100+
skipFonts: false,
101+
backgroundColor: '#ffffff',
137102
})
138-
.then((canvas) => {
139-
generatePDF(canvas)
140-
})
141-
.finally(() => {
142-
loading.value = false
143-
})
103+
generatePDF(canvas)
104+
} catch (e) {
105+
console.error('PDF export error:', e)
106+
} finally {
107+
loading.value = false
144108
}
145109
})
146110
})
147111
}
148112
149113
const generatePDF = (canvas: HTMLCanvasElement) => {
150-
const newCanvas = document.createElement('canvas')
151-
newCanvas.width = canvas.width
152-
newCanvas.height = canvas.height
153-
const ctx = newCanvas.getContext('2d')!
154-
ctx.fillStyle = '#ffffff'
155-
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height)
156-
ctx.drawImage(canvas, 0, 0)
157-
158114
const doc = new jsPDF('p', 'mm', 'a4')
159-
const imgData = newCanvas.toDataURL('image/jpeg', 1)
115+
const imgData = canvas.toDataURL('image/jpeg', 1)
160116
const pageWidth = doc.internal.pageSize.getWidth()
161117
const pageHeight = doc.internal.pageSize.getHeight()
162118
const imgWidth = pageWidth
163-
const imgHeight = (newCanvas.height * imgWidth) / newCanvas.width
119+
const imgHeight = (canvas.height * imgWidth) / canvas.width
164120
165-
doc.addImage(imgData, 'jpeg', 0, 0, imgWidth, imgHeight)
121+
doc.addImage(imgData, 'JPEG', 0, 0, imgWidth, imgHeight)
166122
167123
let heightLeft = imgHeight - pageHeight
168-
169124
while (heightLeft > 0) {
170125
const position = -(imgHeight - heightLeft)
171126
doc.addPage()
172-
doc.addImage(imgData, 'jpeg', 0, position, imgWidth, imgHeight)
127+
doc.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight)
173128
heightLeft -= pageHeight
174129
}
175130
@@ -179,33 +134,27 @@ const generatePDF = (canvas: HTMLCanvasElement) => {
179134
const exportJepg = () => {
180135
loading.value = true
181136
setTimeout(() => {
182-
nextTick(() => {
183-
if (isSafari) {
184-
// Safari: 直接取已有的 canvas
185-
const canvas = svgContainerRef.value.querySelector('canvas')
186-
if (canvas) {
187-
downloadJpeg(canvas)
188-
}
189-
loading.value = false
190-
} else {
191-
html2Canvas(svgContainerRef.value, {
192-
logging: false,
193-
allowTaint: true,
194-
useCORS: true,
137+
nextTick(async () => {
138+
try {
139+
const targetEl = originalElement.value
140+
if (!targetEl) return
141+
const canvas = await htmlToImage.toCanvas(targetEl, {
142+
pixelRatio: window.devicePixelRatio || 1,
143+
quality: 1,
144+
skipFonts: false,
145+
backgroundColor: '#ffffff',
195146
})
196-
.then((canvas) => {
197-
downloadJpeg(canvas)
198-
})
199-
.finally(() => {
200-
loading.value = false
201-
})
147+
downloadJpeg(canvas)
148+
} catch (e) {
149+
console.error('JPEG export error:', e)
150+
} finally {
151+
loading.value = false
202152
}
203153
})
204154
}, 1)
205155
}
206156
207157
const downloadJpeg = (canvas: HTMLCanvasElement) => {
208-
// 创建新 canvas,先填充白色背景
209158
const newCanvas = document.createElement('canvas')
210159
newCanvas.width = canvas.width
211160
newCanvas.height = canvas.height
@@ -225,8 +174,12 @@ const downloadJpeg = (canvas: HTMLCanvasElement) => {
225174
226175
const close = () => {
227176
dialogVisible.value = false
177+
originalElement.value = null
178+
// 清空预览内容
179+
if (svgContainerRef.value) {
180+
svgContainerRef.value.innerHTML = ''
181+
}
228182
}
229183
230184
defineExpose({ open, close })
231185
</script>
232-
<style lang="scss" scoped></style>

0 commit comments

Comments
 (0)