实现 Nuxt3 预览PDF文件

  1. 安装必要的库,这里使用PDF.js库
    npm install pdfjs-dist --save
  2. 为了解决跨域问题,在server/api 下 创建一个请求api, downloadFileByProxy.ts
     
    import { defineEventHandler } from 'h3';export default defineEventHandler(async event => {const { filePath } =  getQuery(event);let matches = filePath?.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);let domain = matches && matches[1]; return proxyRequest(event,`https://${domain}/`, {fetch: ()=>fetch(filePath),})
    })
  3. 支持现代浏览器,新建pdfPreviewForMordern.vue组件
    <script setup lang="ts">import { isPdf } from '~/utils/is';import 'pdfjs-dist/web/pdf_viewer.css';import * as pdfjsLib from 'pdfjs-dist';// import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js'; // 旧版浏览器需要换成这个导入import * as pdfjsViewer from 'pdfjs-dist/web/pdf_viewer';import 'pdfjs-dist/build/pdf.worker.entry';import * as pdfjsSandbox from 'pdfjs-dist/build/pdf.sandbox.js';// import {  PDFDocumentProxy } from 'pdfjs-dist/types/src/display/api';import { debounce } from 'lodash-es';const props = defineProps({path: {type: String,default: '',},preview: {type: Boolean,default: true,},});const SANDBOX_BUNDLE_SRC = pdfjsSandbox;pdfjsLib.GlobalWorkerOptions.workerSrc = window.pdfjsWorker;const CMAP_URL = '/pdfjs-dist/cmaps/';const CMAP_PACKED = true;const STANDARD_FONT_DATA_URL = '/pdfjs-dist/standard_fonts/';window.pdfjsLib = pdfjsLib;window.pdfjsViewer = pdfjsViewer;const pdfEventBus = new pdfjsViewer.EventBus();const pdfScriptingManager = new pdfjsViewer.PDFScriptingManager({eventBus: pdfEventBus,sandboxBundleSrc: SANDBOX_BUNDLE_SRC,});const pdfLinkService = new pdfjsViewer.PDFLinkService({eventBus: pdfEventBus,});// (Optionally) enable find controller.const pdfFindController = new pdfjsViewer.PDFFindController({eventBus: pdfEventBus,linkService: pdfLinkService,});let pdfViewer: pdfjsViewer.PDFViewer | null = null;let pdfDocument: PDFDocumentProxy | null = null;const loading = ref<boolean>(true);const visible = ref<boolean>(false);const setVisible = (value: boolean): void => {if (!props.preview) {return;}visible.value = value;};let oldPath = '';const random = ref(Math.floor(Math.random() * 10001));const bufferCache = ref(null); // 使用缓存避免多次请求,可以试具体情况优化与否watch(() => props.path,async (val) => {if (!val || !isPdf(val)) {return;}setTimeout(() => {debounceRenderHandle();}, 500);},{immediate: true,},);const debounceRenderHandle = debounce(() => {initPage(props.path, `pdfjs-container-${random.value}`, 'page-height');}, 500);const preview = async () => {setVisible(true);if (oldPath === props.path) {return;}if (!props.path) {return;}oldPath = props.path;setTimeout(() => {initPage(props.path, `pdfjs-modal-container-${random.value}`);}, 500);};async function getFile(pdfPath: string) {// 为了防止跨域需要再次请求const { _data } = await $fetch.raw(`/api/downloadFileByProxy`,{method: 'get',params: {// filePath: val.split('/').pop(),filePath: pdfPath,},})let blob = _data;let buffer = await blob?.arrayBuffer();return buffer;}async function initPage(pdfPath: string, domId: string, zoom?: string | number) {if (!pdfPath) return;try {// download pdf from api to prevent CORSbufferCache.value = bufferCache.value || (await getFile(pdfPath));let container = document.getElementById(domId);pdfDocument = await pdfjsLib.getDocument({// url: pdfUrl as unknown as URL,data: useCloneDeep(bufferCache.value),cMapUrl: CMAP_URL,cMapPacked: CMAP_PACKED,standardFontDataUrl: STANDARD_FONT_DATA_URL,}).promise;pdfViewer = new pdfjsViewer.PDFViewer({container: container as unknown as HTMLDivElement,eventBus: pdfEventBus,annotationMode: 0,annotationEditorMode: 0,scriptingManager: pdfScriptingManager,linkService: pdfLinkService,});pdfScriptingManager.setDocument(pdfDocument);pdfScriptingManager.setViewer(pdfViewer);pdfLinkService.setDocument(pdfDocument);pdfLinkService.setViewer(pdfViewer);pdfViewer.setDocument(pdfDocument);pdfEventBus.on('pagesinit', () => {if (pdfViewer) {loading.value = false;// TODO: this code will report error, but not affect results: [offsetParent is not set -- cannot scroll]zoom ? pdfLinkService.setHash(`zoom=${zoom}`) : pdfLinkService.setHash(`zoom=100`);}});} catch {// Init pdf Page error}}
    </script><template><div class="w-full h-full"><div @click="preview" class="absolute inset-0 cursor-pointer z-[100]" v-if="preview"></div><img :src="useRuntimeConfig().public.loadingPicture" class="absolute object-cover w-full h-full" v-if="loading" /><div :id="`pdfjs-container-${random}`" class="page-container page-thumbnail-container no-scrollbar"><div :id="`pdfViewer-${random}`" class="pdfViewer pdf-thumbnail-viewer"></div></div><a-modal v-model:visible="visible" :footer="null" width="100%" wrap-class-name="ant-full-modal"><template #closeIcon><span class="font-semibold bg-white cursor-pointer text-litepie-primary-600 text-[16px]"><ms-icon path="close-icon" type="svg" :w="48" :h="48" /></span></template><div :id="`pdfjs-modal-container-${random}`" class="page-container page-modal-container"><div :id="`pdfModalViewer-${random}`" class="pdfViewer"></div></div></a-modal></div>
    </template><style lang="less">.ant-full-modal {.ant-modal {max-width: 100%;top: 0;padding-bottom: 0;margin: 0;}.ant-modal-content {display: flex;flex-direction: column;height: calc(100vh);}.ant-modal-body {flex: 1;padding: 0;}.ant-modal-close {top: 30px;right: 30px;}}
    </style>
    <style lang="less" scoped>.page-container {position: absolute;inset: 0;width: 100%;height: 100%;overflow: auto;}/* another way to scale pdf, still will report error :deep(.pdf-thumbnail-viewer) {--scale-factor: 0.5 !important;canvas {width: 100% !important;height: 100% !important;}}*/
    </style>
    
  4. 对于旧版浏览器,新建pdfPreviewForOld.vue,唯一不同的地方是需要替换pdfjsLib导入
    import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js';
  5. 新建pdfPreview.vue,导入两个组件
    <script setup lang="ts">const supportedOlderBrowser = computed(() => {return getChromeVersion() <= 88 || isSafari();});
    </script><template><div><!-- don't change v-if order, otherwise will report error --><pdfPreviewForOld v-bind="$attrs" v-if="supportedOlderBrowser"></pdfPreviewForOld><pdfPreviewForMordern v-else v-bind="$attrs"></pdfPreviewForMordern></div>
    </template>
  6. 上面用到的判断浏览器的方法
    /*** Determine whether it is safari browser* @return {Boolean} true,false*/
    export const isSafari = () => getUserAgent().indexOf('safari') > -1 && !isChrome(); /*** Determine whether it is chrome browser* @return {Boolean} true,false*/
    export const isChrome = () => /chrome/.test(getUserAgent()) && !/chromium/.test(getUserAgent());export const getChromeVersion = () =>{  let raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);return raw ? parseInt(raw[2], 10) : false;
    }
  7. 导入后预览pdf文件
    <pdf-preview
    :path="picture.originalUrl"
    >
    </pdf-preview>

