Vue2.0和pdfjs相互结合使用

本篇章记录:V2.0和PDF.Js相互结合使用案例摘要!!

安装:
第一步:npm install pdfjs-dist@2.2.228
第二步:npm  i path2d-polyfill      "path2d-polyfill": "^3.1.2",

根据报错依次安装(可选,不一定)
npm install --save canvas
npm i -g node-gyp
npm install canvas --canvas_binary_host_mirror=https://registry.npmmirror.com/-/binary/canvas
 引用后页面会报错:
Uncaught SyntaxError: Unexpected token '<'
vue.js:1872 Error loading PDF: Error: Setting up fake worker failed: "Cannot read properties of undefined (reading 'WorkerMessageHandler')".


需要在index.html下引用:
  例如:
    <script src="<%= BASE_URL %>cdn/pdfjs-dist/build/pdf.worker.min.js"></script>

下载pdfjs-dist依赖时,出现版本不兼容问题:
在vue.config.js的module.exports={
transpileDependencies: ['@jiaminghi', '@microsoft/signalr', 'pdfjs-dist'],   //此处增加pdfjs-dist   用于低版本兼容,自动解析语法错误
}



import * as pdfjsLib from 'pdfjs-dist';

 <div id="pdfViewer" v-show="Ispdf">

        </div>



  var pdfViewer = document.getElementById('pdfViewer');
      pdfjsLib.GlobalWorkerOptions.workerSrc = '../bgcode/pdfjs/pdf.worker.min.js';
       pdfjsLib.getDocument(url).promise.then(pdfDoc => {
           let renderingTasks = [];
          for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) {
            renderingTasks.push(pdfDoc.getPage(pageNum).then(function(page) {
              console.log(pageNum);
              console.log(page);
              // console.log(page);
              var canvas = document.createElement('canvas');
              canvas.classList.add('i_pagep')
              canvas.id = pageNum;
              var context = canvas.getContext('2d');
              var viewport = page.getViewport({
                scale: 1.5
              });

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

              var renderContext = {
                canvasContext: context,
                viewport: viewport
              };
              //console.log(renderContext);
              page.render(renderContext).promise
                .then(function() {
                  pdfViewer.appendChild(canvas);
                });
            }));
         }
          // 8. 等待所有渲染任务完成
          Promise.all(renderingTasks).then(() => {
            console.log('所有页面都已按顺序渲染为图片。');
          });
        // // 获取第一页
        // pdf.getPage(1).then(page => {
        //   console.log(page);
        //  // const scale = 1.5; // 缩放比例
        //  // const viewport = page.getViewport({ scale });

        //   // // 准备 canvas 使用 PDF 渲染内容
        //   // const canvas = this.$refs.pdfCanvas;
        //   // const context = canvas.getContext('2d');
        //   // canvas.height = viewport.height;
        //   // canvas.width = viewport.width;

        //   // // 渲染 PDF 页
        //   // const renderContext = {
        //   //   canvasContext: context,
        //   //   viewport: viewport,
        //   // };
        //   // page.render(renderContext);
        // });
      }).catch(error => {
         console.error('Error loading PDF:', error);
      });

