实现 Nuxt3 预览PDF文件
- 安装必要的库,这里使用PDF.js库
npm install pdfjs-dist --save - 为了解决跨域问题,在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),}) }) - 支持现代浏览器,新建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> - 对于旧版浏览器,新建pdfPreviewForOld.vue,唯一不同的地方是需要替换pdfjsLib导入
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.js'; - 新建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> - 上面用到的判断浏览器的方法
/*** 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; } - 导入后预览pdf文件
<pdf-preview :path="picture.originalUrl" > </pdf-preview>
待优化的问题:
- 为了兼容需要重复写两个组件,试过动态导入的方式行不通
- 控制台会报[offsetParent is not set -- cannot scroll]的错误,但是不影响预览
相关文章:
实现 Nuxt3 预览PDF文件
安装必要的库,这里使用PDF.js库 npm install pdfjs-dist --save 为了解决跨域问题,在server/api 下 创建一个请求api, downloadFileByProxy.ts import { defineEventHandler } from h3;export default defineEventHandler(async event >…...
udp为什么会比tcp 有更低的延迟
UDP(User Datagram Protocol,用户数据报协议)相比TCP(Transmission Control Protocol,传输控制协议)具有更低的延迟,这主要归因于UDP协议的设计特点和机制。以下是对UDP比TCP延迟低的原因的详细…...
基于java+SpringBoot+Vue的洗衣店订单管理系统设计与实现
项目运行 环境配置: Jdk1.8 Tomcat7.0 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: Springboot mybatis Maven mysql5.7或8.0等等组成&#x…...
HarmonyOS-消息推送
一. 服务简述 Push Kit(推送服务)是华为提供的消息推送平台,建立了从云端到终端的消息推送通道。所有HarmonyOS 应用可通过集成 Push Kit,实现向应用实时推送消息,使消息易见,构筑良好的用户关系࿰…...
数据分析:宏基因组DESeq2差异分析筛选差异物种
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍原理:计算步骤:结果:加载R包准备画图主题数据链接导入数据Differential abundance (No BP vs 2BP TA)构建`countData`矩阵过滤低丰度物种构建DESeq数据对象DESeq2差异分析画图Di…...
出海企业如何借助云计算平台实现多区域部署?
云计算de小白 如需进一步了解,请单击链接了解有关 Akamai 云计算的更多信息 在本文中我们将告诉大家如何在Linode云计算平台上借助VLAN快速实现多地域部署。 首先我们需要明确一些基本概念和思想: 部署多区域 VLAN 为了在多区域部署中在不同的 VLAN …...
硬件---1电路设计安全要点以及欧姆定律
前言: 一直搞的东西都偏软件,硬件也一直在学,元器件、基础电路知识、PCB设计、模电运放都学的马马虎虎,因此决定进行系统性学习,内容基本来源于手里的视频和书本以及自己的感悟。 一电路安全 1电路安全 在初期基础…...
Linux如何更优质调节系统性能
一、硬件优化 增加物理内存:最直接的提升系统性能的方法。内存不足时,系统会频繁进行交换(swapping)活动,这会显著降低系统的响应速度,因为磁盘IO速度远低于内存访问速度。通过增加内存,可以减…...
第三十五章 Vue路由进阶之声明式导航(跳转传参)
目录 一、引言 二、查询参数传参 2.1. 使用方式 2.2. 完整代码 2.2.1. main.js 2.2.2. App.vue 2.2.3. Search.vue 2.2.4. Home.vue 2.2.5. index.js 三、动态路由传参 3.1. 使用方式 3.2. 完整代码 3.2.1. main.js 3.2.2. App.vue 3.2.3. Search.vue 3.2.4. Hom…...
python爬虫自动库DrissionPage保存网页快照mhtml/pdf/全局截图/打印机另存pdf
目录 零一、保存网页快照的三种方法二、利用打印机保存pdf的方法 零 最近星球有人问如何使用页面打印功能,另存为pdf 一、保存网页快照的三种方法 解决方案已经放在星球内:https://articles.zsxq.com/id_55mr53xahr9a.html当然也可以看如下代码&…...
基于毫米波雷达和TinyML的车内检测、定位与分类
英文标题:In-Cabin Detection, Localization and Classification based on mmWave Radar with TinyML 作者信息: 王志飞,程一格,彭辉,周会强,王铮,刘宏全所属机构:Calterah Semico…...
小E的射击训练
问题描述 小E正在训练场进行射击练习,靶有10个环,靶心位于坐标(0, 0)。每个环对应不同的得分,靶心内(半径为1)得10分,依次向外的每个环分数减少1分。若射击点在某个半径为i的圆内,则得11-i分。…...
React的概念以及发展前景如何?
React是一个由Facebook开发的用于构建用户界面的的开源JavaScript库,它主要用于构建大型、动态的Web应用程序。React的主要特点是使用VirtualDOM(虚拟DOM)来优化性能,并使用声明式的编程方式来编写UI。 React的主要概念包括&#…...
PDF生成:全面解析,C# 如何使用iTextSharp库(或其他类似库)生成PDF文档,包括如何将位图图像嵌入PDF中。
一、概述 PDF(Portable Document Format)是一种广泛使用的文档格式,由Adobe公司在1993年推出。PDF的目标是能够在任何设备上呈现固定格式的文档,无论是在不同的操作系统、硬件设备,还是在打印时,都能保证文…...
如何选择最适合的消息队列?详解 Kafka、RocketMQ、RabbitMQ 的使用场景
引言 在日常开发中,消息队列已经成为业务场景中几乎不可或缺的一部分。无论是订单系统、日志收集、分布式事务,还是大数据实时流处理,消息队列都在支撑着这些关键环节。目前市面上常用的消息队列有三种(ActiveMQ 虽然在企业集成中仍有应用&a…...
gitlab项目如何修改主分支main为master,以及可能遇到的问题
如果你希望将 Git 仓库的主分支名称从 main 修改为 master: 1. 本地修改分支名称 首先,切换到 main 分支: git checkout main将 main 分支重命名为 master: git branch -m main master2. 更新远程仓库 将本地更改推送到远程仓库…...
RRF(Reciprocal Rank Fusion,倒数排序融合)
RRF(Reciprocal Rank Fusion,倒数排序融合) 摘要 倒数排序融合 RRF 是一种简单的方法,用于结合多个 IR(Information Retrieval) 系统的文档排名,始终比任何单独的系统产生更好的结果。 通过使用 RRF 来结合几个TREC实验的结果,并建立一个 …...
移动开发(七):.NET MAUI使用RESTAPI实现查询天气笔记
目录 一、接口准备 二、实体部分 三、页面部分 四、后台代码逻辑 五、总结 在移动开发过程中,第三方对接是非常常见的。今天给大家分享.NET MAUI如何使用REST API实现输入城市名称查询天气的示例,希望对大家学习.NET MAUI可以提供一些帮助! 一、接口准备 首先我们需要…...
企业数据无缝对接:从旺店通到金蝶云的入库单管理案例
【类型:盘盈入库】旺店通-入库单管理>金蝶-其他入库单 在企业的日常运营中,数据的高效集成和准确传递是确保业务顺畅运行的关键。本文将分享一个实际案例,展示如何通过轻易云数据集成平台,将旺店通企业奇门的数据无缝对接到金蝶云星空&am…...
青少年编程与数学 02-003 Go语言网络编程 19课题、Go语言Restful编程
青少年编程与数学 02-003 Go语言网络编程 19课题、Go语言Restful编程 课题摘要:一、微服务微服务的主要特点包括:微服务架构的挑战:微服务的应用场景: 二、RESTfulRESTful的核心原则和特征包括:RESTful API的优势:REST…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)
安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...