待优化的问题:

  1. 为了兼容需要重复写两个组件,试过动态导入的方式行不通
  2. 控制台会报[offsetParent is not set -- cannot scroll]的错误,但是不影响预览
     

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/885245.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MacOS实操】如何基于SSH连接远程linux服务器

MacOS上远程连接linux服务器&#xff0c;可以使用ssh命令pem秘钥文件连接。 一、准备pem秘钥文件 如果已经有pem文件&#xff0c;则跳过这一步。如果手上有ppk文件&#xff0c;那么需要先转换为pem文件。 macOS 的默认 SSH 客户端不支持 PPK 格式&#xff0c;你需要将 PPK 文…

SQL server增删改查语句和实例

在 SQL Server 中&#xff0c;增删改查操作分别对应 INSERT、DELETE、UPDATE 和 SELECT 语句。以下是具体介绍及实例&#xff1a; 一、插入数据&#xff08;INSERT&#xff09; 语法&#xff1a; INSERT INTO table_name (column1, column2, column3,...) VALUES (value1, val…

parseInt 是一个内置的 JavaScript 函数,用于将字符串转换为整数。

parseInt(options.checkNumber, 10) 中的 10 表示将字符串转换为十进制整数。 解释 parseInt 函数&#xff1a; parseInt 是一个内置的 JavaScript 函数&#xff0c;用于将字符串转换为整数。它有两个参数&#xff1a; 第一个参数是要转换的字符串。第二个参数是转换时使用的基…

