修复下载,更改logo,优化评查详情内容的显示,修改sidebar的首页,修复文件上传合同的异步上传时序问题,首页最近文件的自动更新文件状态

This commit is contained in:
2025-05-24 23:25:04 +08:00
parent ef6994d072
commit ed3ff4c3b3
10 changed files with 801 additions and 237 deletions
+57 -35
View File
@@ -146,12 +146,15 @@ export async function uploadDocumentToServer(
isTestDocument: boolean = false isTestDocument: boolean = false
): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { ): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> {
try { try {
console.log('【调试】开始上传文档:', { fileName, fileSize: binaryData.byteLength });
// 创建FormData对象 // 创建FormData对象
const formData = new FormData(); const formData = new FormData();
// 将二进制数据转换为Blob并添加到FormData // 将二进制数据转换为Blob并添加到FormData
const blob = new Blob([binaryData], { type: fileType }); const blob = new Blob([binaryData], { type: fileType });
formData.append('file', blob, fileName); formData.append('file', blob, fileName);
console.log('【调试】Blob已创建,文件大小:', blob.size);
// 将信息添加到一个JSON对象中 // 将信息添加到一个JSON对象中
const uploadInfo = { const uploadInfo = {
@@ -164,48 +167,67 @@ export async function uploadDocumentToServer(
// 添加JSON字符串到FormData // 添加JSON字符串到FormData
formData.append('upload_info', JSON.stringify(uploadInfo)); formData.append('upload_info', JSON.stringify(uploadInfo));
console.log('【调试】FormData准备完成:', JSON.stringify(uploadInfo));
console.log('上传信息:', { console.log('【调试】准备发送请求到服务器:', 'http://172.16.0.58:8008/admin/documents/upload');
fileName,
fileType,
typeId: Number(typeId),
priority,
documentNumber: documentNumber || null,
remark: remark || null,
isTestDocument
});
// 发送请求 // 发送请求
// const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, { // const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, {
const response = await fetch('http://172.16.0.58:8008/admin/documents/upload', { try {
// const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', { console.log('【调试】开始fetch请求...');
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', { const response = await fetch('http://172.16.0.58:8008/admin/documents/upload', {
method: 'POST', // const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
headers: { // const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
'X-File-Name': encodeURIComponent(fileName) method: 'POST',
}, headers: {
body: formData 'X-File-Name': encodeURIComponent(fileName)
}); },
body: formData
if (!response.ok) { });
const errorText = await response.text();
console.error(`上传失败 (${response.status}): ${errorText}`); console.log('【调试】收到服务器响应:', { status: response.status, statusText: response.statusText });
return {
error: `上传失败: ${response.status} ${response.statusText}`, if (!response.ok) {
status: response.status const errorText = await response.text();
console.error(`【调试】上传失败 (${response.status}): ${errorText}`);
return {
error: `上传失败: ${response.status} ${response.statusText} - ${errorText}`,
status: response.status
};
}
console.log('【调试】开始解析JSON响应');
let responseData;
try {
responseData = await response.json();
console.log('【调试】JSON响应解析成功:', responseData);
} catch (jsonError) {
console.error('【调试】JSON解析失败:', jsonError);
return {
error: `解析响应JSON失败: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`,
status: 500
};
}
const extractedData = extractApiData<FileUploadResponse>(responseData);
console.log('【调试】提取的数据:', extractedData);
if (!extractedData) {
console.error('【调试】无法提取数据');
return { error: '处理上传响应失败', status: 500 };
}
console.log('【调试】上传成功,返回数据');
return { data: extractedData };
} catch (fetchError) {
console.error('【调试】fetch请求失败:', fetchError);
return {
error: `fetch请求错误: ${fetchError instanceof Error ? fetchError.message : '未知错误'}`,
status: 500
}; };
} }
const responseData = await response.json();
const extractedData = extractApiData<FileUploadResponse>(responseData);
if (!extractedData) {
return { error: '处理上传响应失败', status: 500 };
}
return { data: extractedData };
} catch (error) { } catch (error) {
console.error('上传错误:', error); console.error('【调试】上传过程中发生错误:', error);
return { return {
error: error instanceof Error ? error.message : '上传失败', error: error instanceof Error ? error.message : '上传失败',
status: 500 status: 500
+4 -4
View File
@@ -21,7 +21,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
const menuItems: MenuItem[] = [ const menuItems: MenuItem[] = [
{ {
id: 'home', id: 'home',
title: '首页', title: '系统概览',
path: '/', path: '/',
icon: 'ri-home-line' icon: 'ri-home-line'
}, },
@@ -164,7 +164,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}> <div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
<div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center"> <div className="py-6 px-4 border-b border-gray-100 flex justify-between items-center">
<div className="flex items-center"> <div className="flex items-center">
<i className="ri-file-search-line text-primary text-xl mr-2"></i> <img src="/logo.svg" alt="智慧法务" className="w-12 h-12 mr-2" />
{!collapsed && <h2 className="text-lg font-medium"></h2>} {!collapsed && <h2 className="text-lg font-medium"></h2>}
</div> </div>
<button <button
@@ -177,7 +177,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
</button> </button>
</div> </div>
{!collapsed && ( {/* {!collapsed && (
<div className="user-profile p-4 border-b border-gray-100 flex items-center"> <div className="user-profile p-4 border-b border-gray-100 flex items-center">
<div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center"> <div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center">
<span>管</span> <span>管</span>
@@ -187,7 +187,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
<p className="text-xs text-gray-500">超级管理员</p> <p className="text-xs text-gray-500">超级管理员</p>
</div> </div>
</div> </div>
)} )} */}
<div className="py-4 px-[10px]"> <div className="py-4 px-[10px]">
{menuItems.map((item) => ( {menuItems.map((item) => (
+2 -1
View File
@@ -30,7 +30,8 @@ export function FileInfo({ fileInfo }: FileInfoProps) {
{fileInfo.fileName} {fileInfo.fileName}
</h2> </h2>
<span className="text-xs text-gray-500"> <span className="text-xs text-gray-500">
{fileInfo.contractNumber} {/* 合同编号:{fileInfo.contractNumber} */}
{fileInfo.contractNumber}
</span> </span>
{fileInfo.fileSize && ( {fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2"> <span className="text-xs text-gray-500 ml-2">
+11 -11
View File
@@ -180,22 +180,22 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转 // 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转
if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) { if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
prevTargetPageRef.current = targetPage; prevTargetPageRef.current = targetPage;
const newTargetPage = targetPage; let newTargetPage = targetPage;
// let newTargetPage = targetPage; // let newTargetPage = targetPage;
// console.log("targetPage:", targetPage); // console.log("targetPage:", targetPage);
// console.log("fileContent:", fileContent); // console.log("fileContent:", fileContent);
// 页码偏移量 // 页码偏移量
// try { try {
// // 安全地访问ocrResult // 安全地访问ocrResult
// if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) { if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) {
// // 可以根据需要使用page_offset调整目标页面 // 可以根据需要使用page_offset调整目标页面
// newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset; newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset;
// } }
// } catch (error) { } catch (error) {
// console.error("访问ocrResult时出错:", error); console.error("访问ocrResult时出错:", error);
// toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误')); toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误'));
// } }
const pageElement = document.getElementById(`page-${newTargetPage}`); const pageElement = document.getElementById(`page-${newTargetPage}`);
if (pageElement) { if (pageElement) {
+25 -8
View File
@@ -450,7 +450,7 @@ export function ReviewPointsList({
{Object.entries(reviewPoint.content).map(([key, value], index) => ( {Object.entries(reviewPoint.content).map(([key, value], index) => (
<div <div
key={index} key={index}
className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1" className="mb-2 pb-2 border-b border-gray-100 last:border-b-0 last:mb-0 last:pb-0 cursor-pointer hover:bg-gray-100 transition-colors duration-200 rounded p-1 group"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
console.log(`单独点击${key}----`, reviewPoint); console.log(`单独点击${key}----`, reviewPoint);
@@ -488,8 +488,17 @@ export function ReviewPointsList({
role="button" role="button"
tabIndex={0} tabIndex={0}
aria-label={`查看${key}内容详情`} aria-label={`查看${key}内容详情`}
onMouseLeave={(e) => {
// 获取容器内的滚动区域元素
const scrollContainer = e.currentTarget.querySelector('.text-container');
if (scrollContainer) {
// 在文本缩回之前重置滚动位置
scrollContainer.scrollTop = 0;
}
}}
> >
<div className="flex justify-between items-center mb-1"> {/* <div className="flex justify-between items-center mb-1"> */}
<div className="flex items-center mb-1">
<span className="text-xs pr-5"> <span className="text-xs pr-5">
{key} {key}
</span> </span>
@@ -498,11 +507,18 @@ export function ReviewPointsList({
{value.value?.toString().trim() ? '' : '缺失'} {value.value?.toString().trim() ? '' : '缺失'}
</span> </span>
</div> </div>
<p className="text-xs text-left select-text"> <div className="relative text-container max-h-96 group-hover:overflow-auto overflow-hidden">
{(value.value?.toString().trim() === '') <p
? <span className="invisible"></span> className="text-xs text-left select-text block overflow-hidden !line-clamp-2
: value.value?.toString() || ''} group-hover:!line-clamp-none group-hover:bg-white group-hover:shadow-md group-hover:z-10 group-hover:relative px-1 rounded transition-all duration-300 ease-in-out cursor-text"
</p> // title={value.value?.toString() || ''}
// style={{ userSelect: 'all' }}
>
{(value.value?.toString().trim() === '')
? ""
: value.value?.toString() || ''}
</p>
</div>
</div> </div>
))} ))}
</> </>
@@ -897,7 +913,8 @@ export function ReviewPointsList({
<div className="review-point-location max-w-[40%] flex items-center"> <div className="review-point-location max-w-[40%] flex items-center">
<i className="ri-file-list-line mr-1 flex-shrink-0"></i> <i className="ri-file-list-line mr-1 flex-shrink-0"></i>
<span <span
className="truncate block whitespace-nowrap overflow-hidden hover:overflow-visible hover:text-clip hover:bg-white hover:shadow-md hover:z-10 hover:text-wrap px-1 rounded" className="truncate block whitespace-nowrap overflow-hidden
hover:overflow-visible hover:text-clip hover:bg-white hover:shadow-md hover:z-10 hover:text-wrap px-1 rounded"
title={reviewPoint.groupName} title={reviewPoint.groupName}
style={{ cursor: 'text', userSelect: 'all' }} style={{ cursor: 'text', userSelect: 'all' }}
> >
+60 -3
View File
@@ -76,7 +76,9 @@ export async function loader() {
} }
export default function Index() { export default function Index() {
const { homeData, recentFiles } = useLoaderData<typeof loader>(); const { homeData, recentFiles: initialRecentFiles } = useLoaderData<typeof loader>();
// 使用useState存储最近文档数据,初始值为loader加载的数据
const [recentFiles, setRecentFiles] = useState<DocumentUI[]>(initialRecentFiles || []);
const [currentDateTime, setCurrentDateTime] = useState({ const [currentDateTime, setCurrentDateTime] = useState({
date: '', date: '',
time: '' time: ''
@@ -102,16 +104,71 @@ export default function Index() {
// 清理函数,组件卸载时清除计时器 // 清理函数,组件卸载时清除计时器
return () => clearInterval(timerID); return () => clearInterval(timerID);
}, []); }, []);
// 修改useEffect定时器,每10秒自动获取最近文档数据
// 按照定时器更新最近文档
useEffect(() => {
// 定义一个函数用于获取最新的文档数据
const fetchLatestDocuments = async () => {
try {
const documentSearchParams = {
page: 1,
pageSize: 10,
order: 'updated_at.desc'
};
console.log('定时获取最新文档数据...');
const responseDocuments = await getDocuments(documentSearchParams);
if (responseDocuments.error) {
console.error('获取最近文档数据失败', responseDocuments.error);
return;
}
// 获取新的文档数据
const newRecentFiles = responseDocuments.data?.documents || [];
// 检查数据是否有变化
if (JSON.stringify(newRecentFiles) !== JSON.stringify(recentFiles)) {
console.log('文档数据已更新,直接更新状态');
// 直接更新状态,不需要刷新页面
setRecentFiles(newRecentFiles);
}
} catch (error) {
console.error('自动获取文档数据失败:', error);
}
};
// 设置10秒的定时器
const timerID = setInterval(fetchLatestDocuments, 10000);
// 组件卸载时清除定时器
return () => {
console.log('清除文档数据自动更新定时器');
clearInterval(timerID);
};
}, []); // 不再依赖recentFiles,避免循环依赖
return ( return (
<div className="dashboard-container"> <div className="dashboard-container">
{/* 页面头部 */} {/* 页面头部 */}
<div className="flex justify-between items-center mb-4"> <div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-medium"></h2> <h2 className="text-xl font-medium"></h2>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500 flex flex-row items-center justify-between">
<div className="flex items-center">
<span id="current-date">{currentDateTime.date}</span> <span id="current-date">{currentDateTime.date}</span>
<span className="mx-2">|</span> <span className="mx-2">|</span>
<span id="current-time">{currentDateTime.time}</span> <span id="current-time">{currentDateTime.time}</span>
</div>
<div className="user-profile p-4 border-b border-gray-100 flex items-center">
<div className="avatar w-10 h-10 rounded-full bg-primary text-white flex items-center justify-center">
<span></span>
</div>
<div className="ml-3">
<p className="text-sm font-medium"></p>
<p className="text-xs text-gray-500"></p>
</div>
</div>
</div> </div>
</div> </div>
+58 -17
View File
@@ -181,7 +181,7 @@ export async function action({ request }: ActionFunctionArgs) {
// 文档编辑页面组件 // 文档编辑页面组件
export default function DocumentEdit() { export default function DocumentEdit() {
const { document, documentTypes } = useLoaderData<typeof loader>(); const { document: documentData, documentTypes } = useLoaderData<typeof loader>();
const actionData = useActionData<ActionData>(); const actionData = useActionData<ActionData>();
const navigate = useNavigate(); const navigate = useNavigate();
const [numPages, setNumPages] = useState(0); const [numPages, setNumPages] = useState(0);
@@ -190,10 +190,10 @@ export default function DocumentEdit() {
// 表单状态管理 - 使用受控组件 // 表单状态管理 - 使用受控组件
const [formValues, setFormValues] = useState({ const [formValues, setFormValues] = useState({
documentNumber: document.documentNumber || "", documentNumber: documentData.documentNumber || "",
auditStatus: document.auditStatus, auditStatus: documentData.auditStatus,
isTest: document.isTest || false, isTest: documentData.isTest || false,
remark: document.remark || "" remark: documentData.remark || ""
}); });
// 表单验证错误状态 // 表单验证错误状态
@@ -303,7 +303,7 @@ export default function DocumentEdit() {
return ( return (
<div className="preview-content relative overflow-y-auto max-h-[1000px]"> <div className="preview-content relative overflow-y-auto max-h-[1000px]">
<Document <Document
file={DOCUMENT_URL + document.path} file={DOCUMENT_URL + documentData.path}
onLoadSuccess={onDocumentLoadSuccess} onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(error) => { onLoadError={(error) => {
console.error("PDF加载错误:", error); console.error("PDF加载错误:", error);
@@ -393,9 +393,49 @@ export default function DocumentEdit() {
); );
}; };
// 下载文档
const downloadDocument = async () => {
try {
const downloadUrl = `${DOCUMENT_URL}${documentData.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 = documentData.path.split('/').pop() || documentData.name;
a.download = decodeURIComponent(fileName);
document.body.appendChild(a);
a.click();
// 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
toastService.success('文件下载成功');
} catch (error) {
console.error('下载文件失败:', error);
toastService.error(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
};
// 在新窗口打开文档预览 // 在新窗口打开文档预览
const openPreview = () => { const openPreview = () => {
const previewUrl = `${DOCUMENT_URL}${document.path}`; const previewUrl = `${DOCUMENT_URL}${documentData.path}`;
window.open(previewUrl, '_blank'); window.open(previewUrl, '_blank');
}; };
@@ -428,7 +468,7 @@ export default function DocumentEdit() {
<div className="document-info"> <div className="document-info">
<div className="document-icon"> <div className="document-icon">
<FileTag <FileTag
extension={document.fileType} extension={documentData.fileType}
showIcon={true} showIcon={true}
showText={false} showText={false}
showBackground={false} showBackground={false}
@@ -436,22 +476,22 @@ export default function DocumentEdit() {
/> />
</div> </div>
<div className="document-details"> <div className="document-details">
<div className="document-name">{document.name}</div> <div className="document-name">{documentData.name}</div>
<div className="document-meta"> <div className="document-meta">
<div className="meta-item"> <div className="meta-item">
<i className="ri-file-list-line"></i> <i className="ri-file-list-line"></i>
<span>{getDocumentTypeName(document.type)}</span> <span>{getDocumentTypeName(documentData.type)}</span>
</div> </div>
<div className="meta-item"> <div className="meta-item">
<i className="ri-time-line"></i> <i className="ri-time-line"></i>
<span>{document.uploadTime}</span> <span>{documentData.uploadTime}</span>
</div> </div>
<div className="meta-item"> <div className="meta-item">
<i className="ri-hard-drive-line"></i> <i className="ri-hard-drive-line"></i>
<span>{formatFileSize(document.size)}</span> <span>{formatFileSize(documentData.size)}</span>
</div> </div>
<div className="meta-item"> <div className="meta-item">
{renderStatusBadge(document.auditStatus)} {renderStatusBadge(documentData.auditStatus)}
</div> </div>
</div> </div>
</div> </div>
@@ -477,7 +517,7 @@ export default function DocumentEdit() {
id="type-id" id="type-id"
name="type_id" name="type_id"
className="form-select" className="form-select"
value={document.type} value={documentData.type}
disabled={true} disabled={true}
required required
> >
@@ -566,8 +606,8 @@ export default function DocumentEdit() {
<div className="document-preview"> <div className="document-preview">
<div className="preview-toolbar"> <div className="preview-toolbar">
<div className="flex items-center"> <div className="flex items-center">
<i className={`ri-file-${document.fileType}-line text-${document.fileType === 'pdf' ? 'red' : 'blue'}-500 mr-1`}></i> <i className={`ri-file-${documentData.fileType}-line text-${documentData.fileType === 'pdf' ? 'red' : 'blue'}-500 mr-1`}></i>
<span>{document.name}</span> <span>{documentData.name}</span>
</div> </div>
<div> <div>
<Button <Button
@@ -575,6 +615,7 @@ export default function DocumentEdit() {
size="small" size="small"
icon="ri-download-line" icon="ri-download-line"
className="mr-2" className="mr-2"
onClick={downloadDocument}
> >
</Button> </Button>
@@ -607,7 +648,7 @@ export default function DocumentEdit() {
time: "2023-10-15 15:30", time: "2023-10-15 15:30",
user: "系统", user: "系统",
action: "创建了此文档", action: "创建了此文档",
details: `首次上传文档,文档类型:${getDocumentTypeName(document.type)},状态:待审核` details: `首次上传文档,文档类型:${getDocumentTypeName(documentData.type)},状态:待审核`
}, },
{ {
time: "2023-10-15 16:45", time: "2023-10-15 16:45",
+581 -157
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react"; import { useState, useEffect, useRef } from "react";
import { MetaFunction, ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; import { MetaFunction, ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useLoaderData, useNavigate } from "@remix-run/react"; import { Form, useActionData, useLoaderData, useNavigate, useBlocker } from "@remix-run/react";
import { Card } from "~/components/ui/Card"; import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button"; import { Button } from "~/components/ui/Button";
import { Table } from "~/components/ui/Table"; import { Table } from "~/components/ui/Table";
@@ -145,11 +145,13 @@ async function uploadFileToServer(
} }
// 确保返回有效的FileUploadResponse对象 // 确保返回有效的FileUploadResponse对象
// console.log('上传成功:', response.data);
if (response.data) { if (response.data) {
return response.data; return response.data;
} }
// 如果没有数据,则返回错误 // 如果没有数据,则返回错误
// console.log('上传失败:', response.error);
return { return {
success: false, success: false,
error: '上传失败,未获取到响应数据' error: '上传失败,未获取到响应数据'
@@ -349,10 +351,16 @@ export default function FilesUpload() {
// 状态检查定时器引用 // 状态检查定时器引用
const statusCheckIntervalRef = useRef<NodeJS.Timeout | null>(null); const statusCheckIntervalRef = useRef<NodeJS.Timeout | null>(null);
// 添加组件挂载状态引用
const isMountedRef = useRef<boolean>(true);
// useEffect 处理上传队列状态检查定时器 - 只在组件卸载时清除 // useEffect 处理上传队列状态检查定时器 - 只在组件卸载时清除
useEffect(() => { useEffect(() => {
console.log('设置上传队列状态检查定时器'); console.log('设置上传队列状态检查定时器');
// 标记组件已挂载
isMountedRef.current = true;
// 设置定时器检查队列中文件的状态,初始先加载一次查询 // 设置定时器检查队列中文件的状态,初始先加载一次查询
checkQueueStatus(); checkQueueStatus();
statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000); statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000);
@@ -360,6 +368,8 @@ export default function FilesUpload() {
// 只在组件卸载时清除 // 只在组件卸载时清除
return () => { return () => {
console.log('组件卸载,清除上传队列状态检查定时器'); console.log('组件卸载,清除上传队列状态检查定时器');
// 标记组件已卸载
isMountedRef.current = false;
if (statusCheckIntervalRef.current) { if (statusCheckIntervalRef.current) {
clearInterval(statusCheckIntervalRef.current); clearInterval(statusCheckIntervalRef.current);
statusCheckIntervalRef.current = null; statusCheckIntervalRef.current = null;
@@ -446,24 +456,37 @@ export default function FilesUpload() {
const value = e.target.value; const value = e.target.value;
// 确保只有选择了有效的文件类型才进行设置 // 确保只有选择了有效的文件类型才进行设置
if (value) { if (value) {
console.log('【调试-handleFileTypeChange】文件类型变更为:', value);
setFileType(value as FileType); setFileType(value as FileType);
// 立即清除错误状态 // 立即清除错误状态
setFileTypeError(null); setFileTypeError(null);
// 检查是否选择了合同类型 // 检查是否选择了合同类型
// const selectedType = documentTypes.find(t => t.id.toString() === value); const selectedType = documentTypes.find(t => t.id.toString() === value);
// const isContract = !!(selectedType && selectedType.name.includes('合同')); const isContract = !!(selectedType && selectedType.name.includes('合同'));
// setIsContractType(isContract); console.log('【调试-handleFileTypeChange】文件类型检查:', {
selectedType,
isContract,
typeName: selectedType?.name,
currentFiles: currentFiles.length
});
setIsContractType(isContract);
// 重置文件状态 // 重置文件状态
setContractMainFiles([]); setContractMainFiles([]);
setContractAttachmentFiles([]); setContractAttachmentFiles([]);
setCurrentFiles([]); setCurrentFiles([]);
// 如果已经有选中的文件,且选择了文件类型,则开始上传 // 如果已经有选中的文件,且选择了文件类型,且不是合同类型,则开始上传
// if (currentFiles.length > 0 && !isContract) { if (currentFiles.length > 0 && !isContract) {
// startUpload(currentFiles); console.log('【调试-handleFileTypeChange】自动开始上传非合同类型文件');
// } startUpload(currentFiles);
} else if (currentFiles.length > 0 && isContract) {
console.log('【调试-handleFileTypeChange】合同类型需要手动点击开始上传按钮');
// 合同类型不自动上传,需要用户先上传主文件和附件,然后点击开始上传按钮
setCurrentFiles([]);
}
} else { } else {
setFileType(""); setFileType("");
@@ -475,91 +498,248 @@ export default function FilesUpload() {
// 处理合同主文件选择 // 处理合同主文件选择
const handleContractMainFilesSelected = (files: FileList) => { const handleContractMainFilesSelected = (files: FileList) => {
if (files.length > 0) { try {
// 验证文件类型,只允许PDF文件 console.log('【调试-handleContractMainFilesSelected】开始处理合同主文件选择, 文件数量:', files.length);
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => { // 检查组件是否已卸载
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { if (!isMountedRef.current) {
validFiles.push(file); console.error('【调试-handleContractMainFilesSelected】组件已卸载,取消处理');
} else { return;
hasInvalidFiles = true; }
}
});
if (hasInvalidFiles) { if (files.length > 0) {
// 显示错误提示 // 验证文件类型,只允许PDF文件
messageService.error('只能上传PDF格式的文件', { const validFiles: File[] = [];
title: '文件类型错误', let hasInvalidFiles = false;
confirmText: '确定',
cancelText: '', Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`【调试-handleContractMainFilesSelected】无效的文件类型: ${file.name}, 类型: ${file.type}`);
}
}); });
if (hasInvalidFiles) {
// 显示错误提示
console.error('【调试-handleContractMainFilesSelected】存在无效的文件类型');
messageService.error('只能上传PDF格式的文件', {
title: '文件类型错误',
confirmText: '确定',
cancelText: '',
});
}
if (validFiles.length > 0 && isMountedRef.current) {
console.log('【调试-handleContractMainFilesSelected】有效文件数量:', validFiles.length);
console.log('【调试-handleContractMainFilesSelected】有效文件:', validFiles.map(f => ({ name: f.name, size: f.size, type: f.type })));
setContractMainFiles(validFiles);
} else {
console.error('【调试-handleContractMainFilesSelected】没有有效的PDF文件或组件已卸载');
}
} else {
console.log('【调试-handleContractMainFilesSelected】未选择任何文件');
} }
} catch (error) {
if (validFiles.length > 0) { console.error('【调试-handleContractMainFilesSelected】处理合同主文件选择时发生错误:', error);
setContractMainFiles(validFiles);
checkAndPrepareUpload(validFiles, contractAttachmentFiles);
}
} }
}; };
// 处理合同附件选择 // 处理合同附件选择
const handleContractAttachmentFilesSelected = (files: FileList) => { const handleContractAttachmentFilesSelected = (files: FileList) => {
if (files.length > 0) { try {
// 验证文件类型,只允许PDF文件 console.log('【调试-handleContractAttachmentFilesSelected】开始处理合同附件选择, 文件数量:', files.length);
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => { // 检查组件是否已卸载
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) { if (!isMountedRef.current) {
validFiles.push(file); console.error('【调试-handleContractAttachmentFilesSelected】组件已卸载,取消处理');
} else { return;
hasInvalidFiles = true; }
}
});
if (hasInvalidFiles) { if (files.length > 0) {
// 显示错误提示 // 验证文件类型,只允许PDF文件
messageService.error('只能上传PDF格式的文件', { const validFiles: File[] = [];
title: '文件类型错误', let hasInvalidFiles = false;
confirmText: '确定',
cancelText: '', Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`【调试-handleContractAttachmentFilesSelected】无效的文件类型: ${file.name}, 类型: ${file.type}`);
}
}); });
if (hasInvalidFiles) {
// 显示错误提示
console.error('【调试-handleContractAttachmentFilesSelected】存在无效的文件类型');
messageService.error('只能上传PDF格式的文件', {
title: '文件类型错误',
confirmText: '确定',
cancelText: '',
});
}
if (validFiles.length > 0 && isMountedRef.current) {
console.log('【调试-handleContractAttachmentFilesSelected】有效文件数量:', validFiles.length);
console.log('【调试-handleContractAttachmentFilesSelected】有效文件:', validFiles.map(f => ({ name: f.name, size: f.size, type: f.type })));
setContractAttachmentFiles(validFiles);
} else {
console.error('【调试-handleContractAttachmentFilesSelected】没有有效的PDF文件或组件已卸载');
}
} else {
console.log('【调试-handleContractAttachmentFilesSelected】未选择任何文件');
} }
} catch (error) {
if (validFiles.length > 0) { console.error('【调试-handleContractAttachmentFilesSelected】处理合同附件选择时发生错误:', error);
setContractAttachmentFiles(validFiles);
checkAndPrepareUpload(contractMainFiles, validFiles);
}
} }
}; };
// 检查并准备上传 // 检查并准备上传
const checkAndPrepareUpload = (mainFiles: File[], attachmentFiles: File[]) => { const checkAndPrepareUpload = (mainFiles: File[], attachmentFiles: File[]) => {
// 当两个区域都有文件时才准备上传 try {
if (mainFiles.length > 0 && attachmentFiles.length > 0) { console.log('【调试-checkAndPrepareUpload】开始检查并准备上传文件', {
// 合并所有文件 mainFilesCount: mainFiles.length,
const allFiles = [...mainFiles, ...attachmentFiles]; attachmentFilesCount: attachmentFiles.length,
fileType
// 这里的currentFiles的长度是上传进度条是否显示的关键 });
// setCurrentFiles(allFiles);
// 将准备上传的操作移到这里,暂时不执行 // 检查组件是否已卸载
console.log('准备上传', allFiles.length, '个文件'); if (!isMountedRef.current) {
// startUpload(allFiles); console.error('【调试-checkAndPrepareUpload】组件已卸载,取消操作');
return;
}
// 检查是否选择了文件类型
if (!fileType) {
console.error('【调试-checkAndPrepareUpload】未选择文件类型');
toastService.error('请先选择文件类型');
return;
}
// 检查是否为合同类型
const selectedType = documentTypes.find(t => t.id.toString() === fileType);
const isContract = !!(selectedType && selectedType.name.includes('合同'));
console.log('【调试-checkAndPrepareUpload】文件类型检查', {
selectedType,
isContract,
typeName: selectedType?.name
});
if (isContract) {
console.log('【调试-checkAndPrepareUpload】合同文档类型特殊处理');
// 检查主文件
if(mainFiles.length === 0) {
console.error('【调试-checkAndPrepareUpload】缺少合同主文件');
toastService.error('请上传合同主文件');
return;
}
// 记录主文件和附件文件信息
console.log('【调试-checkAndPrepareUpload】合同主文件:', mainFiles.map(f => ({ name: f.name, size: f.size, type: f.type })));
if (attachmentFiles.length > 0) {
console.log('【调试-checkAndPrepareUpload】合同附件文件:', attachmentFiles.map(f => ({ name: f.name, size: f.size, type: f.type })));
} else {
console.log('【调试-checkAndPrepareUpload】无合同附件文件');
}
}
if (mainFiles.length > 0) {
// 合并所有文件
let allFiles = [...mainFiles];
// 如果附件文件存在,则合并
if (attachmentFiles.length > 0) {
allFiles = [...allFiles, ...attachmentFiles];
}
console.log('【调试-checkAndPrepareUpload】合并文件后总数:', allFiles.length);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-checkAndPrepareUpload】组件已卸载,取消操作');
return;
}
// 这里的currentFiles的长度是上传进度条是否显示的关键
setCurrentFiles(allFiles);
// 将准备上传的操作移到这里,暂时不执行
console.log('【调试-checkAndPrepareUpload】准备上传', allFiles.length, '个文件');
if (fileType) {
try {
// 再次检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-checkAndPrepareUpload】组件已卸载,取消上传');
return;
}
console.log('【调试-checkAndPrepareUpload】开始调用startUpload函数');
// 使用 setTimeout 延迟调用,确保状态已更新
setTimeout(() => {
if (isMountedRef.current) {
try {
startUpload(allFiles);
} catch (delayedError) {
console.error('【调试-checkAndPrepareUpload】延迟调用startUpload失败:', delayedError);
toastService.error(`准备上传文件失败: ${delayedError instanceof Error ? delayedError.message : '未知错误'}`);
}
} else {
console.error('【调试-checkAndPrepareUpload】组件已卸载,取消延迟上传');
}
}, 0);
} catch (uploadError) {
console.error('【调试-checkAndPrepareUpload】调用startUpload失败:', uploadError);
// 检查组件是否已卸载
if (isMountedRef.current) {
toastService.error(`准备上传文件失败: ${uploadError instanceof Error ? uploadError.message : '未知错误'}`);
}
}
} else {
console.error('【调试-checkAndPrepareUpload】未选择文件类型,无法上传');
toastService.error('请选择文件类型');
}
} else {
console.error('【调试-checkAndPrepareUpload】没有文件可上传');
toastService.error('请选择要上传的文件');
}
} catch (error) {
console.error('【调试-checkAndPrepareUpload】准备上传文件过程中发生错误:', error);
// 检查组件是否已卸载
if (isMountedRef.current) {
toastService.error(`准备上传文件失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
} }
}; };
// 开始上传文件 // 开始上传文件
const startUpload = async (files: File[]) => { const startUpload = async (files: File[]) => {
try { try {
console.log('【调试-startUpload】开始上传过程,文件数量:', files.length);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startUpload】组件已卸载,取消上传');
return;
}
// 再次验证所有文件类型,确保只有PDF文件 // 再次验证所有文件类型,确保只有PDF文件
const invalidFiles = files.filter(file => const invalidFiles = files.filter(file =>
file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf') file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')
); );
if (invalidFiles.length > 0) { if (invalidFiles.length > 0) {
console.error('【调试-startUpload】文件类型验证失败:', invalidFiles.map(f => f.name));
throw new Error('只能上传PDF格式的文件'); throw new Error('只能上传PDF格式的文件');
} }
@@ -570,14 +750,23 @@ export default function FilesUpload() {
const totalSize = files.reduce((sum, file) => sum + file.size, 0); const totalSize = files.reduce((sum, file) => sum + file.size, 0);
let uploadedSize = 0; let uploadedSize = 0;
console.log('【调试-startUpload】总文件大小:', formatFileSize(totalSize));
// 更新步骤状态 // 更新步骤状态
const updatedSteps = [...processingSteps]; const updatedSteps = [...processingSteps];
updatedSteps[0].status = "active"; updatedSteps[0].status = "active";
updatedSteps[0].description = `正在上传 ${files.length} 个文件到服务器...`; updatedSteps[0].description = `正在上传 ${files.length} 个文件到服务器...`;
setProcessingSteps(updatedSteps);
// 检查组件是否已卸载
if (isMountedRef.current) {
setProcessingSteps(updatedSteps);
} else {
console.log('【调试-startUpload】组件已卸载,不更新处理步骤');
return;
}
// 转换文件为二进制格式 // 转换文件为二进制格式
console.log("开始转换文件到二进制格式..."); console.log("【调试-startUpload】开始转换文件到二进制格式...");
// 模拟上传进度 // 模拟上传进度
if (progressIntervalRef.current) { if (progressIntervalRef.current) {
@@ -605,45 +794,103 @@ export default function FilesUpload() {
const uploadedFiles: UploadedFile[] = []; const uploadedFiles: UploadedFile[] = [];
for (const file of files) { for (const file of files) {
// 转换文件为二进制格式 try {
const binaryData = await uploadFileToBinary(file); console.log(`【调试-startUpload】准备上传文件: ${file.name}, 大小: ${formatFileSize(file.size)}`);
// 上传文件 // 转换文件为二进制格式
const response = await uploadFileToServer( console.log(`【调试-startUpload】开始转换文件 ${file.name} 为二进制格式`);
binaryData, let binaryData: ArrayBuffer;
file.name, try {
file.type, binaryData = await uploadFileToBinary(file);
fileType as FileType, console.log(`【调试-startUpload】文件 ${file.name} 二进制转换成功,大小: ${binaryData.byteLength} 字节`);
priority, } catch (binaryError) {
documentNumber || null, console.error(`【调试-startUpload】文件 ${file.name} 二进制转换失败:`, binaryError);
remark || null, throw new Error(`文件 ${file.name} 转换失败: ${binaryError instanceof Error ? binaryError.message : '未知错误'}`);
isTestDocument
);
if (!response.success || !response.result) {
throw new Error(response.error || "上传失败");
}
// 更新已上传大小
uploadedSize += file.size;
// 创建新的文件对象
const newFile: UploadedFile = {
id: response.result.id,
name: response.result.file_name,
size: response.result.file_size,
type: file.type,
fileType: fileType as FileType,
priority,
status: DocumentStatus.WAITING,
uploadTime: getCurrentTime(),
processingInfo: {
progress: 0,
currentStep: 0
} }
};
let response: FileUploadResponse;
uploadedFiles.push(newFile); console.log(`【调试-startUpload】开始上传文件 ${file.name} 到服务器,文件类型: ${fileType}`);
try {
// 上传文件
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startUpload】组件已卸载,取消上传');
return { success: false, error: '组件已卸载' } as FileUploadResponse;
}
console.log(`【调试-startUpload】准备上传文件 ${file.name} 到服务器`);
// 使用Promise.race添加超时处理
const uploadPromise = uploadFileToServer(
binaryData,
file.name,
file.type,
fileType as FileType,
priority,
documentNumber || null,
remark || null,
isTestDocument
);
const timeoutPromise = new Promise<FileUploadResponse>((_, reject) => {
setTimeout(() => {
reject(new Error('上传超时'));
}, 30000); // 30秒超时
});
// 使用Promise.race处理超时
response = await Promise.race([uploadPromise, timeoutPromise]);
// 再次检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startUpload】组件已卸载,忽略上传响应');
return;
}
console.log(`【调试-startUpload】文件 ${file.name} 上传响应:`, response);
} catch (error) {
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startUpload】组件已卸载,忽略上传错误');
return;
}
console.error(`【调试-startUpload】文件 ${file.name} 上传错误:`, error);
throw new Error(`文件 ${file.name} 上传失败: ${error instanceof Error ? error.message : '未知错误'}`);
}
if (!response.success || !response.result) {
console.error(`【调试-startUpload】文件 ${file.name} 上传失败:`, response.error);
throw new Error(response.error || `文件 ${file.name} 上传失败`);
}
// 更新已上传大小
uploadedSize += file.size;
// 创建新的文件对象
const newFile: UploadedFile = {
id: response.result.id,
name: response.result.file_name,
size: response.result.file_size,
type: file.type,
fileType: fileType as FileType,
priority,
status: DocumentStatus.WAITING,
uploadTime: getCurrentTime(),
processingInfo: {
progress: 0,
currentStep: 0
}
};
console.log(`【调试-startUpload】文件 ${file.name} 上传成功,文件ID: ${newFile.id}`);
uploadedFiles.push(newFile);
} catch (fileError) {
console.error(`【调试-startUpload】处理文件 ${file.name} 时发生错误:`, fileError);
// 继续抛出错误,让外层catch捕获
throw fileError;
}
} }
// 清除进度定时器 // 清除进度定时器
@@ -669,15 +916,17 @@ export default function FilesUpload() {
}; };
}); });
console.log(`【调试-startUpload】所有文件上传完成,更新队列`);
setQueueFiles(prev => [...newDocuments, ...prev]); setQueueFiles(prev => [...newDocuments, ...prev]);
// 设置当前文件为已上传的文件 // 设置当前文件为已上传的文件
setCompletedFiles(uploadedFiles); setCompletedFiles(uploadedFiles);
// 完成上传后开始处理流程 // 完成上传后开始处理流程
console.log(`【调试-startUpload】开始文件处理流程`);
startProcessing(uploadedFiles); startProcessing(uploadedFiles);
} catch (error) { } catch (error) {
console.error("文件上传错误:", error); console.error("【调试-startUpload】文件上传过程发生错误:", error);
// 更新步骤状态为错误 // 更新步骤状态为错误
const errorSteps = [...processingSteps]; const errorSteps = [...processingSteps];
@@ -701,81 +950,149 @@ export default function FilesUpload() {
} }
}); });
resetUpload(); resetUpload();
// 抛出错误,让React错误边界捕获并显示
throw error;
} }
}; };
// 开始处理上传的文件 // 开始处理上传的文件
const startProcessing = (files: UploadedFile[]) => { const startProcessing = (files: UploadedFile[]) => {
// 更新上传阶段 try {
setUploadStage("processing"); console.log('【调试-startProcessing】开始处理上传的文件:', files.length, '个文件');
// 更新步骤状态 // 检查组件是否已卸载
const updatedSteps = processingSteps.map(step => ({...step})); if (!isMountedRef.current) {
updatedSteps[0].status = "done"; console.error('【调试-startProcessing】组件已卸载,取消处理');
updatedSteps[0].description = "文件已成功上传到服务器"; return;
updatedSteps[1].status = "active"; }
updatedSteps[1].description = "正在转换文档格式,拆分文档内容...";
// 更新上传阶段
setProcessingSteps(updatedSteps); setUploadStage("processing");
// 获取文件ID列表 // 更新步骤状态
const fileIds = files.map(file => file.id).filter(id => id > 0); const updatedSteps = processingSteps.map(step => ({...step}));
updatedSteps[0].status = "done";
console.log('开始处理文件,设置文件处理进度定时器'); updatedSteps[0].description = "文件已成功上传到服务器";
updatedSteps[1].status = "active";
// 清除之前的进度定时器(如果存在) updatedSteps[1].description = "正在转换文档格式,拆分文档内容...";
if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current); setProcessingSteps(updatedSteps);
// 获取文件ID列表
const fileIds = files.map(file => file.id).filter(id => id > 0);
console.log('【调试-startProcessing】文件ID列表:', fileIds);
if (fileIds.length === 0) {
console.error('【调试-startProcessing】没有有效的文件ID,无法开始处理');
throw new Error('没有有效的文件ID,无法开始处理');
}
console.log('【调试-startProcessing】开始处理文件,设置文件处理进度定时器');
// 清除之前的进度定时器(如果存在)
if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current);
}
// 立即开始检查状态
try {
console.log('【调试-startProcessing】立即开始检查处理状态');
checkProcessingStatus(fileIds);
} catch (statusError) {
console.error('【调试-startProcessing】首次检查状态失败:', statusError);
}
// 设置文件处理进度定时器,每10秒检查一次状态
progressIntervalRef.current = setInterval(() => {
console.log('【调试-startProcessing】文件处理进度定时器触发,检查文件状态');
try {
checkProcessingStatus(fileIds);
} catch (intervalError) {
console.error('【调试-startProcessing】定时检查状态失败:', intervalError);
// 不要抛出,继续尝试
}
}, 10000);
} catch (error) {
console.error('【调试-startProcessing】处理文件过程中发生错误:', error);
// 清除进度定时器
if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current);
progressIntervalRef.current = null;
}
// 更新步骤状态为错误
const errorSteps = [...processingSteps];
for (let i = 0; i < errorSteps.length; i++) {
if (errorSteps[i].status === "active") {
errorSteps[i].status = "error";
errorSteps[i].description = `处理失败: ${error instanceof Error ? error.message : '未知错误'}`;
}
}
setProcessingSteps(errorSteps);
// 重置处理状态
setUploadStage("idle");
// 显示错误消息
messageService.error(`文件处理失败:${error instanceof Error ? error.message : '未知错误'}`, {
title: '处理失败',
confirmText: '确定',
cancelText: '',
});
// 抛出错误,让React错误边界捕获
throw error;
} }
// 立即开始检查状态
checkProcessingStatus(fileIds);
// 设置文件处理进度定时器,每10秒检查一次状态
progressIntervalRef.current = setInterval(() => {
console.log('文件处理进度定时器触发,检查文件状态');
checkProcessingStatus(fileIds);
}, 10000);
}; };
// 检查文件处理状态 // 检查文件处理状态
const checkProcessingStatus = async (fileIds: number[]) => { const checkProcessingStatus = async (fileIds: number[]) => {
try { try {
console.log('检查文件处理状态:', fileIds); console.log('【调试-checkProcessingStatus】检查文件处理状态:', fileIds);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-checkProcessingStatus】组件已卸载,取消检查');
return;
}
// 如果没有文件ID,不执行检查 // 如果没有文件ID,不执行检查
if (!fileIds.length) { if (!fileIds.length) {
console.log('没有需要检查的文件'); console.log('【调试-checkProcessingStatus】没有需要检查的文件');
return; return;
} }
// 获取文件状态 // 获取文件状态
console.log('【调试-checkProcessingStatus】发送请求获取文件状态');
const response = await getDocumentsStatus(fileIds); const response = await getDocumentsStatus(fileIds);
if (response.error) { if (response.error) {
console.error('获取文件状态出错:', response.error); console.error('【调试-checkProcessingStatus】获取文件状态出错:', response.error);
return; return;
} }
console.log('文件状态响应:', response.data); console.log('【调试-checkProcessingStatus】文件状态响应:', response.data);
if (!response.data || !response.data.length) { if (!response.data || !response.data.length) {
console.log('没有返回文件状态数据'); console.log('【调试-checkProcessingStatus】没有返回文件状态数据');
return; return;
} }
// 检查是否所有文件都已完成处理 // 检查是否所有文件都已完成处理
const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED); const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED);
console.log('【调试-checkProcessingStatus】文件处理状态:', { allCompleted, statusList: response.data.map(doc => doc.status) });
// 更新步骤状态 // 更新步骤状态
if (allCompleted) { if (allCompleted) {
console.log('所有文件处理完成,更新步骤状态为完成'); console.log('【调试-checkProcessingStatus】所有文件处理完成,更新步骤状态为完成');
// 清除文件处理进度定时器 // 清除文件处理进度定时器
if (progressIntervalRef.current) { if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current); clearInterval(progressIntervalRef.current);
progressIntervalRef.current = null; progressIntervalRef.current = null;
console.log('文件处理完成,清除文件处理进度定时器'); console.log('【调试-checkProcessingStatus】文件处理完成,清除文件处理进度定时器');
} }
// 更新为全部完成状态 // 更新为全部完成状态
@@ -794,14 +1111,17 @@ export default function FilesUpload() {
setUploadStage("completed"); setUploadStage("completed");
} else { } else {
// 根据当前状态更新步骤 // 根据当前状态更新步骤
updateProcessingSteps(response.data[0].status); const currentStatus = response.data[0].status;
console.log('【调试-checkProcessingStatus】根据当前状态更新步骤:', currentStatus);
updateProcessingSteps(currentStatus);
} }
// 刷新队列中的文件状态 // 刷新队列中的文件状态
updateQueueFilesStatus(response.data); updateQueueFilesStatus(response.data);
} catch (error) { } catch (error) {
console.error('检查文件处理状态出错:', error); console.error('【调试-checkProcessingStatus】检查文件处理状态出错:', error);
// 这里不抛出错误,让定时器继续运行
} }
}; };
@@ -952,21 +1272,56 @@ export default function FilesUpload() {
// 处理查看文件 // 处理查看文件
const handleViewFile = async (record: Document) => { const handleViewFile = async (record: Document) => {
// 检查audit_status是否为0,如果是则更新为2 try {
if (record.audit_status === 0 || record.audit_status === null) { console.log('【调试-handleViewFile】开始处理查看文件,文件ID:', record.id);
try {
const response = await updateDocumentAuditStatus(record.id.toString(), 2); // 检查audit_status是否为0,如果是则更新为2
if (response.error) { if (record.audit_status === 0 || record.audit_status === null) {
console.error('更新文件审核状态失败:', response.error); try {
toastService.error('更新文件审核状态失败:' + (response.error || '未知错误')); console.log('【调试-handleViewFile】更新文件审核状态,文件ID:', record.id);
const response = await updateDocumentAuditStatus(record.id.toString(), 2);
if (response.error) {
console.error('【调试-handleViewFile】更新文件审核状态失败:', response.error);
toastService.error('更新文件审核状态失败:' + (response.error || '未知错误'));
} else {
console.log('【调试-handleViewFile】更新文件审核状态成功');
}
} catch (error) {
console.error('【调试-handleViewFile】更新文件审核状态时出错:', error);
toastService.error('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
// 即使更新失败,也继续导航
} }
} catch (error) {
console.error('更新文件审核状态时出错:', error);
toastService.error('更新文件审核状态时出错:' + (error instanceof Error ? error.message : '未知错误'));
} }
console.log(`【调试-handleViewFile】准备导航到文件详情页,文件ID: ${record.id}`);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-handleViewFile】组件已卸载,取消导航');
return;
}
// 使用 setTimeout 延迟导航,确保状态已更新
setTimeout(() => {
try {
if (isMountedRef.current) {
console.log(`【调试-handleViewFile】执行导航,URL: /reviews?id=${record.id}&previousRoute=filesUpload`);
navigate(`/reviews?id=${record.id}&previousRoute=filesUpload`);
} else {
console.error('【调试-handleViewFile】组件已卸载,取消延迟导航');
}
} catch (navError) {
console.error('【调试-handleViewFile】导航执行错误:', navError);
// 不抛出异常,防止组件崩溃
}
}, 0);
} catch (outerError) {
console.error('【调试-handleViewFile】查看文件处理过程中发生错误:', outerError);
toastService.error('查看文件失败:' + (outerError instanceof Error ? outerError.message : '未知错误'));
// 不抛出异常,防止组件崩溃
} }
navigate(`/reviews?id=${record.id}&previousRoute=filesUpload`);
}; };
// 表格列定义 // 表格列定义
@@ -1075,6 +1430,46 @@ export default function FilesUpload() {
} }
]; ];
// 添加路由阻止器
const shouldBlock = uploadStage === "uploading" || uploadStage === "processing";
// 使用useBlocker来阻止页面导航
const blocker = useBlocker(
({ nextLocation }) => {
return shouldBlock && window.location.pathname !== nextLocation.pathname;
}
);
// 处理阻止导航的逻辑
useEffect(() => {
if (blocker.state === "blocked") {
const confirmed = window.confirm(
"文件正在上传或处理中,离开页面将中断操作。确定要离开吗?"
);
if (confirmed) {
blocker.proceed();
} else {
blocker.reset();
}
}
}, [blocker]);
// 添加页面刷新/关闭提示
useEffect(() => {
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
if (shouldBlock) {
e.preventDefault();
e.returnValue = "文件正在上传或处理中,离开页面将中断操作。确定要离开吗?";
return e.returnValue;
}
};
window.addEventListener("beforeunload", handleBeforeUnload);
return () => {
window.removeEventListener("beforeunload", handleBeforeUnload);
};
}, [shouldBlock]);
return ( return (
<div className="file-upload-page"> <div className="file-upload-page">
{/* 页面头部 */} {/* 页面头部 */}
@@ -1164,11 +1559,11 @@ export default function FilesUpload() {
{/* 自定义标题栏 */} {/* 自定义标题栏 */}
<div className="w-full flex justify-between items-center mb-4"> <div className="w-full flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3> <h3 className="text-lg font-medium"></h3>
{contractMainFiles.length > 0 && contractAttachmentFiles.length > 0 && ( {isContractType && uploadStage === "idle" && (
<Button <Button
type="primary" type="primary"
icon="ri-upload-cloud-line" icon="ri-upload-cloud-line"
onClick={() => startUpload([...contractMainFiles, ...contractAttachmentFiles])} onClick={() => checkAndPrepareUpload(contractMainFiles, contractAttachmentFiles)}
> >
</Button> </Button>
@@ -1449,11 +1844,40 @@ export default function FilesUpload() {
); );
} }
export function ErrorBoundary() { export function ErrorBoundary({ error }: { error?: Error }) {
// 记录错误到控制台,以便开发时查看
console.error('文件上传组件错误:', error || '未知错误');
return ( return (
<div className="error-container p-6"> <div className="error-container p-6">
<h1 className="text-xl font-bold text-red-500 mb-4"></h1> <h1 className="text-xl font-bold text-red-500 mb-4"></h1>
<p className="mb-4"></p> <p className="mb-4"></p>
{/* 在开发环境中显示错误详情 */}
<div className="bg-gray-100 p-4 rounded mb-4 overflow-auto max-h-[300px]">
<h2 className="font-bold mb-2"></h2>
{error ? (
<>
<p className="text-red-600">{error.message}</p>
{error.stack && (
<pre className="text-xs mt-2 whitespace-pre-wrap">{error.stack}</pre>
)}
</>
) : (
<p className="text-red-600"> React </p>
)}
<div className="mt-4 p-2 bg-yellow-50 border border-yellow-200 rounded">
<p className="text-sm font-medium"></p>
<ul className="list-disc pl-5 mt-1 text-sm">
<li></li>
<li>API </li>
<li></li>
<li></li>
</ul>
</div>
</div>
<Button type="primary" to="/"></Button> <Button type="primary" to="/"></Button>
</div> </div>
); );
+2 -1
View File
@@ -533,7 +533,8 @@ export default function ReviewDetails() {
{reviewData.fileInfo.fileName} {reviewData.fileInfo.fileName}
</span> </span>
<div className="ml-2 text-xs text-gray-500 flex items-center"> <div className="ml-2 text-xs text-gray-500 flex items-center">
{reviewData.fileInfo.contractNumber} {/* 合同编号:{reviewData.fileInfo.contractNumber} */}
{reviewData.fileInfo.contractNumber}
{reviewData.fileInfo.fileSize && ( {reviewData.fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2"> <span className="text-xs text-gray-500 ml-2">
| {reviewData.fileInfo.fileSize} | {reviewData.fileInfo.fileFormat} | {reviewData.fileInfo.pageCount}&nbsp; | {reviewData.fileInfo.fileSize} | {reviewData.fileInfo.fileFormat} | {reviewData.fileInfo.pageCount}&nbsp;
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1737432031633" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1971" width="300" height="300" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M543 63.3c162.7 1.9 364.2 77.5 360.3 261.5-7.7 83.3-75.5 145.3-162.7 149.2-71.7 0-133.7-38.7-156.9-112.3-11.6-52.3-1.9-100.7 19.4-147.2C492.7 233.9 424.9 321 395.8 421.8l-7.7 40.7-3.9 19.4c-9.7 131.7 27.1 215 120.1 290.6 15.5 11.6 25.2 17.4 29.1 36.8 3.9 19.4 0 36.8-7.8 54.2l-1.9 3.9v1.9h1.9v3.8h1.9v1.9h1.9v1.9h1.9v1.9h7.6l1.9-1.9c31-29.1 31-94.9 31-127.8-1.9-125.9-116.2-224.7-98.8-354.5 7.7-54.2 46.5-120.1 96.9-147.2 1.9-1.9 1.9-1.9 3.9-1.9-34.9 42.6-58.1 94.9-63.9 151.1-1.9 62 19.4 120.1 46.5 176.3 36.8 69.7 69.7 149.2 52.3 228.6l-1.9 1.9h1.9c85.2-69.7 98.8-184 108.5-284.7 0-1.9 1.9-5.8 3.9-5.8 11.6-1.9 19.4 17.4 25.2 23.2C796.7 600 847 662 899.3 724l11.6 13.6 11.6 13.6c-79.4 125.9-209.2 207.3-364.2 217-246 3.9-456.7-193.8-459.1-428.1C96.8 282.2 293.2 71 543 63.3z" fill="#178431" p-id="1972"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB