vant4+vue3上传一个pdf文件并实现pdf的预览。使用插件pdf.js

news2025/4/16 21:49:12

注意下载的插件的版本"pdfjs-dist": "^2.2.228",

npm i  pdfjs-dist@2.2.228

然后封装一个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给组件通知让他不显示也就是销毁子组件的原因。


问题:无意间双击了文件导致遮罩马上显示又隐藏。页面效果就是黑色遮罩闪了一下。

可以使用防抖的方式。延迟关闭。参考上面紫色的关闭函数

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

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

相关文章

2025 数字中国创新大赛数字安全赛道数据安全产业积分争夺赛初赛-东部赛区WriteUp

2025 数字中国创新大赛数字安全赛道数据安全产业积分争夺赛初赛-东部赛区WriteUp 数据安全:ez_upload(60分)&#xff1a; 模型安全&#xff1a;数据分析&#xff1a;溯源与取证&#xff1a;1-1&#xff1a;1-2&#xff1a; 数据社工&#xff1a;2-2:2-3:2-4: 数据跨境&#xff…

2025 年网络安全终极指南

我们生活在一个科技已成为日常生活不可分割的一部分的时代。对数字世界的依赖性日益增强的也带来了更大的网络风险。 网络安全并不是IT专家的专属特权&#xff0c;而是所有用户的共同责任。通过简单的行动&#xff0c;我们可以保护我们的数据、隐私和财务&#xff0c;降低成为…

1.6-抓包技术(Burp Suite\Yakit抓包\Web、APP、小程序)

1.6-抓包技术&#xff08;Burp Suite\Yakit抓包\Web、APP、小程序&#xff09; 如果要使用抓包软件&#xff0c;基本上第一步都是要安装证书的。原因如下&#xff1a; 客户端&#xff08;浏览器或应用&#xff09;会检测到证书不受信任&#xff0c;并弹出 证书错误&#xff0…

图解力扣回溯及剪枝问题的模板应用

文章目录 选哪个的问题17. 电话号码的字母组合题目描述解题代码图解复杂度 选不选的问题78. 子集题目描述解题代码图解复杂度 两相转化77. 组合题目描述解题代码法一&#xff1a;按选哪个的思路法二&#xff1a;按选不选的思路 图解选哪个&#xff1a;选不选 复杂度 选哪个的问…

Elasticsearch 8.X 如何利用嵌入向量提升搜索能力?

众所周知&#xff0c;Elasticsearch 是一个非常流行的搜索引擎&#xff0c;因为它速度快、扩展性强&#xff0c;尤其擅长全文搜索。 近两年&#xff0c;向量嵌入&#xff08;Vector Embedding&#xff09;技术的引入&#xff0c;让 Elasticsearch 在处理高级搜索场景时变得更强…

MySQL体系架构(一)

1.1.MySQL的分支与变种 MySQL变种有好几个,主要有三个久经考验的主流变种:Percona Server,MariaDB和 Drizzle。它们都有活跃的用户社区和一些商业支持,均由独立的服务供应商支持。同时还有几个优秀的开源关系数据库,值得我们了解一下。 1.1.1.Drizzle Drizzle是真正的M…

深度强化学习基础 0:通用学习方法

过去自己学习深度强化学习的痛点&#xff1a; 只能看到各种术语、数学公式勉强看懂&#xff0c;没有建立清晰且准确关联 多变量交互关系浮于表面&#xff0c;有时候连环境、代理控制的变量都混淆 模型种类繁多&#xff0c;概念繁杂难整合、对比或复用&#xff0c;无框架分析所…

虚幻5的C++调试踩坑

本地调试VS附加调试 踩坑1 预编译版本的UE5没有符号文件&#xff0c;无法调试源码 官方代码调试所需要的符号文件bdp需要下载导入。我安装的5.5.4是预编译版本&#xff0c;并非ue5源码。所以不含bdp文件。需要调试官方代码则需要通过EPIC中下载安装。右键UE版本&#xff0c;打…

通信协议详解(十):PSI5 —— 汽车安全传感器的“抗干扰狙击手”

一、PSI5是什么&#xff1f; 一句话秒懂 PSI5就像传感器界的“防弹信使”&#xff1a;在汽车安全系统&#xff08;如气囊&#xff09;中&#xff0c;用两根线同时完成供电数据传输&#xff0c;即便车祸时线路受损&#xff0c;仍能确保关键信号准确送达&#xff01; 基础概念…

