完善评查详情
This commit is contained in:
@@ -3,6 +3,11 @@
|
||||
* 显示文档内容和评查点高亮
|
||||
*/
|
||||
import { useState, useEffect, useRef } 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 './';
|
||||
@@ -11,6 +16,12 @@ import { type ReviewPoint } from './';
|
||||
interface FileContent {
|
||||
title: string;
|
||||
contractNumber: string;
|
||||
path: string;
|
||||
ocrResult?: {
|
||||
__meta?: {
|
||||
page_offset?: number;
|
||||
};
|
||||
}; // 添加ocrResult属性
|
||||
parties: {
|
||||
partyA: {
|
||||
name: string;
|
||||
@@ -35,12 +46,15 @@ interface FilePreviewProps {
|
||||
fileContent: FileContent;
|
||||
reviewPoints: ReviewPoint[];
|
||||
activeReviewPointId: string | null;
|
||||
targetPage?: number; // 新增目标页码参数
|
||||
}
|
||||
|
||||
export function FilePreview({ fileContent, reviewPoints, activeReviewPointId }: FilePreviewProps) {
|
||||
export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, targetPage }: FilePreviewProps) {
|
||||
const [zoomLevel, setZoomLevel] = useState(100);
|
||||
const [highlightsVisible, setHighlightsVisible] = useState(true);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const [numPages, setNumPages] = useState<number | null>(null);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
|
||||
// 放大文档
|
||||
const handleZoomIn = () => {
|
||||
@@ -77,6 +91,28 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId }:
|
||||
}
|
||||
}, [activeReviewPointId]);
|
||||
|
||||
// 处理页面跳转
|
||||
useEffect(() => {
|
||||
if (targetPage && numPages && targetPage <= numPages) {
|
||||
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}页`);
|
||||
pageElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
}
|
||||
}, [targetPage, numPages, fileContent]);
|
||||
|
||||
// 获取评查点对应的样式类
|
||||
const getHighlightClass = (status: string) => {
|
||||
switch (status) {
|
||||
@@ -91,69 +127,114 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId }:
|
||||
}
|
||||
};
|
||||
|
||||
// PDF文档加载成功回调函数
|
||||
function onDocumentLoadSuccess({ numPages }: { numPages: number }) {
|
||||
setNumPages(numPages);
|
||||
console.log("PDF加载成功,页数:", numPages);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染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}`
|
||||
);
|
||||
// console.log("pageReviewPoints-------",pageReviewPoints);
|
||||
|
||||
// 为每一页创建组件
|
||||
pages.push(
|
||||
<div key={i} id={`page-${i}`} className="mb-6">
|
||||
{/* 页码标识,显示在页面上方 */}
|
||||
<div className="text-center text-gray-500 text-sm mb-2">第 {i} 页</div>
|
||||
|
||||
{/* 页面容器,应用缩放变换,设置相对定位用于放置评查点高亮 */}
|
||||
<div style={{
|
||||
transform: `scale(${zoomLevel / 100})`, // 根据zoomLevel应用缩放
|
||||
transformOrigin: 'top center', // 缩放原点设置为顶部中心
|
||||
position: 'relative' // 相对定位,作为评查点高亮的定位参考
|
||||
}}>
|
||||
{/* 渲染PDF页面组件 */}
|
||||
<Page
|
||||
pageNumber={i} // 当前页码
|
||||
renderTextLayer={true} // 启用文本层,使文本可选择
|
||||
renderAnnotationLayer={true} // 启用注释层,显示PDF内置注释
|
||||
className="border border-gray-300 shadow-md" // 添加边框和阴影样式
|
||||
/>
|
||||
|
||||
{/* 渲染评查点高亮区域 */}
|
||||
{highlightsVisible && pageReviewPoints.map(point => {
|
||||
// 判断当前评查点是否为激活状态(被选中)
|
||||
const isActive = point.id === activeReviewPointId;
|
||||
|
||||
return (
|
||||
// 评查点高亮区域
|
||||
<div
|
||||
key={point.id}
|
||||
// 添加多个类名:基础高亮区域、PDF专用高亮、状态样式类、激活状态类
|
||||
className={`highlight-area pdf-highlight ${getHighlightClass(point.status)} ${isActive ? 'highlight-focus' : ''}`}
|
||||
// 设置唯一标识,用于滚动定位和DOM查询
|
||||
data-review-id={point.id}
|
||||
// 设置高亮区域的样式
|
||||
style={{
|
||||
position: 'absolute', // 绝对定位,相对于页面容器
|
||||
zIndex: isActive ? 20 : 10, // 激活状态时提高层级,确保在最上层显示
|
||||
boxShadow: isActive ? '0 0 0 2px yellow, 0 0 10px rgba(0,0,0,0.3)' : '', // 激活时添加特殊阴影效果
|
||||
// 根据评查点的位置信息计算高亮区域位置,实际项目中需根据真实位置坐标计算
|
||||
top: `${point.position?.index ? point.position.index * 20 : 20}px`,
|
||||
left: '50px',
|
||||
width: '300px',
|
||||
height: '30px',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 返回所有页面组件数组
|
||||
return pages;
|
||||
};
|
||||
|
||||
// 渲染文档内容
|
||||
const renderDocumentContent = () => {
|
||||
return (
|
||||
<div className="word-document" ref={contentRef} style={{transform: `scale(${zoomLevel/100})`, transformOrigin: 'center top'}}>
|
||||
<h1>{fileContent.title}</h1>
|
||||
<p style={{textAlign: 'right'}}>合同编号:{fileContent.contractNumber}</p>
|
||||
|
||||
<div style={{margin: '20px 0'}}>
|
||||
<p><strong>甲方(供方):</strong>{fileContent.parties.partyA.name}</p>
|
||||
<p>地址:{fileContent.parties.partyA.address}</p>
|
||||
<p>法定代表人:{fileContent.parties.partyA.representative}</p>
|
||||
<p>联系电话:{fileContent.parties.partyA.phone}</p>
|
||||
<p> </p>
|
||||
<p><strong>乙方(需方):</strong>{fileContent.parties.partyB.name}</p>
|
||||
<p>地址:{fileContent.parties.partyB.address}</p>
|
||||
<p>法定代表人:{fileContent.parties.partyB.representative}</p>
|
||||
<p>联系电话:{fileContent.parties.partyB.phone}</p>
|
||||
</div>
|
||||
|
||||
<p>根据《中华人民共和国合同法》及有关法律法规的规定,经双方协商一致,签订本合同,共同遵守。</p>
|
||||
|
||||
{fileContent.sections.map((section, sectionIndex) => (
|
||||
<div key={sectionIndex}>
|
||||
<h2>{section.title}</h2>
|
||||
{renderSectionContent(section.content, section.title, sectionIndex)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Document
|
||||
file={'http://172.18.0.100:9000/docauditai/'+fileContent.path}
|
||||
onLoadSuccess={onDocumentLoadSuccess}
|
||||
onLoadError={(error) => {
|
||||
console.error("PDF加载错误:", error);
|
||||
setLoadError("PDF文档加载失败:" + (error.message || "未知错误"));
|
||||
}}
|
||||
className="flex flex-col items-center"
|
||||
error={<div className="text-red-500">PDF文档加载失败,请检查链接或网络连接。</div>}
|
||||
noData={<div>无数据</div>}
|
||||
loading={<div className="text-center py-10">PDF加载中...</div>}
|
||||
>
|
||||
{renderAllPages()}
|
||||
</Document>
|
||||
);
|
||||
};
|
||||
|
||||
// 渲染章节内容,处理高亮
|
||||
const renderSectionContent = (content: string, sectionTitle: string, sectionIndex: number) => {
|
||||
const lines = content.split('\n');
|
||||
|
||||
return lines.map((line, lineIndex) => {
|
||||
// 查找该行对应的评查点
|
||||
const reviewPoint = reviewPoints.find(point =>
|
||||
point.position &&
|
||||
point.position.section === sectionTitle &&
|
||||
point.position.index === lineIndex
|
||||
);
|
||||
|
||||
if (reviewPoint && highlightsVisible) {
|
||||
// 如果有对应的评查点,添加高亮
|
||||
const isActive = reviewPoint.id === activeReviewPointId;
|
||||
return (
|
||||
<div
|
||||
key={`${sectionIndex}-${lineIndex}`}
|
||||
className={`highlight-area ${getHighlightClass(reviewPoint.status)} ${isActive ? 'highlight-focus' : ''}`}
|
||||
data-review-id={reviewPoint.id}
|
||||
style={isActive ? {zIndex: 20, boxShadow: '0 0 0 2px yellow, 0 0 10px rgba(0,0,0,0.3)'} : {}}
|
||||
>
|
||||
<p>{line}</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// 没有评查点,正常显示
|
||||
return <p key={`${sectionIndex}-${lineIndex}`}>{line}</p>;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="file-preview">
|
||||
<div className="file-preview-header py-2 px-4">
|
||||
@@ -182,8 +263,14 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId }:
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="file-preview-content">
|
||||
{renderDocumentContent()}
|
||||
<div className="file-preview-content" ref={contentRef}>
|
||||
{loadError ? (
|
||||
<div className="text-red-500 p-4">
|
||||
<p>{loadError}</p>
|
||||
</div>
|
||||
) : (
|
||||
renderDocumentContent()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user