实现 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 CORS
          bufferCache.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]的错误,但是不影响预览
     

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux相关概念和易错知识点(20)(dentry、分区、挂载)

目录 1.dentry &#xff08;1&#xff09;路径缓存的原因 &#xff08;2&#xff09;dentry的结构 ①多叉树结构 ②file和dentry之间的联系 ③路径概念存在的意义 2.分区 &#xff08;1&#xff09;为什么要确认分区 &#xff08;2&#xff09;挂载 ①进入分区 ②被挂…

Redis 缓存击穿

目录 缓存击穿 什么是缓存击穿&#xff1f; 有哪些解决办法&#xff1f; 缓存穿透和缓存击穿有什么区别&#xff1f; 缓存雪崩 什么是缓存雪崩&#xff1f; 有哪些解决办法&#xff1f; 缓存预热如何实现&#xff1f; 缓存雪崩和缓存击穿有什么区别&#xff1f; 如何保…

电信网关配置管理系统 upload_channels.php 文件上传致RCE漏洞复现

0x01 产品简介 中国电信集团有限公司(英文名称“China Telecom”、简称“中国电信”)成立于2000年9月,是中国特大型国有通信企业、上海世博会全球合作伙伴。电信网关配置管理系统是一个用于管理和配置电信网络中网关设备的软件系统。它可以帮助网络管理员实现对网关设备的远…

澳鹏通过高质量数据支持 Onfido 优化AI反欺诈功能

“Appen 在 Onfido 的发展中发挥了至关重要的作用&#xff0c;并已成为我们运营的重要组成部分。我们很高兴在 Appen 找到了可靠的合作伙伴。” – Onfido 数据和分析总监 Francois Jehl 简介&#xff1a;利用人工智能和机器学习增强欺诈检测 在当今日益数字化的世界&#xff…

网站架构知识之Ansible模块(day021)

1.Ansible模块 作用:通过ansible模块实现批量管理 2.command模块与shell模块 command模块是ansible默认的模块&#xff0c;适用于执行简单的命令&#xff0c;不支持特殊符号 案列01&#xff0c;批量获取主机名 ansible all -m command -a hostname all表示对主机清单所有组…

应对AI与机器学习的安全与授权管理新挑战,CodeMeter不断创新引领保护方案

人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;技术正在快速发展&#xff0c;逐渐应用到全球各类主流系统、设备及关键应用场景中&#xff0c;尤其是在政府、商业和工业组织不断加深互联的情况下&#xff0c;AI和ML技术的影响日益广泛。虽然AI技术的…

实现uniapp-微信小程序 搜索框+上拉加载+下拉刷新

pages.json 中的配置 { "path": "pages/message", "style": { "navigationBarTitleText": "消息", "enablePullDownRefresh": true, "onReachBottomDistance": 50 } }, <template><view class…

布谷直播源码部署服务器关于数据库配置的详细说明

布谷直播源码搭建部署配置接口数据库 /public/db.php&#xff08;2019年8月后的系统在该路径下配置数据库&#xff0c;老版本继续走下面的操作&#xff09; 在项目代码中执行命令安装依赖库&#xff08;⚠️注意&#xff1a;如果已经有了vendor内的依赖文件的就不用执行了&am…

【C++】STL— stack的常见用法和模拟实现

目录 1、stack的介绍 2、stack的使用 构造一个空栈 stack的简单接口应用 3、stack的模拟实现 4、栈的相关题目 4.1 最小栈 4.1.2思路 4.1.3 实现代码 4.2 栈的压入、弹出序列 4.2.2 思路 4.2.3程序实现 1、stack的介绍 在C中&#xff0c;stack是一种标准模板库&am…

vue大疆建图航拍功能实现

介绍 无人机在规划一块区域的时候&#xff0c;我们需要手动的给予一些参数来影响无人机飞行&#xff0c;对于一块地表&#xff0c;无人机每隔N秒在空中间隔的拍照地表的一块区域&#xff0c;在整个任务执行结束后&#xff0c;拍到的所有区域照片能够完整的表达出一块地表&…

[ DOS 命令基础 2 ] DOS 命令详解-网络相关命令

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

es自动补全(仅供自己参考)

elasticssearch提供了CompletionSuggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询效率&#xff0c;对于文档中字段的类型有一些约束&#xff1a; 查询类型必须是&#xff1a;completion 字段内容是多个补全词条形成的数组 P…

react jsx基本语法,脚手架,父子传参,refs等详解

1&#xff0c;简介 1.1 概念 react是一个渲染html界面的一个js库&#xff0c;类似于vue&#xff0c;但是更加灵活&#xff0c;写法也比较像原生js&#xff0c;之前我们写出一个完成的是分为html&#xff0c;js&#xff0c;css&#xff0c;现在我们使用react库我们把html和js结…

Chrome浏览器如何导出所有书签并导入书签

前言 我平常在开发中&#xff0c;基本是用的谷歌的浏览器&#xff0c;也就是Chrome&#xff0c;因为这个对于开发来说&#xff0c;比较友好。在开发中&#xff0c;包括调试接口&#xff0c;打断点&#xff0c;查看打印日志等&#xff0c;都是非常不错的。另一方面&#xff0c;…

[WSL][桌面][X11]WSL2 Ubuntu22.04 安装Ubuntu桌面并且实现GUI转发(Gnome)

1. WSL安装 这里不再赘述&#xff0c;WSL2支持systemd&#xff0c;如果你发现其没有systemd相关指令&#xff0c;那么你应该看看下面这个 https://blog.csdn.net/noneNull0/article/details/135950369 但是&#xff0c;Ubuntu2204用不了这个脚本&#xff0c;比较蛋疼。 – …

C语言中的 printf( ) 与 scanf( )

时隔多日&#xff0c;小编我又回来咯小编相信之前的博客能够给大家带来不少的收获。在我们之前的文章中&#xff0c;许多代码块的例子都用到了printf( ) 与 scanf( )这两个函数&#xff0c;大家都知道他们需要声明头文件之后才能使用&#xff0c;那这两个函数是什么呢&#xff…

【Homework】【1--4】Learning resources for DQ Robotics in MATLAB

Learning resources for DQ Robotics in MATLAB Lesson 1 代码 % Step 2: Define the real numbers a1 and a2 a1 123; a2 321;% Step 3: Calculate and display a3 a1 a2 a3 a1 a2; disp([a3 (a1 a2) , num2str(a3)])% Step 4: Calculate and display a3 a1 * a2 a3…

前端刺客系列----Vue 3 入门介绍

目录 一.什么是 Vue 3&#xff1f; 二.Vue 3 的主要特性 三,Vue3项目实战 四.总结 在前端开发的世界里&#xff0c;Vue.js 作为一款渐进式的 JavaScript 框架&#xff0c;已成为许多开发者的首选工具。自从 Vue 3 发布以来&#xff0c;它带来了许多重要的改进和新特性&…

Linux入门:环境变量与进程地址空间

一. 环境变量 1. 概念 1️⃣基本概念&#xff1a; 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#x…

2024版最新最全的Kali Linux操作系统安装使用教程(非常详细)超十万字解说Kali零基础入门到精通教程,收藏这一篇就够了

前言 这是向阳给粉丝盆友们整理的网络安全渗透测试入门阶段渗透测试工具Kali全套教程&#xff0c;本文超十万字超长分析&#xff0c;建议大家收藏慢慢学习&#xff01; 喜欢的朋友们&#xff0c;记得给向阳点赞支持和收藏一下&#xff0c;关注我&#xff0c;学习黑客技术 Ka…