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">返 回</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">放 大</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">缩 小</el-button></div><el-dropdown size="mini" trigger="click" class="more-dropdown" style="line-height: 35px;"><el-button size="mini" type="primary">缩 放</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,实现预加载,滚动条翻页。pdfjs的版本很重要,换了好多版本,终于有一个能用的 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,这是一款高度灵活的机器人平台,专为机器人研究、教育实验和商业自动化设计。Stretch 3 结合了先进的移动机器人技术、灵巧操作能力和开源软件生态系统,为用户提供了一个功能强…...
从零到一:我的元宵灯谜小程序诞生记
缘起:一碗汤圆引发的灵感 去年元宵节,我正捧着热腾腾的汤圆刷朋友圈,满屏都是"转发锦鲤求灯谜答案"的动态。看着大家对着手机手忙脚乱地切换浏览器查答案,我突然拍案而起:为什么不做一个能即时猜灯谜的微信…...
Docker 安装与配置 Nginx
摘要 1、本文全面介绍了如何在 Docker 环境中安装和配置 Nginx 容器。 2、文中详细解释了如何设置 HTTPS 安全连接及配置 Nginx 以实现前后端分离的代理服务。 2、同时,探讨了通过 IP 和域名两种方式访问 Nginx 服务的具体配置方法 3、此外,文章还涵…...
Oracle常见语法
一、求交集 SELECT column1, column2 FROM table1 INTERSECT SELECT column1, column2 FROM table2;INTERSECT 操作符是 Oracle 和一些其他数据库(如 PostgreSQL 和 SQL Server)特有的集合操作符,在 MySQL 中并不直接支持。MYSQL同效果代码&a…...
在Vue3中使用Echarts的示例 两种方法
在Vue 3中使用ECharts可以通过两种主要方法实现:全局安装和组件封装。下面我将分别 介绍这两种方法的具体实现步骤。 方法1:全局安装 1.安装ECharts 在你的Vue项目中,首先需要安装ECharts。打开终端,运行以下命令: 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镜像,id为f78c8f2f8ad5 空白文件夹中新建文件Dockerfile 写入&#…...
具身智能训练新思路!将生成视频用于训练机器人
将生成视频用于训练具身智能(Embodied AI)确实是近年来备受关注的前沿方向,这一思路通过结合生成式AI(如扩散模型、神经辐射场等)与机器人学习,为解决真实世界数据稀缺、训练成本高等问题提供了新可能。以下从技术逻辑、潜在优势、挑战及案例方向展开分析: 一、技术逻辑…...
15、深度学习-自学之路-反向传播程序展示、激活函数的应用,反向权重的更新、2层神经网络的应用,输入输出相关性的理解。
这个里面要学习和展示的内容会比较多,需要好好的认真思考 第一个要思考的就是:输入和输出相关性的理解,我们先拿一层的神经网络来说明一下, 输入有2个因素,对应有两个权重,输出有一个结果。 输入的两个因…...
【JavaEE进阶】依赖注入 DI详解
目录 🌴什么是依赖注入 🎄依赖注入的三种方法 🚩属性注⼊(Field Injection) 🚩Setter注入 🚩构造方法注入 🚩三种注⼊的优缺点 🌳Autowired存在的问题 🌲解决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(绝缘栅双极型晶体管)的两级关断(Two-stage turn-off)是一种优化关断过程的方法,主要用于减少关断时的电压过冲和dv/dt(电压变化率)过高的问题,特别是在大功率应用中(…...
微服务与网关
什么是网关 背景 单体项目中,前端只用访问指定的一个端口8080,就可以得到任何想要的数据 微服务项目中,ip是不断变化的,端口是多个的 解决方案:网关 网关:就是网络的关口,负责请求的路由、转发…...
“云计算一哥”一口气发布6个大模型、3nm芯片!多模态还要搞Any-to-Any
金磊 发自 拉斯维加斯量子位 | 公众号 QbitAI 就在刚刚,云计算一哥亚马逊云科技,在大模型这件事儿上搞了波大的—— 亚马逊CEO Andy Jassy亲自站台re:Invent24,发布自家新款AI多模态系列大模型,名曰Amazon Nova。 而且是一口气涵盖…...
pytest生成报告no tests ran in 0.01s
除了基本的环境配置、用例名要以test_开头,有个地方是我自己忽略了,在执行时没有指定用例文件,所以没有找到。 if __name__ __main__:pytest.main(["testcases/test_demo.py","-svq", __file__, --alluredir./allure-r…...
如何修改DNS解析?
DNS(域名系统)就像互联网的“电话簿”,负责将我们输入的网址转换为计算机能够理解的IP地址。如果DNS解析出现问题,访问网站就会受到影响。那我们该如何修改DNS解析呢?接下来,我们就来介绍一下这个话题。 为什么要修改DNS解析? 使用默认的…...
PyTorch 中 `torch.cuda.amp` 相关警告的解决方法
在最近的写代码过程中,遇到了两个与 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 组件,使⽤Spring Cloud LoadBalancer 组件来代 替 Ribbon 实现客户端负载均衡 loadbalancer负载均衡: 复制一份provider项目,服务名一致,端口号不一致,让consumer调…...
如何本地部署DeepSeek
第一步:安装ollama https://ollama.com/download 打开官网,选择对应版本 第二步:选择合适的模型 https://ollama.com/ 模型名称中的 1.5B、7B、8B 等数字代表模型的参数量(Parameters),其中 B 是英文 B…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
