一、相关pdf库
1、查看
-
vue-pdf
-
iframe
-
pdfjs-dist
2、生成
-
pdfjs
-
electron生成pdf
-
puppeteer
-
bookjs-eazy
3、打印
-
vue-pdf(pdf)
-
window.print(html)
-
iframe+window.pirnt(pdf)
-
vue-esay-print(html)
-
vue-print-nb(html)
二、相关pdf库的详细介绍
1、vue-pdf(www.npmjs.com/package/vue…
根据url查看和打印pdf
-
优点
- 直接通过url即可访问
- 支持多页查看
-
缺点
-
仅支持单页打印
-
会出现文字空白,每一页多一个空白页等情况, 解决方案:
-
<template>
<pdf src="./path/to/static/relativity.pdf"></pdf>
</template>
<script>
import pdf from 'vue-pdf'
export default {
components: {
pdf
}
}
2、pdfjs-dist (pdfjs)(mozilla.github.io/pdf.js/exam…
pdfjs-dist是 PDF.js 的一个编译后的版本,适用于那些需要在旧浏览器中使用的场景中查看pdf
- 优点
- 官方提供,底层控件,当iframe查看不了pdf的时候, pdfjs-dist能够支持查看
- 缺点
- 使用相对麻烦
- 仅提供查看功能, 打印功能不提供
<template>
<div class="interviewVideo_main" id="videoContainer">
<!--此处根据pdf的页数动态生成相应数量的canvas画布-->
<canvas
v-for="pageIndex in pdfPages"
:id="`pdf-canvas-` + pageIndex"
:key="pageIndex"
style="display: block"
></canvas>
</div>
</template>
<script setup>
import { ref } from "vue";
import * as pdfjsLib from "pdfjs-dist/build/pdf";
let pdfDoc = reactive({}); // 保存加载的pdf文件流
let pdfPages = ref(0); // pdf文件的页数
let pdfUrl = ref(""); //pdf文件的链接
let pdfScale = ref(1.0); // 缩放比例
//调用loadFile方法
onMounted(() => {
loadFile(pdfUrl.value);
});
//获取pdf文档流与pdf文件的页数
const loadFile = async (url) => {
pdfjsLib.GlobalWorkerOptions.workerSrc =
"../../node_modules/pdfjs-dist/build/pdf.worker.min.js";
const loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then((pdf) => {
console.log(pdf);
pdfDoc = pdf;
pdfPages.value = pdf.numPages;
nextTick(() => {
renderPage(1);
});
});
};
//渲染pdf文件
const renderPage = (num) => {
pdfDoc.getPage(num).then((page) => {
const canvasId = "pdf-canvas-" + num;
const canvas = document.getElementById(canvasId);
const ctx = canvas.getContext("2d");
const viewport = page.getViewport({ scale: pdfScale.value });
canvas.width = viewport.width ;
canvas.height = viewport.height;
const renderContext = {
canvasContext: ctx,
viewport: viewport,
};
page.render(renderContext);
if (num < pdfPages.value) {
renderPage(num + 1);
}
});
};
</script>
<style>
#videoContainer {
height: 842px;
}
</style>
3、jspdf(mrrio.github.io/)
生成pdf, 可以保存pdf, 或者导出buffer流
-
优点
- 可以编辑pdf
- 可以通过配合html2canvas将内容生成为pdf
-
缺点
-
pdf文件会乱码, 需要导入字体库中文乱码与自动换行难题攻克
-
html2canvas会过长会导出导致页面为空白
-
html2canvas性能不太行, 目前是通过ignoreElements进行了简单的优化
-
存在着分页的问题:JSPDF + html2canvas A4分页截断
-
async triggerPrint() {
const canvas1 = await this.transformPDF('canvas1Ref')
const canvas2 = await this.transformPDF('canvas2Ref')
const pageCanvas = this.dealCanvas([canvas1, canvas2]) // 每个canvas能在新一页pdf中展示
this.downloadPDF(pageCanvas) // 导出
this.printPDF(pageCanvas) // 打印
},
transformPDF(dom) {
const element = this.$refs[dom]
return html2canvas(element, {
useCORS: true, // 允许跨域
allowTaint: false,
logging: false,
letterRendering: true,
ddpi: window.devicePixelRatio * 4, // 将分辨率提高到特定的DPI 提高四倍
scale: 2, // 按比例增加分辨率
background: '#fff', // pdf背景色为白色,默认是黑色的
ignoreElements: (e) => {
if (e.contains(element) || element.contains(e) || e.nodeName === 'HEAD' || e.nodeName === 'STYLE' || e.nodeName === 'LINK') {
return false
}
return true
} // 优化html2canvas的性能
})
},
dealCanvas(canvasArr) {
const pageCanvas = []
const { a4h, a4w } = this.canvasParam
canvasArr.forEach((canvas) => {
const ctx = canvas.getContext('2d')
const imgHeight = Math.floor((a4h * canvas.width) / a4w) // 按A4显示比例换算一页图像的像素高度
let renderedHeight = 0
const page = document.createElement('canvas')
page.width = canvas.width
while (renderedHeight < canvas.height) {
console.log(renderedHeight, canvas.height)
page.height = Math.min(imgHeight, canvas.height - renderedHeight) // 可能内容不足一页
// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, page.height), 0, 0)
pageCanvas.push({
dataUrl: page.toDataURL('image/jpeg', 0.2),
renderHeight: Math.min(a4h, (a4w * page.height) / page.width)
})
renderedHeight += imgHeight
}
})
return pageCanvas
},
downloadPDF(pageCanvas) {
const { a4h, a4w } = this.canvasParam
// eslint-disable-next-line
const PDF = new jsPDF({
orientation: 'p', // 参数: l:横向 p:纵向
unit: 'mm', // 参数:测量单位("pt","mm", "cm", "m", "in" or "px")
format: 'a4' // A4纸
})
pageCanvas.forEach((item, idx) => {
PDF.addImage(item.dataUrl, 'JPEG', 10, 10, a4w, item.renderHeight)
const pageNumberText = `第${idx + 1}页,共${pageCanvas.length}页`
// 设置中文字体
PDF.setFont('simhei')
// 设置文字大小
PDF.setFontSize(8)
// 设置文字颜色
PDF.setTextColor('#999999')
// 在页脚添加页码
PDF.text(pageNumberText, a4w / 2, a4h + 18)
if (idx !== pageCanvas.length - 1) PDF.addPage()
})
PDF.save('pdf.pdf')
},
printPDF(pageCanvas) {
const { a4h, a4w } = this.canvasParam
// eslint-disable-next-line
const PDF = new jsPDF({
orientation: 'p', // 参数: l:横向 p:纵向
unit: 'mm', // 参数:测量单位("pt","mm", "cm", "m", "in" or "px")
format: 'a4' // A4纸
})
pageCanvas.forEach((item, idx) => {
PDF.addImage(item.dataUrl, 'JPEG', 10, 10, a4w, item.renderHeight)
const pageNumberText = `第${idx + 1}页,共${pageCanvas.length}页`
// 设置中文字体
PDF.setFont('simhei')
// 设置文字大小
PDF.setFontSize(8)
// 设置文字颜色
PDF.setTextColor('#999999')
// 在页脚添加页码
PDF.text(pageNumberText, a4w / 2, a4h + 18)
if (idx !== pageCanvas.length - 1) PDF.addPage()
})
const pdfBlob = PDF.output('blob')
const reader = new FileReader()
reader.readAsDataURL(pdfBlob)
reader.onload = (e) => {
this.pdfURL = e.target.result // 通过vue-pdf加载url,就可以调用vue-pdf的print功能进行打印
}
}
4、electron生成pdf(www.electronjs.org/zh/docs/lat…
提供html转化成为pdf
-
优点
- 原生electron自带功能, 不需要下载插件
- 可以使用原来的样式效果, 不需要重新编写
-
缺点
- 部分样式失效,图片边框和字体样式能生效, 背景色效果显示不出来
- 打印的是整个页面, 如果该页面存在弹窗这些, 也会一齐打印出来
electron部分
htmlToPdf() {
const win = new BrowserWindow({
autoHideMenuBar: true,
show: false, // 不调试时隐藏窗口
webPreferences: {
nodeIntegration: true,
webSecurity: false,
devTools: false
}
})
let ipcMainEvent
ipcMain.on('htmlToPdf', (event, arg) => {
const { url } = arg
if (!url) return
ipcMainEvent = event
win.loadURL(global.terminalInfo.winURL + url)
})
win.webContents.on('did-finish-load', () => {
setTimeout(async () => {
const data = await win.webContents.printToPDF({})
ipcMainEvent.reply('replyHtmlToPdf', Buffer.from(data).toString('base64'))
})
})
}
网页部分
base64ToBlob(base64, mimeType) {
// 移除 Base64 字符串中的填充字符(如果存在)
const base64WithoutPadding = base64.replace(/\s+/g, '')
// 解码 Base64 字符串
const byteCharacters = atob(base64WithoutPadding)
// 创建一个 8-bit unsigned 整数数组
const byteNumbers = new Array(byteCharacters.length)
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i)
}
// 将整数数组转换为 Uint8Array
const byteArray = new Uint8Array(byteNumbers)
// 使用 Uint8Array 创建 Blob 对象
return new Blob([byteArray], { type: mimeType })
},
// 导出pdf
triggerDownload() {
ipcRenderer.send('htmlToPdf', { url: `#/analysis_preview/${this.id}` })
ipcRenderer.on('replyHtmlToPdf', (_event, data) => {
const blob = this.base64ToBlob(data, 'application/pdf')
const blobUrl = URL.createObjectURL(blob)
// 创建一个隐藏的 <a> 元素
const link = document.createElement('a')
link.style.display = 'none'
link.href = blobUrl
link.download = 'download' // 设置下载文件的默认名称
// 将 <a> 元素添加到文档中
document.body.appendChild(link)
// 模拟点击 <a> 元素来触发下载
link.click()
// 下载完成后,撤销 URL 对象以释放内存
document.body.removeChild(link)
URL.revokeObjectURL(blobUrl)
})
},
// 打印pdf, 需要借助vue-pdf
triggerPrint() {
ipcRenderer.send('htmlToPdf', { url: `#/analysis_preview/${this.id}` })
ipcRenderer.on('replyHtmlToPdf', (_event, data) => {
// 该pdf可以提供给vue-pdf预览打印
this.pdfUlr = 'data:application/pdf;filename=generated.pdf;base64,'+data;
})
}
5、iframe打印
提供页面局部区域打印
- 优点
- 原生控件, 不需要下载插件
- 缺点
- 之前用了编译后的样式,打印的时候这部分样式不生效,需要重写样式
- 部分样式会失效, 如float等
triggerPrint() {
try {
// 获取需要打印区域的id
const oldStr = document.getElementById('analysisPDF')
const newStr = document.createElement('IFRAME')
document.body.appendChild(newStr)
const doc = newStr.contentWindow.document
// 这里可以自定义样式
doc.write(
`<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
</head>
<!--zoom缩放页面适配打印纸张大小的效果-->
<body style="zoom: 0.7;">
<div id="printchart">
${oldStr.innerHTML}
</div>
</body>
</html>`
)
doc.close()
// 为iframe添加Id方便后续操作
newStr.setAttribute('id', 'printIframe')
newStr.setAttribute('frameborder', '0')
newStr.setAttribute('style', 'position:absolute;width:0;height:0;left:-500px;top:-500px;')
newStr.contentWindow.focus()
newStr.contentWindow.onload = function () {
newStr.contentWindow.print()
// 去除iframe元素
document.body.removeChild(newStr)
}
} catch (error) {
console.log(error)
}
},
6、puppeteer
- 优点
- 完整的 Chrome 渲染引擎,支持所有现代 HTML、CSS 和 JavaScript。
- 强大的功能,支持页面操作、截图、生成 PDF 等。
- 缺点,
- 需要较大的服务器端资源。
- 配置和使用相对复杂。
7、bookjs-eazy(gitcode.com/gh_mirrors/…
记录上百页html生成pdf的历程和坑(bookjs-easy解决:生成、拼接pdf、接收服务端pdf、自定义pdf字体
- 优点
- 完美解决分页问题
- 缺点
- 不支持现代js框架 VUE、React等单页面多路由场景,需要在html中用script标签直接引入,不能在import引入再经过编译
- 不支持动态刷新,重新渲染需要刷新整个页面
- PDF页面需要单独的html文件入口
- 如果想嵌入应用网页内部,可使用iframe方式
8、vue-easy-print(www.npmjs.com/package/vue…
- 优点
- 提供打印功能
- 缺点
- 需要拷贝一个打印模块到插槽中
<template>
<div id="app">
<button @click="printDemo">测试打印</button>
<vue-easy-print ref="easyPrint" >
打印内容
</vue-easy-print>
</div>
</template>
<script>
import vueEasyPrint from "vue-easy-print";
export default {
name: "App",
methods:{
printDemo(){
this.$refs.easyPrint.print()
}
},
components: {
vueEasyPrint,
}
};
</script>
9、vue-print-nb(www.npmjs.com/package/vue…
- 优点
- 无需像vue-easy-print一样拷贝一个html渠道vue-easy-print中
- 支持vue2和vue3
- 支持id和url来选择打印内容,
- 缺点
- url存在跨域问题
- 部分样式失效, 如果float, background等
<div id="printMe" style="background:red;">
<p>葫芦娃,葫芦娃</p>
<p>一根藤上七朵花 </p>
<p>小小树藤是我家 啦啦啦啦 </p>
<p>叮当当咚咚当当 浇不大</p>
<p> 叮当当咚咚当当 是我家</p>
<p> 啦啦啦啦</p>
<p>...</p>
</div>
<button v-print="'#printMe'">Print local range</button>
二、相关doc预览方案
最全的docx,pptx,xlsx(excel),pdf文件预览方案
vue-office-docx、docx-preview、mammoth.js效果对比
前端使用docx-preview展示docx + 后端doc转docx
1、@vue-office(www.npmjs.com/package/doc…)
支持docx、pdf、excel
支持url查看
样式还原度一般,间距太大,分页也有问题
2、docx-preview(www.npmjs.com/package/mam…
仅支持docx
仅支持 Blob | ArrayBuffer | Uint8Array查看
样式还原度一般,无分页
startRender() {
this.getFileBuffer(this.url).then((file) => {
this.renderFile(file)
})
},
getFileBuffer(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.responseType = 'arraybuffer'
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response)
} else {
reject()
}
}
xhr.onloadend = () => {
reject()
}
xhr.send()
})
},
renderFile(file) {
renderAsync(file, this.$refs.viewRef, null, {})
}
3、mammoth(www.npmjs.com/package/mam…
样式有问题
浏览器执行的时候, 同样需要调用上面的getFileBuffer方法来进行读取二进制流来展示
4、kkFileView(kkfileview.keking.cn/zh-cn/index…)
需要后台支持, 文件种类支持最全,但是仅支持查看, 不适合过多的拓展
5、onlyOffice(www.onlyoffice.com/zh/)
需要后台支持, 支持文件种类没有kkFileView多, 对office三件套有很好的支持、支持对文件进行查看和编辑
三、打印样式
关于CSS 打印你应该知道的样式配置
文档打印强制分页以及指定元素避免分页
-
page-break-before : 元素之前插入分页符
-
page-break-after : 元素之后插入分页符
-
page-break-inside: 避免将元素分割到不同的页面
-
@page 规则和 size 属性来定义打印页面的大小
@media print { @page { size: A4; /* 可以使用常见的纸张尺寸如 A4、Letter、Legal 等 */ } } -
隐藏链接的 URL
@media print { a::after { content: none; } } -
调整页眉和页脚
@media print { @page { @top-left { content: "页眉内容"; } @bottom-center { content: "页脚内容"; } } }