前端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),备注加群,进入我们内部交流群,了解更多前端知识。

相关推荐
江沉晚呤时1 小时前
深入解析 .NET 中的依赖项加载机制:原理、实现与最佳实践
前端·数据库·c#·.netcore
哟哟耶耶1 小时前
knowledge-微前端(多个前端应用聚合的一个应用架构体系,每个小的应用可独立运行,独立开发,独立部署上线)
前端
Enjoy_zhuo1 小时前
xss-labs第八、九关卡以及XSS GAME的Ok,Boomer关卡
前端·安全·xss
你挚爱的强哥2 小时前
【sgAutocomplete_v2】自定义组件:基于elementUI的el-input组件开发的搜索输入框(支持本地保存历史搜索关键词、后台获取匹配项)
javascript·vue.js·elementui
hikktn2 小时前
【开源宝藏】30天学会CSS - DAY2 第二课 Loader Ring Demo
前端·css·开源
晓夜残歌5 小时前
安全基线-rm命令防护
运维·服务器·前端·chrome·安全·ubuntu
inxunoffice5 小时前
批量删除 PPT 空白幻灯片页面
前端·powerpoint
Setsuna_F_Seiei7 小时前
前端切图仔的一次不务正业游戏开发之旅
前端·游戏·cocos creator
laimaxgg7 小时前
Qt窗口控件之颜色对话框QColorDialog
开发语言·前端·c++·qt·命令模式·qt6.3
爱编程的鱼8 小时前
Unity—从入门到精通(第一天)
前端·unity·ue5·游戏引擎