从零开始学Python游戏编程18-函数3

《从零开始学Python游戏编程17-函数2》中&#xff0c;通过代码重构的方式将游戏的主要代码写入到自定义函数runGame()中。对于runGame()中的代码&#xff0c;可以继续对其进行重构&#xff0c;以达到简化代码结构的目的。 1 自定义函数askPlayer() 1.1 函数作用 自定义函数a…

Spring事务传播机制

Spring 事务传播机制定义了在多个事务方法相互调用时&#xff0c;事务如何在这些方法间传播。它决定了一个事务方法调用另一个事务方法时&#xff0c;新的事务是如何开启、是否要加入已有的事务等情况。Spring 提供了 7 种事务传播行为&#xff0c;下面是详细介绍。 解释说明&…

Java常用工具算法-5--哈希算法、加密算法、签名算法关系梳理

1、哈希算法 数学本质&#xff1a;将任意长度输入&#xff08;明文&#xff09;映射为固定长度输出&#xff08;哈希值&#xff0c;也称摘要&#xff09;。主要特点&#xff1a; 不可逆性&#xff1a;无法通过哈希值反推原始输入。雪崩效应&#xff1a;输入的微小变化导致哈希…

python之安装PaddlePaddle和PaddleX解析pdf表格

目录标题 飞桨PaddlePaddle本地安装教程1-1. 基于 Docker 安装飞桨1-2. 基于 pip 安装飞桨2. 我两个环境 都选择的是pip 安装10. 如果报错10. 离线安装 飞桨PaddlePaddle本地安装教程 源码下载&#xff1a;https://github.com/PaddlePaddle/PaddleX/blob/release/3.0-beta1/do…

【11408学习记录】英语语法核心突破:揭秘表语从句结构与通知写作实战技巧

表语从句与通知写作 英语语法总结——主从复合句表语从句语句结构系动词表语从句的位置 写作通知写作第二段第三段落款 每日一句词汇第一步&#xff1a;找谓语第二步&#xff1a;断句第三步&#xff1a;简化第一句第二句第三句第四句第五句 英语 语法总结——主从复合句 表语…

封装红黑树实现map和set

前言&#xff1a; 之前我们学习了set与map容器的如何使用&#xff0c;红黑树的实现。接下来我们来看看如何通过封装红黑树&#xff0c;实现我们自己的set与map 相关文章&#xff1a;oi&#xff01;让我来给你唠唠咋实现红黑树☝️-CSDN博客 超详细介绍map&…

解码AI大脑:Claude的思维显微镜与语言炼金术

&#xff08;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff09;。 一、多语言思维实验&#xff1a;Claude的“概念空间”如何运转&#xff1f; 跨语言谜题&#xff1a;反义词的…

中科岩创基坑自动化监测解决方案

1.行业现状 城市基坑开挖具有施工风险高、施工难度大等特点。由于地下土体性质、荷载条件、施工环境的复杂性&#xff0c;单根据地质勘察资料和室内土工试验参数来确定设计和施工方案&#xff0c;往往含有许多不确定因素&#xff0c;对在施工过程中引发的土体性状、环境、邻近建…

机器学习01-支持向量机(SVM)(未完)

参考浙大 胡浩基老师 的课以及以下链接&#xff1a; https://blog.csdn.net/m0_74100344/article/details/139560508 https://blog.csdn.net/2301_78630677/article/details/132657023 https://blog.csdn.net/lsb2002/article/details/131338700 一、一些定义 T是倒置&…

[ctfshow web入门] web32

前置知识 协议相关博客&#xff1a;https://blog.csdn.net/m0_73353130/article/details/136212770 include&#xff1a;include "filename"这是最常用的方法&#xff0c;除此之外还可以 include url&#xff0c;被包含的文件会被当做代码执行。 data://&#xff1a…

鸿蒙开发中的并发与多线程

文章目录 前言异步并发 (Promise和async/await)多线程并发并发能力选择耗时任务并发执行场景常见业务场景 常驻任务并发执行场景常见业务场景 传统共享内存并发业务长时任务并发执行场景常见业务场景 并发任务管理线程间通信同语言线程间通信&#xff08;ArkTS内&#xff09;线…