vue2 多页面pdf预览

        使用pdfjs-dist预览pdf,实现预加载,滚动条翻页。pdfjs的版本很重要,换了好多版本,终于有一个能用的

node 20.18.1
"pdfjs-dist": "^2.2.228",

        vue页面代码如下

<template>
  <div v-loading="loading">
    <div class="fixed-toolbar">
      <div class="flex flex-direction">
        <div class="mb4">
          <el-button @click="onClose()" size="mini"
            type="warning">返&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;回</el-button>
        </div>
        <div class="mb4">
          <el-button @click="switchViewMode()" size="mini" type="success">切换模式</el-button>
        </div>
        <div class="mb4">
          <el-button @click="scalBig()" size="mini"
            type="primary">放&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;大</el-button>
        </div>
        <!-- <div>
          <el-button class="mb4" @click="renderPdf(1)" size="mini" type="primary">默认</el-button>
        </div> -->
        <div>
          <el-button @click="scalSmall()" size="mini"
            type="primary">缩&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;小</el-button>
        </div>
        <el-dropdown size="mini" trigger="click" class="more-dropdown" style="line-height: 35px;">
          <el-button size="mini" type="primary">缩&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;放</el-button>
          <el-dropdown-menu slot="dropdown">
            <el-dropdown-item><span class="el-button--text-" @click="scale = 1">1X</span></el-dropdown-item>
            <el-dropdown-item><span class="el-button--text-" @click="scale = 2">2X</span></el-dropdown-item>
            <el-dropdown-item><span class="el-button--text-" @click="scale = 3">3X</span></el-dropdown-item>
          </el-dropdown-menu>
        </el-dropdown>
      </div>
    </div>
    <!-- PDF容器 -->
    <div ref="pdfContainer" class="pdf-container" @scroll="handleScroll">
      <!-- 占位符,用于撑开滚动区域 -->
      <div :style="{ height: totalHeight + 'px' }"></div>

      <!-- 渲染可见页面 -->
      <div v-for="page in visiblePages" :key="page.pageNumber" :ref="`page-${page.pageNumber}`" class="page-container"
        :style="{ top: getPageTop(page.pageNumber) + 'px' }">
        <canvas :ref="`canvas-${page.pageNumber}`"></canvas>
        <div v-if="page.loading" class="loading-indicator">加载中...</div>
      </div>
    </div>
  </div>
</template>

<script>
import pdfjsLib from 'pdfjs-dist/build/pdf';
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
import { debounce } from 'lodash';

pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;

