前端Vue后端Nodejs 实现 pdf下载和预览,如何实现?

大家好,我是前端小张同学,今天跟大家分享一下前端实现pdf下载和预览功能,应该如何去实现,话说这个问题还是我 女朋友 提出来的,那我就写一篇文章跟大家分享一下,希望本文能给大家带来帮助。

实现效果

1:实现思路

  1. 前端使用pdfjs进行渲染
  2. 服务端读取pdf文件,转换为流文件
  3. 发送接口响应给前端进行渲染

2:前端如何实现

1: 我们前端有两种形式获取服务端流文件 , JSON or Blob

优劣势:json返回的数据字段更灵活,可根据不同情况来处理,Blob形式更简单,整个数据对象即为Blob类型。

2.1:使用Blob 实现pdf下载预览

首先Blob形式,我们需要定义接口的返回类型 responseType为 Blob,这种形式会将你的后端返回的所有数据整合成一个Bolb类型的数据,什么是Blob 这里我就不多讲了。

js 复制代码
  const createLinkDownload = (buffer, filename = '可回溯.pdf') => {
    const blob = new Blob([buffer], { type: 'application/pdf' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
  };
  
 // blob 下载
  const blobDownload = async () => {
    const response = await axios.get('/api/download', {
      responseType: 'blob', // 定义返回类型
    });
    createLinkDownload(response.data);
  };

服务端 接口 逻辑

前端 response

可以在里看的出,即使我服务端返回了 加载成功等信息,但前端,依然没有这些字段,这是因为你在请求的时候已经将 responseType:Bold 了,而不是 application/json

2.2:使用JSON格式实现pdf下载预览

使用JSON的核心就是将 后端读取到的pdf文件转换为base64编码,因为base64本身其实就是字符串, 它与Blob不一样,它需要将base64 转换为Buffer。

与Blob的不同点 json更明确字段返回,更具体。

js 复制代码
  const createLinkDownload = (buffer, filename = '可回溯.pdf') => {
    const blob = new Blob([buffer], { type: 'application/pdf' });
    const link = document.createElement('a');
    link.href = URL.createObjectURL(blob);
    link.download = filename;
    link.click();
  };
  // json 下载
  const jsonDownload = async () => {
    const response = await axios.post('/api/download', {});
    console.log('response', response);
    const arrerBuffer = base64ToBytes(response.data.data);
    createLinkDownload(arrerBuffer);
  };

3:后端如何实现

JS 复制代码
const expressApp = require('express')
const path = require('path')
const fs = require('fs/promises')
const { commonResponse } = require('./common')
const app = expressApp()
const port = 8085
const resolveDir = (fileDir) => path.resolve(__dirname , fileDir)
const pdfDir = resolveDir('../assets/pdf/可回溯逻辑梳理.pdf')
app.get('/download' , (req , res) => {
  // 使用 responseType
  res.download(pdfDir , '可回溯pdf')
  res.send(commonResponse('加载成功' , '1' , {}))
})

app.post('/download' , async (req , res) => {
  // 使用JSON 形式
  // 读取pdf内容 转换为 base64
  const pdfStream = await fs.readFile(pdfDir , { encoding: 'base64' })
  if(!pdfStream){
    return res.send(commonResponse('加载失败' , '-2' , {}))
  }
  res.send(commonResponse('加载成功' , '1' , pdfStream))
})

app.listen(port , () =>{
  console.log("服务启动成功" , port)
})

使用pdfjs转换为canvas渲染pdf

如何实现?

这里大家可以核心看理解一下 如何让pdf变清晰,核心要素就是缩放pdf,缩放为最适合屏幕的大小,即最清晰。

js 复制代码
  /**
   * 加载页面
   * @param pdfDocument pdf
   * @param pageNumber 页数
   */
  const renderPdfPage = async (pageNumber) => {
    // 获取页面
    const page = await pdfDocumentInstance.getPage(pageNumber);
    console.log('page', page);

    // 获取设备像素比
    const dpr = window.devicePixelRatio || 1;
    // 实际设备宽度
    const deviceWidth = window.innerWidth || 414;

    // 计算rem基准值(1rem = 100px)
    const remBase = 100;

    // 获取原始viewport以计算合适的缩放比例
    const originalViewport = page.getViewport({ scale: 1.0 });
    // 计算适合屏幕宽度的缩放比例
    const fitScale = (deviceWidth * dpr) / originalViewport.width;
    // 设置更高的基础缩放比例以提高清晰度
    const qualityScale = 2.5;

    // 设置画布尺寸,使用较高的缩放比例来提高清晰度
    const viewport = page.getViewport({ scale: fitScale * qualityScale });
    const canvasEls = pdfCanvas.value[pageNumber - 1];
    const context = canvasEls.getContext('2d', { alpha: false });

    // 设置画布的实际尺寸(考虑设备像素比和质量缩放)
    canvasEls.height = viewport.height;
    canvasEls.width = viewport.width;

    // 设置画布的显示尺寸(使用rem单位)
    canvasEls.style.height = `${viewport.height / (dpr * qualityScale) / remBase}rem`;
    canvasEls.style.width = `${viewport.width / (dpr * qualityScale) / remBase}rem`;

    // 启用图像平滑
    context.imageSmoothingEnabled = true;
    context.imageSmoothingQuality = 'high';

    // 根据设备像素比和质量缩放来调整上下文
    context.scale(1, 1);

    // 渲染页面
    const renderContext = {
      canvasContext: context,
      viewport: viewport,
    };
    await page.render(renderContext).promise;
    if (maxRenderPdfNum.value > pageNumber) {
      renderPdfPage(pageNumber + 1);
    }
  };

  const getPdf = async () => {
    console.log('1111');
    const response = await getPdfFileStream();
    console.log('response', response);
    if (response && response.data?.STATUS === '1') {
      pdfBufferData.value = response.data.data;
    }
  };
  onMounted(async () => {
    new pdfjsLib.GlobalWorkerOptions().workerSrc = pdfjsWorker || {};
    // 发送请求获取pdf
    await getPdf();

    // 将 Blob 数据转换为 ArrayBuffer
    const arrayBuffer = await base64ToBytes(pdfBufferData.value);

    // 将 ArrayBuffer 数据加载到 pdfjs-dist
    const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });

    pdfDocumentInstance = await loadingTask.promise; // 加载pdf文档

    maxRenderPdfNum.value = pdfDocumentInstance._pdfInfo.numPages; // 存储最大页数
    // 渲染第一页
    renderPdfPage(1);
  });

加入我们的开发群,添加我微信(v:+86 13593659800),备注加群,进入我们内部交流群,了解更多前端知识。

相关推荐
Avan_菜菜33 分钟前
AI 能写代码了,为什么我反而开始要求它先写文档?
前端·github·ai编程
JieE2124 小时前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法
JieE2124 小时前
LeetCode 104. 二叉树的最大深度|递归思路超详细拆解
javascript·算法
爱勇宝5 小时前
鸿蒙生态的下半场:开发者不只要能开发,还要能赚钱
android·前端·程序员
IT_陈寒8 小时前
SpringBoot这个自动配置坑我跳了三次
前端·人工智能·后端
kyriewen8 小时前
我用 AI 一周写完了整个项目,上线第一天就崩了——这是我踩过最贵的 5 个坑
前端·javascript·ai编程
Larcher8 小时前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式
默_笙8 小时前
🃏 JS 只有 8 种数据类型,但我花了 2 天才搞懂 null 和 undefined 的区别
javascript
牧艺9 小时前
从零到协同:构建类飞书在线文档系统的五个技术重难点
前端·人工智能
jump_jump9 小时前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化