html中vue和pdf.js的应用

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF.js 预览 Demo</title>
    <!-- Element Plus -->
    <link rel="stylesheet" href="https://unpkg.com/element-plus@2.4.4/dist/index.css">
    <!-- Vue -->
    <script src="https://unpkg.com/vue@3.4.21/dist/vue.global.prod.js"></script>
    <script src="https://unpkg.com/element-plus@2.4.4/dist/index.full.js"></script>
    <!-- PDF.js -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: #1a1a2e;
            color: #fff;
            min-height: 100vh;
        }

        .demo-header {
            text-align: center;
            padding: 28px 20px 16px;
            background: linear-gradient(135deg, #16213e, #1a1a2e);
            border-bottom: 1px solid rgba(255,255,255,0.06);
        }

        .demo-header h1 {
            font-size: 22px;
            font-weight: 600;
            color: #fff;
            margin-bottom: 6px;
        }

        .demo-header p {
            font-size: 13px;
            color: rgba(255,255,255,0.45);
        }

        /* 文件选择区 */
        .file-picker {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 12px;
            padding: 16px 20px;
            background: #20213a;
            border-bottom: 1px solid rgba(255,255,255,0.06);
        }

        .file-picker .el-input {
            max-width: 480px;
            flex: 1;
        }

        .file-picker .el-input__wrapper {
            background: rgba(255,255,255,0.07) !important;
            border: 1px solid rgba(255,255,255,0.12) !important;
            box-shadow: none !important;
        }

        .file-picker .el-input__inner {
            color: rgba(255,255,255,0.8) !important;
        }

        .file-picker .el-input__inner::placeholder {
            color: rgba(255,255,255,0.3) !important;
        }

        .file-picker .el-button {
            flex-shrink: 0;
        }

        /* PDF.js 预览容器 */
        .pdf-viewer-wrap {
            width: 100%;
            height: calc(100vh - 160px);
            display: flex;
            flex-direction: column;
            overflow: hidden;
            background: #525659;
        }

        .pdf-toolbar {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 12px;
            padding: 8px 12px;
            background: #3d4043;
            color: #fff;
            font-size: 13px;
            flex-shrink: 0;
            user-select: none;
        }

        .pdf-toolbar button {
            background: rgba(255,255,255,0.15);
            border: none;
            color: #fff;
            border-radius: 4px;
            padding: 4px 10px;
            font-size: 13px;
            cursor: pointer;
            line-height: 1.5;
            touch-action: manipulation;
            min-width: 32px;
            transition: background 0.15s;
        }

        .pdf-toolbar button:hover {
            background: rgba(255,255,255,0.25);
        }

        .pdf-toolbar button:active {
            background: rgba(255,255,255,0.35);
        }

        .pdf-toolbar button:disabled {
            opacity: 0.35;
            cursor: default;
        }

        .pdf-page-info {
            min-width: 80px;
            text-align: center;
            font-size: 13px;
            color: #ccc;
        }

        .pdf-scale-btns {
            display: flex;
            gap: 4px;
        }

        .pdf-scale-btns button {
            min-width: 34px;
        }

        .pdf-scale-btns button.zoom-label {
            min-width: 52px;
            font-size: 12px;
            cursor: pointer;
        }

        .pdf-canvas-scroll {
            flex: 1;
            overflow-y: auto;
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            display: flex;
            flex-direction: column;
            align-items: center;
            padding: 12px 0;
            gap: 8px;
            background: #525659;
            scroll-behavior: smooth;
        }

        .pdf-canvas-scroll canvas {
            display: block;
            box-shadow: 0 2px 16px rgba(0,0,0,0.5);
            background: #fff;
            max-width: 100%;
        }

        .pdf-loading {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            color: #ccc;
            font-size: 14px;
            gap: 14px;
        }

        .pdf-spinner {
            width: 38px;
            height: 38px;
            border: 3px solid rgba(255,255,255,0.15);
            border-top-color: #fff;
            border-radius: 50%;
            animation: spin 0.8s linear infinite;
        }

        @keyframes spin {
            to { transform: rotate(360deg); }
        }

        /* 空状态 */
        .pdf-empty {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: calc(100vh - 160px);
            gap: 16px;
            color: rgba(255,255,255,0.35);
            background: #1a1a2e;
        }

        .pdf-empty .empty-icon {
            font-size: 56px;
            opacity: 0.3;
        }

        .pdf-empty p {
            font-size: 14px;
            text-align: center;
            line-height: 1.6;
        }

        /* 提示文字 */
        .pdf-hint {
            text-align: center;
            padding: 8px 20px;
            font-size: 12px;
            color: rgba(255,255,255,0.3);
            background: #1a1a2e;
            border-top: 1px solid rgba(255,255,255,0.04);
        }
    </style>
</head>
<body>
    <div id="app">
        <!-- 标题区 -->
        <div class="demo-header">
            <h1>📕 PDF.js 预览 Demo</h1>
            <p>支持翻页 · 缩放按钮 · 鼠标滚轮缩放</p>
        </div>

        <!-- 文件路径选择区 -->
        <div class="file-picker">
            <el-input
                v-model="pdfFilePath"
                placeholder="输入 PDF 文件路径,例如:C:\Users\xxx\Downloads\测试.pdf"
                clearable
                @keyup.enter="loadPdfFromPath"
            ></el-input>
            <el-button type="primary" @click="loadPdfFromPath">加载 PDF</el-button>
            <el-button @click="pickFile">选择文件</el-button>
            <!-- 隐藏的文件选择器 -->
            <input
                ref="fileInputRef"
                type="file"
                accept="application/pdf"
                style="display:none"
                @change="handleFileChange"
            />
        </div>

        <!-- PDF 预览区 -->
        <div class="pdf-viewer-wrap" v-if="hasLoaded">
            <!-- 工具栏 -->
            <div class="pdf-toolbar">
                <button :disabled="pdfCurrentPage <= 1" @click="pdfPrevPage" title="上一页">&#8592;</button>
                <span class="pdf-page-info">{{ pdfCurrentPage }} / {{ pdfTotalPages }}</span>
                <button :disabled="pdfCurrentPage >= pdfTotalPages" @click="pdfNextPage" title="下一页">&#8594;</button>
                <div class="pdf-scale-btns">
                    <button @click="pdfZoomOut" title="缩小">-</button>
                    <button class="zoom-label" @click="pdfZoomReset" title="重置为 300%">{{ Math.round(pdfScale * 100) }}%</button>
                    <button @click="pdfZoomIn" title="放大">+</button>
                </div>
            </div>
            <!-- Canvas 渲染区 -->
            <div class="pdf-canvas-scroll" ref="pdfScrollRef" @wheel.prevent="handlePdfWheel">
                <div v-if="pdfLoading" class="pdf-loading">
                    <div class="pdf-spinner"></div>
                    <span>PDF 加载中...</span>
                </div>
                <canvas v-show="!pdfLoading" ref="pdfCanvasRef"></canvas>
            </div>
        </div>

        <!-- 空状态 -->
        <div class="pdf-empty" v-else>
            <div class="empty-icon">📄</div>
            <p>请在上方输入 PDF 文件路径<br>然后点击"加载 PDF"按钮</p>
        </div>

        <!-- 底部提示 -->
        <div class="pdf-hint" v-if="hasLoaded">
            💡 提示:在 PDF 区域内滚动鼠标滚轮可放大/缩小 · 点击百分比数字可重置为 300%
        </div>
    </div>

    <script>
        const { createApp, ref, nextTick } = Vue;
        const { ElMessage } = ElementPlus;

        const app = createApp({
            setup() {
                // ---------- 文件选择 ----------
                const fileInputRef = ref(null);
                const pdfFilePath = ref('');

                // ---------- PDF.js 相关 ----------
                const pdfCanvasRef = ref(null);
                const pdfScrollRef = ref(null);
                const pdfLoading = ref(false);
                const pdfCurrentPage = ref(1);
                const pdfTotalPages = ref(0);
                const pdfScale = ref(3.0);        // 默认 300%
                let pdfDocInstance = null;
                let pdfRenderTask = null;
                // 基准缩放:首次加载时根据容器宽度计算一次,后续基于它缩放
                let pdfBaseScale = 0;

                // 是否已加载过(用于显示/隐藏预览区)
                const hasLoaded = ref(false);

                // 配置 pdf.js worker
                if (typeof pdfjsLib !== 'undefined') {
                    pdfjsLib.GlobalWorkerOptions.workerSrc =
                        'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
                }

                // ---------- 渲染指定页 ----------
                const renderPdfPage = async (pageNum) => {
                    if (!pdfDocInstance) return;
                    if (pdfRenderTask) {
                        pdfRenderTask.cancel();
                        pdfRenderTask = null;
                    }
                    try {
                        const page = await pdfDocInstance.getPage(pageNum);
                        const canvas = pdfCanvasRef.value;
                        if (!canvas) return;
                        const ctx = canvas.getContext('2d');

                        // 首次加载:基于容器宽度计算基准缩放(只算一次)
                        if (pdfBaseScale === 0) {
                            const containerWidth = (pdfScrollRef.value ? pdfScrollRef.value.clientWidth : 500) - 24;
                            const viewport = page.getViewport({ scale: 1 });
                            pdfBaseScale = containerWidth / viewport.width;
                        }
                        const finalScale = pdfBaseScale * pdfScale.value;
                        const scaledViewport = page.getViewport({ scale: finalScale });

                        canvas.width = scaledViewport.width;
                        canvas.height = scaledViewport.height;

                        const renderContext = {
                            canvasContext: ctx,
                            viewport: scaledViewport
                        };
                        pdfRenderTask = page.render(renderContext);
                        await pdfRenderTask.promise;
                        pdfRenderTask = null;
                        if (pdfScrollRef.value) {
                            pdfScrollRef.value.scrollTop = 0;
                        }
                    } catch (e) {
                        if (e && e.name !== 'RenderingCancelledException') {
                            console.error('PDF 渲染出错:', e);
                        }
                    }
                };

                // ---------- 加载 PDF ----------
                const loadPdf = async (pathOrUrl) => {
                    if (typeof pdfjsLib === 'undefined') {
                        ElMessage.error('PDF.js 未加载');
                        return;
                    }
                    hasLoaded.value = false;
                    pdfBaseScale = 0;
                    pdfLoading.value = true;
                    pdfCurrentPage.value = 1;
                    pdfTotalPages.value = 0;
                    pdfDocInstance = null;
                    try {
                        // 优先尝试作为 URL 直接加载(网络路径或 file:// 路径)
                        const loadingTask = pdfjsLib.getDocument({ url: pathOrUrl, withCredentials: false });
                        const pdfDoc = await loadingTask.promise;
                        pdfDocInstance = pdfDoc;
                        pdfTotalPages.value = pdfDoc.numPages;
                        pdfLoading.value = false;
                        hasLoaded.value = true;
                        await nextTick();
                        await renderPdfPage(1);
                        ElMessage.success('PDF 加载成功,共 ' + pdfDoc.numPages + ' 页');
                    } catch (e) {
                        pdfLoading.value = false;
                        hasLoaded.value = false;
                        console.error('PDF 加载失败:', e);
                        ElMessage.error('PDF 加载失败,请检查文件路径是否正确,或换用"选择文件"按钮');
                    }
                };

                // ---------- 翻页 ----------
                const pdfPrevPage = async () => {
                    if (pdfCurrentPage.value > 1) {
                        pdfCurrentPage.value--;
                        await renderPdfPage(pdfCurrentPage.value);
                    }
                };

                const pdfNextPage = async () => {
                    if (pdfCurrentPage.value < pdfTotalPages.value) {
                        pdfCurrentPage.value++;
                        await renderPdfPage(pdfCurrentPage.value);
                    }
                };

                // ---------- 缩放(按钮) ----------
                // 放大(最大 500%)
                const pdfZoomIn = async () => {
                    pdfScale.value = Math.min(5.0, parseFloat((pdfScale.value + 0.25).toFixed(2)));
                    await renderPdfPage(pdfCurrentPage.value);
                };

                // 缩小(最小 10%)
                const pdfZoomOut = async () => {
                    pdfScale.value = Math.max(0.1, parseFloat((pdfScale.value - 0.25).toFixed(2)));
                    await renderPdfPage(pdfCurrentPage.value);
                };

                // 重置为 300%
                const pdfZoomReset = async () => {
                    pdfScale.value = 3.0;
                    await renderPdfPage(pdfCurrentPage.value);
                };

                // ---------- 滚轮缩放 ----------
                const handlePdfWheel = async (e) => {
                    if (!pdfDocInstance) return;
                    e.preventDefault();
                    const delta = e.deltaY || e.wheelDelta;
                    if (delta > 0) {
                        pdfScale.value = Math.max(0.1, parseFloat((pdfScale.value - 0.15).toFixed(2)));
                    } else {
                        pdfScale.value = Math.min(5.0, parseFloat((pdfScale.value + 0.15).toFixed(2)));
                    }
                    await renderPdfPage(pdfCurrentPage.value);
                };

                // ---------- 文件选择 ----------
                // 点击"选择文件"按钮
                const pickFile = () => {
                    fileInputRef.value && fileInputRef.value.click();
                };

                // 文件选择后处理
                const handleFileChange = async (e) => {
                    const file = e.target.files && e.target.files[0];
                    if (!file) return;
                    pdfFilePath.value = file.name;
                    if (file.path) {
                        // Electron 环境下 file.path 包含完整路径
                        await loadPdf(file.path);
                    } else {
                        // 普通浏览器环境:使用 File API 创建 URL
                        const url = URL.createObjectURL(file);
                        // 重置 worker 方式
                        pdfDocInstance = null;
                        pdfBaseScale = 0;
                        pdfLoading.value = true;
                        pdfCurrentPage.value = 1;
                        pdfTotalPages.value = 0;
                        hasLoaded.value = false;
                        try {
                            const loadingTask = pdfjsLib.getDocument({ url: url, withCredentials: false });
                            const pdfDoc = await loadingTask.promise;
                            pdfDocInstance = pdfDoc;
                            pdfTotalPages.value = pdfDoc.numPages;
                            pdfLoading.value = false;
                            hasLoaded.value = true;
                            await nextTick();
                            await renderPdfPage(1);
                            ElMessage.success('PDF 加载成功,共 ' + pdfDoc.numPages + ' 页');
                        } catch (err) {
                            pdfLoading.value = false;
                            hasLoaded.value = false;
                            ElMessage.error('PDF 加载失败');
                        }
                    }
                    // 清空 input,允许重复选择同一文件
                    e.target.value = '';
                };

                // 从路径输入框加载
                const loadPdfFromPath = async () => {
                    const path = pdfFilePath.value.trim();
                    if (!path) {
                        ElMessage.warning('请输入 PDF 文件路径');
                        return;
                    }
                    await loadPdf(path);
                };

                return {
                    // 文件
                    fileInputRef,
                    pdfFilePath,
                    pickFile,
                    handleFileChange,
                    loadPdfFromPath,
                    // PDF 状态
                    hasLoaded,
                    pdfCanvasRef,
                    pdfScrollRef,
                    pdfLoading,
                    pdfCurrentPage,
                    pdfTotalPages,
                    pdfScale,
                    // 方法
                    pdfPrevPage,
                    pdfNextPage,
                    pdfZoomIn,
                    pdfZoomOut,
                    pdfZoomReset,
                    handlePdfWheel,
                };
            }
        });

        app.use(ElementPlus);
        app.mount('#app');
    </script>
</body>
</html>

 

posted @ 2025-05-01 21:58  小鱼记忆  阅读(384)  评论(0)    收藏  举报