Files
leaudit-platform-frontend/app/components/reviews/ReviewTabs.tsx
T

399 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 评查选项卡组件
* 提供三个选项卡:评查结果、AI智能分析、文件信息
*/
import { ReactNode, useState, useRef } from 'react';
import { useNavigate, useRevalidator } from 'react-router-dom';
import { loadingBarService } from '~/components/ui/LoadingBar';
import { Modal } from '~/components/ui/Modal';
import { UploadArea, type UploadAreaRef } from '~/components/ui/UploadArea';
import { Button } from '~/components/ui/Button';
import { toastService } from '~/components/ui/Toast';
import { DOCUMENT_URL } from "~/api/axios-client";
import { uploadFileToBinary, uploadDocumentToServer } from '~/api/files/files-upload';
interface ReviewTabsProps {
activeTab: string;
onTabChange: (tabKey: string) => void;
children: ReactNode;
fileInfo: {
id?: number;
previousRoute?: string;
path?: string;
auditStatus?: number;
type?: string;
};
onConfirmResults: () => void;
}
export function ReviewTabs({ activeTab, onTabChange, children, fileInfo, onConfirmResults }: ReviewTabsProps) {
const [isNavigating, setIsNavigating] = useState(false);
const [isReuploadModalOpen, setIsReuploadModalOpen] = useState(false);
const [selectedTemplateFiles, setSelectedTemplateFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false);
const uploadAreaRef = useRef<UploadAreaRef>(null);
const navigate = useNavigate();
const revalidator = useRevalidator();
// 返回上一级
const handleBack = () => {
// 防抖处理 - 如果已经在导航中,不重复触发
if (isNavigating) return;
// 设置导航状态为true
setIsNavigating(true);
loadingBarService.show();
// 根据来源页面返回
const previousRoute = fileInfo.previousRoute || 'documents';
const returnTo = previousRoute === 'documents'
? "/documents"
: previousRoute === 'filesUpload'
? "/files/upload"
: "/rules-files";
// 立即导航返回
navigate(returnTo);
// 触发上级页面数据重新加载
revalidator.revalidate();
};
// 下载原文件
const handleDownloadFile = async () => {
try {
const downloadUrl = `${DOCUMENT_URL}${fileInfo.path}`;
// 使用fetch获取文件内容
const response = await fetch(downloadUrl);
if (!response.ok) {
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
}
// 将响应转换为Blob
const blob = await response.blob();
// 创建Blob URL
const blobUrl = URL.createObjectURL(blob);
// 创建一个隐藏的a标签并点击它
const a = document.createElement('a');
a.style.display = 'none';
a.href = blobUrl;
// 从路径中获取文件名
const fileName = fileInfo.path?.split('/').pop() || 'document';
a.download = decodeURIComponent(fileName);
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
} catch (error) {
console.error('下载文件失败:', error);
alert(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
};
// 打开重新上传模板模态框
const handleOpenReuploadModal = () => {
setIsReuploadModalOpen(true);
setSelectedTemplateFiles([]);
};
// 关闭重新上传模板模态框
const handleCloseReuploadModal = () => {
setIsReuploadModalOpen(false);
setSelectedTemplateFiles([]);
// 重置文件输入
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
};
// 处理模板文件选择
const handleTemplateFilesSelected = (files: FileList) => {
try {
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`无效的文件类型: ${file.name}, 类型: ${file.type}`);
}
});
if (hasInvalidFiles) {
toastService.error('只能上传PDF格式的文件');
}
if (validFiles.length > 0) {
setSelectedTemplateFiles(validFiles);
}
}
} catch (error) {
console.error('处理模板文件选择时发生错误:', error);
toastService.error('文件选择失败,请重试');
}
};
// 确认上传模板文件
const handleConfirmUpload = async () => {
if (selectedTemplateFiles.length === 0) {
toastService.error('请先选择要上传的模板文件');
return;
}
try {
setIsUploading(true);
// 这里可以调用上传API
let binaryData: ArrayBuffer;
try {
binaryData = await uploadFileToBinary(selectedTemplateFiles[0]);
} catch (error) {
console.error('上传文件失败:', error);
throw new Error(`文件 ${selectedTemplateFiles[0].name} 转换失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
// const uploadInfo = {
// binaryData,
// fileName: selectedTemplateFiles[0].name,
// fileType: 'pdf',
// documentType: '1',
// priority: 'normal',
// documentNumber: null,
// remark: null,
// isTestDocument: false,
// documentId: fileInfo
// };
// console.log('uploadInfo',uploadInfo);
const uploadResult = await uploadDocumentToServer(
binaryData,
selectedTemplateFiles[0].name,
'pdf', //file_type 文件类型:pdf
'1', //fileType(type_id) 合同id1
'normal', //priority 优先级:normal
null, //document_number 文档编号
null, //remark 备注
false, //is_test_document 是否为测试文档:false
fileInfo.id, //document_id 主文档id
true //is_reupload 是否为重新上传:true
);
// console.log('重新上传合同模板',uploadResult);
if (uploadResult.error) {
throw new Error(uploadResult.error);
}
toastService.success('模板文件上传成功,结构比对数据将会发生更新,即将返回上一页...');
await new Promise(resolve => setTimeout(resolve, 2000));
handleCloseReuploadModal();
handleBack();
} catch (error) {
console.error('上传模板文件失败:', error);
toastService.error(`上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setIsUploading(false);
}
};
// 格式化文件大小
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
return (
<div className="tab-container w-full flex-1">
<div className="tab-nav w-full flex justify-between">
{/* 评查结果、AI智能分析、文件信息 */}
<div className="flex">
<button
className={`tab-nav-item ${activeTab === 'preview' ? 'active' : ''}`}
onClick={() => onTabChange('preview')}
type="button"
aria-pressed={activeTab === 'preview'}
>
<i className="ri-file-text-line"></i>
</button>
{/* <button
className={`tab-nav-item ${activeTab === 'analysis' ? 'active' : ''}`}
onClick={() => onTabChange('analysis')}
type="button"
aria-pressed={activeTab === 'analysis'}
>
<i className="ri-lightbulb-line"></i> AI智能分析
</button> */}
{fileInfo.type === '1' && (
<button
className={`tab-nav-item ${activeTab === 'filecompare' ? 'active' : ''}`}
onClick={() => onTabChange('filecompare')}
type="button"
aria-pressed={activeTab === 'filecompare'}
>
<i className="ri-flip-horizontal-line"></i>
</button>
)}
<button
className={`tab-nav-item ${activeTab === 'fileinfo' ? 'active' : ''}`}
onClick={() => onTabChange('fileinfo')}
type="button"
aria-pressed={activeTab === 'fileinfo'}
>
<i className="ri-information-line"></i>
</button>
</div>
{/* 操作按钮 */}
<div className="flex space-x-3">
{/* 重新上传 */}
{activeTab === 'filecompare' && (
<button
className="ant-btn ant-btn-default flex items-center my-2 mr-4"
onClick={handleOpenReuploadModal}
>
<i className="ri-refresh-line mr-1"></i>
</button>
)}
{/* 返回上一级 */}
<button
className="ant-btn ant-btn-default flex items-center my-2"
onClick={() => handleBack()}
disabled={isNavigating}
>
<i className="ri-arrow-left-line mr-1"></i> {isNavigating ? '返回中...' : '返回'}
</button>
<button
className="ant-btn ant-btn-default flex items-center my-2"
onClick={handleDownloadFile}
>
<i className="ri-file-download-line mr-1"></i>
</button>
{/* <button
className="ant-btn ant-btn-default flex items-center"
onClick={handleExportReport}
>
<i className="ri-file-copy-line mr-1"></i> 导出评查报告
</button> */}
<button
className={`ant-btn ant-btn-primary my-2 flex items-center ${fileInfo.auditStatus === 1 ? 'hidden' : ''}`}
onClick={onConfirmResults}
>
<i className="ri-check-double-line mr-1"></i>
</button>
</div>
</div>
<div className="tab-content w-full">
{children}
</div>
{/* 重新上传模板模态框 */}
<Modal
isOpen={isReuploadModalOpen}
onClose={handleCloseReuploadModal}
title="重新上传模板"
size="medium"
footer={
<div className="flex justify-end space-x-3">
<Button
type="default"
onClick={handleCloseReuploadModal}
disabled={isUploading}
>
</Button>
<Button
type="primary"
onClick={handleConfirmUpload}
disabled={selectedTemplateFiles.length === 0 || isUploading}
icon={isUploading ? 'ri-loader-4-line animate-spin' : undefined}
>
{isUploading ? '上传中...' : '确定上传'}
</Button>
</div>
}
>
<div className="space-y-4">
<div className="text-sm text-gray-600 mb-4">
<p></p>
<p className="mt-2 text-orange-600">
<i className="ri-information-line mr-1"></i>
PDF格式的文件
</p>
</div>
<UploadArea
ref={uploadAreaRef}
onFilesSelected={handleTemplateFilesSelected}
accept=".pdf,application/pdf"
multiple={false}
icon="ri-file-pdf-line"
buttonText="选择模板文件"
mainText="点击或拖拽PDF文件到此区域"
tipText={
<span className="text-xs text-gray-500">
PDF
</span>
}
disabled={isUploading}
/>
{/* 已选择的文件列表 */}
{selectedTemplateFiles.length > 0 && (
<div className="mt-4">
<h4 className="text-sm font-medium text-gray-900 mb-2"></h4>
<div className="space-y-2">
{selectedTemplateFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg border"
>
<div className="flex items-center">
<i className="ri-file-pdf-line text-red-500 mr-2"></i>
<div>
<div className="text-sm font-medium text-gray-900">
{file.name}
</div>
<div className="text-xs text-gray-500">
{formatFileSize(file.size)}
</div>
</div>
</div>
<button
className="text-gray-400 hover:text-red-500 transition-colors"
onClick={() => {
setSelectedTemplateFiles(prev =>
prev.filter((_, i) => i !== index)
);
if (uploadAreaRef.current) {
uploadAreaRef.current.resetFileInput();
}
}}
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
</div>
</div>
)}
</div>
</Modal>
</div>
);
}