pdf 预览
本文记录使用 pdf.js 进行文件预览的过程及踩过的一些坑。
安装依赖
sh
npm install pdfjs-dist注意
如果项目使用 vue-cli ,且有打包 pdf 的需求
需要先安装 file-loader 并且 vue-config.js 需增加以下打包配置
sh
npm install file-loader -Djs
// vue.config.js
chainWebpack: (config) => {
config.module
.rule("pdf")
.test(/\.pdf$/)
.use("file-loader")
.loader("file-loader")
.options({
name: "[name].[ext]",
});
},引入依赖
引入后报错
sh
ERROR in ./node_modules/pdfjs-dist/build/pdf.mjs
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: D:\UserData\kuanglong\desktop\code\gitlab\vue-demo\node_modules\pdfjs-dist\build\pdf.mjs: Class private methods are not enabled. Please add `@babel/plugin-transform-private-methods` to your configuration.原因是源码内使用了 class 私有方法的语法, babel 转义时未开启私有方法的支持,需要装对应的插件进行支持
sh
npm install --save-dev @babel/plugin-transform-private-methodsbabel 配置更新后如下
diff
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
+ plugins: ["@babel/plugin-transform-private-methods"],
};还是报错,继续装对应的插件
sh
@babel/plugin-transform-class-static-block解决上述转义问题后,能正常启动,但是运行时报错
sh
Uncaught TypeError: Object.defineProperty called on non-object查了一下与 Webpack 的 devtool 配置有关,不能使用 eval 开头的配置:github issue
将 devtool 配置修改成 cheap-source-map
还是有运行时错误。。。
去社区查了一下,发现有人用了比较旧的版本能正常运行,固尝试降低依赖版本。
降低版本至 2.7.570 后能直接使用,无需配置 babel 和 webpack。
组件封装
vue
<template>
<div class="pdf-viewer" v-loading="loading">
<div v-if="error" class="fallback">
<p>加载失败</p>
</div>
<div v-else>
<div v-for="(page, index) in pages" :key="index" class="pdf-page">
<img :src="page" :alt="'pdf ' + (index + 1)" class="pdf-image" />
</div>
</div>
</div>
</template>
<script>
/**
* @description 使用 pdfjs-dist 的 2.7.570 版本的 es5 产物
* @description 该版本的 es5 的 build 产物没有特殊写法,对本项目 webpack、babel的兼容性最好
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import * as pdfjsLib from 'pdfjs-dist/es5/build/pdf';
// eslint-disable-next-line import/no-extraneous-dependencies
import pdfWorker from 'pdfjs-dist/es5/build/pdf.worker.entry';
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker;
/**
* @description 关闭 eval 支持(防止漏洞)
* @description 漏洞链接: https://www.venustech.com.cn/new_type/aqtg/20240514/27492.
*/
pdfjsLib.GlobalWorkerOptions.isEvalSupported = false;
export default {
name: 'PdfViewer',
props: {
url: String
},
data() {
return {
pages: [],
loading: false,
error: false
};
},
watch: {
url: {
immediate: true,
handler(newValue) {
this.loadPdf(newValue);
}
}
},
methods: {
reset() {
this.loading = false;
this.error = false;
},
async loadPdf(url) {
if (!url) {
this.reset();
return;
}
try {
this.loading = true;
const loadingTask = pdfjsLib.getDocument(url);
const pdf = await loadingTask.promise;
const pageImages = [];
for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {
// eslint-disable-next-line no-await-in-loop
const page = await pdf.getPage(pageNum);
const viewport = page.getViewport({ scale: 2 }); // scale 调大可提高清晰度
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = viewport.width;
canvas.height = viewport.height;
// eslint-disable-next-line no-await-in-loop
await page.render({ canvasContext: context, viewport }).promise;
pageImages.push(canvas.toDataURL());
}
this.pages = pageImages;
this.loading = false;
} catch (e) {
console.error('PDF 加载失败:', e);
this.reset();
}
}
}
};
</script>
<style scoped>
.pdf-viewer {
width: 100%;
height: 100%;
max-height: 100%;
overflow: auto;
}
.pdf-image {
width: 100%;
display: block;
object-fit: contain;
}
.fallback {
text-align: center;
color: #999;
font-size: 14px;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>