【避坑指“难”】全网最全面的-基于React实现PDF预览功能(react-pdf-js VS react-pdf)

·  阅读 1134

由于业务需求,PM让我实现H5直接预览PDF,好家伙,我以为真的就像他说的一样,殊不知,横竖都是二,到处都是坑,真的是“井田”啊!

常见的PDF预览功能主要三个方向来实现:

react-pdf

直达地址:www.npmjs.com/package/rea…

1、安装 npm install react-pdf 或者 yarn add react-pdf

2、代码实现

import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';

function MyApp() {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }

  return (
    <div>
      <Document
        file="somefile.pdf" //PDF文件在此
        onLoadSuccess={onDocumentLoadSuccess}
      >
        <Page pageNumber={pageNumber} />
      </Document>
      <p>Page {pageNumber} of {numPages}</p>
    </div>
  );
}
复制代码

注意:这里直接读的是PDF文件,如果只有PDF链接地址,可以直接修改file的值,直接设置为链接地址即可,如:file={"http:www.baidu.com"}

3、如何一次性将所有的PDF全部展示出来

对上面的代码稍作修改

import React, { useState } from 'react';
import { Document, Page } from 'react-pdf';

function MyApp() {
  const [numPages, setNumPages] = useState(null);
  const [pageNumber, setPageNumber] = useState(1);

  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }
  function render(nums: number){
        return new Array(nums).fill(1).map((e,i:number)=>{
            if(i>0){
                return <Document
                            key={i}
                            file="somefile.pdf"
                            onLoadSuccess={onDocumentLoadSuccess}
                        >
                            <Page pageNumber={i+1} />
                        </Document>
            }
        })
   }
  return (
    <div>
      <Document
        file="somefile.pdf" //PDF文件在此
        onLoadSuccess={onDocumentLoadSuccess}
      >
        <Page pageNumber={pageNumber} />
      </Document>
     	{
            numPages > 0 && render(numPages) //这里显示除了第一张PDF,剩下所有的PDF
        }
    </div>
  );
}
复制代码

到这里,基本上已满足90%的业务场景,你可以下班了。

什么时候不能直接用链接跳转的方式?

某些场景下,请求链接是需要带请求头header的,这是为了告知后端,用户是从某一入口进来的,这种情况下请求头参数就是一个标识作用。随着业务的扩展,我们可能有会开放不同的入口给不同场景下的用户使用,在此条件下,链接跳转的方式就无法满足业务需求。所有绝大多少情况下,考虑到延展性,我们会使用读PDF文件的方式,来实现PDF预览的功能。

又一特殊情况出现了。。。后端无法直接将PDF文件返过来,只能返PDF文件流

PDF文件流长什么样?长的一点都不优雅,这是前端能看得懂的程度吗? 在这里插入图片描述

header参数为 content-type: application/pdf

react-pdf-js

那么react-pdf-js就登场了。我们可以通过把一点都不优雅的PDF文件流转化为base64,再读出来进行PDF预览。

直达链接:github.com/mikecousins…

1、安装 yarn add @mikecousins/react-pdf 或者 npm install @mikecousins/react-pdf

2、代码实现

import React, { useEffect, useState } from "react";
import Pagination from '@material-ui/lab/Pagination';  //本场景下使用的是Material-ui组件库,一般的用Antd就ok啦
import PDF from 'react-pdf-js';

function MyApp() {
  const [page, setPage] = useState(1);
  const [totalPage, setTotalPages] = useState(1);
  const [currentPage, setCurrentPage] = useState(1);
  
  const [blob, setBlob] = useState<any>(null);
  const [pdfblob, setPdfBlob] = useState<any>(null);
  
  useEffect(() => {	//重点在此!!!!!如何将PDF文件流转base64
    fetchData({
        url:'填接口地址噢',
        method: 'get',
        responseType: 'blob', //必写!
        params:{
          number: number, // 接口传参
        }
      }).then((res)=>{
        setBlob(res.data)
        let reader = new FileReader();
        reader.readAsDataURL(res.data); // 转换为base64,可以直接放入a标签href
        reader.addEventListener("load", function () {
            let base64 = reader.result as string
            setPdfBlob(base64.split(',')[1]) 
        });
      })
  }, []);
  //史诗级重点!不进行异常处理会报错!全网找不到的!
  
  let newpdfblob = "data:application/pdf;base64,"+pdfblob
  if(!pdfblob) return null
  
  function onDocumentLoadSuccess(totalPage:any) {
    setTotalPages(totalPage);
    setCurrentPage(1);
  }
  function onChangePage(page:any) {
    setCurrentPage(page);
    console.log(page)
  }

  return (
    <div>
      <PDF
            scale={0.61}
            file={newpdfblob}  //这里的newpdfblob已经是base64格式了
            onDocumentComplete={onDocumentLoadSuccess}
            page={currentPage}
        />
        <Pagination onChange={()=>onChangePage(page)} count={totalPage} page={currentPage}/>
    </div>
  );
}
复制代码

还是要再次强调一下:史诗级重点!不进行异常处理会报错!全网找不到的!

在这里插入图片描述

  let newpdfblob = "data:application/pdf;base64,"+pdfblob
  if(!pdfblob) return null
复制代码

3、如何一次性将所有的PDF全部展示出来

<PDF
            scale={0.61}
            file={newpdfblob}
            onDocumentComplete={onDocumentLoadSuccess}
            page={currentPage}
        />
        {
          totalPage > 1 && directlyRenderPdf(totalPage)
        }
复制代码
function directlyRenderPdf(nums: number){
      const x = [];
      for (let i = 2; i <= totalPage; i++)
        x.push(<PDF page={i} key={`x${i}`} file={newpdfblob} scale={0.61}/>);
      return x;
    }
复制代码

同样的,图片文件流也可以转成base64的格式进行展示:<img src="data:image/jpeg;base64,base64转码后的字符串" ></img>

写在最后

目前,Data URI scheme支持的类型有:

  • data:,文本数据
  • data:text/plain,文本数据
  • data:text/html,HTML代码
  • data:text/html;base64,base64编码的HTML代码
  • data:text/css,CSS代码
  • data:text/css;base64,base64编码的CSS代码
  • data:text/javascript,Javascript代码
  • data:text/javascript;base64,base64编码的Javascript代码
  • data:image/gif;base64,base64编码的gif图片数据
  • data:image/png;base64,base64编码的png图片数据
  • data:image/jpeg;base64,base64编码的jpeg图片数据
  • data:image/x-icon;base64,base64编码的icon图片数据

base64简单地说,它把一些 8-bit 数据翻译成标准 ASCII 字符,网上有很多免费的base64 编码和解码的工具,在PHP中可以用函数base64_encode() 进行编码,如echo base64_encode(file_get_contents(‘wg.png’));

目前,IE8、Firfox、Chrome、Opera浏览器都支持这种小文件嵌入。

分类:
前端
标签: