本人项目中使用的是vue3.2
+vite
+ts
,现在打算使用pdf.js库来加载预览pdf文件,下面是我的一些踩坑记录!真的太难受了!!!
1、安装pdf.js
骚瑞,版本不用考虑,直接cnpm就完事了
cnpm i pdfjs-dist
2、踩坑坑
引入后控制台报错,报错内容翻译过来是:
Cannot read from private field
读不到私有变量。
Cannot read from private field
经排查是pdfDoc.getPage
这里出了了问题:
和vue3的proxy有关,
- vue2是通过defineProperty来监听数据的改变和读取,
- vue3是通过proxy来改变数据的。
但是在pdfjs-dist源码中,做了一个拦截校验,校验内容就是当前传入的参数,是否有obj对象,如果没有的话,直接抛出读不到私有变量错误。在之前老项目中,这样写是没有问题的:在pdfjs-dist拦截校验的时候,是一个MaskXXX(好像叫这个名字)的对象。在vue3中,pdfjs-dist拦截校验的时候,获取到的是一个proxy对象。
正确解法:pdfDoc.getPage的pdfDoc不要使用响应式写法!!!
此处参考一个文章:https://www.jianshu.com/p/1432ccd5089a
下面是正确写法,(代码略乱,后边再改成组件形式,显示只是测试讲解用)
<template>
<div class="usinghelp-container">
<div class="pdf-view">
<canvas :id="`pdfCanvas${page}`" v-for="page in state.pdfPages" :key="page"></canvas>
</div>
<div class="pdf-bottom">我是操作区域</div>
</div>
</template>
<script lang="ts" setup name="usinghelp">
import { reactive, onMounted, nextTick } from 'vue';
import { ElMessage } from 'element-plus';
import * as PDF from "pdfjs-dist";
import workerSrc from "pdfjs-dist/build/pdf.worker.entry.js"; // 引入时如果报红线错误,不影响运行, 或在index.d.ts中声明declare
const state = reactive<any>({
pdfPath: "/pdf/华康医疗公司简介.pdf", //本地PDF文件路径放在/public中
pdfPages: '', // 页数
pdfWidth: "", // 宽度
pdfSrc: "", // 地址
pdfScale: 1.0, // 放大倍数
});
// 此处为正确写法---原来我的参数用的响应式写法,就会报错Cannot read from private field
let pdfDoc:any = ""; // 文档内容---必须使用非响应式存储
// 页面加载时
onMounted(() => {
initTableData();
});
// 初始化表格数据
const initTableData = () => {
PDF.GlobalWorkerOptions.workerSrc = workerSrc;
console.log("开始 pDF测试");
console.log(workerSrc);
console.log(PDF);
console.log("结束");
loadFile(state.pdfPath);
};
// 加载pdf文件
const loadFile = (url: string) => {
let loadingTask = PDF.getDocument(url);
loadingTask.promise.then((pdf: any) => {
console.log('PDF loaded');
debugger
pdfDoc = pdf;
state.pdfPages = pdf.numPages;
nextTick(() => {
renderPage(1); // 表示渲染第 1 页
});
});
}
// 渲染指定页面的内容
const renderPage = (num: number) => {
console.log('PDF 开始渲染',num);
console.log(pdfDoc);
pdfDoc.getPage(num).then((page: any) => {
console.log('Page loaded');
let canvas:any = document.getElementById(`pdfCanvas${num}`);
let ctx = canvas.getContext("2d");
let dpr = window.devicePixelRatio || 1;
let bsr =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1;
let ratio = dpr / bsr;
let viewport = page.getViewport({ scale: state.pdfScale });
canvas.width = viewport.width * ratio;
canvas.height = viewport.height * ratio;
// canvas.style.width = viewport.width + "px";
// canvas.style.height = viewport.height + "px";
canvas.style.width = "100%";
canvas.style.height = "100%";
state.pdfWidth = viewport.width + "px";
ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
// 将 PDF 页面渲染到 canvas 上下文中
let renderContext = {
canvasContext: ctx,
viewport: viewport,
};
page.render(renderContext);
if (state.pdfPages > num) {
renderPage(num + 1);
}
});
}
</script>
<style lang="scss" scoped>
.usinghelp-container{
.pdf-view {
width: 80vw;
height: 80vh;
margin: 0 auto;
border: 1px solid #f90;
overflow: auto;
}
}
</style>
3、其他方案
服务器上部署pdfjs-viewer文件目录,然后使用下面这种形式预览文件
http://124.70.166.83/pdfjs-viewer/web/viewer.html?file=http%3A%2F%2F124.70.166.83%2Ffile%2Fmaintenance%2F123_001.pdf
注意本地调试的时候会报错,这个会跨域,实际要部署到服务端才可以使用
pdfjs报错
Uncaught (in promise) Error: file origin does not match viewer's
at validateFileURL (app.js:2282:11)
at webViewerInitialized (app.js:2327:3)
源代码
<PdfViewer v-if="selectedFile?.fileType?.includes('pdf')" :pdfPath="selectedFile?.url"></PdfViewer>
<!--
* @Description: pdf自带的viewer组件页面
-->
<template>
<div class="pdf-container">
<iframe id="pdfConcent" :src="state.pdfSrc" frameborder="0" width="100%" height="100%"></iframe>
</div>
</template>
<script setup lang="ts">
import { nextTick, reactive, watch } from 'vue';
const props = defineProps({
// 文件路径
pdfPath: {
type: String,
default: () => '',
},
});
const state = reactive<any>({
pdfPath: '', // 本地PDF文件路径放在/public中
viewerUrl: '/pdfjs-viewer/web/viewer.html', // pdfjs模板位置
pdfSrc: '', // 最终显示的pdf链接
wordData: '',
pdfName: '',
});
watch(
() => props.pdfPath,
(newData: any) => {
if (newData) {
nextTick(() => {
initData(newData);
});
}
},
{
immediate: true,
}
);
// 初始化表格数据
const initData = (pdfPath: any) => {
state.pdfSrc = `${state.viewerUrl}?file=${encodeURIComponent(pdfPath)}`;
};
// // 以下是模式1从接口获取
// const renderPDF = async () => {
// let data: any = 111; // 此处为借口获取数据
// let pdfData = atob(data['pdfContent']); // $.base64.
// let uint8Array = new Uint8Array(pdfData.length);
// for (let i = 0; i < pdfData.length; i++) {
// uint8Array[i] = pdfData.charCodeAt(i);
// }
// state.wordData = atob(data['wordContent']);
// state.pdfSrc = `/pdfjs-viewer/web/viewer.html?file=${encodeURIComponent(
// `${getObjectUrl(new Blob([uint8Array], { type: 'application/pdf;chartset=UFT-8' }))}#${state.pdfName}.pdf`
// )}`;
// };
// const getObjectUrl = (file: any) => {
// let url = null;
// // @ts-ignore
// if (window.createObjectURL != undefined) {
// // basic
// // @ts-ignore
// url = window.createObjectURL(file);
// } else if (window.URL != undefined) {
// // mozilla(firefox)
// url = window.URL.createObjectURL(file);
// } else if (window.webkitURL != undefined) {
// // webkit or chrome
// url = window.webkitURL.createObjectURL(file);
// }
// return url;
// };
</script>
<style scoped lang="scss">
.pdf-container {
width: 100%;
height: 100%;
}
</style>