鸿蒙ArkTS中的布局容器组件(Scroll、List、Tabs)

1、Scroll组件 Scroll组件是一个可滚动的容器组件&#xff0c;用于在子组件的布局尺寸超过父组件尺寸时提供滚动功能。它允许在其内部容纳超过自身显示区域的内容&#xff0c;并通过滚动机制来查看全部内容。这对于显示大量信息&#xff08;如长列表、长篇文本或大型图像等&…

ElasticSearch备考 -- Manage the index lifecycle (ILM)

一、题目 在集群中&#xff0c;数据首先分布在data_hot节点&#xff0c;rollover 设置max_age:3d, max_docs:5,max_size:50gb, 优先级为100。 max_age:15s, forcemarge 段合并&#xff0c;数据迁移到data_warm节点&#xff0c; 副本数为0&#xff0c;优先级为50 max_age:30s, 数…

信息安全工程师(81)网络安全测评质量管理与标准

一、网络安全测评质量管理 遵循标准和流程 网络安全测评应严格遵循国家相关标准和流程&#xff0c;确保测评工作的规范性和一致性。这些标准和流程通常包括测评方法、测评步骤、测评指标等&#xff0c;为测评工作提供明确的指导和依据。 选择合格的测评团队 测评团队应具备相关…

使用 Python 构建代理池并测试其有效性

前言 在本篇文章中,我们将介绍如何通过 Python 脚本来构建一个代理池,并且对这些代理的有效性进行测试。整个流程涵盖了从网站抓取代理信息、存储这些信息以及异步地测试代理的有效性。这个脚本可以用作网络爬虫或其他需要使用代理服务器的应用的基础工具。 目标网站 一、…

设计者模式之策略模式

前言 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多样&#xff0c;经常改变&#xff0c;如果将这些算法都写在对象中&#xff0c;将会使对象变得异常复杂&#xff1b;而且有时候支持不频繁使用的算法也是一个性能负担。 如何在运行时根据需要透明地更改对象的算…

tomcat 开启远程debug模式

1.修改位置 CATALINA_OPTS"-Xdebug -Xrunjdwp:transportdt_socket,address*:8000,servery,suspendn"2.修改环境变量的方式 apache-tomcat-9.0.86/bin/setenv.sh export JAVA_HOME/opt/jdk1.8.0_171 export CATALINA_HOME/opt/apache-tomcat-9.0.86 export JAVA_OP…

AI辅助论文写作的利弊

人工智能的时代&#xff0c;AI从自动驾驶到智能家居&#xff0c;慢慢的都成为了我们生活中的一部分。可当AI被放到学术研究领域&#xff0c;特别是撰写论文这一问题上时&#xff0c;却出现了大量的争议&#xff0c;认为AI撰写论文会削弱该有的批判性思维能力。那不用AI撰写论文…

vue3+less使用主题定制(多主题定制)可切换主题

