react-pdf预览在线PDF的使用
1、在react项目中安装react-pdf依赖包
建议安装8.0.2版本的react-pdf,如果安装更高版本的可能出现一些浏览器的兼容性问题;
npm install react-pdf@8.0.2 -S
1、PC端的使用
1.1、封装一个组件:PdfViewModal.tsx
import React, { useState } from 'react'import { Modal, Spin, Alert } from'antd'import { Document, Page, pdfjs } from'react-pdf'import'react-pdf/dist/esm/Page/AnnotationLayer.css'import'react-pdf/dist/esm/Page/TextLayer.css';//配置 PDF.js 的 worker 文件 pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).toString()
interface PDFPreviewModalProps {
fileName: string| nullfileUrl: string| null //传入的 PDF 文件地址 onCancel: () => void //关闭弹框的回调 }
const PDFPreviewModal: React.FC<PDFPreviewModalProps> = ({ fileName, fileUrl, onCancel }) =>{
const [numPages, setNumPages]= useState<number | null>(null)
const [pdfWidth, setPdfWidth]= useState<number>(600) //默认宽度为 600px const [loading, setLoading] = useState<boolean>(true) //控制加载状态 const [error, setError] = useState<boolean>(false) //控制加载错误状态 //当 PDF 加载成功时,设置页面数量 const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) =>{
setNumPages(numPages)
setLoading(false) //加载成功后,隐藏 loading }//加载失败时,设置错误状态 const onDocumentLoadError = () =>{
setLoading(false)
setError(true) //出错时显示错误提示 }//获取 PDF 页面加载后的宽度 const onPageLoadSuccess = ({ width }: { width: number }) =>{
setPdfWidth(width)
}return(<Modal
title={`【${fileName}】预览`}
open
onCancel={onCancel}
footer={null}
width={pdfWidth + 100}
style={{ top: 20}}>{error?(<Alert message="加载 PDF 文件失败" type="error" showIcon /> ) : (<>{loading&&(<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '80vh' }}> <Spin size="large" /> </div> )}
{fileUrl&&(<> <div style={{ height: '88vh', overflowY: 'auto', padding: '24px' }}> <Document//file={new URL('/public/temp/DXF文件要求.pdf',import.meta.url).toString()} file={fileUrl}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={onDocumentLoadError}>{Array.from(new Array(numPages), (el, index) =>(<Page key={`page_${index + 1}`} pageNumber={index + 1} onLoadSuccess={onPageLoadSuccess} /> ))}</Document> </div> </> )}</> )}</Modal> )
}
exportdefault PDFPreviewModal
1.2、业务代码中引入该组件
import React, { useState, useEffect, useCallback } from 'react'import { Form } from'antd'import { List } from'antd'import PDFPreviewModal from'@/components/PdfViewModal.tsx' const PdfTest = (props: any) =>{const [previewFile, setPreviewFile]= useState<any>()
const onTestPdf = () => {
setPreviewFile({
fileName: 'abc.pdf',
fileUrl: 'http://****/abc.pdf'
})
}return(<div className="mrUp mrLink">
<div onClick={onTestPdf}>测试预览PDF</div>
{!!previewFile?.publicFileUrl &&(<PDFPreviewModal
fileName={previewFile?.fileName}
fileUrl={previewFile?.publicFileUrl}
onCancel={() => setPreviewFile('')}/> )}</div> )
}
exportdefault PdfTest
2、H5移动端的使用
移动端加入放大、缩小、上一页、下一页的功能;
2.1、封装一个组件:PDFViwer.tsx
import React, { useState } from 'react';
import { Button, Modal, Space, Toast, Divider } from'antd-mobile'import { UpOutline, DownOutline, AddCircleOutline, MinusCircleOutline } from'antd-mobile-icons'import { Document, Page, pdfjs } from'react-pdf';
import'react-pdf/dist/esm/Page/AnnotationLayer.css'; //样式导入 import 'react-pdf/dist/esm/Page/TextLayer.css' //配置 PDF.js 的 worker 文件 pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).toString()
interface PDFPreviewModalProps {
fileUrl: string| null; //传入的 PDF 文件地址 }
const styleBtnDv={
display:'flex',
justifyContent:'center',
height:'1rem',
alignItems:'center',
gap:'0.4rem',
margin:'0.3rem 1rem',
padding:'0 0.6rem',
background:'#444',
borderRadius:'0.5rem'}
const styleBtn={
flex:1,
display:'flex',
justifyContent:'center',
height:'0.6rem',
alignItems:'center',
}//PDF预览功能 const PDFViwer: React.FC<PDFPreviewModalProps> = ({ fileUrl }) =>{
const [pageNumber, setPageNumber]= useState(1);
const [numPages, setNumPages]= useState(1);
const [scale, setScale]= useState(0.65);//当 PDF 加载成功时,设置页面数量 const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) =>{
setNumPages(numPages);
};//上一页 functionlastPage() {if (pageNumber == 1) {
Toast.show({
content:'已是第一页'})return;
}
const page= pageNumber - 1;
setPageNumber(page);
}//下一页 functionnextPage() {if (pageNumber ==numPages) {
Toast.show("已是最后一页");return;
}
const page= pageNumber + 1;
setPageNumber(page);
}//缩小 functionpageZoomOut() {if (scale <= 0.3) {
Toast.show("已缩放至最小");return;
}
const newScale= scale - 0.1;
setScale(newScale);
}//放大 functionpageZoomIn() {if (scale >= 5) {
Toast.show("已放大至最大");return;
}
const newScale= scale + 0.1;
setScale(newScale);
}return(<div>{/*预览 PDF 文件*/}
{fileUrl?(<div style={{ height: 'calc(100vh - 4.5rem)', overflowY: 'auto', padding: '24px' }}> <Document//写死的pdf文件地址,用于本地测试使用,打包提交前需要注释掉 //file={new URL("/public/temp/AI销售助手-宽带&套餐&战新.pdf", import.meta.url).toString()} //真实传入的pdf地址 file={fileUrl}
onLoadSuccess={onDocumentLoadSuccess}> <Page pageNumber={pageNumber} scale={scale} /> </Document> </div> ) : (<p>没有选择文件</p> )}<div style={styleBtnDv}> <div style={styleBtn} onClick={lastPage}><UpOutline color='#fff' fontSize={'0.6rem'} /></div> <div style={{ color: '#fff', fontSize: '0.35rem', ...styleBtn }}>{pageNumber}/{numPages}</div> <div style={styleBtn} onClick={nextPage}><DownOutline color='#fff' fontSize={'0.6rem'} /></div> <div style={styleBtn} onClick={pageZoomIn}><AddCircleOutline color='#fff' fontSize={'0.6rem'} /></div> <div style={styleBtn} onClick={pageZoomOut}><MinusCircleOutline color='#fff' fontSize={'0.6rem'} /></div> </div> </div> );
};
exportdefault PDFViwer;
2.2、业务代码中引入该组件
import React, { useMemo, useRef, useState } from 'react'import { ErrorBlock, Swiper, SwiperRef, Popup, } from'antd-mobile'import PDFViwer from'@/components/PDFViwer';
const ellipsis1={"white-space": "nowrap","overflow": "hidden","text-overflow": "ellipsis",
} const IntroduceDocList = (props: any) =>{
const { loading, introduceDocList }=props//const introduceDocList = [ //{publicFileUrl: '/public/temp/DXF文件要求.pdf', fileName:'DXF文件要求.pdf'}, //{publicFileUrl: '/public/temp/AI销售助手-宽带&套餐&战新.pdf', fileName:'AI销售助手-宽带&套餐&战新.pdf'}, //]
const [introduceDocList, setIntroduceDocList] = useState({
{publicFileUrl: 'http://****/abc.pdf', fileName:'abc.pdf'},
{publicFileUrl: 'http://****/def.pdf', fileName:'def.pdf'},
});
const [pdf, setPdf] = useState({ id: 1});
const [showPdfViwer, setShowPdfViwer]= useState(false)
const onOpenPdfViewer= (item) =>{
console.log(item);
setPdf(item);
setShowPdfViwer(true);
}return( <div>{
introduceDocList?.map(item =>(<div data-url={item?.publicFileUrl} style={{ marginBottom: '0.3rem', fontSize: '0.4rem' }}> <span style={{color:'#0B75FF'}} onClick={() => onOpenPdfViewer(item)}>{item.fileName}</span> </div> ))
}<Popup
position='right'visible={showPdfViwer}
showCloseButton
bodyStyle={{ width: '100%'}}
destroyOnClose={true}
onClose={() =>{
setShowPdfViwer(false)
setPdf({ id:1})
}}> <div style={{ padding: '0.3rem 1rem', fontSize: '0.35rem', fontWeight: 600, textAlign:'center', ...ellipsis1 }}>{pdf?.fileName}</div> <div style={{ height: '100%' }} data-url={pdf?.publicFileUrl}> <PDFViwer fileUrl={pdf?.publicFileUrl} /> </div> </Popup> </div> )
}
exportdefault IntroduceDocList
效果图:
注意:挡在本地开发时,如果预览的pdf文件地址是线上地址,则会报跨域的问题,需要服务端解决跨域问题。