修复下载,更改logo,优化评查详情内容的显示,修改sidebar的首页,修复文件上传合同的异步上传时序问题,首页最近文件的自动更新文件状态
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}页
|
| {reviewData.fileInfo.fileSize} | {reviewData.fileInfo.fileFormat} | {reviewData.fileInfo.pageCount}页
|
||||||
|
|||||||
@@ -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 |
Reference in New Issue
Block a user