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

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

复制代码
npm i  [email protected]

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


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

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

相关推荐
腾讯TNTWeb前端团队2 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
拉不动的猪6 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪6 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
uhakadotcom7 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom7 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom7 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom7 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试
咖啡教室8 小时前
前端开发日常工作每日记录笔记(2019至2024合集)
前端·javascript
咖啡教室9 小时前
前端开发中JavaScript、HTML、CSS常见避坑问题
前端·javascript·css
市民中心的蟋蟀11 小时前
第五章 使用Context和订阅来共享组件状态
前端·javascript·react.js