当前位置: 首页 > news >正文

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.pageSizethis.desiredTotal = pageSize?pageSize:this.desiredTotalconsole.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 : 1for (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;}}// 扩展预加载范围,总页数不超过10const 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>

相关文章:

vue2 多页面pdf预览

使用pdfjs-dist预览pdf&#xff0c;实现预加载&#xff0c;滚动条翻页。pdfjs的版本很重要&#xff0c;换了好多版本&#xff0c;终于有一个能用的 node 20.18.1 "pdfjs-dist": "^2.2.228", vue页面代码如下 <template><div v-loading"loa…...

【python】matplotlib(animation)

文章目录 1、matplotlib.animation1.1、FuncAnimation1.2、修改 matplotlib 背景 2、matplotlib imageio2.1、折线图2.2、条形图2.3、散点图 3、参考 1、matplotlib.animation 1.1、FuncAnimation matplotlib.animation.FuncAnimation 是 Matplotlib 库中用于创建动画的一个…...

Hello Robot 推出Stretch 3移动操作机器人,赋能研究与商业应用

Hello Robot公司近日发布了其新一代开源移动操作机器人Stretch 3&#xff0c;这是一款高度灵活的机器人平台&#xff0c;专为机器人研究、教育实验和商业自动化设计。Stretch 3 结合了先进的移动机器人技术、灵巧操作能力和开源软件生态系统&#xff0c;为用户提供了一个功能强…...

从零到一:我的元宵灯谜小程序诞生记

缘起&#xff1a;一碗汤圆引发的灵感 去年元宵节&#xff0c;我正捧着热腾腾的汤圆刷朋友圈&#xff0c;满屏都是"转发锦鲤求灯谜答案"的动态。看着大家对着手机手忙脚乱地切换浏览器查答案&#xff0c;我突然拍案而起&#xff1a;为什么不做一个能即时猜灯谜的微信…...

Docker 安装与配置 Nginx

摘要 1、本文全面介绍了如何在 Docker 环境中安装和配置 Nginx 容器。 2、文中详细解释了如何设置 HTTPS 安全连接及配置 Nginx 以实现前后端分离的代理服务。 2、同时&#xff0c;探讨了通过 IP 和域名两种方式访问 Nginx 服务的具体配置方法 3、此外&#xff0c;文章还涵…...

Oracle常见语法

一、求交集 SELECT column1, column2 FROM table1 INTERSECT SELECT column1, column2 FROM table2;INTERSECT 操作符是 Oracle 和一些其他数据库&#xff08;如 PostgreSQL 和 SQL Server&#xff09;特有的集合操作符&#xff0c;在 MySQL 中并不直接支持。MYSQL同效果代码&a…...

在Vue3中使用Echarts的示例 两种方法

在Vue 3中使用ECharts可以通过两种主要方法实现:全局安装和组件封装。下面我将分别 介绍这两种方法的具体实现步骤。 方法1:全局安装 1.安装ECharts 在你的Vue项目中&#xff0c;首先需要安装ECharts。打开终端&#xff0c;运行以下命令: Bash copy code npm install ec…...

【docker】docker改动镜像并重新编译举例

docker改动镜像并重新编译举例 使用vllm启动Qwen VL 2.5出现报错 0.7.2 Docker Container doesnt support Qwen VL 2.5 Instruct 查看镜像 docker images 会发现本地有vllm/vllm-openai:v0.7.2镜像&#xff0c;id为f78c8f2f8ad5 空白文件夹中新建文件Dockerfile 写入&#…...

具身智能训练新思路!将生成视频用于训练机器人

将生成视频用于训练具身智能(Embodied AI)确实是近年来备受关注的前沿方向,这一思路通过结合生成式AI(如扩散模型、神经辐射场等)与机器人学习,为解决真实世界数据稀缺、训练成本高等问题提供了新可能。以下从技术逻辑、潜在优势、挑战及案例方向展开分析: 一、技术逻辑…...

15、深度学习-自学之路-反向传播程序展示、激活函数的应用,反向权重的更新、2层神经网络的应用,输入输出相关性的理解。

这个里面要学习和展示的内容会比较多&#xff0c;需要好好的认真思考 第一个要思考的就是&#xff1a;输入和输出相关性的理解&#xff0c;我们先拿一层的神经网络来说明一下&#xff0c; 输入有2个因素&#xff0c;对应有两个权重&#xff0c;输出有一个结果。 输入的两个因…...

