修复下载,更改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
): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> {
try {
console.log('【调试】开始上传文档:', { fileName, fileSize: binaryData.byteLength });
// 创建FormData对象
const formData = new FormData();
// 将二进制数据转换为Blob并添加到FormData
const blob = new Blob([binaryData], { type: fileType });
formData.append('file', blob, fileName);
console.log('【调试】Blob已创建,文件大小:', blob.size);
// 将信息添加到一个JSON对象中
const uploadInfo = {
@@ -164,48 +167,67 @@ export async function uploadDocumentToServer(
// 添加JSON字符串到FormData
formData.append('upload_info', JSON.stringify(uploadInfo));
console.log('【调试】FormData准备完成:', JSON.stringify(uploadInfo));
console.log('上传信息:', {
fileName,
fileType,
typeId: Number(typeId),
priority,
documentNumber: documentNumber || null,
remark: remark || null,
isTestDocument
});
console.log('【调试】准备发送请求到服务器:', 'http://172.16.0.58:8008/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', {
// const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
method: 'POST',
headers: {
'X-File-Name': encodeURIComponent(fileName)
},
body: formData
});
if (!response.ok) {
const errorText = await response.text();
console.error(`上传失败 (${response.status}): ${errorText}`);
return {
error: `上传失败: ${response.status} ${response.statusText}`,
status: response.status
try {
console.log('【调试】开始fetch请求...');
const response = await fetch('http://172.16.0.58:8008/admin/documents/upload', {
// const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
// const response = await fetch('http://172.16.0.119:8000/admin/documents/upload', {
method: 'POST',
headers: {
'X-File-Name': encodeURIComponent(fileName)
},
body: formData
});
console.log('【调试】收到服务器响应:', { status: response.status, statusText: response.statusText });
if (!response.ok) {
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) {
console.error('上传错误:', error);
console.error('【调试】上传过程中发生错误:', error);
return {
error: error instanceof Error ? error.message : '上传失败',
status: 500
+4 -4
View File
@@ -21,7 +21,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
const menuItems: MenuItem[] = [
{
id: 'home',
title: '首页',
title: '系统概览',
path: '/',
icon: 'ri-home-line'
},
@@ -164,7 +164,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
<div className={`sidebar ${collapsed ? 'collapsed' : ''}`}>
<div className="py-6 px-4 border-b border-gray-100 flex justify-between 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>}
</div>
<button
@@ -177,7 +177,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
</button>
</div>
{!collapsed && (
{/* {!collapsed && (
<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>
@@ -187,7 +187,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
<p className="text-xs text-gray-500">超级管理员</p>
</div>
</div>
)}
)} */}
<div className="py-4 px-[10px]">
{menuItems.map((item) => (
+2 -1
View File
@@ -30,7 +30,8 @@ export function FileInfo({ fileInfo }: FileInfoProps) {
{fileInfo.fileName}
</h2>
<span className="text-xs text-gray-500">
{fileInfo.contractNumber}
{/* 合同编号:{fileInfo.contractNumber} */}
{fileInfo.contractNumber}
</span>
{fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2">
+11 -11
View File
@@ -180,22 +180,22 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage
// 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转
if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointResultId)) {
prevTargetPageRef.current = targetPage;
const newTargetPage = targetPage;
let newTargetPage = targetPage;
// let newTargetPage = targetPage;
// console.log("targetPage:", targetPage);
// console.log("fileContent:", fileContent);
// 页码偏移量
// try {
// // 安全地访问ocrResult
// if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) {
// // 可以根据需要使用page_offset调整目标页面
// newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset;
// }
// } catch (error) {
// console.error("访问ocrResult时出错:", error);
// toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误'));
// }
try {
// 安全地访问ocrResult
if (fileContent.ocrResult && fileContent.ocrResult.__meta && fileContent.ocrResult.__meta.page_offset) {
// 可以根据需要使用page_offset调整目标页面
newTargetPage = targetPage + fileContent.ocrResult.__meta.page_offset;
}
} catch (error) {
console.error("访问ocrResult时出错:", error);
toastService.error("访问ocrResult时出错:" + (error instanceof Error ? error.message : '未知错误'));
}
const pageElement = document.getElementById(`page-${newTargetPage}`);
if (pageElement) {
+25 -8
View File
@@ -450,7 +450,7 @@ export function ReviewPointsList({
{Object.entries(reviewPoint.content).map(([key, value], index) => (
<div
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) => {
e.stopPropagation();
console.log(`单独点击${key}----`, reviewPoint);
@@ -488,8 +488,17 @@ export function ReviewPointsList({
role="button"
tabIndex={0}
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">
{key}
</span>
@@ -498,11 +507,18 @@ export function ReviewPointsList({
{value.value?.toString().trim() ? '' : '缺失'}
</span>
</div>
<p className="text-xs text-left select-text">
{(value.value?.toString().trim() === '')
? <span className="invisible"></span>
: value.value?.toString() || ''}
</p>
<div className="relative text-container max-h-96 group-hover:overflow-auto overflow-hidden">
<p
className="text-xs text-left select-text block overflow-hidden !line-clamp-2
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"
// title={value.value?.toString() || ''}
// style={{ userSelect: 'all' }}
>
{(value.value?.toString().trim() === '')
? ""
: value.value?.toString() || ''}
</p>
</div>
</div>
))}
</>
@@ -897,7 +913,8 @@ export function ReviewPointsList({
<div className="review-point-location max-w-[40%] flex items-center">
<i className="ri-file-list-line mr-1 flex-shrink-0"></i>
<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}
style={{ cursor: 'text', userSelect: 'all' }}
>
+60 -3
View File
@@ -76,7 +76,9 @@ export async function loader() {
}
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({
date: '',
time: ''
@@ -102,16 +104,71 @@ export default function Index() {
// 清理函数,组件卸载时清除计时器
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 (
<div className="dashboard-container">
{/* 页面头部 */}
<div className="flex justify-between items-center mb-4">
<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 className="mx-2">|</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>
+58 -17
View File
@@ -181,7 +181,7 @@ export async function action({ request }: ActionFunctionArgs) {
// 文档编辑页面组件
export default function DocumentEdit() {
const { document, documentTypes } = useLoaderData<typeof loader>();
const { document: documentData, documentTypes } = useLoaderData<typeof loader>();
const actionData = useActionData<ActionData>();
const navigate = useNavigate();
const [numPages, setNumPages] = useState(0);
@@ -190,10 +190,10 @@ export default function DocumentEdit() {
// 表单状态管理 - 使用受控组件
const [formValues, setFormValues] = useState({
documentNumber: document.documentNumber || "",
auditStatus: document.auditStatus,
isTest: document.isTest || false,
remark: document.remark || ""
documentNumber: documentData.documentNumber || "",
auditStatus: documentData.auditStatus,
isTest: documentData.isTest || false,
remark: documentData.remark || ""
});
// 表单验证错误状态
@@ -303,7 +303,7 @@ export default function DocumentEdit() {
return (
<div className="preview-content relative overflow-y-auto max-h-[1000px]">
<Document
file={DOCUMENT_URL + document.path}
file={DOCUMENT_URL + documentData.path}
onLoadSuccess={onDocumentLoadSuccess}
onLoadError={(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 previewUrl = `${DOCUMENT_URL}${document.path}`;
const previewUrl = `${DOCUMENT_URL}${documentData.path}`;
window.open(previewUrl, '_blank');
};
@@ -428,7 +468,7 @@ export default function DocumentEdit() {
<div className="document-info">
<div className="document-icon">
<FileTag
extension={document.fileType}
extension={documentData.fileType}
showIcon={true}
showText={false}
showBackground={false}
@@ -436,22 +476,22 @@ export default function DocumentEdit() {
/>
</div>
<div className="document-details">
<div className="document-name">{document.name}</div>
<div className="document-name">{documentData.name}</div>
<div className="document-meta">
<div className="meta-item">
<i className="ri-file-list-line"></i>
<span>{getDocumentTypeName(document.type)}</span>
<span>{getDocumentTypeName(documentData.type)}</span>
</div>
<div className="meta-item">
<i className="ri-time-line"></i>
<span>{document.uploadTime}</span>
<span>{documentData.uploadTime}</span>
</div>
<div className="meta-item">
<i className="ri-hard-drive-line"></i>
<span>{formatFileSize(document.size)}</span>
<span>{formatFileSize(documentData.size)}</span>
</div>
<div className="meta-item">
{renderStatusBadge(document.auditStatus)}
{renderStatusBadge(documentData.auditStatus)}
</div>
</div>
</div>
@@ -477,7 +517,7 @@ export default function DocumentEdit() {
id="type-id"
name="type_id"
className="form-select"
value={document.type}
value={documentData.type}
disabled={true}
required
>
@@ -566,8 +606,8 @@ export default function DocumentEdit() {
<div className="document-preview">
<div className="preview-toolbar">
<div className="flex items-center">
<i className={`ri-file-${document.fileType}-line text-${document.fileType === 'pdf' ? 'red' : 'blue'}-500 mr-1`}></i>
<span>{document.name}</span>
<i className={`ri-file-${documentData.fileType}-line text-${documentData.fileType === 'pdf' ? 'red' : 'blue'}-500 mr-1`}></i>
<span>{documentData.name}</span>
</div>
<div>
<Button
@@ -575,6 +615,7 @@ export default function DocumentEdit() {
size="small"
icon="ri-download-line"
className="mr-2"
onClick={downloadDocument}
>
</Button>
@@ -607,7 +648,7 @@ export default function DocumentEdit() {
time: "2023-10-15 15:30",
user: "系统",
action: "创建了此文档",
details: `首次上传文档,文档类型:${getDocumentTypeName(document.type)},状态:待审核`
details: `首次上传文档,文档类型:${getDocumentTypeName(documentData.type)},状态:待审核`
},
{
time: "2023-10-15 16:45",
+581 -157
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef } from "react";
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 { Button } from "~/components/ui/Button";
import { Table } from "~/components/ui/Table";
@@ -145,11 +145,13 @@ async function uploadFileToServer(
}
// 确保返回有效的FileUploadResponse对象
// console.log('上传成功:', response.data);
if (response.data) {
return response.data;
}
// 如果没有数据,则返回错误
// console.log('上传失败:', response.error);
return {
success: false,
error: '上传失败,未获取到响应数据'
@@ -349,10 +351,16 @@ export default function FilesUpload() {
// 状态检查定时器引用
const statusCheckIntervalRef = useRef<NodeJS.Timeout | null>(null);
// 添加组件挂载状态引用
const isMountedRef = useRef<boolean>(true);
// useEffect 处理上传队列状态检查定时器 - 只在组件卸载时清除
useEffect(() => {
console.log('设置上传队列状态检查定时器');
// 标记组件已挂载
isMountedRef.current = true;
// 设置定时器检查队列中文件的状态,初始先加载一次查询
checkQueueStatus();
statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000);
@@ -360,6 +368,8 @@ export default function FilesUpload() {
// 只在组件卸载时清除
return () => {
console.log('组件卸载,清除上传队列状态检查定时器');
// 标记组件已卸载
isMountedRef.current = false;
if (statusCheckIntervalRef.current) {
clearInterval(statusCheckIntervalRef.current);
statusCheckIntervalRef.current = null;
@@ -446,24 +456,37 @@ export default function FilesUpload() {
const value = e.target.value;
// 确保只有选择了有效的文件类型才进行设置
if (value) {
console.log('【调试-handleFileTypeChange】文件类型变更为:', value);
setFileType(value as FileType);
// 立即清除错误状态
setFileTypeError(null);
// 检查是否选择了合同类型
// const selectedType = documentTypes.find(t => t.id.toString() === value);
// const isContract = !!(selectedType && selectedType.name.includes('合同'));
// setIsContractType(isContract);
const selectedType = documentTypes.find(t => t.id.toString() === value);
const isContract = !!(selectedType && selectedType.name.includes('合同'));
console.log('【调试-handleFileTypeChange】文件类型检查:', {
selectedType,
isContract,
typeName: selectedType?.name,
currentFiles: currentFiles.length
});
setIsContractType(isContract);
// 重置文件状态
setContractMainFiles([]);
setContractAttachmentFiles([]);
setCurrentFiles([]);
// 如果已经有选中的文件,且选择了文件类型,则开始上传
// if (currentFiles.length > 0 && !isContract) {
// startUpload(currentFiles);
// }
// 如果已经有选中的文件,且选择了文件类型,且不是合同类型,则开始上传
if (currentFiles.length > 0 && !isContract) {
console.log('【调试-handleFileTypeChange】自动开始上传非合同类型文件');
startUpload(currentFiles);
} else if (currentFiles.length > 0 && isContract) {
console.log('【调试-handleFileTypeChange】合同类型需要手动点击开始上传按钮');
// 合同类型不自动上传,需要用户先上传主文件和附件,然后点击开始上传按钮
setCurrentFiles([]);
}
} else {
setFileType("");
@@ -475,91 +498,248 @@ export default function FilesUpload() {
// 处理合同主文件选择
const handleContractMainFilesSelected = (files: FileList) => {
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
try {
console.log('【调试-handleContractMainFilesSelected】开始处理合同主文件选择, 文件数量:', files.length);
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
}
});
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-handleContractMainFilesSelected】组件已卸载,取消处理');
return;
}
if (hasInvalidFiles) {
// 显示错误提示
messageService.error('只能上传PDF格式的文件', {
title: '文件类型错误',
confirmText: '确定',
cancelText: '',
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`【调试-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】未选择任何文件');
}
if (validFiles.length > 0) {
setContractMainFiles(validFiles);
checkAndPrepareUpload(validFiles, contractAttachmentFiles);
}
} catch (error) {
console.error('【调试-handleContractMainFilesSelected】处理合同主文件选择时发生错误:', error);
}
};
// 处理合同附件选择
const handleContractAttachmentFilesSelected = (files: FileList) => {
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
try {
console.log('【调试-handleContractAttachmentFilesSelected】开始处理合同附件选择, 文件数量:', files.length);
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
}
});
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-handleContractAttachmentFilesSelected】组件已卸载,取消处理');
return;
}
if (hasInvalidFiles) {
// 显示错误提示
messageService.error('只能上传PDF格式的文件', {
title: '文件类型错误',
confirmText: '确定',
cancelText: '',
if (files.length > 0) {
// 验证文件类型,只允许PDF文件
const validFiles: File[] = [];
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
validFiles.push(file);
} else {
hasInvalidFiles = true;
console.error(`【调试-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】未选择任何文件');
}
if (validFiles.length > 0) {
setContractAttachmentFiles(validFiles);
checkAndPrepareUpload(contractMainFiles, validFiles);
}
} catch (error) {
console.error('【调试-handleContractAttachmentFilesSelected】处理合同附件选择时发生错误:', error);
}
};
// 检查并准备上传
const checkAndPrepareUpload = (mainFiles: File[], attachmentFiles: File[]) => {
// 当两个区域都有文件时才准备上传
if (mainFiles.length > 0 && attachmentFiles.length > 0) {
// 合并所有文件
const allFiles = [...mainFiles, ...attachmentFiles];
// 这里的currentFiles的长度是上传进度条是否显示的关键
// setCurrentFiles(allFiles);
try {
console.log('【调试-checkAndPrepareUpload】开始检查并准备上传文件', {
mainFilesCount: mainFiles.length,
attachmentFilesCount: attachmentFiles.length,
fileType
});
// 将准备上传的操作移到这里,暂时不执行
console.log('准备上传', allFiles.length, '个文件');
// startUpload(allFiles);
// 检查组件是否已卸载
if (!isMountedRef.current) {
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[]) => {
try {
console.log('【调试-startUpload】开始上传过程,文件数量:', files.length);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startUpload】组件已卸载,取消上传');
return;
}
// 再次验证所有文件类型,确保只有PDF文件
const invalidFiles = files.filter(file =>
file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')
);
if (invalidFiles.length > 0) {
console.error('【调试-startUpload】文件类型验证失败:', invalidFiles.map(f => f.name));
throw new Error('只能上传PDF格式的文件');
}
@@ -570,14 +750,23 @@ export default function FilesUpload() {
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
let uploadedSize = 0;
console.log('【调试-startUpload】总文件大小:', formatFileSize(totalSize));
// 更新步骤状态
const updatedSteps = [...processingSteps];
updatedSteps[0].status = "active";
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) {
@@ -605,45 +794,103 @@ export default function FilesUpload() {
const uploadedFiles: UploadedFile[] = [];
for (const file of files) {
// 转换文件为二进制格式
const binaryData = await uploadFileToBinary(file);
// 上传文件
const response = await uploadFileToServer(
binaryData,
file.name,
file.type,
fileType as FileType,
priority,
documentNumber || null,
remark || null,
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
try {
console.log(`【调试-startUpload】准备上传文件: ${file.name}, 大小: ${formatFileSize(file.size)}`);
// 转换文件为二进制格式
console.log(`【调试-startUpload】开始转换文件 ${file.name} 为二进制格式`);
let binaryData: ArrayBuffer;
try {
binaryData = await uploadFileToBinary(file);
console.log(`【调试-startUpload】文件 ${file.name} 二进制转换成功,大小: ${binaryData.byteLength} 字节`);
} catch (binaryError) {
console.error(`【调试-startUpload】文件 ${file.name} 二进制转换失败:`, binaryError);
throw new Error(`文件 ${file.name} 转换失败: ${binaryError instanceof Error ? binaryError.message : '未知错误'}`);
}
};
uploadedFiles.push(newFile);
let response: FileUploadResponse;
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]);
// 设置当前文件为已上传的文件
setCompletedFiles(uploadedFiles);
// 完成上传后开始处理流程
console.log(`【调试-startUpload】开始文件处理流程`);
startProcessing(uploadedFiles);
} catch (error) {
console.error("文件上传错误:", error);
console.error("【调试-startUpload】文件上传过程发生错误:", error);
// 更新步骤状态为错误
const errorSteps = [...processingSteps];
@@ -701,81 +950,149 @@ export default function FilesUpload() {
}
});
resetUpload();
// 抛出错误,让React错误边界捕获并显示
throw error;
}
};
// 开始处理上传的文件
const startProcessing = (files: UploadedFile[]) => {
// 更新上传阶段
setUploadStage("processing");
// 更新步骤状态
const updatedSteps = processingSteps.map(step => ({...step}));
updatedSteps[0].status = "done";
updatedSteps[0].description = "文件已成功上传到服务器";
updatedSteps[1].status = "active";
updatedSteps[1].description = "正在转换文档格式,拆分文档内容...";
setProcessingSteps(updatedSteps);
// 获取文件ID列表
const fileIds = files.map(file => file.id).filter(id => id > 0);
console.log('开始处理文件,设置文件处理进度定时器');
// 清除之前的进度定时器(如果存在)
if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current);
try {
console.log('【调试-startProcessing】开始处理上传的文件:', files.length, '个文件');
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-startProcessing】组件已卸载,取消处理');
return;
}
// 更新上传阶段
setUploadStage("processing");
// 更新步骤状态
const updatedSteps = processingSteps.map(step => ({...step}));
updatedSteps[0].status = "done";
updatedSteps[0].description = "文件已成功上传到服务器";
updatedSteps[1].status = "active";
updatedSteps[1].description = "正在转换文档格式,拆分文档内容...";
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[]) => {
try {
console.log('检查文件处理状态:', fileIds);
console.log('【调试-checkProcessingStatus】检查文件处理状态:', fileIds);
// 检查组件是否已卸载
if (!isMountedRef.current) {
console.error('【调试-checkProcessingStatus】组件已卸载,取消检查');
return;
}
// 如果没有文件ID,不执行检查
if (!fileIds.length) {
console.log('没有需要检查的文件');
console.log('【调试-checkProcessingStatus】没有需要检查的文件');
return;
}
// 获取文件状态
console.log('【调试-checkProcessingStatus】发送请求获取文件状态');
const response = await getDocumentsStatus(fileIds);
if (response.error) {
console.error('获取文件状态出错:', response.error);
console.error('【调试-checkProcessingStatus】获取文件状态出错:', response.error);
return;
}
console.log('文件状态响应:', response.data);
console.log('【调试-checkProcessingStatus】文件状态响应:', response.data);
if (!response.data || !response.data.length) {
console.log('没有返回文件状态数据');
console.log('【调试-checkProcessingStatus】没有返回文件状态数据');
return;
}
// 检查是否所有文件都已完成处理
const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED);
console.log('【调试-checkProcessingStatus】文件处理状态:', { allCompleted, statusList: response.data.map(doc => doc.status) });
// 更新步骤状态
if (allCompleted) {
console.log('所有文件处理完成,更新步骤状态为完成');
console.log('【调试-checkProcessingStatus】所有文件处理完成,更新步骤状态为完成');
// 清除文件处理进度定时器
if (progressIntervalRef.current) {
clearInterval(progressIntervalRef.current);
progressIntervalRef.current = null;
console.log('文件处理完成,清除文件处理进度定时器');
console.log('【调试-checkProcessingStatus】文件处理完成,清除文件处理进度定时器');
}
// 更新为全部完成状态
@@ -794,14 +1111,17 @@ export default function FilesUpload() {
setUploadStage("completed");
} else {
// 根据当前状态更新步骤
updateProcessingSteps(response.data[0].status);
const currentStatus = response.data[0].status;
console.log('【调试-checkProcessingStatus】根据当前状态更新步骤:', currentStatus);
updateProcessingSteps(currentStatus);
}
// 刷新队列中的文件状态
updateQueueFilesStatus(response.data);
} catch (error) {
console.error('检查文件处理状态出错:', error);
console.error('【调试-checkProcessingStatus】检查文件处理状态出错:', error);
// 这里不抛出错误,让定时器继续运行
}
};
@@ -952,21 +1272,56 @@ export default function FilesUpload() {
// 处理查看文件
const handleViewFile = async (record: Document) => {
// 检查audit_status是否为0,如果是则更新为2
if (record.audit_status === 0 || record.audit_status === null) {
try {
const response = await updateDocumentAuditStatus(record.id.toString(), 2);
if (response.error) {
console.error('更新文件审核状态失败:', response.error);
toastService.error('更新文件审核状态失败:' + (response.error || '未知错误'));
try {
console.log('【调试-handleViewFile】开始处理查看文件,文件ID:', record.id);
// 检查audit_status是否为0,如果是则更新为2
if (record.audit_status === 0 || record.audit_status === null) {
try {
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 (
<div className="file-upload-page">
{/* 页面头部 */}
@@ -1164,11 +1559,11 @@ export default function FilesUpload() {
{/* 自定义标题栏 */}
<div className="w-full flex justify-between items-center mb-4">
<h3 className="text-lg font-medium"></h3>
{contractMainFiles.length > 0 && contractAttachmentFiles.length > 0 && (
{isContractType && uploadStage === "idle" && (
<Button
type="primary"
type="primary"
icon="ri-upload-cloud-line"
onClick={() => startUpload([...contractMainFiles, ...contractAttachmentFiles])}
onClick={() => checkAndPrepareUpload(contractMainFiles, contractAttachmentFiles)}
>
</Button>
@@ -1449,11 +1844,40 @@ export default function FilesUpload() {
);
}
export function ErrorBoundary() {
export function ErrorBoundary({ error }: { error?: Error }) {
// 记录错误到控制台,以便开发时查看
console.error('文件上传组件错误:', error || '未知错误');
return (
<div className="error-container p-6">
<h1 className="text-xl font-bold text-red-500 mb-4"></h1>
<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>
</div>
);
+2 -1
View File
@@ -533,7 +533,8 @@ export default function ReviewDetails() {
{reviewData.fileInfo.fileName}
</span>
<div className="ml-2 text-xs text-gray-500 flex items-center">
{reviewData.fileInfo.contractNumber}
{/* 合同编号:{reviewData.fileInfo.contractNumber} */}
{reviewData.fileInfo.contractNumber}
{reviewData.fileInfo.fileSize && (
<span className="text-xs text-gray-500 ml-2">
| {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