cbvirtua 1.0.107 → 1.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/888.zip ADDED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cbvirtua",
3
- "version": "1.0.107",
3
+ "version": "1.0.109",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/pdfsss.txt ADDED
@@ -0,0 +1,900 @@
1
+ dompdf
2
+ 该脚本允许您直接在用户浏览器上将网页或部分网页生成为可编辑、非图片式、可打印的 pdf。由于生成是基于 DOM 的,因此可能与实际表现不会 100% 一致。如果是复杂的pdf生成需求,不建议使用。
3
+
4
+ 在线体验:在线体验
5
+
6
+ pdf生成示例
7
+ pdf生成示例
8
+
9
+ 它是如何工作的
10
+ 该脚本基于html2canvas和jspdf,与以往将 html 页面通过 html2canvas 渲染为图片,再通过 jspdf 将图片生成 pdf 文件不同,该脚本通过读取 DOM 和应用于元素的不同样式,改造了 html2canvas 的 canvas-renderer 文件,调用 jspdf 的方法生成 pdf 文件。 所以他有以下优势:
11
+
12
+ 不需要服务器端的任何渲染,因为整个 pdf 是在客户端浏览器上创建的。
13
+ 生成的是真正的 pdf 文件,而不是图片式的,这样生成的 pdf 质量更高,您也可以编辑和打印生成 pdf 文件。
14
+ 更小的 pdf 文件体积
15
+ 当然,它也有一些缺点:
16
+
17
+ 由于是基于 DOM 的,所以可能与实际表现不会 100% 一致。
18
+ 有的 css 属性还没有被支持,查看支持的 css 属性。
19
+ 不适合在 nodejs 中使用。
20
+ 有的样式可能无法被正确渲染,比如:
21
+ text-shadow
22
+ 已实现功能
23
+ 功能 状态 说明
24
+ 文本渲染 ✅ 支持基础文本内容渲染,font-family,font-size,font-style,font-variant,color 等,支持文字描边,不支持文字阴影
25
+ 图片渲染 ✅ 支持网络图片,base64 图片,svg 图片
26
+ 边框 ✅ 支持 border-width,border-color,border-style,border-radius,暂时只实现了实线边框
27
+ 背景 ✅ 支持背景颜色,背景图片,背景渐变
28
+ canvas ✅ 支持渲染 canvas
29
+ svg ✅ 支持渲染 svg
30
+ 阴影渲染 ✅ 使用 foreignObjectRendering,支持边框阴影渲染
31
+ 渐变渲染 ✅ 使用 foreignObjectRendering,支持背景渐变渲染
32
+ iframe ❌ 支持渲染 iframe
33
+ foreignObjectRendering 使用
34
+ 在 dom 十分复杂,或者 pdf 无法绘制的情况(比如:复杂的表格,边框阴影,渐变等),可以考虑使用 foreignObjectRendering。 给要渲染的元素添加 foreignObjectRendering 属性,就可以通过 svg 的 foreignObject 将它渲染成一张背景图插入到 pdf 文件中。
35
+
36
+ 但是,由于 foreignObject 元素的渲染依赖于浏览器的实现,因此在不同的浏览器中可能会有不同的表现。 所以,在使用 foreignObjectRendering 时,需要注意以下事项:
37
+
38
+ foreignObject 元素的渲染依赖于浏览器的实现,因此在不同的浏览器中可能会有不同的表现。
39
+ IE 浏览器完全不支持,推荐在 chrome 和 firefox,edge 中使用。
40
+ 生成的图片会导致 pdf 文件体积变大。
41
+ 示例
42
+
43
+ <div style="width: 100px;height: 100px;" foreignObjectRendering>
44
+ <div
45
+ style="width: 50px;height: 50px;border: 1px solid #000;box-shadow: 2px 2px 5px rgba(0,0,0,0.3);background: linear-gradient(45deg, #ff6b6b, #4ecdc4);"
46
+ >
47
+ 这是一个div元素
48
+ </div>
49
+ </div>
50
+ 浏览器兼容性
51
+ 该库应该可以在以下浏览器上正常工作(需要 Promise polyfill):
52
+
53
+ Firefox 3.5+
54
+ Google Chrome
55
+ Opera 12+
56
+ IE9+
57
+ Safari 6+
58
+ 使用方法
59
+ dompdf 库使用 Promise 并期望它们在全局上下文中可用。如果您希望支持不原生支持 Promise 的较旧浏览器,请在引入 dompdf 之前包含一个 polyfill,比如 es6-promise。
60
+
61
+ 安装:
62
+
63
+ npm install dompdf.js --save
64
+ CDN引入:
65
+
66
+ <script src="https://cdn.jsdelivr.net/npm/dompdf.js@1.0.4/dist/dompdf.min.js"></script>
67
+ 基础用法
68
+ import dompdf from 'dompdf.js';
69
+ dompdf(document.querySelector("#capture"), {
70
+ useCORS: true //是否允许跨域
71
+ })
72
+ .then(function (blob) {
73
+ const url = URL.createObjectURL(blob);
74
+ const a = document.createElement('a');
75
+ a.href = url;
76
+ a.download = 'example.pdf';
77
+ document.body.appendChild(a);
78
+ a.click();
79
+ })
80
+ .catch(function (err) {
81
+ console.log(err, 'err');
82
+ });
83
+ 乱码问题-字体导入支持
84
+ 由于jspdf只支持英文,所以其他语言会出现乱码的问题,需要导入对应的字体文件来解决,如果需要自定义字体,在这里将字体 tff 文件转化成 base64 格式的 js 文件,中文字体推荐使用思源黑体,体积较小。 在代码中引入该文件即可。
85
+
86
+ <script type="text/javascript" src="./SourceHanSansSC-Normal-Min-normal.js"></script>
87
+ dompdf(document.querySelector("#capture"), {
88
+ useCORS: true, //是否允许跨域
89
+ fontConfig: {
90
+ fontFamily: 'SourceHanSansSC-Normal-Min',
91
+ fontBase64: window.fontBase64,
92
+ },
93
+ })
94
+ .then(function (blob) {
95
+ const url = URL.createObjectURL(blob);
96
+ const a = document.createElement('a');
97
+ a.href = url;
98
+ a.download = 'example.pdf';
99
+ document.body.appendChild(a);
100
+ a.click();
101
+ })
102
+ .catch(function (err) {
103
+ console.log(err, 'err');
104
+ });
105
+ 构建
106
+ 克隆 git 仓库:
107
+
108
+ $ git clone git@github.com:lmn1919/dompdf.js.git
109
+ 安装依赖:
110
+
111
+ $ npm install
112
+ 构建浏览器包:
113
+
114
+ $ npm run build
115
+
116
+
117
+ 方案二
118
+
119
+ html2canvas + jspdf,使用html2canvas将使用canvas将页面转为base64图片流,并插入jspdf插件中,保存并下载pdf。
120
+
121
+ 使用
122
+
123
+ 1.安装:
124
+ npm install --save html2canvas
125
+ npm install --save jspdf
126
+
127
+
128
+ 2.绘制较短页面
129
+
130
+
131
+ 新建htmlToPdf.js导出文件
132
+
133
+ js 体验AI代码助手 代码解读复制代码// utils/htmlToPdf.js:导出页面为PDF格式
134
+ import html2Canvas from 'html2canvas'
135
+ import JsPDF from 'jspdf'
136
+
137
+ export default {
138
+ install(Vue, options) {
139
+ // id-导出pdf的div容器;title-导出文件标题
140
+ Vue.prototype.htmlToPdf = (id, title) => {
141
+ const element = document.getElementById(`${id}`)
142
+ const opts = {
143
+ scale: 12, // 缩放比例,提高生成图片清晰度
144
+ useCORS: true, // 允许加载跨域的图片
145
+ allowTaint: false, // 允许图片跨域,和 useCORS 二者不可共同使用
146
+ tainttest: true, // 检测每张图片已经加载完成
147
+ logging: true // 日志开关,发布的时候记得改成 false
148
+ }
149
+
150
+ html2Canvas(element, opts)
151
+ .then((canvas) => {
152
+ console.log(canvas)
153
+ const contentWidth = canvas.width
154
+ const contentHeight = canvas.height
155
+ // 一页pdf显示html页面生成的canvas高度;
156
+ const pageHeight = (contentWidth / 592.28) * 841.89
157
+ // 未生成pdf的html页面高度
158
+ let leftHeight = contentHeight
159
+ // 页面偏移
160
+ let position = 0
161
+ // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
162
+ const imgWidth = 595.28
163
+ const imgHeight = (592.28 / contentWidth) * contentHeight
164
+ const pageData = canvas.toDataURL('image/jpeg', 1.0)
165
+ console.log(pageData)
166
+ // a4纸纵向,一般默认使用;new JsPDF('landscape'); 横向页面
167
+ const PDF = new JsPDF('', 'pt', 'a4')
168
+
169
+ // 当内容未超过pdf一页显示的范围,无需分页
170
+ if (leftHeight < pageHeight) {
171
+ // addImage(pageData, 'JPEG', 左,上,宽度,高度)设置
172
+ PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
173
+ } else {
174
+ // 超过一页时,分页打印(每页高度841.89)
175
+ while (leftHeight > 0) {
176
+ PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
177
+ leftHeight -= pageHeight
178
+ position -= 841.89
179
+ if (leftHeight > 0) {
180
+ PDF.addPage()
181
+ }
182
+ }
183
+ }
184
+ PDF.save(title + '.pdf')
185
+ })
186
+ .catch((error) => {
187
+ console.log('打印失败', error)
188
+ })
189
+ }
190
+ }
191
+ }
192
+
193
+
194
+ index.vue中使用导出方法
195
+
196
+ js 体验AI代码助手 代码解读复制代码<template>
197
+ <div>
198
+ <div
199
+ id="pdfDom"
200
+ >
201
+ 测试数据
202
+ </div>
203
+ <el-button type="primary" round style="background: #4849FF" @click="btnClick">导出PDF</el-button>
204
+ </div>
205
+ </template>
206
+ <script>
207
+ import JsPDF from 'jspdf'
208
+ import html2Canvas from 'html2canvas'
209
+ methods: {
210
+ // 导出pdf
211
+ btnClick() {
212
+ this.$nextTick(() => {
213
+ this.htmlToPdf('pdfDom', '个人报告')
214
+ })
215
+ },
216
+ },
217
+ </script>
218
+
219
+ 问题及解决方案
220
+ 1.页面绘制转码时间过长
221
+
222
+ 可以考虑在页面初始化完成后就对页面进行抓取绘制及转码,将转码数据保存,在点击下载时直接生成pdf并保存。
223
+
224
+ 2.html2canvas能够抓取的页面长度大约为1440,两个A4页面左右,超出不会抓取,需要控制多个节点,循环绘制。
225
+
226
+ 绘制多个节点
227
+
228
+
229
+ 新建htmlToPdf.js导出文件
230
+
231
+ js 体验AI代码助手 代码解读复制代码import html2Canvas from 'html2canvas'
232
+ import JsPDF from 'jspdf'
233
+
234
+ export default {
235
+ install(Vue, options) {
236
+ // id-导出pdf的div容器;title-导出文件标题
237
+ Vue.prototype.htmlToPdf = (name, title) => {
238
+ const element = document.querySelectorAll(`.${name}`)
239
+ let count = 0
240
+ const PDF = new JsPDF('', 'pt', 'a4')
241
+ const pageArr = []
242
+ const opts = {
243
+ scale: 12, // 缩放比例,提高生成图片清晰度
244
+ useCORS: true, // 允许加载跨域的图片
245
+ allowTaint: false, // 允许图片跨域,和 useCORS 二者不可共同使用
246
+ tainttest: true, // 检测每张图片已经加载完成
247
+ logging: true // 日志开关,发布的时候记得改成 false
248
+ }
249
+ for (const index in Array.from(element)) {
250
+ html2Canvas(element[index], opts).then(function(canvas) {
251
+ // a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
252
+ const contentWidth = canvas.width
253
+ const contentHeight = canvas.height
254
+ const imgWidth = 595.28
255
+ const imgHeight = (592.28 / contentWidth) * contentHeight
256
+ const pageData = canvas.toDataURL('image/jpeg', 1.0)
257
+ // 一页pdf显示html页面生成的canvas高度;
258
+ const pageHeight = (contentWidth / 592.28) * 841.89
259
+ // 未生成pdf的html页面高度
260
+ const leftHeight = contentHeight
261
+ pageArr[index] = { pageData: pageData, pageHeight: pageHeight, leftHeight: leftHeight, imgWidth: imgWidth, imgHeight: imgHeight }
262
+ if (++count === element.length) {
263
+ // 转换完毕,可进行下一步处理 pageDataArr
264
+ let counts = 0
265
+ for (const data of pageArr) {
266
+ // 页面偏移
267
+ let position = 0
268
+ // 转换完毕,save保存名称后浏览器会自动下载
269
+ // 当内容未超过pdf一页显示的范围,无需分页
270
+ if (data.leftHeight < data.pageHeight) {
271
+ // addImage(pageData, 'JPEG', 左,上,宽度,高度)设置
272
+ PDF.addImage(data.pageData, 'JPEG', 0, 0, data.imgWidth, data.imgHeight)
273
+ } else {
274
+ // 超过一页时,分页打印(每页高度841.89)
275
+ while (data.leftHeight > 0) {
276
+ PDF.addImage(data.pageData, 'JPEG', 0, position, data.imgWidth, data.imgHeight)
277
+ data.leftHeight -= data.pageHeight
278
+ position -= 841.89
279
+ if (data.leftHeight > 0) {
280
+ PDF.addPage()
281
+ }
282
+ }
283
+ }
284
+ if (++counts === pageArr.length) {
285
+ PDF.save(title + '.pdf')
286
+ } else {
287
+ // 未转换到最后一页时,pdf增加一页
288
+ PDF.addPage()
289
+ }
290
+ }
291
+ }
292
+ })
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+
299
+ index.vue中使用导出方法
300
+
301
+ js 体验AI代码助手 代码解读复制代码<template>
302
+ <div>
303
+ <div
304
+ class="pdfDom"
305
+ >
306
+ 测试数据
307
+ </div>
308
+ <div
309
+ class="pdfDom"
310
+ >
311
+ 测试数据2
312
+ </div>
313
+ <div
314
+ class="pdfDom"
315
+ >
316
+ 测试数据3
317
+ </div>
318
+ <el-button type="primary" round style="background: #4849FF" @click="btnClick">导出PDF</el-button>
319
+ </div>
320
+ </template>
321
+ <script>
322
+ import JsPDF from 'jspdf'
323
+ import html2Canvas from 'html2canvas'
324
+ methods:{
325
+ // 导出pdf
326
+ btnClick() {
327
+ this.$nextTick(() => {
328
+ this.htmlToPdf('pdfDom', '个人报告')
329
+ })
330
+ },
331
+ },
332
+ </script>
333
+
334
+
335
+ html2canvas:github.com/niklasvh/ht…
336
+ jspdf:github.com/parallax/js…
337
+ 实现效果
338
+
339
+
340
+ 方案三(推荐)
341
+
342
+ puppeteer(中文翻译”操纵木偶的人”) 是 Google Chrome 团队官方的无界面(Headless)Chrome 工具,它是一个 Node 库,提供了一个高级的 API 来控制 DevTools协议上的无头版 Chrome 。也可以配置为使用完整(非无头)的 Chrome。
343
+ Puppeteer 能做些什么
344
+
345
+
346
+ 生成页面的截图和PDF。
347
+ 抓取SPA并生成预先呈现的内容(即“SSR”)。
348
+ 从网站抓取你需要的内容。
349
+ 自动表单提交,UI测试,键盘输入等
350
+ 创建一个最新的自动化测试环境。使用最新的JavaScript和浏览器功能,直接在最新版本的Chrome中运行测试。
351
+ 捕获您的网站的时间线跟踪,以帮助诊断性能问题。
352
+
353
+
354
+ 我们只需关注并使用生成页面的截图PDF功能
355
+ Puppeteer的使用
356
+ 使用express框架搭建简单的node服务
357
+ 安装:
358
+ npm i puppeteer 或 yarn add puppeteer
359
+
360
+ 作者:ArvinC
361
+ 链接:https://juejin.cn/post/7090368199291568165
362
+ 来源:稀土掘金
363
+ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
364
+
365
+
366
+ Vue 实现 PDF 导出功能
367
+ 2023-10-18
368
+ 2,423
369
+ 阅读4分钟
370
+ 本文旨在通过 html2canvas 和 jspdf,先将页面的 html 转成 canvas,再将 canvas 转成 pdf,同时解决了分页截断的问题。
371
+
372
+ 安装依赖
373
+ yarn add html2canvas
374
+ yarn add jspdf
375
+ 思路
376
+ 通过网上的一些教程,初步实现了 html 转 pdf 的功能,将一整个 DOM 元素放进去,虽然可以粗糙实现,但是出现了很多地方被分页截断的情况,这个时候就需要在某一张图片被截断时,将其自动切换到下一页中。
377
+
378
+ 1.拆解父节点
379
+ 所以第一步:拆解父节点,一行一行细分为很多子节点,循环遍历这些子节点,累加这些子节点的高度,如果超出了 a4 纸(210*297)的高度,则分页。
380
+
381
+ import html2Canvas from "html2canvas";
382
+ import JsPDF from "jspdf";
383
+
384
+ export function oneNodeMultipleChildren(title, node) {
385
+ html2Canvas(node, {
386
+ scale: 2, // 清晰度
387
+ }).then(function (canvas) {
388
+ let PDF = new JsPDF("", "mm", "a4"); // 以mm为单位
389
+ let position = 0; // 页面偏移
390
+ let contentWidth = canvas.width; // 转换成canvas后的宽度
391
+ let contentHeight = canvas.height; // 转换成canvas后的高度
392
+ let proportion = 210 / node.offsetWidth; // html缩小至a4纸大小时的比例
393
+ let currentHeight = 0; // 当前高度
394
+ let imgWidth = 210; // canvas缩小至a4纸大小时的宽度
395
+ let imgHeight = (210 / contentWidth) * contentHeight; // canvas缩小至a4纸大小时的高度
396
+ let pageData = canvas.toDataURL("image/jpeg", 1.0); // 将canvas转成图片
397
+
398
+ for (let j = 0; j < node.children.length; j++) {
399
+ let childHeight = (node.children[j].offsetHeight + 8) * proportion; // 页面中每行的间距 margin-bottom: 8px
400
+
401
+ if (currentHeight + childHeight > 297) {
402
+ // 如果加上这个子节点后内容超出a4纸高度,就从当前位置开始分页
403
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
404
+ position -= currentHeight; // 这一页放了多少高度的内容,下一页就从这个高度开始偏移
405
+ if (position >= -contentHeight) {
406
+ PDF.addPage(); // 添加新pdf页
407
+ }
408
+ currentHeight = childHeight; // 下一页第一个元素的高度
409
+ } else {
410
+ currentHeight += childHeight;
411
+ }
412
+ }
413
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight); // 最后一页
414
+ PDF.save(title + ".pdf");
415
+ });
416
+ }
417
+
418
+ function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
419
+ PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
420
+ PDF.setFillColor(255, 255, 255); // 遮挡的颜色
421
+ PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
422
+ // PDF.rect参数分别为:起始横坐标、起始纵坐标、绘制宽度、绘制高度、填充色
423
+ }
424
+ 2.合并父节点
425
+ 经过上述步骤,一个父节点多个子节点,并且每个子节点独占一行的布局可以实现分页,那要是有很多父节点呢?就需要遍历每个父节点,合并所有子节点,进行分页截断。
426
+
427
+ import html2Canvas from "html2canvas";
428
+ import JsPDF from "jspdf";
429
+
430
+ export function exportPdf(title, id) {
431
+ let content = document.querySelector(`#${id}`);
432
+ let first = content.firstElementChild.firstElementChild;
433
+ let second = content.lastElementChild;
434
+ oneNodeMultipleChildren(title, content, [first, second]);
435
+ }
436
+
437
+ export function oneNodeMultipleChildren(title, content, nodes) {
438
+ html2Canvas(content, {
439
+ scale: 2,
440
+ }).then(function (canvas) {
441
+ let PDF = new JsPDF("", "mm", "a4");
442
+ let position = 0;
443
+ let contentWidth = canvas.width;
444
+ let contentHeight = canvas.height;
445
+ let proportion = 200 / content.offsetWidth;
446
+ let currentHeight = 0;
447
+ let imgWidth = 200;
448
+ let imgHeight = (200 / contentWidth) * contentHeight;
449
+ let pageData = canvas.toDataURL("image/jpeg", 1.0);
450
+
451
+ for (let i = 0; i < nodes.length; i++) {
452
+ // 根据传入的父节点数量进行循环,遍历父节点,合并所有子节点
453
+ for (let j = 0; j < nodes[i].children.length; j++) {
454
+ let childHeight = (nodes[i].children[j].offsetHeight + 8) * proportion;
455
+
456
+ if (currentHeight + childHeight > 287) {
457
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
458
+ position -= currentHeight;
459
+ if (position >= -contentHeight) {
460
+ PDF.addPage();
461
+ }
462
+ currentHeight = childHeight;
463
+ } else {
464
+ currentHeight += childHeight;
465
+ }
466
+ }
467
+ }
468
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
469
+ PDF.save(title + ".pdf");
470
+ });
471
+ }
472
+
473
+ function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
474
+ PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
475
+ PDF.setFillColor(255, 255, 255); // 遮挡的颜色
476
+ PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
477
+ }
478
+ 3.每行多个元素
479
+ 这个时候新的问题出现了,由于页面布局为 flex 布局,不同缩放下,每行的元素数量会出现变化。所以我们获取第一个子元素与 a4 纸宽度关系,如果为 n 倍,那后面 n-1 个子元素的高度不进行累加。
480
+
481
+ 这里只解决了一行 n 个子元素宽度相等,且近似等于 a4 纸宽度的 1/n 的情况。
482
+
483
+ import html2Canvas from "html2canvas";
484
+ import JsPDF from "jspdf";
485
+
486
+ export function exportAssetPdf(title, id) {
487
+ let content = document.querySelector(`#${id}`);
488
+ let first = content.firstElementChild.firstElementChild;
489
+ let second = content.lastElementChild;
490
+ oneNodeMultipleChildren(title, content, [first, second]);
491
+ }
492
+
493
+ export function oneNodeMultipleChildren(title, content, nodes) {
494
+ html2Canvas(content, {
495
+ scale: 2,
496
+ }).then(function (canvas) {
497
+ let PDF = new JsPDF("", "mm", "a4");
498
+ let position = 0;
499
+ let contentWidth = canvas.width;
500
+ let contentHeight = canvas.height;
501
+ let proportion = 200 / content.offsetWidth;
502
+ let currentHeight = 0;
503
+ let imgWidth = 200;
504
+ let imgHeight = (200 / contentWidth) * contentHeight;
505
+ let pageData = canvas.toDataURL("image/jpeg", 1.0);
506
+ let sameIndex = 1;
507
+ let widthX = 1;
508
+
509
+ for (let i = 0; i < nodes.length; i++) {
510
+ for (let j = 0; j < nodes[i].children.length; j++) {
511
+ let childHeight = (nodes[i].children[j].offsetHeight + 8) * proportion;
512
+ let childWidth = nodes[i].children[j].offsetWidth * proportion;
513
+ if (sameIndex === 1) {
514
+ widthX = Math.round(200 / childWidth);
515
+ }
516
+ if (sameIndex < widthX) {
517
+ childHeight = 0;
518
+ sameIndex++;
519
+ } else {
520
+ sameIndex = 1;
521
+ }
522
+
523
+ if (currentHeight + childHeight > 287) {
524
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
525
+ position -= currentHeight;
526
+ if (position >= -contentHeight) {
527
+ PDF.addPage();
528
+ }
529
+ currentHeight = childHeight;
530
+ } else {
531
+ currentHeight += childHeight;
532
+ }
533
+ }
534
+ }
535
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
536
+ PDF.save(title + ".pdf");
537
+ });
538
+ }
539
+
540
+ function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
541
+ PDF.addImage(pageData, "JPEG", 0, position, imgWidth, imgHeight); // 在当前pdf页添加图片
542
+ PDF.setFillColor(255, 255, 255); // 遮挡的颜色
543
+ PDF.rect(0, currentHeight, 210, Math.ceil(297 - currentHeight), "F"); // 添加空白遮挡
544
+ }
545
+ 4.添加左右间距和页眉页脚
546
+ 为了美化 pdf 布局,上下左右留白,就需要添加左右间距和页眉页脚:减少 html 缩小至 a4 纸大小时的比例和 canvas 缩小至 a4 纸大小时宽高,增加偏移量,并对页眉页脚进行空白遮挡。
547
+
548
+ import html2Canvas from "html2canvas";
549
+ import JsPDF from "jspdf";
550
+
551
+ export function exportAssetPdf(title, id) {
552
+ let content = document.querySelector(`#${id}`);
553
+ let first = content.firstElementChild.firstElementChild;
554
+ let second = content.lastElementChild;
555
+ oneNodeMultipleChildren(title, content, [first, second]);
556
+ }
557
+
558
+ export function oneNodeMultipleChildren(title, fNode, sNode) {
559
+ html2Canvas(fNode, {
560
+ scale: 2,
561
+ }).then(function (canvas) {
562
+ let PDF = new JsPDF("", "mm", "a4");
563
+ let position = 0;
564
+ let contentWidth = canvas.width;
565
+ let contentHeight = canvas.height;
566
+ let proportion = 200 / fNode.offsetWidth; // 减少10mm
567
+ let currentHeight = 0;
568
+ let imgWidth = 200; // 减少10mm
569
+ let imgHeight = (200 / contentWidth) * contentHeight; // 减少10mm
570
+ let pageData = canvas.toDataURL("image/jpeg", 1.0);
571
+ let sameIndex = 1;
572
+ let widthX = 1;
573
+
574
+ for (let i = 0; i < sNode.length; i++) {
575
+ for (let j = 0; j < sNode[i].children.length; j++) {
576
+ let childHeight = (sNode[i].children[j].offsetHeight + 8) * proportion;
577
+ let childWidth = sNode[i].children[j].offsetWidth * proportion;
578
+ if (sameIndex === 1) {
579
+ widthX = Math.round(200 / childWidth); // 减少10mm
580
+ }
581
+ if (sameIndex < widthX) {
582
+ childHeight = 0;
583
+ sameIndex++;
584
+ } else {
585
+ sameIndex = 1;
586
+ }
587
+
588
+ if (currentHeight + childHeight > 287) {
589
+ // 减小10mm
590
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
591
+ position -= currentHeight;
592
+ if (position >= -contentHeight) {
593
+ PDF.addPage();
594
+ }
595
+ currentHeight = childHeight;
596
+ } else {
597
+ currentHeight += childHeight;
598
+ }
599
+ }
600
+ }
601
+ addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight);
602
+ PDF.save(title + ".pdf");
603
+ });
604
+ }
605
+
606
+ function addImage(PDF, pageData, position, imgWidth, imgHeight, currentHeight) {
607
+ PDF.addImage(pageData, "JPEG", 5, position + 5, imgWidth, imgHeight); // 增加偏移量
608
+ PDF.setFillColor(255, 255, 255);
609
+ PDF.rect(0, 0, 210, 4, "F"); // 添加页眉遮挡
610
+ PDF.rect(0, currentHeight + 5, 210, Math.ceil(292 - currentHeight), "F"); // 添加页脚遮挡
611
+ }
612
+ 成果展示
613
+ 不同缩放下导出 PDF 对比:
614
+
615
+
616
+ ----------------------------------------------------------------------
617
+ import html2Canvas from "html2canvas";
618
+ import JsPDF from "jspdf";
619
+
620
+ class PdfLoader {
621
+ /**
622
+ * PDF导出工具类
623
+ * @param {HTMLElement} ele - 要导出为PDF的DOM元素
624
+ * @param {string} pdfFileName - 导出的PDF文件名(不带扩展名)
625
+ * @param {string} [splitClassName] - 用于分页判断的类名(可选)
626
+ * @param {number} [scrollWidth] - 容器滚动宽度(可选)
627
+ * @param {Object} [options] - 配置选项
628
+ * @param {number} [options.scale=2] - 缩放比例
629
+ * @param {number} [options.dpi=300] - DPI设置
630
+ * @param {number} [options.margin=20] - PDF页边距
631
+ * @param {boolean} [options.useCORS=true] - 是否使用CORS
632
+ * @param {boolean} [options.allowTaint=true] - 是否允许污染画布
633
+ */
634
+ constructor(ele, pdfFileName, splitClassName, scrollWidth, options = {}) {
635
+ if (!ele || !pdfFileName) {
636
+ throw new Error("必须提供有效的DOM元素和PDF文件名");
637
+ }
638
+
639
+ this.ele = ele;
640
+ this.pdfFileName = pdfFileName;
641
+ this.splitClassName = splitClassName;
642
+ this.scrollWidth = scrollWidth || ele.scrollWidth || ele.offsetWidth;
643
+
644
+ this.A4_WIDTH = 595;
645
+ this.A4_HEIGHT = 842;
646
+
647
+ this.options = {
648
+ scale: 2,
649
+ dpi: 300,
650
+ margin: 20,
651
+ useCORS: true,
652
+ allowTaint: true,
653
+ ...options
654
+ };
655
+ }
656
+
657
+ /**
658
+ * 生成PDF并下载
659
+ * @returns {Promise<void>}
660
+ */
661
+ async outPutPdfFn() {
662
+ try {
663
+ await this._preprocessForPagination();
664
+
665
+ const canvas = await this._generateCanvas();
666
+
667
+ this._cleanupPreprocessElements();
668
+
669
+ const pdfData = await this._generatePdfFromCanvas(canvas);
670
+
671
+ return pdfData;
672
+ } catch (error) {
673
+ console.error("生成PDF时出错:", error);
674
+ throw error; // 重新抛出错误以便外部处理
675
+ }
676
+ }
677
+
678
+ /**
679
+ * 预处理:添加分页标记
680
+ * @private
681
+ */
682
+ async _preprocessForPagination() {
683
+ if (!this.splitClassName) return;
684
+ const childList = this.ele.getElementsByClassName(this.splitClassName);
685
+ const eleBounding = this.ele.getBoundingClientRect();
686
+ const pageHeight = (this.scrollWidth / this.A4_WIDTH) * this.A4_HEIGHT;
687
+ let pageNum = 1;
688
+
689
+ for (const node of childList) {
690
+ const bound = node.getBoundingClientRect();
691
+ const diff2Ele = bound.top - eleBounding.top;// 当前元素距离容器顶部距离
692
+ const shouldInPage = Math.ceil((bound.bottom - eleBounding.top) / pageHeight);
693
+
694
+ if (pageNum < shouldInPage) {
695
+ pageNum = shouldInPage;
696
+ // console.log(pageNum, shouldInPage);
697
+ // console.log(bound.bottom, eleBounding.top,pageHeight);
698
+
699
+ const parentNode = node.parentNode;
700
+ const emptyNode = this._createEmptyNode(pageHeight, pageNum, diff2Ele);
701
+ const pageHead = this._createPageHeadNode();
702
+
703
+ parentNode.insertBefore(emptyNode, node);
704
+ parentNode.insertBefore(pageHead, node);
705
+ }
706
+ }
707
+ }
708
+
709
+ /**
710
+ * 创建空节点用于分页
711
+ * @private
712
+ */
713
+ _createEmptyNode(pageHeight, pageNum, diff2Ele) {
714
+ const emptyNode = document.createElement("div");
715
+ emptyNode.className = "emptyDiv";
716
+ emptyNode.style.background = "transparent";
717
+ emptyNode.style.width = "100%";
718
+ emptyNode.style.height = `${pageHeight * (pageNum - 1) - diff2Ele + this.options.margin}px`;
719
+ return emptyNode;
720
+ }
721
+
722
+ /**
723
+ * 创建页眉节点
724
+ * @private
725
+ */
726
+ _createPageHeadNode() {
727
+ const pageHead = document.createElement("div");
728
+ pageHead.className = "pageHead";
729
+ pageHead.innerHTML = `<h3 style="margin: 0; padding: 0;">${this.pdfFileName}</h3>`;
730
+ return pageHead;
731
+ }
732
+
733
+ /**
734
+ * 生成canvas
735
+ * @private
736
+ */
737
+ _generateCanvas() {
738
+ return html2Canvas(this.ele, {
739
+ width: this.scrollWidth,
740
+ height: this.ele.offsetHeight,
741
+ scale: this.options.scale,
742
+ dpi: this.options.dpi,
743
+ useCORS: this.options.useCORS,
744
+ allowTaint: this.options.allowTaint,
745
+ logging: false, // 关闭日志提高性能
746
+ });
747
+ }
748
+
749
+ /**
750
+ * 清理预处理添加的DOM元素
751
+ * @private
752
+ */
753
+ _cleanupPreprocessElements() {
754
+ if (!this.splitClassName) return;
755
+
756
+ const emptyNodes = this.ele.querySelectorAll('.emptyDiv');
757
+ const headNodes = this.ele.querySelectorAll('.pageHead');
758
+
759
+ emptyNodes.forEach(item => item.parentNode?.removeChild(item));
760
+ headNodes.forEach(item => item.parentNode?.removeChild(item));
761
+ }
762
+
763
+ /**
764
+ * 从canvas生成PDF
765
+ * @private
766
+ */
767
+ _generatePdfFromCanvas(canvas) {
768
+ return new Promise((resolve) => {
769
+ const contentWidth = canvas.width;
770
+ const contentHeight = canvas.height;
771
+ const pageHeight = (contentWidth / this.A4_WIDTH) * this.A4_HEIGHT;
772
+ const imgWidth = this.A4_WIDTH - this.options.margin;
773
+ const imgHeight = (this.A4_WIDTH / contentWidth) * contentHeight;
774
+ const pageData = canvas.toDataURL('image/jpeg', 1.0);
775
+
776
+ const pdf = new JsPDF('', 'pt', 'a4');
777
+ let restHeight = contentHeight;
778
+ let position = 0;
779
+
780
+ if (restHeight < pageHeight) {
781
+ // 单页
782
+ // addImage(pageData, 'JPEG', 左,上,宽度,高度)
783
+ pdf.addImage(pageData, 'JPEG', this.options.margin / 2, this.options.margin, imgWidth, imgHeight);
784
+ } else {
785
+ // 多页
786
+ while (restHeight > 0) {
787
+ pdf.addImage(pageData, 'JPEG', this.options.margin / 2, position + this.options.margin, imgWidth, imgHeight);
788
+ restHeight -= pageHeight;
789
+ position -= this.A4_HEIGHT;
790
+
791
+ if (restHeight > 0) {
792
+ pdf.addPage();
793
+ }
794
+ }
795
+ }
796
+
797
+ // 保存PDF
798
+ pdf.save(`${this.pdfFileName}.pdf`);
799
+ resolve();
800
+ });
801
+ }
802
+ }
803
+
804
+ export default PdfLoader;
805
+
806
+
807
+
808
+
809
+
810
+
811
+
812
+ 第三步,在需要的页面导入该js,并引用
813
+
814
+ xml 体验AI代码助手 代码解读复制代码 <template>
815
+ <div>
816
+ <div>这一块是其他内容</div>
817
+ <div class="v-pdfwrap">
818
+ <div class="element">
819
+ <p>报告日期:2199-13-14</p>
820
+ </div>
821
+ <div class="element">
822
+ <A />
823
+ </div>
824
+ <div class="element">
825
+ <B />
826
+ </div>
827
+ <div class="element">
828
+ <C />
829
+ </div>
830
+ <div class="element">
831
+ <D />
832
+ <E />
833
+ </div>
834
+ <div class="element">
835
+ <F />
836
+ </div>
837
+ <div class="element">
838
+ <PartG />
839
+ </div>
840
+ <div class="element">
841
+ <PartH />
842
+ </div>
843
+ <div class="element">
844
+ <PartI />
845
+ </div>
846
+ <div class="element">
847
+ <PartJ />
848
+ </div>
849
+ <div class="element">
850
+ <PartK />
851
+ </div>
852
+ </div>
853
+ </div>
854
+ </template>
855
+
856
+ <script>
857
+ import PdfLoader from '@/utils/pagetopdf.js';
858
+ import A from "./A.vue";
859
+ import B from "./B.vue";
860
+ import C from "./C.vue";
861
+ import D from "./D.vue";
862
+ import E from "./E.vue";
863
+ import F from "./F.vue";
864
+ import PartG from "./PartG.vue";
865
+ import PartH from "./PartH.vue";
866
+ import PartI from "./PartI.vue";
867
+ import PartJ from "./PartJ.vue";
868
+ import PartK from "./PartK.vue";
869
+ export default {
870
+ data() {
871
+ return {
872
+ };
873
+ },
874
+ components: {
875
+ A, B, C, D, E, F, PartG, PartH, PartI, PartJ, PartK
876
+ },
877
+ watch: {
878
+ },
879
+ methods: {
880
+ exportPDF() {
881
+ let pdf = new PdfLoader(
882
+ document.querySelector(".v-pdfwrap"),
883
+ "pdf名",
884
+ "element",
885
+ 1920
886
+ );
887
+ pdf.outPutPdfFn().then(() => {
888
+ console.log('PDF生成完毕');
889
+ });
890
+ },
891
+ }
892
+ };
893
+ </script>
894
+
895
+ <style lang="scss"></style>
896
+
897
+ 作者:劫大大
898
+ 链接:https://juejin.cn/post/7533145564751855670
899
+ 来源:稀土掘金
900
+ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Binary file
Binary file
Binary file
Binary file
Binary file