发布时间:2025-06-24 18:39:47 作者:北方职教升学中心 阅读量:865
<template><div id="pdf-view"ref="pdfView"><!--<canvas v-for="page in state.pdfPages":key="page"id="pdfCanvas"/>--><div ref="pdfViewContainer"><div v-for="pageNumber in state.pdfPages"v-show="state.pdfPageList.includes(pageNumber)":key="pageNumber":ref="(el) => (pageRefs[pageNumber - 1] = el)"></div></div><je-loading v-show="loading"/></div></template><script setup>//解决 structuredClone// https://developer.mozilla.org/en-US/docs/Web/API/structuredClone#browser_compatibility// https://gitcode.com/zloirock/core-js/overview?utm_source=csdn_github_acceleratorimportstructuredClone from'core-js-pure/actual/structured-clone';// 解决 TypeError: key.split(...).at is not a function// https://github.com/wojtekmaj/react-pdf/issues/1465import'core-js/features/array/at';import*aspdfjsWorker from'pdfjs-dist/lib/pdf.worker.js';// 解决 pdfjsWorker 未定义window.pdfjsWorker =pdfjsWorker;window.structuredClone =structuredClone;// if (!Array.prototype.at) {// Array.prototype.at = function (index) {// if (index < 0) {// index = this.length + index;//}// if (index >= 0 && index < this.length) {// return this[index];//}// return undefined;// };// }importHammer from'hammerjs';import*aspdfjsWorker from'pdfjs-dist/lib/pdf.worker.js';// 解决 pdfjsWorker 未定义window.pdfjsWorker =pdfjsWorker;import'pdfjs-dist/web/pdf_viewer.css';import*asPDFfrom'pdfjs-dist';// import * as PDF from 'pdfjs-dist/build/pdf.js';import{useRoute }from'vue-router';import{ref,reactive,onMounted,nextTick,defineProps }from'vue';import{showFailToast }from'vant';constroute =useRoute();constprops =defineProps({src:{type:String,default:'',},});constpdfViewContainer =ref(null);constpdfView =ref(null);constpageRefs =ref([]);constloading =ref(false);conststate =reactive({// 总页数pdfPages:1,pdfPageList:[],//有效页码列表// 页面缩放pdfScale:1,});letpdfDoc =null;asyncfunctionloadFile(url){// {// url,// cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',// cMapPacked: true,//}loading.value =true;// 设置配置选项 手势缩放PDF?.DefaultViewerConfig?.set({handToolOnDblClick:true,mouseWheelScale:true,});letarrayBufferPDF;//// if (navigator.userAgent.indexOf('QQ')) {// const pdfData = await fetch(url);// arrayBufferPDF = await pdfData.arrayBuffer();//}// 解决部分机型浏览器 undefined is not an object(evaluating 'response.body.getReader')// https://www.qingcong.tech/technology/javascript/a-pdfjs-bug-in-qq.html#%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95fetch(url).then(async(pdfData)=>{console.log('pdfData',pdfData);if(!pdfData.ok){loading.value =false;showFailToast({message:'预览地址不存在或已失效',duration:0,});// window.JE.alert('预览地址不存在', 'error');return;}arrayBufferPDF =awaitpdfData.arrayBuffer();constloadingTask =arrayBufferPDF ?PDF.getDocument({data:arrayBufferPDF }):PDF.getDocument(url);loadingTask.promise.then((pdf)=>{pdfDoc =pdf;// 获取pdf文件总页数state.pdfPages =pdf.numPages;nextTick(()=>{for(leti =0;i <state.pdfPages;i++){renderPage(i +1);// 从第一页开始渲染}});});});}functioninitPinchZoom(){constpdfViewEl =pdfView.value;consthammer =newHammer(pdfViewEl);// 启用捏合缩放手势hammer.get('pinch').set({enable:true});// 启用拖动手势,设置拖动方向为所有方向,阈值为0hammer.get('pan').set({direction:Hammer.DIRECTION_ALL,threshold:0});letinitialScale =1;// 初始缩放比例letdeltaX =0;// 当前水平拖动距离letdeltaY =0;// 当前垂直拖动距离letstartX =0;// 拖动开始时的水平位置letstartY =0;// 拖动开始时的垂直位置constMIN_SCALE=1;// 最小缩放比例constMAX_SCALE=4;// 最大缩放比例letlastPinchTime =0;// 上一次捏合事件的时间戳letlastPanTime =0;// 上一次拖动事件的时间戳// 捏合开始事件处理函数hammer.on('pinchstart',(event)=>{initialScale =state.pdfScale;// 记录初始缩放比例startX =deltaX;// 记录拖动开始时的水平位置startY =deltaY;// 记录拖动开始时的垂直位置});// 捏合移动事件处理函数hammer.on('pinchmove',(event)=>{constcurrentTime =Date.now();// 节流控制,限制事件触发频率if(currentTime -lastPinchTime >50){event.preventDefault();constscale =event.scale;// 获取当前捏合的缩放比例constnewScale =Math.min(Math.max(initialScale *scale,MIN_SCALE),MAX_SCALE);// 计算新的缩放比例,限制在最小和最大缩放比例之间state.pdfScale =newScale;// 更新缩放比例状态applyTransform();// 应用变换lastPinchTime =currentTime;// 更新上一次捏合事件的时间戳}});// 捏合结束事件处理函数hammer.on('pinchend',(event)=>{initialScale =state.pdfScale;// 更新初始缩放比例为当前缩放比例limitPanPosition();// 限制拖动位置范围renderPages();// 重新渲染页面});// 拖动开始事件处理函数hammer.on('panstart',(event)=>{pdfViewEl.style.transition ='none';// 禁用拖动过渡效果startX =deltaX;// 记录拖动开始时的水平位置startY =deltaY;// 记录拖动开始时的垂直位置});// 拖动移动事件处理函数hammer.on('panmove',(event)=>{constcurrentTime =Date.now();// 节流控制,限制事件触发频率if(currentTime -lastPanTime >50){constdx =event.deltaX;// 获取当前拖动的水平距离constdy =event.deltaY;// 获取当前拖动的垂直距离deltaX =startX +dx;// 计算新的水平拖动距离deltaY =startY +dy;// 计算新的垂直拖动距离applyTransform();// 应用变换lastPanTime =currentTime;// 更新上一次拖动事件的时间戳}});// 拖动结束事件处理函数hammer.on('panend',(event)=>{pdfViewEl.style.transition ='transform 0.3s ease';// 启用拖动过渡效果limitPanPosition();// 限制拖动位置范围});// 限制拖动位置范围的函数functionlimitPanPosition(){constpdfWidth =pdfViewEl.clientWidth *state.pdfScale;// 计算PDF页面的实际宽度constcontainerWidth =pdfViewContainer.value.clientWidth;// 获取容器的宽度constcontainerHeight =pdfViewContainer.value.clientHeight;// 获取容器的高度// 计算单个页面的平均高度constaveragePageHeight =pageRefs.value.reduce((totalHeight,pageRef)=>{returntotalHeight +(pageRef ?pageRef.clientHeight :0);},0)/state.pdfPageList.length;// 估算总高度,使用PDF文档的总页数乘以单个页面的平均高度constestimatedTotalHeight =state.pdfPages *averagePageHeight *state.pdfScale;// 限制水平拖动距离,确保PDF页面在容器内部deltaX =Math.min(0,Math.max(deltaX,containerWidth -pdfWidth));// 限制垂直拖动距离,确保PDF页面在容器内部,使用估算的总高度deltaY =Math.min(0,Math.max(deltaY,containerHeight -estimatedTotalHeight));applyTransform();// 应用变换}// 应用变换的函数functionapplyTransform(){pdfViewEl.style.transform =`translate(${deltaX}px, ${deltaY}px) scale(${state.pdfScale})`;// 设置PDF页面的变换样式}}functionrenderPages(){state.pdfPageList =[];for(leti =0;i <state.pdfPages;i++){renderPage(i +1);}}functionrenderPage(num){pdfDoc.getPage(num).then((page)=>{// 获取当前页面对应的DOM容器元素constcontainer =pageRefs.value[num -1];// 创建一个新的canvas元素constcanvas =document.createElement('canvas');// 获取canvas的2D渲染上下文constctx =canvas.getContext('2d');// 获取设备像素比letdevicePixelRatio =window.devicePixelRatio ||1;// 获取画布的backing store ratioletbackingStoreRatio =ctx.webkitBackingStorePixelRatio ||ctx.mozBackingStorePixelRatio ||ctx.msBackingStorePixelRatio ||ctx.oBackingStorePixelRatio ||ctx.backingStorePixelRatio ||1;// 获取pdfViewContainer元素的宽度constpdfWrapperElWidth =pdfViewContainer.value.clientWidth ||pdfViewContainer.value.offsetWidth ||pdfViewContainer.value.style.width;// 获取PDF页面的初始视口,缩放比例为1constintialisedViewport =page.getViewport({scale:1});// 计算缩放比例,使PDF页面宽度与容器宽度一致constscale =pdfWrapperElWidth /intialisedViewport.width;// 计算设备像素比与backing store ratio的比值letratio =devicePixelRatio /backingStoreRatio;// 根据缩放比例获取PDF页面的视口constviewport =page.getViewport({scale });// 设置canvas的宽度为容器宽度乘以ratio,确保高分辨率下的清晰度canvas.width =pdfWrapperElWidth *ratio;// 设置canvas的高度为视口高度乘以ratio,确保高分辨率下的清晰度canvas.height =viewport.height *ratio;// 设置canvas的样式宽度为100%,与容器宽度一致canvas.style.width ='100%';// 设置canvas的样式高度为auto,根据宽度自适应canvas.style.height ='auto';// 缩放画布的渲染上下文,根据ratio进行缩放,确保在高分辨率下绘制的清晰度ctx.scale(ratio,ratio);constrenderContext ={canvasContext:ctx,viewport,};// 设置页面容器的高度为视口高度container.style.height =`${viewport.height}px`;page .render(renderContext).promise.then(()=>{state.pdfPageList.push(num);// 如果 container 存在 canvas元素 覆盖canvas元素container?.firstChild &&container.removeChild(container.firstChild);container &&container.appendChild(canvas);}).finally(()=>{if(num ===state.pdfPages){loading.value =false;}});});}onMounted(()=>{constfile =route.query.file &&JSON.parse(decodeURIComponent(route.query.file));const{relName,previewUrl }=file ||{};if(relName){// 设置 uniapp 当前页面标题uni.setNavigationBarTitle({title:relName,});}if(previewUrl){loadFile(previewUrl);// nextTick(() => {// initPinchZoom();//});}else{showFailToast({message:'预览地址不存在',duration:0,});}});</script><style scoped lang="less">uni-page-body {overflow-y:scroll;}</style>