/** * 文件预览组件 * 显示文档内容和评查点高亮 */ import { useState, useEffect, useRef, ChangeEvent } from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; // 设置worker路径为public目录下的worker文件 // 使用已经下载的兼容版本 (pdfjs-dist v2.12.313) pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js'; // 导入统一的ReviewPoint类型 import { type ReviewPoint } from './'; import { toastService } from '../ui/Toast'; /** * 自定义样式 * 这些样式解决了PDF页面在放大时互相重叠的问题 */ const styles = { pdfContainer: { display: 'flex', flexDirection: 'column' as const, alignItems: 'center', width: '100%', position: 'relative' as const, }, pageContainer: { display: 'flex', flexDirection: 'column' as const, alignItems: 'center', width: '100%', position: 'relative' as const, } }; // 定义文档内容类型 interface FileContent { title: string; contractNumber: string; path: string; ocrResult?: { __meta?: { page_offset?: number; }; }; // 添加ocrResult属性 parties: { partyA: { name: string; address: string; representative: string; phone: string; }; partyB: { name: string; address: string; representative: string; phone: string; }; }; sections: { title: string; content: string; }[]; } interface FilePreviewProps { fileContent: FileContent; reviewPoints: ReviewPoint[]; activeReviewPointId: string | null; targetPage?: number; // 新增目标页码参数 } export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, targetPage }: FilePreviewProps) { const [zoomLevel, setZoomLevel] = useState(100); // const [highlightsVisible, setHighlightsVisible] = useState(true); const contentRef = useRef(null); const [numPages, setNumPages] = useState(null); const [loadError, setLoadError] = useState(null); const [pageInputValue, setPageInputValue] = useState(''); // 放大文档 const handleZoomIn = () => { if (zoomLevel < 200) { setZoomLevel(prevZoom => prevZoom + 10); } }; // 缩小文档 const handleZoomOut = () => { if (zoomLevel > 50) { setZoomLevel(prevZoom => prevZoom - 10); } }; // 切换高亮显示 // const toggleHighlights = () => { // setHighlightsVisible(!highlightsVisible); // }; // 当选中的评查点变化时,滚动到对应位置 // useEffect(() => { // if (activeReviewPointId && contentRef.current) { // const highlightElement = contentRef.current.querySelector(`[data-review-id="${activeReviewPointId}"]`); // if (highlightElement) { // highlightElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); // // highlightElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); // // 添加临时突出显示效果 // highlightElement.classList.add('highlight-focus'); // setTimeout(() => { // highlightElement.classList.remove('highlight-focus'); // }, 1500); // } // } // }, [activeReviewPointId]); // 处理页面跳转 const prevTargetPageRef = useRef(undefined); useEffect(() => { // 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转 if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointId)) { prevTargetPageRef.current = targetPage; let newTargetPage = targetPage; try { // 安全地访问ocrResult if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) { // 可以根据需要使用page_offset调整目标页面 newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset; } } catch (error) { console.error("访问ocrResult时出错:", error); } const pageElement = document.getElementById(`page-${newTargetPage}`); if (pageElement) { console.log(`跳转到第${newTargetPage}页,对应评查点结果ID: ${activeReviewPointId}`); pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } }, [targetPage, numPages, fileContent, activeReviewPointId]); // 获取评查点对应的样式类 const getHighlightClass = (status: string) => { switch (status) { case 'warning': return 'warning'; case 'error': return 'error'; case 'success': return 'success'; default: return 'warning'; } }; // 处理页码输入变化 const handlePageInputChange = (e: ChangeEvent) => { // 只允许输入数字 const value = e.target.value.replace(/\D/g, ''); setPageInputValue(value); }; // 处理页码跳转 const handlePageJump = () => { if (!pageInputValue || !numPages) return; const targetPageNum = parseInt(pageInputValue, 10); // 验证页码是否在有效范围内 if (targetPageNum > 0 && targetPageNum <= numPages) { // 找到目标页面元素并滚动到该位置 const pageElement = document.getElementById(`page-${targetPageNum}`); if (pageElement) { pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } else { // 页码超出范围,显示错误信息或重置输入 toastService.warning(`请输入有效页码 (1-${numPages})`); setPageInputValue(''); } }; // 处理回车键跳转 const handlePageInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handlePageJump(); } }; // PDF文档加载成功回调函数 function onDocumentLoadSuccess({ numPages }: { numPages: number }) { setNumPages(numPages); console.log("PDF加载成功,页数:", numPages); } // 计算页面在缩放后的实际间距 const calculatePageMargin = (zoomFactor: number) => { // 基础间距为30px,随着缩放倍数线性增加 const baseMargin = 30; // 页面缩放后,需要额外添加的间距 = (缩放倍数 - 1) * 页面高度 const additionalMargin = Math.max(0, (zoomFactor - 1) * 800); // 800是估计的页面高度 return baseMargin + additionalMargin; }; // 滚动到顶部 const handleScrollToTop = () => { if (contentRef.current) { contentRef.current.scrollTo({ top: 0, behavior: 'smooth' }); } }; /** * 渲染PDF文档的所有页面 * * 功能描述: * 1. 生成PDF所有页面的渲染数组,每个页面包含页码标识和实际页面内容 * 2. 处理页面缩放,通过CSS transform实现页面大小调整 * 3. 在每个页面上标记对应的评查点高亮区域 * 4. 处理评查点的激活状态,显示特殊的高亮效果 * * @returns {JSX.Element[] | null} 返回所有页面组件的数组,如果没有页数信息则返回null */ const renderAllPages = () => { // 如果还没有获取到PDF总页数,返回null if (!numPages) return null; // 用于存储所有页面组件的数组 const pages = []; // 遍历每一页,生成对应的页面组件 for (let i = 1; i <= numPages; i++) { // 查找该页面上的评查点,基于position.section匹配页面ID const pageReviewPoints = reviewPoints.filter(point => point.position && point.position.section === `page-${i}` ); // 计算当前缩放级别下的页面容器样式 const zoomFactor = zoomLevel / 100; const pageContainerStyle = { ...styles.pageContainer, marginBottom: `${calculatePageMargin(zoomFactor)}px`, // 动态计算页面间距 }; // 为每一页创建组件 pages.push(
{/* 页码标识,显示在页面上方 */}
第 {i} 页
{/* 页面容器,应用缩放变换,设置相对定位用于放置评查点高亮 */}
{/* 渲染PDF页面组件 */} {/* 渲染评查点高亮区域 */} {/* {highlightsVisible && pageReviewPoints.map(point => { // 判断当前评查点是否为激活状态(被选中) const isActive = point.id === activeReviewPointId; return ( // 评查点高亮区域
); })} */}
); } // 返回所有页面组件数组 return pages; }; // 渲染文档内容 const renderDocumentContent = () => { return (
{ console.error("PDF加载错误:", error); setLoadError("PDF文档加载失败:" + (error.message || "未知错误")); }} className="flex flex-col items-center w-full" error={
PDF文档加载失败,请检查链接或网络连接。
} noData={
无数据
} loading={
PDF加载中...
} > {renderAllPages()}
); }; return (
文件预览
{/* 页码跳转控件 */}
{numPages && / {numPages}}
{/* */} {"比例:"+zoomLevel+"%"}
{loadError ? (

{loadError}

) : ( renderDocumentContent() )}
); }