import React, { useEffect, useState, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist';
pdfjsLib.GlobalWorkerOptions.workerSrc = '/pdf.worker.min.js';
const PDFViewer = ({ url }) => {
const [pdf, setPdf] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [numPages, setNumPages] = useState(0);
const [pageRendering, setPageRendering] = useState(false);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [zoom, setZoom] = useState(1);
const canvasRef = useRef(null);
const linkLayerRef = useRef(null);
const containerRef = useRef(null);
const touchStartRef = useRef({ x: 0, y: 0, distance: 0 });
const lastTapRef = useRef(0);
// ... addWatermark 和 setupLinkLayer 函数保持不变 ...
// 处理移动端手势
const handleTouchStart = (e) => {
if (e.touches.length === 2) {
// 双指缩放
const distance = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
touchStartRef.current = {
x: (e.touches[0].clientX + e.touches[1].clientX) / 2,
y: (e.touches[0].clientY + e.touches[1].clientY) / 2,
distance
};
} else if (e.touches.length === 1) {
// 单指点击
const now = Date.now();
if (now - lastTapRef.current < 300) {
// 双击缩放
e.preventDefault();
handleDoubleTap(e.touches[0].clientX, e.touches[0].clientY);
}
lastTapRef.current = now;
}
};
const handleTouchMove = (e) => {
if (e.touches.length === 2) {
e.preventDefault();
const distance = Math.hypot(
e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY
);
const scale = distance / touchStartRef.current.distance;
const newZoom = Math.min(Math.max(zoom * scale, 0.5), 3.0);
setZoom(newZoom);
}
};
const handleDoubleTap = (x, y) => {
if (zoom > 1) {
setZoom(1); // 重置缩放
} else {
setZoom(2); // 放大到2倍
}
};
// 监听屏幕方向变化
useEffect(() => {
const handleOrientationChange = () => {
// 延迟执行以等待布局完成
setTimeout(() => {
if (containerRef.current) {
renderPage(currentPage);
}
}, 100);
};
// 监听屏幕方向变化
window.addEventListener('orientationchange', handleOrientationChange);
// 监听视口大小变化(某些设备需要)
const mediaQuery = window.matchMedia('(orientation: portrait)');
mediaQuery.addListener(handleOrientationChange);
return () => {
window.removeEventListener('orientationchange', handleOrientationChange);
mediaQuery.removeListener(handleOrientationChange);
};
}, [currentPage]);
// 修改渲染页面函数
const renderPage = async (pageNum) => {
if (pageRendering || !pdf) return;
setPageRendering(true);
try {
if (!pageCache.current.has(pageNum)) {
const page = await pdf.getPage(pageNum);
pageCache.current.set(pageNum, page);
}
const page = pageCache.current.get(pageNum);
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
// 获取容器宽度(考虑移动端视口)
const containerWidth = containerRef.current.clientWidth;
const viewport = page.getViewport({ scale: 1 });
// 计算缩放比例(考虑设备像素比)
const devicePixelRatio = window.devicePixelRatio || 1;
const baseScale = (containerWidth / viewport.width);
const finalScale = baseScale * zoom * devicePixelRatio;
// 设置canvas尺寸
const scaledViewport = page.getViewport({ scale: finalScale });
canvas.width = scaledViewport.width;
canvas.height = scaledViewport.height;
// 设置显示尺寸
const displayWidth = containerWidth * zoom;
const displayHeight = (containerWidth * viewport.height / viewport.width) * zoom;
canvas.style.width = `${displayWidth}px`;
canvas.style.height = `${displayHeight}px`;
// 更新链接层
if (linkLayerRef.current) {
linkLayerRef.current.style.width = `${displayWidth}px`;
linkLayerRef.current.style.height = `${displayHeight}px`;
}
// 渲染PDF
const renderContext = {
canvasContext: ctx,
viewport: scaledViewport,
enableWebGL: true,
};
await page.render(renderContext).promise;
setupLinkLayer(page, scaledViewport);
addWatermark(canvas, finalScale);
} catch (error) {
console.error('Error rendering page:', error);
setError('页面渲染失败,请刷新重试');
} finally {
setPageRendering(false);
}
};
// 返回的 JSX
return (
<div className="pdf-viewer">
<div className="pdf-controls">
<div className="navigation-controls">
<button
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage <= 1 || pageRendering}
className="control-button"
>
上一页
</button>
<span className="page-info">{`${currentPage} / ${numPages}`}</span>
<button
onClick={() => setCurrentPage(prev => Math.min(prev + 1, numPages))}
disabled={currentPage >= numPages || pageRendering}
className="control-button"
>
下一页
</button>
</div>
</div>
<div
className="pdf-container"
ref={containerRef}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
>
<div className="canvas-container">
<canvas ref={canvasRef} className="pdf-canvas" />
<div
ref={linkLayerRef}
className="link-layer"
/>
</div>
</div>
</div>
);
};
export default PDFViewer;