1717 justify-content : center ;
1818 "
1919 >
20- <div ref =" cloneContainerRef" style =" width : 100% " ></div >
2120 <div ref =" svgContainerRef" ></div >
2221 </div >
2322 <template #footer >
3938 </template >
4039 </el-dialog >
4140</template >
41+
4242<script setup lang="ts">
4343import * as htmlToImage from ' html-to-image'
4444import { ref , nextTick } from ' vue'
45- import html2Canvas from ' html2canvas'
4645import { jsPDF } from ' jspdf'
4746
4847const loading = ref <boolean >(false )
4948const svgContainerRef = ref ()
50- const cloneContainerRef = ref ()
5149const dialogVisible = ref <boolean >(false )
52- const isSafari = / ^ ((?!chrome| android). )* safari/ i .test (navigator .userAgent )
50+
51+ // 保存原始元素引用,用于导出
52+ const originalElement = ref <HTMLElement | null >(null )
5353
5454const 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
12190const 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
149113const 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) => {
179134const 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
207157const 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
226175const close = () => {
227176 dialogVisible .value = false
177+ originalElement .value = null
178+ // 清空预览内容
179+ if (svgContainerRef .value ) {
180+ svgContainerRef .value .innerHTML = ' '
181+ }
228182}
229183
230184defineExpose ({ open , close })
231185 </script >
232- <style lang="scss" scoped></style >
0 commit comments