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> )
}

export
default 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> )
}

export
default 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> );
};

export
default 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> )
}
export
default IntroduceDocList

效果图:

注意:挡在本地开发时,如果预览的pdf文件地址是线上地址,则会报跨域的问题,需要服务端解决跨域问题。

标签: none

添加新评论