@@ -44,54 +44,55 @@ import * as htmlToImage from 'html-to-image'
4444import { ref , nextTick } from ' vue'
4545import html2Canvas from ' html2canvas'
4646import { jsPDF } from ' jspdf'
47+
4748const loading = ref <boolean >(false )
4849const svgContainerRef = ref ()
4950const cloneContainerRef = ref ()
5051const dialogVisible = ref <boolean >(false )
52+ const isSafari = / ^ ((?!chrome| android). )* safari/ i .test (navigator .userAgent )
53+
5154const 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+
166179const 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+
191226const close = () => {
192227 dialogVisible .value = false
193228}
229+
194230defineExpose ({ open , close })
195231 </script >
196232<style lang="scss" scoped></style >
0 commit comments