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) // 默认宽度为 600pxconst [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 (<Modaltitle={`【${fileName}】预览`}openonCancel={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 && (<PDFPreviewModalfileName={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);};//上一页function lastPage() {if (pageNumber == 1) {Toast.show({content: '已是第一页'})return;}const page = pageNumber - 1;setPageNumber(page);}//下一页function nextPage() {if (pageNumber == numPages) {Toast.show("已是最后一页");return;}const page = pageNumber + 1;setPageNumber(page);}//缩小function pageZoomOut() {if (scale <= 0.3) {Toast.show("已缩放至最小");return;}const newScale = scale - 0.1;setScale(newScale);}//放大function pageZoomIn() {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> ))}<Popupposition='right'visible={showPdfViwer}showCloseButtonbodyStyle={{ 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文件地址是线上地址,则会报跨域的问题,需要服务端解决跨域问题。