PDF.js 渲染存档 + 疑难杂症

当前使用部分如下
前端部分

<template>
  <div class="container" style="margin: 0;padding: 0;">
    <div id="pdf-view" class="pdf-view">      <!-- 渲染PDF -->
      <div v-for="page in state.pdfPages" :key="page" class="page-container">
        <canvas :id="`canvas-page-${page}`" class="pdf-canvas"></canvas>
      </div>
      <div id="text-view"></div>
    </div>

    <v-navigation-drawer permanent location="right" :width="500" class="floating-drawer" style="position: fixed;">
      <v-list dense>
        <v-list-item v-for="item in items" :key="item.title">
          <v-list-item-icon>
            <v-icon>{{ item.icon }}</v-icon>
          </v-list-item-icon>

          <v-list-item-content>
            <v-list-item-title>{{ item.title }}</v-list-item-title>
          </v-list-item-content>
        </v-list-item>
      </v-list>
    </v-navigation-drawer>

    <!-- 浮动抽屉 -->
  </div>

</template>

脚本


<script setup>
//需要用到的文件
  import * as pdfjsLib from 'pdfjs-dist';
  import { TextLayerBuilder, EventBus } from 'pdfjs-dist/web/pdf_viewer'; // 从 pdf_viewer.js 引入
  import 'pdfjs-dist/web/pdf_viewer.css';
  import { reactive, onMounted, nextTick } from 'vue';

  // 文件路径
  import pdf from '/Documents/computer_net1.pdf';

  const state = reactive({
    pdfPath: pdf, // PDF 文件路径
    pdfPages: 1, // PDF 总页数
    pdfScale: 1.5, // 缩放比例
  });

  const items = [
    { title: 'Home', icon: 'mdi-home' },
    { title: 'Settings', icon: 'mdi-account' },
    { title: 'Logout', icon: 'mdi-logout' }
  ];

  let pdfDoc = null;

  onMounted(() => {
    pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';

    loadFile(state.pdfPath);
  });

  function loadFile (url) {
    pdfjsLib
      .getDocument({
        url,
        cMapUrl: 'https://cdn.jsdelivr.net/npm/pdfjs-dist@2.16.105/cmaps/',
        cMapPacked: true,
      })
      .promise.then((pdf) => {
        pdfDoc = pdf;
        state.pdfPages = pdf.numPages;
        nextTick(() => {
          renderPages(); // 渲染所有页面
        });
      })
      .catch((error) => {
        console.error('Error loading PDF:', error);
      });
  }

  function renderPages () {
    for (let num = 1; num <= state.pdfPages; num++) {
      renderPage(num);
    }
  }

  function renderPage (num) {
    pdfDoc.getPage(num).then((page) => {
      const viewport = page.getViewport({ scale: state.pdfScale });

      // 渲染 Canvas 层
      const canvas = document.getElementById(`canvas-page-${num}`);
      const ctx = canvas.getContext('2d');
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      const renderContext = {
        canvasContext: ctx,
        viewport,
      };

      // 获取文本内容和渲染页面的 Promise
      const getTextContentPromise = page.getTextContent();
      const renderPagePromise = page.render(renderContext);

      Promise.all([getTextContentPromise, renderPagePromise])
        .then(([textContent]) => {
          const textLayerDiv = document.createElement('div');
          textLayerDiv.setAttribute('class', 'textLayer');

          // 设置容器样式
          textLayerDiv.style.position = 'absolute';
          textLayerDiv.style.left = canvas.offsetLeft + 'px';
          textLayerDiv.style.top = canvas.offsetTop + 'px';
          textLayerDiv.style.height = canvas.offsetHeight + 'px';
          textLayerDiv.style.width = canvas.offsetWidth + 'px';
          textLayerDiv.style.zIndex = '1';
          textLayerDiv.style.opacity = '1';

          const textView = document.querySelector('#text-view');
          textView.appendChild(textLayerDiv);

          // 正确使用 EventBus
          const eventBus = new EventBus();

          const textLayer = new TextLayerBuilder({
            textLayerDiv,
            pageIndex: page.pageIndex,
            viewport,
            eventBus,
          });

          textLayer.setTextContent(textContent);
          textLayer.render();
        })
        .catch((error) => {
          console.error('Error rendering text layer:', error);
        });
    });
  }
</script>

CSS部分

<style>
  .textLayer span::selection {
    color: white;
    /* 选中文本的颜色 */
    background: rgba(0, 0, 255, 0.5);
    /* 半透明蓝色背景 */
  }

  /* 抽屉样式 */
  .floating-drawer {
    height: 100vh;
    /* 抽屉高度固定占满整个视口 */
    position: sticky;
    /* 抽屉固定在右侧 */
    right: 0;
    /* 靠右 */
    top: 0;
    /* 从页面顶部开始 */
    overflow-y: auto;
    /* 抽屉内容可以滚动 */
    box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1);
    /* 可选:增加阴影效果 */
    background-color: rgb(192, 191, 191);
    /* 背景颜色 */
    z-index: 999;
  }



  .pdf-view {
    height: 100%;
    overflow: auto;
  }
</style>

另外有一些比较值得注意的点如下。

  1. 如果需要从后端获取静态路径文件PDF,那么代码参考上一篇文章。其中,需要留意到vite.config.json
// https://vite.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    vuetify({ autoImport: true }), // 配置 vuetify 插件
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  },
  server: {
    proxy: {
      // 代理静态文件请求到 Django 服务器
      '/static': 'http://127.0.0.1:8000' //问题1:static重复,问题2:后端ipv4 16位,前端ipv6,32位,回环,强制改为127.0.0.1
    },
  },
})

前端部分

const fullPdfUrl = `${this.pdfUrl}`;   //不取完整路径

2.关于抽屉的问题
position:fixed 不能和 position:absolute并存 ,且需要父子盒(类)

posted @ 2024-12-06 21:07  Akimizuss101  阅读(84)  评论(0)    收藏  举报