vue PDF预览,传入线上地址即可
·
- npm install pdfjs-dist@2.5.207 --save
pdfView.vue <template> <div class="filePreview"> <PdfScroll :highlights="highlights" :currentPdfPage="currentPdfPage" :currentPage="currentPage" :src="fileSrc" /> </div> </template> <script> import PdfScroll from "@/views/components/PdfScroll.vue"; export default { name: "filePreview", components: { PdfScroll }, data() { return { fileSrc: "", currentPdfPage: 1, currentPage: 1, highlights: [], }; }, created() { try { // 从路由参数中获取文件路径,并进行URL解码 const { fileSrc, page } = this.$route.query; if (fileSrc) { this.fileSrc = decodeURIComponent(fileSrc); // const bbox = JSON.parse(this.$route.query.bbox || "[]"); // this.highlights = [ // { // page: parseInt(page), // rects: [{ x1: bbox[0], y1: bbox[1], x2: bbox[2], y2: bbox[3] }], // color: "#3a903d4d", // 绿色高亮(降低透明度) // }, // ]; // this.currentPage = parseInt(page); this.currentPdfPage = parseInt(page); } else { // 如果没有提供文件路径,使用默认的PDF文件 this.fileSrc = "/static/api.pdf"; console.warn("未提供文件路径参数,使用默认PDF文件"); } } catch (error) { console.error("解析文件路径参数失败:", error); this.fileSrc = "/static/api.pdf"; } }, }; </script> <style scoped lang="scss"></style>pdfScroll.vue <template> <div class="knowledge-detail-container"> <div class="pdf-preview-section w100"> <!-- PDF滚动容器 --> <div class="pdf-render-container" ref="pdfCanvasContainer" @scroll="onScroll" > <div v-for="pageNum in totalPages" :key="pageNum" class="pdf-page-wrapper" :data-page="pageNum" > <!-- 层包裹,便于 overlay 定位 --> <div class="pdf-page-layer"> <!-- PDF底图 --> <canvas :ref="'pdfPageCanvas' + pageNum" class="pdf-canvas" ></canvas> <!-- 高亮叠加层(透明) --> <canvas :ref="'highlightCanvas' + pageNum" class="highlight-canvas" ></canvas> </div> </div> <div v-if="!pdfDoc" class="pdf-loading"><p>加载中...</p></div> </div> </div> <!-- 页码提示(可按需打开) --> <!-- <div class="pdf-page-indicator"> {{ currentPdfPage }} / {{ totalPages || 0 }} </div> --> </div> </template> <script> import * as pdfjsLib from "pdfjs-dist/build/pdf.js"; import pdfWorker from "pdfjs-dist/build/pdf.worker.entry"; pdfjsLib.GlobalWorkerOptions.workerSrc = pdfWorker; export default { name: "PdfScroll", props: { src: { type: String, default: "", }, currentPdfPage: { type: Number, default: 1, }, currentPage: { type: Number, default: 1, }, highlights: { type: Array, default: () => [], }, }, data() { return { pdfDoc: null, totalPages: 0, pdfZoom: 1.0, // 存储每页的 viewport(用于从 PDF 坐标映射到 canvas 坐标) pageViewports: {}, // { [pageNum]: viewport } }; }, methods: { async initPdf(pdfUrl) { try { if (this.pdfDoc) { this.pdfDoc.destroy(); this.pageViewports = {}; } this.pdfDoc = await pdfjsLib.getDocument(pdfUrl).promise; this.totalPages = this.pdfDoc.numPages; // 渲染全部页面(可按需改为懒加载) for (let pageNum = 1; pageNum <= this.totalPages; pageNum++) { await this.renderPdfPage(pageNum); } // 等待DOM更新后滚动到指定页码 this.$nextTick(() => { this.scrollToPage(this.currentPdfPage); }); } catch (error) { console.error("加载PDF失败:", error); } }, // 滚动到指定页码 scrollToPage(pageNum) { const container = this.$refs.pdfCanvasContainer; const pageWrapper = container.querySelector( `.pdf-page-wrapper[data-page="${pageNum}"]` ); if (pageWrapper) { container.scrollTop = pageWrapper.offsetTop; } }, async renderPdfPage(pageNum) { try { const page = await this.pdfDoc.getPage(pageNum); const viewport = page.getViewport({ scale: this.pdfZoom }); // 保存 viewport 以便后续高亮绘制使用 this.pageViewports[pageNum] = viewport; // PDF 主 canvas const pdfCanvas = this.$refs[`pdfPageCanvas${pageNum}`][0]; const pdfCtx = pdfCanvas.getContext("2d"); pdfCanvas.height = viewport.height; pdfCanvas.width = viewport.width; // 高亮 overlay canvas(尺寸保持与 PDF canvas 一致) const highlightCanvas = this.$refs[`highlightCanvas${pageNum}`][0]; const highlightCtx = highlightCanvas.getContext("2d"); highlightCanvas.height = viewport.height; highlightCanvas.width = viewport.width; // 渲染 PDF 页面到底图 canvas const renderContext = { canvasContext: pdfCtx, viewport }; await page.render(renderContext).promise; // 绘制该页(如果有)的高亮到 overlay(不会影响底图) this.renderHighlights(pageNum); } catch (error) { console.error("渲染PDF页面失败:", error); } }, // 渲染某页的高亮(只绘制 overlay canvas) renderHighlights(pageNum) { const viewport = this.pageViewports[pageNum]; if (!viewport) return; // 如果 viewport 还没准备好则跳过 const highlightCanvas = this.$refs[`highlightCanvas${pageNum}`]?.[0]; if (!highlightCanvas) return; const ctx = highlightCanvas.getContext("2d"); // 清空 overlay(只清除高亮,不触碰底图) ctx.clearRect(0, 0, highlightCanvas.width, highlightCanvas.height); // 取出该页的高亮信息 const pageHighlights = this.highlights.filter((h) => h.page === pageNum); if (pageHighlights.length === 0) return; ctx.save(); pageHighlights.forEach((highlight) => { if (highlight.rects) { highlight.rects.forEach((rect) => { const canvasRect = this.convertPdfRectToCanvas(rect, viewport); ctx.fillStyle = highlight.color || "#ffeb3b"; ctx.globalAlpha = 0.4; ctx.fillRect( canvasRect.x, canvasRect.y, canvasRect.width, canvasRect.height ); ctx.globalAlpha = 1.0; }); } else if (highlight.text) { // 文本搜索高亮逻辑可在此扩展 console.warn("文本搜索高亮功能尚未实现"); } }); ctx.restore(); }, // 将PDF坐标转换为Canvas坐标 convertPdfRectToCanvas(rect, viewport) { // rect 假设为 {x1, y1, x2, y2} const scale = viewport.scale; return { x: rect.x1 * scale, y: rect.y1 * scale, width: (rect.x2 - rect.x1) * scale, height: (rect.y2 - rect.y1) * scale, }; }, // 滚动监听,计算当前页码 onScroll() { const container = this.$refs.pdfCanvasContainer; const pageWrappers = container.querySelectorAll(".pdf-page-wrapper"); let currentPage = 1; for (let i = 0; i < pageWrappers.length; i++) { const el = pageWrappers[i]; if (el.offsetTop - container.scrollTop <= container.clientHeight / 2) { currentPage = i + 1; } } if (currentPage !== this.currentPdfPage) { this.$emit("update:currentPdfPage", currentPage); this.$emit("handleUpdateCurrentPdfPage", currentPage); } }, }, watch: { src: { handler(newSrc, oldSrc) { if (newSrc && newSrc !== oldSrc) { this.initPdf(newSrc); } else if (!newSrc) { this.pdfDoc = null; this.totalPages = 0; this.pageViewports = {}; this.$emit("update:currentPdfPage", 1); } }, immediate: true, }, // 当高亮信息变化时:清除其他页 overlay,仅重绘当前页 overlay highlights: { handler() { if (!this.pdfDoc) return; this.$nextTick(() => { const container = this.$refs.pdfCanvasContainer; // 清除所有非当前页的高亮 overlay(只影响 overlay canvas,不重绘 pdf) for (let pageNum = 1; pageNum <= this.totalPages; pageNum++) { const highlightCanvas = this.$refs[`highlightCanvas${pageNum}`]?.[0]; if (highlightCanvas) { const ctx = highlightCanvas.getContext("2d"); if (pageNum !== this.currentPage) { ctx.clearRect( 0, 0, highlightCanvas.width, highlightCanvas.height ); } } } // 仅重绘当前页的高亮(如果有) this.renderHighlights(this.currentPage); }); }, deep: true, }, }, mounted() { // 初始化已移至 watch(src) 中处理 }, }; </script> <style scoped lang="scss"> .pdf-render-container { height: 100%; overflow-y: auto; background: #fafafa; position: relative; } .pdf-page-wrapper { margin-bottom: 14px; display: flex; justify-content: center; &:last-child { margin-bottom: 0; } } /* 包裹层,便于 overlay 绝对定位 */ .pdf-page-layer { position: relative; display: inline-block; } /* PDF 底图 canvas */ .pdf-canvas { display: block; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } /* 高亮 overlay canvas,和 pdf-canvas 同尺寸,绝对覆盖 */ .highlight-canvas { position: absolute; top: 0; left: 0; pointer-events: none; /* 不阻塞交互 */ z-index: 2; } /* 其它样式保持不变 */ .pdf-page-indicator { position: fixed; right: 20px; bottom: 40px; background: rgba(0, 0, 0, 0.6); color: white; padding: 4px 8px; border-radius: 4px; } /* 美化PDF加载动画 */ .pdf-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; min-height: 400px; position: absolute; top: 0; left: 0; right: 0; background-color: #f8f9fa; z-index: 10; transition: opacity 0.3s ease; } .pdf-loading p { margin-top: 20px; font-size: 16px; color: #666; font-weight: 500; letter-spacing: 0.5px; } /* 旋转加载动画 */ .pdf-loading::before { content: ""; width: 48px; height: 48px; border: 3px solid #f3f3f3; border-top: 3px solid #4caf50; border-radius: 50%; animation: spin 1s linear infinite; box-shadow: 0 0 15px rgba(76, 175, 80, 0.1); } /* 加载文本的淡入效果 */ .pdf-loading p { opacity: 0; animation: fadeIn 0.5s ease 0.3s forwards; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } </style>
更多推荐

所有评论(0)