Skip to content

Commit 3027676

Browse files
committed
fix: Resource proxy error
1 parent db418f2 commit 3027676

File tree

2 files changed

+137
-102
lines changed

2 files changed

+137
-102
lines changed

apps/chat/urls.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
path('mcp', mcp_view),
1111
path('auth/anonymous', views.AnonymousAuthentication.as_view()),
1212
path('profile', views.AuthProfile.as_view()),
13-
path('resource_proxy',views.ResourceProxy.as_view()),
1413
path('application/profile', views.ApplicationProfile.as_view(), name='profile'),
1514
path('chat_message/<str:chat_id>', views.ChatView.as_view(), name='chat'),
1615
path('open', views.OpenView.as_view(), name='open'),

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

Lines changed: 137 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -44,54 +44,55 @@ import * as htmlToImage from 'html-to-image'
4444
import { ref, nextTick } from 'vue'
4545
import html2Canvas from 'html2canvas'
4646
import { jsPDF } from 'jspdf'
47+
4748
const loading = ref<boolean>(false)
4849
const svgContainerRef = ref()
4950
const cloneContainerRef = ref()
5051
const dialogVisible = ref<boolean>(false)
52+
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
53+
5154
const open = (element: HTMLElement | null) => {
5255
dialogVisible.value = true
5356
loading.value = true
5457
if (!element) {
58+
loading.value = false
5559
return
5660
}
5761
const cElement = element.cloneNode(true) as HTMLElement
58-
const images = cElement.querySelectorAll('img')
59-
const loadPromises = Array.from(images).map((img) => {
60-
if (!img.src.startsWith(window.origin) && img.src.startsWith('http')) {
61-
img.src = `${window.MaxKB.prefix}/api/resource_proxy?url=${encodeURIComponent(img.src)}`
62-
}
63-
img.setAttribute('onerror', '')
64-
return new Promise((resolve) => {
65-
// 已加载完成的图片直接 resolve
66-
if (img.complete) {
67-
resolve({ img, success: img.naturalWidth > 0 })
68-
return
69-
}
70-
71-
// 未加载完成的图片监听事件
72-
img.onload = () => resolve({ img, success: true })
73-
img.onerror = () => resolve({ img, success: false })
74-
})
75-
})
76-
Promise.all(loadPromises).finally(() => {
77-
setTimeout(() => {
78-
nextTick(() => {
79-
cloneContainerRef.value.appendChild(cElement)
80-
htmlToImage
81-
.toSvg(cElement, {
82-
pixelRatio: 1,
83-
quality: 1,
84-
onImageErrorHandler: (
85-
event: Event | string,
86-
source?: string,
87-
lineno?: number,
88-
colno?: number,
89-
error?: Error,
90-
) => {
91-
console.log(event, source, lineno, colno, error)
92-
},
93-
})
94-
.then((dataUrl) => {
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 等:保持原逻辑
9596
return fetch(dataUrl)
9697
.then((response) => {
9798
return response.text()
@@ -104,93 +105,128 @@ const open = (element: HTMLElement | null) => {
104105
svgContainerRef.value.appendChild(svgElement)
105106
svgContainerRef.value.style.height = svgElement.scrollHeight + 'px'
106107
})
108+
}
109+
})
110+
.finally(() => {
111+
loading.value = false
112+
})
113+
.catch((e) => {
114+
console.error(e)
115+
loading.value = false
116+
})
117+
})
118+
}, 1)
119+
}
120+
121+
const exportPDF = () => {
122+
loading.value = true
123+
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,
137+
})
138+
.then((canvas) => {
139+
generatePDF(canvas)
107140
})
108141
.finally(() => {
109142
loading.value = false
110143
})
111-
.catch((e) => {
112-
loading.value = false
113-
})
114-
})
115-
}, 1)
144+
}
145+
})
116146
})
117147
}
118148
119-
const exportPDF = () => {
120-
loading.value = true
121-
setTimeout(() => {
122-
nextTick(() => {
123-
html2Canvas(svgContainerRef.value, {
124-
logging: false,
125-
allowTaint: true,
126-
useCORS: true,
127-
})
128-
.then((canvas) => {
129-
const doc = new jsPDF('p', 'mm', 'a4')
130-
// 将canvas转换为图片
131-
const imgData = canvas.toDataURL(`image/jpeg`, 1)
132-
// 获取PDF页面尺寸
133-
const pageWidth = doc.internal.pageSize.getWidth()
134-
const pageHeight = doc.internal.pageSize.getHeight()
135-
// 计算图像在PDF中的尺寸
136-
const imgWidth = pageWidth
137-
const imgHeight = (canvas.height * imgWidth) / canvas.width
138-
// 添加图像到PDF
139-
doc.addImage(imgData, 'jpeg', 0, 0, imgWidth, imgHeight)
149+
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)
140157
141-
// 如果内容超过一页,自动添加新页面
142-
let heightLeft = imgHeight
143-
let position = 0
158+
const doc = new jsPDF('p', 'mm', 'a4')
159+
const imgData = newCanvas.toDataURL('image/jpeg', 1)
160+
const pageWidth = doc.internal.pageSize.getWidth()
161+
const pageHeight = doc.internal.pageSize.getHeight()
162+
const imgWidth = pageWidth
163+
const imgHeight = (newCanvas.height * imgWidth) / newCanvas.width
144164
145-
// 第一页已经添加
146-
heightLeft -= pageHeight
165+
doc.addImage(imgData, 'jpeg', 0, 0, imgWidth, imgHeight)
147166
148-
// 当内容超过一页时
149-
while (heightLeft >= 0) {
150-
position = heightLeft - imgHeight
151-
doc.addPage()
152-
doc.addImage(imgData, 'jpeg', 0, position, imgWidth, imgHeight)
153-
heightLeft -= pageHeight
154-
}
167+
let heightLeft = imgHeight - pageHeight
155168
156-
// 保存PDF
157-
doc.save('导出文档.pdf')
158-
return 'ok'
159-
})
160-
.finally(() => {
161-
loading.value = false
162-
})
163-
})
164-
})
169+
while (heightLeft > 0) {
170+
const position = -(imgHeight - heightLeft)
171+
doc.addPage()
172+
doc.addImage(imgData, 'jpeg', 0, position, imgWidth, imgHeight)
173+
heightLeft -= pageHeight
174+
}
175+
176+
doc.save('导出文档.pdf')
165177
}
178+
166179
const exportJepg = () => {
167180
loading.value = true
168181
setTimeout(() => {
169182
nextTick(() => {
170-
html2Canvas(svgContainerRef.value, {
171-
logging: false,
172-
allowTaint: true,
173-
useCORS: true,
174-
})
175-
.then((canvas) => {
176-
// 将canvas转换为图片
177-
const imgData = canvas.toDataURL(`image/jpeg`, 1)
178-
const link = document.createElement('a')
179-
link.download = `webpage-screenshot.jpeg`
180-
link.href = imgData
181-
document.body.appendChild(link)
182-
link.click()
183-
return 'ok'
184-
})
185-
.finally(() => {
186-
loading.value = false
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,
187195
})
196+
.then((canvas) => {
197+
downloadJpeg(canvas)
198+
})
199+
.finally(() => {
200+
loading.value = false
201+
})
202+
}
188203
})
189204
}, 1)
190205
}
206+
207+
const downloadJpeg = (canvas: HTMLCanvasElement) => {
208+
// 创建新 canvas,先填充白色背景
209+
const newCanvas = document.createElement('canvas')
210+
newCanvas.width = canvas.width
211+
newCanvas.height = canvas.height
212+
const ctx = newCanvas.getContext('2d')!
213+
ctx.fillStyle = '#ffffff'
214+
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height)
215+
ctx.drawImage(canvas, 0, 0)
216+
217+
const imgData = newCanvas.toDataURL('image/jpeg', 1)
218+
const link = document.createElement('a')
219+
link.download = 'webpage-screenshot.jpeg'
220+
link.href = imgData
221+
document.body.appendChild(link)
222+
link.click()
223+
document.body.removeChild(link)
224+
}
225+
191226
const close = () => {
192227
dialogVisible.value = false
193228
}
229+
194230
defineExpose({ open, close })
195231
</script>
196232
<style lang="scss" scoped></style>

0 commit comments

Comments
 (0)