vue2 多页面pdf预览

server/2025/2/12 23:50:07/

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

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

        vue页面代码如下

javascript"><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>


http://www.ppmy.cn/server/167191.html

相关文章

uniapp中使用uCharts折线图X轴数据间隔显示

1、先看官网 https://www.ucharts.cn/ 2、设置代码 "xAxisDemo3":function(val, index, opts){if(index % 2 0){return val}else {return }}, 再在数据中引入设置好样式

Kotlin Android 环境搭建

Kotlin Android 环境搭建 引言 随着移动应用的日益普及,Android 开发成为了一个热门的技术领域。Kotlin 作为一种现代的编程语言,因其简洁、安全、互操作性强等特点,被越来越多的开发者所喜爱。本文将详细介绍 Kotlin Android 环境搭建的步骤,帮助您快速上手 Kotlin Andr…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

【C#零基础从入门到精通】(三)——C#变量和数据类型详解

【C#零基础从入门到精通】(三)——C#变量和数据类型详解 数据类型 在 C# 中,数据类型是对数据进行分类的方式,它定义了变量可以存储的数据的种类、范围以及可以对这些数据执行的操作。C# 的数据类型主要分为值类型、引用类型和指针类型(指针类型通常在不安全代码中使用),…

Linux内核模块参数与性能优化:__read_mostly属性的深度剖析

在Linux内核开发中,模块参数和性能优化是两个至关重要的主题。模块参数允许开发者和用户在加载内核模块时动态配置模块的行为,而性能优化则是确保内核高效运行的关键。本文将深入探讨Linux内核中的模块参数机制以及__read_mostly属性的使用,通过实际代码示例和详细解释,帮助…

DeepSeek与GPT大语言模型教程

ChatGPT和DeepSeek能够快速理解和生成复杂的语言&#xff0c;帮助研究人员在撰写论文时提高效率&#xff0c;不仅生成高质量的文章内容&#xff0c;还能优化论文结构和语言表达。在数据分析方面&#xff0c;这些模型能够迅速处理和分析大量数据&#xff0c;帮助提取有价值的规律…

关于“#pragma arm section zidata = “mgr_buffer_section“的解析

#pragma arm section zidata “mgr_buffer_section” 是 ARM 编译器特有的指令&#xff0c;用于控制变量在内存中的分配位置。其作用如下&#xff1a; 核心解析 #pragma arm section 这是 ARM 编译器支持的编译指示&#xff08;pragma&#xff09;&#xff0c;用于指定代码或…

【AcWing】蓝桥杯辅导课-数学与简单DP

目录 数学 买不到的数目 蚂蚁感冒 饮料换购 DP 01背包问题 摘花生 最长上升子序列 地宫取宝 波动数列 数学 买不到的数目 1205. 买不到的数目 - AcWing题库 这道题的意思就是给定两个正整数p和q&#xff0c;求xpyq这一个组合不能凑出来的最大正整数是多少 首先我们…