【JavaEE进阶】依赖注入 DI详解

目录 &#x1f334;什么是依赖注入 &#x1f384;依赖注入的三种方法 &#x1f6a9;属性注⼊(Field Injection) &#x1f6a9;Setter注入 &#x1f6a9;构造方法注入 &#x1f6a9;三种注⼊的优缺点 &#x1f333;Autowired存在的问题 &#x1f332;解决Autowired存在的…...

医疗影响分割 | 使用 Swin UNETR 训练自己的数据集(3D医疗影像分割教程)

<Swin UNETR: Swin Transformers for Semantic Segmentation of Brain Tumors in MRI Images> 代码地址:unetr 论文地址:https://arxiv.org/pdf/2201.01266 一、下载代码 在Github上下载代码,然后进入SWINUNETR,前两个是针对两个数据集(BRATS21、BTCV)的操作,这里…...

IGBT的两级关断

IGBT&#xff08;绝缘栅双极型晶体管&#xff09;的两级关断&#xff08;Two-stage turn-off&#xff09;是一种优化关断过程的方法&#xff0c;主要用于减少关断时的电压过冲和dv/dt&#xff08;电压变化率&#xff09;过高的问题&#xff0c;特别是在大功率应用中&#xff08…...

微服务与网关

什么是网关 背景 单体项目中&#xff0c;前端只用访问指定的一个端口8080&#xff0c;就可以得到任何想要的数据 微服务项目中&#xff0c;ip是不断变化的&#xff0c;端口是多个的 解决方案&#xff1a;网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发…...

“云计算一哥”一口气发布6个大模型、3nm芯片!多模态还要搞Any-to-Any

金磊 发自 拉斯维加斯量子位 | 公众号 QbitAI 就在刚刚&#xff0c;云计算一哥亚马逊云科技&#xff0c;在大模型这件事儿上搞了波大的—— 亚马逊CEO Andy Jassy亲自站台re:Invent24&#xff0c;发布自家新款AI多模态系列大模型&#xff0c;名曰Amazon Nova。 而且是一口气涵盖…...

pytest生成报告no tests ran in 0.01s

除了基本的环境配置、用例名要以test_开头&#xff0c;有个地方是我自己忽略了&#xff0c;在执行时没有指定用例文件&#xff0c;所以没有找到。 if __name__ __main__:pytest.main(["testcases/test_demo.py","-svq", __file__, --alluredir./allure-r…...

如何修改DNS解析?

DNS(域名系统)就像互联网的“电话簿”&#xff0c;负责将我们输入的网址转换为计算机能够理解的IP地址。如果DNS解析出现问题&#xff0c;访问网站就会受到影响。那我们该如何修改DNS解析呢?接下来&#xff0c;我们就来介绍一下这个话题。 为什么要修改DNS解析? 使用默认的…...

PyTorch 中 `torch.cuda.amp` 相关警告的解决方法

在最近的写代码过程中&#xff0c;遇到了两个与 PyTorch 的混合精度训练相关的警告信息。这里随手记录一下。 警告内容 警告 1: torch.cuda.amp.autocast FutureWarning: torch.cuda.amp.autocast(args...) is deprecated. Please use torch.amp.autocast(cuda, args...) i…...

微服务组件LoadBalancer负载均衡

SpringCloud 从 2020.0.1 版本开始,移除了 Ribbon 组件&#xff0c;使⽤Spring Cloud LoadBalancer 组件来代 替 Ribbon 实现客户端负载均衡 loadbalancer负载均衡&#xff1a; 复制一份provider项目&#xff0c;服务名一致&#xff0c;端口号不一致&#xff0c;让consumer调…...

如何本地部署DeepSeek

第一步&#xff1a;安装ollama https://ollama.com/download 打开官网&#xff0c;选择对应版本 第二步&#xff1a;选择合适的模型 https://ollama.com/ 模型名称中的 1.5B、7B、8B 等数字代表模型的参数量&#xff08;Parameters&#xff09;&#xff0c;其中 B 是英文 B…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

MySQL用户和授权

开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务&#xff1a; test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

C++.OpenGL (20/64)混合(Blending)

混合(Blending) 透明效果核心原理 #mermaid-svg-SWG0UzVfJms7Sm3e {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-icon{fill:#552222;}#mermaid-svg-SWG0UzVfJms7Sm3e .error-text{fill…...

【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制

使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下&#xff0c;限制某个 IP 的访问频率是非常重要的&#xff0c;可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案&#xff0c;使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…...