export default {
  data() {
    return {
      pdfDoc: null,
      loading: true,
      totalPages: 0,
      pageHeights: [],
      totalHeight: 0,
      visiblePages: [],
      scale: 1,
      baseScale: 1, // 移动端基准缩放比例
      scrollTop: 0,
      viewport: null,
      renderTasks: {},
      loadedPages: {}, // 新增:记录已加载的页面
      desiredTotal: 5, // 每次加载的页面数量
      canvasPool: []
    };
  },
  mounted() {
    const pageSize = this.$route.query.pageSize
    this.desiredTotal = pageSize?pageSize:this.desiredTotal
    console.log('预加载页面数量: -->', this.desiredTotal);
    this.loadPdf(this.pdfUrl);
    this.debouncedHandleScroll = debounce(this.updateVisiblePages, 100);
    this.debouncedHandleResize = debounce(() => {
      this.calculatePageHeights();
      this.updateVisiblePages();
    }, 100);
    window.addEventListener('resize', this.handleResize);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.handleResize);
    this.debouncedHandleScroll.cancel();
    this.debouncedHandleResize.cancel();
    Object.values(this.renderTasks).forEach((task) => task.cancel());
  },
  computed: {
    pdfUrl() {
      return this.$route.query.pdfUrl;
    },
  },
  watch: {
    scale(newVal) {
      this.loadedPages = {}
      this.updateVisiblePages()
    }
  },
  methods: {
    switchViewMode() {
      this.$router.replace({
        name: 'pagePreviewPdf',
        query: {
          pdfUrl: this.pdfUrl
        }
      })
    },
    onClose() {
      this.$router.go(-1)
    },
    // 放大
    scalBig() {
      this.scale = this.scale + 0.2
    },
    // 缩小
    scalSmall() {
      if (this.scale > 1.2) {
        this.scale = this.scale - 0.2
      }
    },
    async loadPdf(url) {
      try {
        const loadingTask = pdfjsLib.getDocument(url);
        this.pdfDoc = await loadingTask.promise;
        this.totalPages = this.pdfDoc.numPages;
        await this.calculatePageHeights();
        this.updateVisiblePages();
        this.loading = false;
      } catch (error) {
        console.error('加载PDF失败:', error);
        this.loading = false;
        this.$message.error('加载PDF失败:' + error);
      }
    },
    handleScroll() {
      this.debouncedHandleScroll();
    },
    handleResize() {
      this.debouncedHandleResize();
    },

    async calculatePageHeights() {
      this.pageHeights = [];
      let page = await this.pdfDoc.getPage(1);
      let viewport = page.getViewport({ scale: 1 });
      this.baseScale = this.isMobile ? window.innerWidth / viewport.width : 1
      for (let i = 1; i <= this.totalPages; i++) {
        const page = await this.pdfDoc.getPage(i);
        const viewport = page.getViewport({ scale: this.scale*this.baseScale });
        this.pageHeights.push(viewport.height);
        console.log(i, ' 页面高度:', viewport.height, 'px,页面宽度:', viewport.width, 'px')
      }

      this.totalHeight = this.pageHeights.reduce((sum, height) => sum + height, 0);
    },

    getPageTop(pageNumber) {
      const top = this.pageHeights.slice(0, pageNumber - 1).reduce((sum, height) => sum + height, 0)
      // console.log('当前显示页面top:', top)
      return top;
    },

    updateVisiblePages() {
      const container = this.$refs.pdfContainer;
      const scrollTop = container.scrollTop;
      const containerHeight = container.clientHeight;

      // 计算初始可见范围
      let startPage = 1;
      let endPage = this.totalPages;
      let currentHeight = 0;

      // 计算起始页
      for (let i = 0; i < this.pageHeights.length; i++) {
        currentHeight += this.pageHeights[i];
        if (currentHeight > scrollTop) {
          startPage = i + 1;
          break;
        }
      }

      // 计算结束页
      currentHeight = 0;
      for (let i = 0; i < this.pageHeights.length; i++) {
        currentHeight += this.pageHeights[i];
        if (currentHeight > scrollTop + containerHeight) {
          endPage = i + 1;
          break;
        }
      }

      // 扩展预加载范围,总页数不超过10
      const desiredTotal = this.desiredTotal;
      const visibleCount = endPage - startPage + 1;
      let remaining = desiredTotal - visibleCount;

      if (remaining > 0) {
        let preloadBefore = Math.floor(remaining / 2);
        let preloadAfter = remaining - preloadBefore;

        let newStart = Math.max(1, startPage - preloadBefore);
        let newEnd = Math.min(this.totalPages, endPage + preloadAfter);

        // 边界调整
        const addedBefore = startPage - newStart;
        const addedAfter = newEnd - endPage;

        if (addedBefore < preloadBefore) {
          newEnd = Math.min(this.totalPages, newEnd + (preloadBefore - addedBefore));
        } else if (addedAfter < preloadAfter) {
          newStart = Math.max(1, newStart - (preloadAfter - addedAfter));
        }

        startPage = newStart;
        endPage = newEnd;

        // 确保不超过总页数限制
        if (endPage - startPage + 1 > desiredTotal) {
          endPage = startPage + desiredTotal - 1;
          if (endPage > this.totalPages) endPage = this.totalPages;
        }
      } else {
        // 可见页数超过10时调整
        endPage = startPage + desiredTotal - 1;
        if (endPage > this.totalPages) {
          endPage = this.totalPages;
          startPage = Math.max(1, endPage - desiredTotal + 1);
        }
      }

      // 生成可见页面数组
      this.visiblePages = [];
      console.log('渲染显示范围:', startPage, ' --- ', endPage)
      const pages = []
      for (let j = startPage; j <= endPage; j++) {
        pages.push(j + '')
      }
      console.log('>>>>loadedPages:', JSON.stringify(this.loadedPages))
      // 移除不在显示区的页面
      Object.keys(this.loadedPages).filter(k => !pages.includes(k)).forEach(pageNumber => {
        this.$delete(this.loadedPages, pageNumber);
      })
      console.log('>>>>cleaned loadedPages:', JSON.stringify(this.loadedPages))

      for (let i = startPage; i <= endPage; i++) {
        const isLoaded = !!this.loadedPages[i];
        this.visiblePages.push({
          pageNumber: i,
          loading: !isLoaded,
        });
        if (!isLoaded) {
          this.renderPage(i);
          console.log('渲染', i)
        } else {
          console.log('DONE:', i)
        }
      }
    },

    async renderPage(pageNumber) {
      console.log('>>>>loadedPages:', JSON.stringify(this.loadedPages))
      if (this.loadedPages[pageNumber]) {
        const index = this.visiblePages.findIndex((p) => p.pageNumber === pageNumber);
        if (index !== -1) this.$set(this.visiblePages[index], 'loading', false);
        return;
      }

      if (this.renderTasks[pageNumber]) this.renderTasks[pageNumber].cancel();

      try {
        const page = await this.pdfDoc.getPage(pageNumber);
        const canvas = this.$refs[`canvas-${pageNumber}`][0];
        const context = canvas.getContext('2d');
        const viewport = page.getViewport({ scale: this.scale*this.baseScale });

        canvas.height = viewport.height;
        canvas.width = viewport.width;

        const renderTask = page.render({ canvasContext: context, viewport });
        this.renderTasks[pageNumber] = renderTask;

        await renderTask.promise;
        this.$set(this.loadedPages, pageNumber, true); // 标记为已加载

        const index = this.visiblePages.findIndex((p) => p.pageNumber === pageNumber);
        if (index !== -1) this.$set(this.visiblePages[index], 'loading', false);
      } catch (error) {
        if (error.name !== 'RenderingCancelledException') console.error('渲染失败:', error);
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.pdf-container {
  height: 100vh;
  overflow-y: auto;
  width: 100%;
  position: relative;
}

.page-container {
  position: absolute;
  width: 100%;
  background: #f5f5f5;
  margin-bottom: 20px;
  display: flex;
  justify-content: center;
}

.loading-indicator {
  text-align: center;
  padding: 20px;
}


.fixed-toolbar {
  position: fixed;
  bottom: 50%;
  right: 0;
  background-color: white;
  /* 可选:设置背景颜色 */
  opacity: 0.7;
  z-index: 1000;
  /* 确保工具栏在其他内容之上 */
  padding: 10px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  /* 可选:添加阴影效果 */
  margin-bottom: 10px;
  flex-wrap: wrap;
}

.mb4 {
  margin-bottom: 4px;
}

@media screen and (max-width: 768px) {}
</style>

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

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

相关文章

堆排序

目录 堆排序&#xff08;不稳定&#xff09;&#xff1a; 代码实现&#xff1a; 思路分析&#xff1a; 总结&#xff1a; 堆排序&#xff08;不稳定&#xff09;&#xff1a; 如果想要一段数据从小到大进行排序&#xff0c;则要先建立大根堆&#xff0c;因为这样每次堆顶上都能…

【C++】多态原理剖析

目录 1.虚表指针与虚表 2.多态原理剖析 1.虚表指针与虚表 &#x1f36a;类的大小计算规则 一个类的大小&#xff0c;实际就是该类中成员变量之和&#xff0c;需要注意内存对齐空类&#xff1a;编译器给空类一个字节来唯一标识这个类的对象 对于下面的Base类&#xff0c;它的…

【Git】完美解决git push报错403

remote: Permission to xx.git denied to xx. fatal: unable to access https://github.com/xx/xx.git/: The requested URL returned error: 403出现这个就是因为你的&#xff08;personal access tokens &#xff09;PAT过期了 删掉旧的token 生成一个新的 mac系统 在mac的…

初窥强大,AI识别技术实现图像转文字(OCR技术)

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据、人工智能领域创作者。目前从事python全栈、爬虫和人工智能等相关工作&#xff0c;主要擅长领域有&#xff1a;python…

黑马Redis详细笔记(实战篇---短信登录)

目录 一.短信登录 1.1 导入项目 1.2 Session 实现短信登录 1.3 集群的 Session 共享问题 1.4 基于 Redis 实现共享 Session 登录 一.短信登录 1.1 导入项目 数据库准备 -- 创建用户表 CREATE TABLE user (id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID,phone …

逻辑回归不能解决非线性问题,而svm可以解决

逻辑回归和支持向量机&#xff08;SVM&#xff09;是两种常用的分类算法&#xff0c;它们在处理数据时有一些不同的特点&#xff0c;特别是在面对非线性问题时。 1. 逻辑回归 逻辑回归本质上是一个线性分类模型。它的目的是寻找一个最适合数据的直线&#xff08;或超平面&…

41.兼职网站管理系统(基于springbootvue的Java项目)

目录 1.系统的受众说明 2.相关技术 2.1 B/S架构 2.2 Java技术介绍 2.3 mysql数据库介绍 2.4 Spring Boot框架 3.系统分析 3.1 需求分析 3.2 系统可行性分析 3.2.1技术可行性&#xff1a;技术背景 3.2.2经济可行性 3.2.3操作可行性&#xff1a; 3.3 项目设计目…

MS08067练武场--WP

免责声明&#xff1a;本文仅用于学习和研究目的&#xff0c;不鼓励或支持任何非法活动。所有技术内容仅供个人技术提升使用&#xff0c;未经授权不得用于攻击、侵犯或破坏他人系统。我们不对因使用本文内容而引起的任何法律责任或损失承担责任。 注&#xff1a;此文章为快速通关…

Elasticsearch:如何使用 Elastic 检测恶意浏览器扩展

作者&#xff1a;来着 Elastic Aaron Jewitt 当你的 CISO 询问你的任何工作站上是否安装过特定的浏览器扩展时&#xff0c;你多快能得到正确答案&#xff1f;恶意浏览器扩展是一个重大威胁&#xff0c;许多组织无法管理或检测。这篇博文探讨了 Elastic Infosec 团队如何使用 os…

检测网络安全漏洞 工具 网络安全 漏洞扫描 实验

实验一的名称为信息收集和漏洞扫描 实验环境&#xff1a;VMware下的kali linux2021和Windows7 32&#xff0c;网络设置均为NAT&#xff0c;这样子两台机器就在一个网络下。攻击的机器为kali,被攻击的机器为Windows 7。 理论知识记录&#xff1a; 1.信息收集的步骤 2.ping命令…

esxi添加内存条因为资源不足虚拟机无法开机——避坑

exsi8.0我加了6根内存条&#xff0c;然后将里面的ubuntu的内存增加 haTask-2-vim.VirtualMachine.powerOn-919 描述 打开该虚拟机电源 虚拟机 ub22 状况 失败 - 模块“MonitorLoop”打开电源失败。 错误 模块“MonitorLoop”打开电源失败。无法将交换文件 /vmfs/volumes…

Vision Transformer:打破CNN垄断,全局注意力机制重塑计算机视觉范式

目录 引言 一、ViT模型的起源和历史 二、什么是ViT&#xff1f; 图像处理流程 图像切分 展平与线性映射 位置编码 Transformer编码器 分类头&#xff08;Classification Head&#xff09; 自注意力机制 注意力图 三、Coovally AI模型训练与应用平台 四、ViT与图像…

自动驾驶---如何打造一款属于自己的自动驾驶系统

在笔者的专栏《自动驾驶Planning决策规划》中&#xff0c;主要讲解了行车的相关知识&#xff0c;从Routing&#xff0c;到Behavior Planning&#xff0c;再到Motion Planning&#xff0c;以及最后的Control&#xff0c;笔者都做了相关介绍&#xff0c;其中主要包括算法在量产上…

vulnhub 靶场 —— NullByte

免责声明 本博客文章仅供教育和研究目的使用。本文中提到的所有信息和技术均基于公开来源和合法获取的知识。本文不鼓励或支持任何非法活动&#xff0c;包括但不限于未经授权访问计算机系统、网络或数据。 作者对于读者使用本文中的信息所导致的任何直接或间接后果不承担任何…

Unity做2D小游戏2------创建地形和背景

我是跟着这个up主做的&#xff1a;【unity/2d/超基础】教你做一款2d横版游戏 打开Unity Hub后&#xff0c;点击项目--新项目&#xff0c;进入下面的界面&#xff0c;可以根据想要做的项目选择对应的模型&#xff0c;我现在要做2D小游戏&#xff0c;所以选择第一个2D核心模板。…

判断函数是否为react组件或lazy包裹的组件

function Modal(){return <p>123</p> } 实参里填入函数名,是false 实参里填入标签形式的函数,是true isValidElement(Modal)//false isValidElement(<Modal></Modal>)//true 官方说明 isValidElement – React 中文文档 但是官方并不建议用isValidE…

Vue笔记(八)

一、Pinia &#xff08;一&#xff09;手动添加Piaia到Vue项目 1.安装Pinia&#xff1a;使用包管理器进行安装&#xff0c;在项目目录下运行 npm install pinia 或 yarn add pinia &#xff0c;为项目引入Pinia状态管理库。 2.创建Pinia实例&#xff1a;在项目的JavaScript代…

如何将3DMAX中的3D文件转换为AutoCAD中的2D图形?

大家好,今天我们来探讨一下如何将3DMAX中的3D文件转换为AutoCAD中的2D图形。无论是出于设计交流、施工准备还是其他实际需求,这种转换在工程设计领域都是一项非常实用的技能。接下来,我将为大家详细介绍几种实现这一转换的方法,帮助大家轻松跨越3D与2D设计之间的鸿沟。让我…

javaEE-11.javaScript入门

目录 一.什么是javaScript 二.快速实现 三.JS引入方式 1.行内引入: 2.内部引入: 3.外部引入: 四.基础语法 1.变量 变量命名规则: 2.数据类型 3.运算符 五.JS对象 1.数组 创建数组: 2.操作数组 3.函数 函数注意事项: 函数参数: 4.对象 1.使用字面量 创建对象:…

机器学习 - 进一步理解最大似然估计和高斯分布的关系

一、高斯分布得到的是一个概率吗&#xff1f; 高斯分布&#xff08;也称为正态分布&#xff09;描述的是随机变量在某范围内取值的概率分布情况。其概率密度函数&#xff08;PDF&#xff09;为&#xff1a; 其中&#xff0c;μ 是均值&#xff0c;σ 是标准差。 需要注意的是…