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

相关推荐
To_OC2 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
To_OC3 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
vipbic3 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy3 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志3 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble4 小时前
c端系统,其实更像一个信息展示平台
前端
你怎么知道我是队长5 小时前
JavaScript的变量和数据类型介绍
开发语言·javascript·ecmascript
李姆斯5 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜6 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司6 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能