注意下载的插件的版本"pdfjs-dist": "^2.2.228",
npm i [email protected]
然后封装一个pdf的遮罩。因为pdf文件有多页,所以我用了swiper轮播的形式展示。因为用到移动端,手动滑动页面这样比点下一页下一页的方便多了。
直接贴代码了
PdfPreview/index.vue
<!--预览pdf文件的组件--> <template> <van-overlay :show="show" @click="close()"> <div class="pdf-viewer" > <van-swipe class="my-swipe" indicator-color="red" @click.stop> <van-swipe-item v-for="item in pageNum" :key="item"> <canvas :id="`pdf-canvas-${item}`" class="pdf-page"/> </van-swipe-item> <template #indicator="{ active, total }"> <div class="custom-indicator">{{ active + 1 }}/{{ total }}</div> </template> </van-swipe> <van-empty v-if="loadError" image="error" description="PDF加载出错了..." /> </div> <van-icon name="close" color="#fff" size="0.3rem"/> </van-overlay> </template> <script setup lang="tsx"> import {ref, nextTick, watch} from 'vue'; import {closeToast, showLoadingToast, showSuccessToast} from "vant"; // 引入pdf预览插件相关的参数,注意这块开始试了很多网上方法都不好用 import \* as pdfjs from 'pdfjs-dist'; import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url'; // 设置 worker 路径 pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker; const show = ref(true); // html部分涉及的参数 const loadError = ref(false); const detail = ref({}); let pdfDoc = null; // 一定不能使用响应式的数据,会报错Cannot read from private field---pdf.js const pageNum = ref(0); const props = defineProps({ pdfUrl: { type: String, default: "" }, }) const emit= defineEmits(['close']) watch(() => props.pdfUrl, (newVal) => { // console.log("监听", newVal, props.pdfUrl) showLoadingToast('加载中'); nextTick(() => { loadingPdf(props.pdfUrl); }) }, {immediate: true,deep:true})
// 防抖 debounce 函数的实现正确。 const debounce(func, wait, options = {}) { let timeout; const { leading = false, trailing = true } = options; return function(...args) { const later = () => { timeout = null; if (!leading) func.apply(this, args); }; const callNow = leading && !timeout; clearTimeout(timeout); timeout = setTimeout(later, wait); if (callNow) func.apply(this, args); }; }
// 使用防抖函数,300ms内只执行一次,避免多次点击立刻打开又关闭的情况 const close = debounce(() => { show.value = false; emit('close') }, 300, { leading: true, trailing: false }); //加载pdf const loadingPdf = (url) => { const afterUrl = { url, httpHeaders: { token: `Bearer-${localStorage.getItem('token')}`,//微信小程序里面打开这个模块,发现请求401,报错信息是登陆访问超时,发现pdfjs加载pdf时没有携带token,于是在加载url时添加token即可 }, }; const loadingTask = pdfjs.getDocument(afterUrl); loadingTask.promise .then((pdf) => { pdfDoc = pdf; pageNum.value = pdf.numPages; nextTick(() => { renderPage(); }); }) .catch(() => { loadError.value = true; }); } // 渲染pdf const renderPage = (num = 1) => { pdfDoc.getPage(num).then((page) => { const canvas = document.getElementById(`pdf-canvas-${num}`); if(!canvas){return} const ctx = canvas.getContext('2d'); const scale = 1.5; const viewport = page.getViewport({scale}); // 画布大小,默认值是width:300px,height:150px canvas.height = viewport.height; canvas.width = viewport.width; // 画布的dom大小, 设置移动端,宽度设置铺满整个屏幕 const {clientWidth} = document.body; // 减去2rem使用因为我的页面左右加了padding canvas.style.width = `calc(${clientWidth}px - 2rem)`; // 根据pdf每页的宽高比例设置canvas的高度 canvas.style.height = `${ clientWidth * (viewport.height / viewport.width) }px`; canvas.height = viewport.height; canvas.width = viewport.width; page.render({ canvasContext: ctx, viewport, }); //隐藏渲染所有的页面 if (num < pageNum.value) { renderPage(num + 1); } else { closeToast(); } }); } </script> <style scoped> .pdf-viewer{ display: flex; justify-content: center; align-items: center; height:100vh; width:100vw; text-align: center; } .custom-indicator { position: absolute; left: 50%; bottom: 15px; transform: translateX(-50%); padding: 2px 5px; font-size: 18px; color: #fff; background: rgba(0, 0, 0, 0.1); } </style>
上传的页面先按照这个地址这篇文章写好。稍微改动一下就可以了vant4+vue3封装一个上传公共组件.有上传和删除访问接口的过程。限制上传的格式和上传文件大小-CSDN博客
然后给组件添加一个点击预览的事件 。并把上面写好的预览组件引入
import PdfPreview from "@/components/PdfPreview/index.vue";
// 点击预览文件 const showPreview=(file)=>{ if(file.absoluteUrl.endsWith('.pdf')){ pdfUrl.value=file.absoluteUrl; preview.value=true; } }
遇到的问题:
如果报错Uncaught (in promise) TypeError: Cannot read properties of null (reading 'getContext')
可能是canvas要找的那个id在页面还没有渲染出来。所以我用的nextTick,还在获取canvas后面判断了一下找到了再继续 ,注意上面棕色加粗的地方。
如果报错vue-router.mjs:3518SyntaxError: The requested module '/node_modules/.vite/deps/pdfjs-dist_build_pdf__worker__entry.js?v=8ae4d11f' does not provide an export named 'default'
检查一下你引入插件的地方。如果是
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';这样写的就是错的
改成
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?url';
问题:如果你点击第一次弹窗展示了,但是再点击就没有弹出。
原因是预览的组件渲染是监听的pdf的url的地址。如果你第一个打开没有把组件销毁。那么再次显示的时候没有走监听。就不会显示。所以要在每次关闭弹窗是组件也销毁。这就是上面我要在子组件中用@close给组件通知让他不显示也就是销毁子组件的原因。
问题:无意间双击了文件导致遮罩马上显示又隐藏。页面效果就是黑色遮罩闪了一下。
可以使用防抖的方式。延迟关闭。参考上面紫色的关闭函数