138 lines
3.7 KiB
JavaScript
138 lines
3.7 KiB
JavaScript
import { useState, useMemo } from 'react'
|
||
import { Document, Page, pdfjs } from 'react-pdf'
|
||
import { Button, Space, InputNumber, message, Spin } from 'antd'
|
||
import {
|
||
LeftOutlined,
|
||
RightOutlined,
|
||
ZoomInOutlined,
|
||
ZoomOutOutlined,
|
||
} from '@ant-design/icons'
|
||
import 'react-pdf/dist/Page/AnnotationLayer.css'
|
||
import 'react-pdf/dist/Page/TextLayer.css'
|
||
import './PDFViewer.css'
|
||
|
||
// 配置 PDF.js worker - 使用本地文件
|
||
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf-worker/pdf.worker.min.mjs'
|
||
|
||
function PDFViewer({ url, filename }) {
|
||
const [numPages, setNumPages] = useState(null)
|
||
const [pageNumber, setPageNumber] = useState(1)
|
||
const [scale, setScale] = useState(1.0)
|
||
|
||
// 使用 useMemo 避免不必要的重新加载
|
||
const fileConfig = useMemo(() => ({ url }), [url])
|
||
|
||
const onDocumentLoadSuccess = ({ numPages }) => {
|
||
setNumPages(numPages)
|
||
setPageNumber(1)
|
||
}
|
||
|
||
const onDocumentLoadError = (error) => {
|
||
message.error('PDF文件加载失败')
|
||
}
|
||
|
||
const goToPrevPage = () => {
|
||
setPageNumber((prev) => Math.max(prev - 1, 1))
|
||
}
|
||
|
||
const goToNextPage = () => {
|
||
setPageNumber((prev) => Math.min(prev + 1, numPages))
|
||
}
|
||
|
||
const zoomIn = () => {
|
||
setScale((prev) => Math.min(prev + 0.2, 3.0))
|
||
}
|
||
|
||
const zoomOut = () => {
|
||
setScale((prev) => Math.max(prev - 0.2, 0.5))
|
||
}
|
||
|
||
const handlePageChange = (value) => {
|
||
if (value >= 1 && value <= numPages) {
|
||
setPageNumber(value)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="pdf-viewer-container">
|
||
{/* 工具栏 */}
|
||
<div className="pdf-toolbar">
|
||
<Space>
|
||
<Button
|
||
icon={<LeftOutlined />}
|
||
onClick={goToPrevPage}
|
||
disabled={pageNumber <= 1}
|
||
size="small"
|
||
>
|
||
上一页
|
||
</Button>
|
||
<Space.Compact>
|
||
<InputNumber
|
||
min={1}
|
||
max={numPages || 1}
|
||
value={pageNumber}
|
||
onChange={handlePageChange}
|
||
size="small"
|
||
style={{ width: 60 }}
|
||
/>
|
||
<Button size="small" disabled>
|
||
/ {numPages || 0}
|
||
</Button>
|
||
</Space.Compact>
|
||
<Button
|
||
icon={<RightOutlined />}
|
||
onClick={goToNextPage}
|
||
disabled={pageNumber >= numPages}
|
||
size="small"
|
||
>
|
||
下一页
|
||
</Button>
|
||
</Space>
|
||
|
||
<Space>
|
||
<Button icon={<ZoomOutOutlined />} onClick={zoomOut} size="small">
|
||
缩小
|
||
</Button>
|
||
<span style={{ minWidth: 50, textAlign: 'center' }}>
|
||
{Math.round(scale * 100)}%
|
||
</span>
|
||
<Button icon={<ZoomInOutlined />} onClick={zoomIn} size="small">
|
||
放大
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
|
||
{/* PDF内容区 */}
|
||
<div className="pdf-content">
|
||
<Document
|
||
file={fileConfig}
|
||
onLoadSuccess={onDocumentLoadSuccess}
|
||
onLoadError={onDocumentLoadError}
|
||
loading={
|
||
<div className="pdf-loading">
|
||
<Spin size="large" />
|
||
<div style={{ marginTop: 16 }}>正在加载PDF...</div>
|
||
</div>
|
||
}
|
||
error={<div className="pdf-error">PDF加载失败,请稍后重试</div>}
|
||
>
|
||
<Page
|
||
pageNumber={pageNumber}
|
||
scale={scale}
|
||
renderTextLayer={true}
|
||
renderAnnotationLayer={true}
|
||
loading={
|
||
<div className="pdf-loading">
|
||
<Spin size="large" />
|
||
<div style={{ marginTop: 16 }}>正在渲染页面...</div>
|
||
</div>
|
||
}
|
||
/>
|
||
</Document>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default PDFViewer
|