Electron PDF.js 预览文档问题解决

使用 electron 打包成 exe 安装文件后,PDF.js 自定义 viewer.html 预览 pdf 时,可能会出现预览路径加载不对的问题,还有右键复制功能无效的问题,都需要特殊处理

1. pdfjs 预览加载路径不对

直接使用浏览器内置的 pdf 阅读器预览,无论是在线网站运行,还是 electron 打包后,预览均没有问题,缺点是页面无法自定义,不够灵活。

使用 pdfjs 自定义页面 viewer.html 预览,网站运行时,没有问题

但是 electron 打包后,pdf 加载路径就不对了,有跨域问题,electron开发好难搞呀

1.1 网站在线运行

jsx 复制代码
<iframe
  title="预览文档"
  src="/web/mViewer.html?file=../pdf/15.pdf"
  width="100%"
  height="100%"
></iframe>

在线运行时,本地启动服务地址为http://localhost:3000

pdf 文档预览调用链,如下所示:

http://localhost:3000/web/mViewer.html?file=../pdf/15.pdf

-> http://localhost:3000/pdf/15.pdf

控制台显示如下:

1.2 electron 打包后运行

使用 electron 打包后,pdfjs 加载离线的 pdf 文件就会报错

报错信息如下所示:

Request URL: file:///web/mViewer.html?file=../pdf/15.pdf

Referrer Policy: no-referrer

解决方案:在 iframe 标签的 src 属性中使用相对路径或者绝对路径链接到 pdfjs 的相关资源。

我使用了process.env.PUBLIC_URL,增加了referrerPolicy="no-referrer"配置,确保在打包编译后生成的文件中,相关资源能够正确加载。

配置了之后,在线运行也是没问题的。

jsx 复制代码
<iframe
  ref={iframeRef}
  title="预览文档"
  src={`${process.env.PUBLIC_URL}/web/mViewer.html?file=${
    "." + curSubmenu.pdfPath
  }#page=${curSubmenu.pdfStartPage}`}
  width="100%"
  height="100%"
  referrerPolicy="no-referrer"
></iframe>

更改配置后,electron 中 pdf 文档预览时,加载路径如下所示:

2. pdfjs 右键复制功能

网站运行时,pdf 文档预览,可以选中文本,然后右键复制

也可以选中后,用 ctrl+c 快捷键复制,这是浏览器自带的功能。

但是使用 electron 打包后,选中pdf文本后,默认右键是没有功能的

需要自己处理右键菜单,要在main.js中添加复制功能,如下所示:

js 复制代码
const { Menu, MenuItem } = require("electron");

// 创建上下文菜单
const contextMenu = new Menu();
contextMenu.append(
  new MenuItem({
    label: "复制",
    role: "copy",
  })
);

// 在 PDF 预览窗口上绑定上下文菜单
win.webContents.on("context-menu", (e, params) => {
  contextMenu.popup(win, params.x, params.y);
});

展示效果如下图所示:

3. 复制当前 pdf 页文本

项目要求,需要自定义按钮实现复制当前 pdf 页文本内容,目前插件是不支持的。

我的实现方案是,获取到页码和页面元素,使用textContent属性获取文本内容

直接查看源码,获取对应元素,如下所示:

data-page-number=11中的11可以替换成变量

js 复制代码
// 定义iframe元素
const iframeRef = useRef < HTMLIFrameElement > null;

// 复制方法
function handleCopy() {
    if (iframe?.contentDocument) {
        // 获取dom元素
        const document = iframe.contentDocument;
        // 获取当前页码元素
        const pageNumberEle = document.getElementById(
          "pageNumber"
        ) as HTMLInputElement;
        // 获取当前页码
        const pageNumber = pageNumberEle.value;
        // 获取当前页元素
        const page = document.querySelector('.page[data-page-number=11]');
        // 获取当前页文本内容
        const textContent = page?.querySelector(".textLayer")?.textContent || "";
        // 复制到剪切板的代码
        console.log("textContent", textContent);
        // 将选定的文本保存到剪贴板
        navigator.clipboard.writeText(textContent);
    }
}
相关推荐
郝开几秒前
7. React组件基础样式控制:行内样式,class类名控制
react.js
开水好喝25 分钟前
Code Coverage Part I
前端
DoraBigHead44 分钟前
🧭 React 理念:让时间屈服于 UI —— 从同步到可中断的演化之路
前端·javascript·面试
敢敢J的憨憨L1 小时前
GPTL(General Purpose Timing Library)使用教程
java·服务器·前端·c++·轻量级计时工具库
喝拿铁写前端1 小时前
Vue 组件通信的两种世界观:`.sync` 与普通 `props` 到底有什么不同?
前端·vue.js·前端框架
美酒没故事°2 小时前
npm源管理器:nrm
前端·npm·npm源
用户22152044278002 小时前
vue3组件间的通讯方式
前端·vue.js
三十_A2 小时前
【实录】使用 patch-package 修复第三方 npm 包中的 Bug
前端·npm·bug
下位子2 小时前
『AI 编程』用 Claude Code 从零到一开发全栈减脂追踪应用
前端·ai编程·claude
tyro曹仓舒2 小时前
Vue单文件组件到底需不需要写name
前端·vue.js