假如要使用两套主题&#xff1a;蓝色、红色 例如&#xff1a; 首先确保自己的vue3项目有less&#xff0c;这边不多做接入解释 1、在src目录下建一个styles文件夹&#xff0c;在syles文件夹下面新建两个less文件&#xff1a;theme.less和variables.less&#xff1b; theme.le…

后端Node学习项目-项目基础搭建

前言 各位好&#xff0c;我是前端SkyRain。最近为了响应公司号召&#xff0c;开始对后端知识的学习&#xff0c;作为纯粹小白&#xff0c;记录下每一步的操作流程。 项目仓库&#xff1a;https://gitee.com/sky-rain-drht/drht-node 因为写了文档&#xff0c;代码里注释不是很…

6-1.Java 面向对象 - 初级(对象与属性、对象与方法、递归、重载、可变参数、作用域、构造器、对象创建流程详解、this 关键字)

一、对象与属性 1、基本介绍 属性是类的一个组成部分&#xff0c;一般是基本数据类型&#xff0c;也可以是引用数据类型 属性的定义语法类似变量 【访问修饰符】 【属性类型】 【属性名】;属性的定义类型可以为任何类型&#xff08;基本数据类型、引用数据类型&#xff09;…

Maven(六)mvn 命令将 jar 包推送到 远程/本地仓库

目录 一、deploy - 推送到远程仓库1.1 命令语法&#xff1a;1.2 执行结果&#xff1a;1.3 可能遇到的问题问题1&#xff1a;with status code 401问题2&#xff1a;with status code 405问题3&#xff1a;Cannot deploy artifact from the local repository 二、install - 推送…

C语言心型代码解析

方法一 心型极坐标方程 爱心代码你真的理解吗 笛卡尔的心型公式&#xff1a; for (y 1.5; y > -1.5; y - 0.1) for (x -1.5; x < 1.5; x 0.05) 代码里面用了二个for循环&#xff0c;第一个代表y轴&#xff0c;第二个代表x轴 二个增加的单位不同&#xff0c;能使得…

JVM 由多个模块组成,每个模块负责特定的功能

Java虚拟机&#xff08;JVM, Java Virtual Machine&#xff09;是一个抽象的计算机&#xff0c;它提供了一个运行环境&#xff0c;使得Java字节码可以在不同的平台上执行。JVM 由多个模块组成&#xff0c;每个模块负责特定的功能。以下是 JVM 的主要模块及其功能&#xff1a; …

Java 中基于优先级执行任务的线程池实现

在日常开发中&#xff0c;我们经常遇到这样一种需求&#xff1a;需要按照任务的优先级顺序来执行&#xff0c;而不是简单的先进先出&#xff08;FIFO&#xff09;。Java 提供了 PriorityBlockingQueue&#xff0c;这是一个基于优先级排序的线程安全队列&#xff0c;可以用于实现…

CES2025展会媒体发布现场邀约采访计划来啦!海外科技电子媒体报纸新闻宣传方案

【本篇由 言同数字全球媒体投放 原创】一、CES 2025 展会概述 CES&#xff08;国际消费电子展&#xff09; 是全球规模最大、最具影响力的消费电子展会之一&#xff0c;由美国消费技术协会&#xff08;CTA&#xff09;主办。每年1月&#xff0c;CES吸引来自全球各地的行业领袖…

Axure设计之三级联动选择器教程(中继器)

使用Axure设计三级联动选择器&#xff08;如省市区选择器&#xff09;时&#xff0c;可以利用中继器的数据存储和动态交互功能来实现。下面介绍中继器三级联动选择器设计的教程&#xff1a; 一、效果展示&#xff1a; 1、在三级联动选择器中&#xff0c;首先选择省份&#xff…

【1】 Kafka快速入门-从原理到实践

文章目录 🔍 一、引言📜 二、Kafka 的历史🏗️ 三、Kafka 的核心结构🖥️ (一)Broker📋 (二)Topic📄 (三)Partition📤 (四)Producer📥 (五)Consumer🐒 (六)Zookeeper💡 四、Kafka 的重点概念📨 (一)消息📏 (二)偏移量(Offset)🔄 (…