React 实现PDF预览(数据源使用文件流而不是url)
一 前提
应公司要求,需要进行上传文件(pdf)的预览功能,网上大部分都是使用url作为预览数据源,但是现在后端那边只返回了pdf文件流,所以本文主要是用文件流来预览pdf。
二 首先需要获取pdf文件流,并转化为base64格式的数据。
1. 封装axios方法,用于获取pdf文件流
const apiClient = axios.create({
baseURL: '/',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
});
// 封装的axiosget方法,获取pdf文件流
const axiosGetFileBlob = async (url: string) => {
try {
const configs: any = {
method: 'get',
url: `${url}`,
responseType: 'blob'
};
const res = await apiClient.get(url, configs);
return res.data;
} catch (error) {
// 抛出异常信息
}
return null as any;
};
2.调用封装的方法,获取文件流并转为base64格式的数据。
// 将文件流转为base64格式
const getBase64 = (file: any) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
};
const getData = async () => {
const res = await axiosGetFileBlob("这里是获取pdf的url");
if (res) {
// 这里获取到了文件流,首先需要转化为base64格式的数据
const baseData = await getBase64(res);
// 转化为base64的数据有一个文件类型的前缀,需要去掉。
const resourceData = baseData.split(',')[1];
// 这里需要拼接一个前缀,表示这个是一个pdf文件
const finalData= "data:application/pdf;base64," + resourceData ;
consloe.log("finalData就是获取到的pdf文件流");
}
}
三 进行pdf预览功能
根据第二步,可以拿到后端获取后并转化为base64格式的文件流数据。
pdf预览有下列几种方式,主要分为系统自带预览(iframe,object,embed)以及插件预览(react-pdf-js)。大家可以自行选用合适。
1. 使用系统自带的方式预览(iframe,object,embed)。
使用方式很简单,直接将第二步的finalData放到各自的数据源属性下。代码如下:
// 使用iframe
<iframe src={finalData+ "#toolbar=0"} height={800}>您的电脑不支持iframe</iframe>
// 使用embed
<embed src={finalData+ "#toolbar=0"} height={800}/>
// 使用 object
<object data={finalData+ "#toolbar=0"} height={800}></object>
效果如下:
备注:
预览的pdf大小自由设置标签的属性即可。
#toolbar=0 代码的意思是隐藏顶部工具栏(下载、打印等),如果不加,则效果如下:
2.使用插件的方式预览(react-pdf-js)。
(1) 首先需要安装:
yarn add @mikecousins/react-pdf
或者 npm install @mikecousins/react-pdf
(2)实现代码如下:
import React, {useEffect, useState} from "react";
import PDF from 'react-pdf-js';
// getFileBlob 就是上面提到过的:从后端拿取数据流
import {getFileBlob} from "@/services/common";
// getBase64 就是将数据转为base64格式
import {getBase64} from "@/utils/common";
export default () => {
const [totalPage, setTotalPages] = useState(1);
const [currentPage, setCurrentPage] = useState(1);
// 保存后端获取到后并转化为base64的数据流
const [pdfBlob, setPdfBlob] = useState<any>(null);
const getData = async () => {
const res = await getFileBlob("20240902095451482799");
debugger
if (res) {
const baseData = await getBase64(res);
const resourceData = baseData.split(',')[1];
if (resourceData) {
// 有数据再进行拼接,否则无效
const finalData = "data:application/pdf;base64," + resourceData;
setPdfBlob(finalData);
}
}
}
useEffect(() => {
getData();
});
const onDocumentLoadSuccess = (totalPage: any) => {
setTotalPages(totalPage);
setCurrentPage(1);
}
// 一次展示所有界面
const showAllPages = () => {
const page = [];
for (let i = 2; i <= totalPage; i++)
page.push(<PDF page={i} key={`page-${i}`} file={finalPdfBlob} scale={1.5}/>);
return page;
}
return (
<div>
{/* 一定要注意(必须要先进行pdfBlob的判定,pdfBlob 为空的话,不能继续下去。否则系统会报错。)*/}
{
pdfBlob && <div>
<PDF
scale={1.5}
file={pdfBlob}
onDocumentComplete={onDocumentLoadSuccess}
page={currentPage}
/>
{/* 一次性展示全部页面:代码如下 */}
{totalPage > 1 && showAllPages()}
{/* 这里也可以添加分页组件 ,进行分页展示,调用 setCurrentPage 即可。 */}
</div>
}
</div>
);
}
四 应用(在浏览器新打开的窗口中预览)
我这里的预览使用的是iframe。将上面拿到的base64串,放到iframe中作为数据源。再结合window方法即可。代码如下:
<Button onClick={()=>getPdfContent("123456")}>预览<Button>
// 点击方法
const getPdfContent = async (id: string) => {
try {
// 从后端获取pdf数据流
const pdfBlob = await getFileBlob(id);
if (pdfBlob) {
// 将pdf数据流转为base64字符串
const pdfBase64 = await getBase64(pdfBlob);
if (pdfBase64) {
//获取的数据有前缀,不满足下载的pdf格式。需要去掉后,单独拼接pdf的格式
const pdfData = pdfBase64.toString().split(',')[1];
const iframeSrc = `data:application/pdf;base64,${pdfData}`;
// -----------------关键位置------------
// 创建window.open方法,并将iframe结合数据源写入。
const pdfWindow = window.open("", id);
pdfWindow?.document.write(`<iframe width='100%' height='100%' src=${iframeSrc}></iframe>`);
pdfWindow?.document.close();
}
}
} catch (e) {
console.log("系统错误!");
}
};