697 lines
23 KiB
TypeScript
697 lines
23 KiB
TypeScript
import { useState, useEffect, useRef, useCallback } from "react";
|
||
import { MetaFunction, ActionFunctionArgs } from "@remix-run/node";
|
||
import { Card } from "~/components/ui/Card";
|
||
import { Button } from "~/components/ui/Button";
|
||
import { Table } from "~/components/ui/Table";
|
||
import { UploadArea, UploadAreaRef } from "~/components/ui/UploadArea";
|
||
import { FileProgress} from "~/components/ui/FileProgress";
|
||
import { ProcessingSteps, Step } from "~/components/ui/ProcessingSteps";
|
||
import uploadStyles from "~/styles/pages/files_upload.css?url";
|
||
|
||
export function links() {
|
||
return [
|
||
{ rel: "stylesheet", href: uploadStyles }
|
||
];
|
||
}
|
||
|
||
export const meta: MetaFunction = () => {
|
||
return [
|
||
{ title: "待审核文件上传 - 中国烟草AI合同及卷宗审核系统" },
|
||
{
|
||
name: "description",
|
||
content: "上传待审核的合同文件、专卖许可证申请、行政处罚决定书等文档,进行AI智能审核"
|
||
},
|
||
{
|
||
name: "keywords",
|
||
content: "文件上传,合同审核,专卖许可证,行政处罚,AI审核,中国烟草"
|
||
}
|
||
];
|
||
};
|
||
|
||
// 文件类型定义
|
||
export enum FileType {
|
||
CONTRACT = "contract",
|
||
LICENSE = "license",
|
||
PUNISHMENT = "punishment",
|
||
OTHER = "other"
|
||
}
|
||
|
||
// 文件类型标签映射
|
||
export const FILE_TYPE_LABELS: Record<FileType, string> = {
|
||
[FileType.CONTRACT]: "合同文档",
|
||
[FileType.LICENSE]: "专卖许可证",
|
||
[FileType.PUNISHMENT]: "行政处罚决定书",
|
||
[FileType.OTHER]: "其他文档"
|
||
};
|
||
|
||
// 优先级定义
|
||
export enum Priority {
|
||
NORMAL = "normal",
|
||
HIGH = "high",
|
||
URGENT = "urgent"
|
||
}
|
||
|
||
// 优先级标签映射
|
||
export const PRIORITY_LABELS: Record<Priority, string> = {
|
||
[Priority.NORMAL]: "普通",
|
||
[Priority.HIGH]: "优先",
|
||
[Priority.URGENT]: "紧急"
|
||
};
|
||
|
||
// 处理状态定义
|
||
export enum ProcessingStatus {
|
||
WAITING = "waiting",
|
||
PROCESSING = "processing",
|
||
SUCCESS = "success",
|
||
ERROR = "error"
|
||
}
|
||
|
||
// 上传的文件信息接口
|
||
export interface UploadedFile {
|
||
id: string;
|
||
name: string;
|
||
size: number;
|
||
type: string;
|
||
fileType: FileType;
|
||
priority: Priority;
|
||
status: ProcessingStatus;
|
||
uploadTime: string;
|
||
processingInfo?: {
|
||
progress: number;
|
||
currentStep?: number;
|
||
};
|
||
}
|
||
|
||
// action处理文件上传请求
|
||
export async function action({ request }: ActionFunctionArgs) {
|
||
try {
|
||
const formData = await request.formData();
|
||
|
||
// 由于无法直接从Remix的action中处理文件上传,
|
||
// 实际环境中应使用FormData将文件发送到后端API
|
||
// 这里我们模拟处理过程,创建一个响应对象
|
||
|
||
const fileType = formData.get("fileType") as FileType;
|
||
const priority = formData.get("priority") as Priority;
|
||
|
||
if (!fileType) {
|
||
return Response.json(
|
||
{ success: false, error: "请选择文件类型" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 模拟文件上传成功响应
|
||
return Response.json({
|
||
success: true,
|
||
message: "文件上传请求已接收",
|
||
fileId: `file_${Date.now()}`,
|
||
fileType,
|
||
priority: priority || Priority.NORMAL,
|
||
});
|
||
} catch (error) {
|
||
console.error("文件上传失败:", error);
|
||
return Response.json(
|
||
{ success: false, error: "文件上传失败,请重试" },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|
||
|
||
// 上传队列中的文件列表模拟数据
|
||
const MOCK_QUEUE_FILES: UploadedFile[] = [
|
||
{
|
||
id: "1",
|
||
name: "烟草产品销售合同(2023版).pdf",
|
||
size: 5.2 * 1024 * 1024, // 5.2MB
|
||
type: "application/pdf",
|
||
fileType: FileType.CONTRACT,
|
||
priority: Priority.NORMAL,
|
||
status: ProcessingStatus.PROCESSING,
|
||
uploadTime: "2023-10-25 14:25:18",
|
||
processingInfo: {
|
||
progress: 60,
|
||
currentStep: 2
|
||
}
|
||
},
|
||
{
|
||
id: "2",
|
||
name: "专卖许可证申请表.docx",
|
||
size: 2.8 * 1024 * 1024, // 2.8MB
|
||
type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||
fileType: FileType.LICENSE,
|
||
priority: Priority.HIGH,
|
||
status: ProcessingStatus.WAITING,
|
||
uploadTime: "2023-10-25 14:28:45"
|
||
},
|
||
{
|
||
id: "3",
|
||
name: "XX公司违规处罚决定书.pdf",
|
||
size: 3.1 * 1024 * 1024, // 3.1MB
|
||
type: "application/pdf",
|
||
fileType: FileType.PUNISHMENT,
|
||
priority: Priority.NORMAL,
|
||
status: ProcessingStatus.SUCCESS,
|
||
uploadTime: "2023-10-25 14:15:30"
|
||
}
|
||
];
|
||
|
||
// 文件上传页面组件
|
||
export default function FilesUpload() {
|
||
// 状态管理
|
||
const [fileType, setFileType] = useState<FileType | "">("");
|
||
const [priority, setPriority] = useState<Priority>(Priority.NORMAL);
|
||
const [currentFile, setCurrentFile] = useState<File | null>(null);
|
||
const [uploadProgress, setUploadProgress] = useState(0);
|
||
const [uploadSpeed, setUploadSpeed] = useState("0KB/s");
|
||
const [uploadStage, setUploadStage] = useState<"idle" | "uploading" | "processing" | "completed">("idle");
|
||
const [processingSteps, setProcessingSteps] = useState<Step[]>([
|
||
{ title: "文件上传", description: "等待上传文件到服务器...", status: "waiting" },
|
||
{ title: "文档转换拆分", description: "转换文档格式,拆分文档内容", status: "waiting" },
|
||
{ title: "评查点抽取", description: "DeepSeek 抽取中", status: "waiting" },
|
||
{ title: "评查点审核", description: "DeepSeek 评查中", status: "waiting" },
|
||
{ title: "审核准备", description: "文档已准备就绪,等待审核", status: "waiting" }
|
||
]);
|
||
|
||
// 队列文件状态
|
||
const [queueFiles, setQueueFiles] = useState<UploadedFile[]>(MOCK_QUEUE_FILES);
|
||
|
||
// 上传完成后的文件信息
|
||
const [completedFile, setCompletedFile] = useState<UploadedFile | null>(null);
|
||
|
||
// 计时器引用
|
||
const progressIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||
const processingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||
|
||
// UploadArea组件引用
|
||
const uploadAreaRef = useRef<UploadAreaRef>(null);
|
||
|
||
// 清理定时器
|
||
useEffect(() => {
|
||
return () => {
|
||
if (progressIntervalRef.current) {
|
||
clearInterval(progressIntervalRef.current);
|
||
}
|
||
if (processingIntervalRef.current) {
|
||
clearInterval(processingIntervalRef.current);
|
||
}
|
||
};
|
||
}, []);
|
||
|
||
// 处理文件选择
|
||
const handleFilesSelected = useCallback((selectedFiles: FileList) => {
|
||
console.log("selectedFiles", selectedFiles);
|
||
if (selectedFiles.length === 0) return;
|
||
|
||
if (!fileType) {
|
||
alert("请先选择文件类型");
|
||
// 重置文件输入框
|
||
if (uploadAreaRef.current) {
|
||
uploadAreaRef.current.resetFileInput();
|
||
}
|
||
return;
|
||
}
|
||
|
||
setCurrentFile(selectedFiles[0]);
|
||
console.log("currentFile", currentFile);
|
||
startUpload(selectedFiles[0]);
|
||
}, [fileType, currentFile]);
|
||
|
||
// 开始上传文件
|
||
const startUpload = (file: File) => {
|
||
setUploadStage("uploading");
|
||
setUploadProgress(0);
|
||
|
||
// 更新步骤状态
|
||
const updatedSteps = [...processingSteps];
|
||
updatedSteps[0].status = "active";
|
||
updatedSteps[0].description = `正在上传文件"${file.name}"到服务器...`;
|
||
setProcessingSteps(updatedSteps);
|
||
|
||
// 模拟上传进度
|
||
if (progressIntervalRef.current) {
|
||
clearInterval(progressIntervalRef.current);
|
||
}
|
||
|
||
progressIntervalRef.current = setInterval(() => {
|
||
setUploadProgress(prev => {
|
||
const newProgress = prev + 5;
|
||
// 根据文件大小调整上传速度
|
||
const speedFactor = Math.min(file.size / (1024 * 1024) + 1, 5);
|
||
setUploadSpeed(`${Math.floor(Math.random() * 100 * speedFactor) + 50}KB/s`);
|
||
|
||
if (newProgress >= 100) {
|
||
if (progressIntervalRef.current) {
|
||
clearInterval(progressIntervalRef.current);
|
||
setUploadSpeed("完成");
|
||
|
||
// 完成上传后开始处理流程
|
||
startProcessing();
|
||
}
|
||
return 100;
|
||
}
|
||
|
||
return newProgress;
|
||
});
|
||
}, 200);
|
||
};
|
||
|
||
// 开始处理文件
|
||
const startProcessing = () => {
|
||
setUploadStage("processing");
|
||
|
||
// 更新步骤状态 - 将第一步标记为完成
|
||
const updatedSteps = [...processingSteps];
|
||
updatedSteps[0].status = "done";
|
||
updatedSteps[0].description = "文件上传已完成";
|
||
setProcessingSteps(updatedSteps);
|
||
|
||
let currentStepIndex = 1;
|
||
|
||
if (processingIntervalRef.current) {
|
||
clearInterval(processingIntervalRef.current);
|
||
}
|
||
|
||
processingIntervalRef.current = setInterval(() => {
|
||
if (currentStepIndex >= processingSteps.length) {
|
||
if (processingIntervalRef.current) {
|
||
clearInterval(processingIntervalRef.current);
|
||
completeProcessing();
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 更新当前步骤为活动状态
|
||
const updatedSteps = [...processingSteps];
|
||
updatedSteps[currentStepIndex].status = "active";
|
||
setProcessingSteps(updatedSteps);
|
||
|
||
// 2.5秒后标记当前步骤为完成,进入下一步骤
|
||
setTimeout(() => {
|
||
const nextUpdatedSteps = [...processingSteps];
|
||
nextUpdatedSteps[currentStepIndex].status = "done";
|
||
|
||
// 更新完成状态的描述
|
||
switch(currentStepIndex) {
|
||
case 1:
|
||
nextUpdatedSteps[currentStepIndex].description = "转换文档格式并拆分文档完成";
|
||
break;
|
||
case 2:
|
||
nextUpdatedSteps[currentStepIndex].description = "DeepSeek 抽取已完成";
|
||
break;
|
||
case 3:
|
||
nextUpdatedSteps[currentStepIndex].description = "DeepSeek 评查已完成";
|
||
break;
|
||
case 4:
|
||
nextUpdatedSteps[currentStepIndex].description = "审核准备已就绪";
|
||
break;
|
||
}
|
||
|
||
setProcessingSteps(nextUpdatedSteps);
|
||
currentStepIndex++;
|
||
|
||
// 如果这是最后一个步骤,确保完成
|
||
// if (currentStepIndex >= processingSteps.length) {
|
||
// setTimeout(() => {
|
||
// completeProcessing();
|
||
// }, 1000);
|
||
// }
|
||
}, 2000);
|
||
|
||
}, 2500);
|
||
};
|
||
|
||
// 完成处理流程
|
||
const completeProcessing = () => {
|
||
// 设置当前状态为已完成
|
||
setUploadStage("completed");
|
||
|
||
// 创建完成的文件对象
|
||
if (currentFile) {
|
||
console.log("创建完成的文件对象...");
|
||
const newFile: UploadedFile = {
|
||
id: `file_${Date.now()}`,
|
||
name: currentFile.name,
|
||
size: currentFile.size,
|
||
type: currentFile.type,
|
||
fileType: fileType as FileType,
|
||
priority,
|
||
status: ProcessingStatus.SUCCESS,
|
||
uploadTime: getCurrentTime()
|
||
};
|
||
|
||
setCompletedFile(newFile);
|
||
console.log("完成文件设置:", newFile);
|
||
|
||
// 添加到队列中
|
||
setQueueFiles(prev => [newFile, ...prev]);
|
||
} else {
|
||
console.log("没有当前文件");
|
||
}
|
||
};
|
||
|
||
// 重置上传状态
|
||
const resetUpload = () => {
|
||
setUploadStage("idle");
|
||
setUploadProgress(0);
|
||
setUploadSpeed("0KB/s");
|
||
setCurrentFile(null);
|
||
setCompletedFile(null);
|
||
|
||
// 重置步骤状态
|
||
const resetSteps = processingSteps.map(step => ({
|
||
...step,
|
||
status: "waiting" as Step["status"]
|
||
}));
|
||
resetSteps[0].description = "等待上传文件到服务器...";
|
||
resetSteps[1].description = "转换文档格式,拆分文档内容";
|
||
resetSteps[2].description = "DeepSeek 抽取中";
|
||
resetSteps[3].description = "DeepSeek 评查中";
|
||
resetSteps[4].description = "文档已准备就绪,等待审核";
|
||
|
||
setProcessingSteps(resetSteps);
|
||
};
|
||
|
||
// 获取当前时间字符串
|
||
const getCurrentTime = () => {
|
||
const now = new Date();
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||
const day = String(now.getDate()).padStart(2, '0');
|
||
const hours = String(now.getHours()).padStart(2, '0');
|
||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||
|
||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||
};
|
||
|
||
// 格式化文件大小显示
|
||
const formatFileSize = (bytes: number) => {
|
||
if (bytes === 0) return '0 Bytes';
|
||
|
||
const k = 1024;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
};
|
||
|
||
// 表格列定义
|
||
const columns = [
|
||
{
|
||
title: "文件名",
|
||
key: "name",
|
||
width: "40%",
|
||
render: (_: unknown, record: UploadedFile) => (
|
||
<div className="flex items-center">
|
||
<i className={`${record.type.includes('pdf') ? 'ri-file-pdf-line text-red-500' : 'ri-file-word-2-line text-blue-500'} mr-2 text-lg`}></i>
|
||
<span className="truncate">{record.name}</span>
|
||
</div>
|
||
)
|
||
},
|
||
{
|
||
title: "文件类型",
|
||
key: "fileType",
|
||
width: "15%",
|
||
render: (_: unknown, record: UploadedFile) => {
|
||
let typeClass = "";
|
||
let typeIcon = "";
|
||
|
||
switch(record.fileType) {
|
||
case FileType.CONTRACT:
|
||
typeClass = "file-type-contract";
|
||
typeIcon = "ri-file-list-3-line";
|
||
break;
|
||
case FileType.LICENSE:
|
||
typeClass = "file-type-license";
|
||
typeIcon = "ri-vip-crown-line";
|
||
break;
|
||
case FileType.PUNISHMENT:
|
||
typeClass = "file-type-punishment";
|
||
typeIcon = "ri-scales-line";
|
||
break;
|
||
default:
|
||
typeClass = "file-type-contract";
|
||
typeIcon = "ri-file-list-3-line";
|
||
}
|
||
|
||
return (
|
||
<span className={`file-type-badge ${typeClass}`}>
|
||
<i className={typeIcon}></i>
|
||
{FILE_TYPE_LABELS[record.fileType]}
|
||
</span>
|
||
);
|
||
}
|
||
},
|
||
{
|
||
title: "大小",
|
||
key: "size",
|
||
width: "15%",
|
||
render: (_: unknown, record: UploadedFile) => formatFileSize(record.size)
|
||
},
|
||
{
|
||
title: "状态",
|
||
key: "status",
|
||
width: "15%",
|
||
render: (_: unknown, record: UploadedFile) => {
|
||
let statusClass = "";
|
||
let statusIcon = "";
|
||
let statusText = "";
|
||
|
||
switch(record.status) {
|
||
case ProcessingStatus.WAITING:
|
||
statusClass = "status-waiting";
|
||
statusIcon = "ri-time-line";
|
||
statusText = "等待中";
|
||
break;
|
||
case ProcessingStatus.PROCESSING:
|
||
statusClass = "status-processing";
|
||
statusIcon = "ri-loader-4-line";
|
||
statusText = "解析中";
|
||
break;
|
||
case ProcessingStatus.SUCCESS:
|
||
statusClass = "status-success";
|
||
statusIcon = "ri-checkbox-circle-line";
|
||
statusText = "已完成";
|
||
break;
|
||
case ProcessingStatus.ERROR:
|
||
statusClass = "status-error";
|
||
statusIcon = "ri-error-warning-line";
|
||
statusText = "失败";
|
||
break;
|
||
}
|
||
|
||
return (
|
||
<span className={`status-badge ${statusClass}`}>
|
||
<i className={`${statusIcon} mr-1`}></i>
|
||
{statusText}
|
||
</span>
|
||
);
|
||
}
|
||
},
|
||
{
|
||
title: "操作",
|
||
key: "operation",
|
||
width: "15%",
|
||
render: (_: unknown, record: UploadedFile) => (
|
||
<Button
|
||
type="default"
|
||
size="small"
|
||
disabled={record.status !== ProcessingStatus.SUCCESS}
|
||
icon="ri-eye-line"
|
||
onClick={() => alert(`查看文件详情: ${record.name}`)}
|
||
>
|
||
查看
|
||
</Button>
|
||
)
|
||
}
|
||
];
|
||
|
||
return (
|
||
<div className="file-upload-page">
|
||
{/* 页面头部 */}
|
||
<div className="page-header">
|
||
<h2 className="page-title">待审核文件上传</h2>
|
||
</div>
|
||
|
||
{/* 文件类型选择 */}
|
||
<Card title={<h3>选择文件类型</h3>} className="mb-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div className="form-group">
|
||
<label htmlFor="file-type-select" className="form-label">文件类型 <span className="text-red-500">*</span></label>
|
||
<select
|
||
id="file-type-select"
|
||
className="form-select"
|
||
value={fileType}
|
||
onChange={(e) => setFileType(e.target.value as FileType)}
|
||
disabled={uploadStage !== "idle"}
|
||
>
|
||
<option value="">请选择文件类型</option>
|
||
<option value={FileType.CONTRACT}>合同文档</option>
|
||
<option value={FileType.LICENSE}>专卖许可证</option>
|
||
<option value={FileType.PUNISHMENT}>行政处罚决定书</option>
|
||
<option value={FileType.OTHER}>其他文档</option>
|
||
</select>
|
||
<div className="form-tip">不同类型的文档将应用不同的审核规则</div>
|
||
</div>
|
||
<div className="form-group">
|
||
<label htmlFor="priority-select" className="form-label">审核优先级</label>
|
||
<select
|
||
id="priority-select"
|
||
className="form-select"
|
||
value={priority}
|
||
onChange={(e) => setPriority(e.target.value as Priority)}
|
||
disabled={uploadStage !== "idle"}
|
||
>
|
||
<option value={Priority.NORMAL}>普通</option>
|
||
<option value={Priority.HIGH}>优先</option>
|
||
<option value={Priority.URGENT}>紧急</option>
|
||
</select>
|
||
<div className="form-tip">优先级影响文档在队列中的处理顺序</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 文件上传区域 */}
|
||
<Card title={<h3>文件上传</h3>} className="mb-4">
|
||
{/* 初始上传区域 */}
|
||
{uploadStage === "idle" && (
|
||
<UploadArea
|
||
ref={uploadAreaRef}
|
||
onFilesSelected={handleFilesSelected}
|
||
multiple={false}
|
||
accept=".pdf,.doc,.docx,.xls,.xlsx,.jpg,.jpeg,.png"
|
||
tipText="支持单个或批量上传,文件格式:PDF、Word、Excel、图片"
|
||
/>
|
||
)}
|
||
|
||
{/* 上传进度显示 */}
|
||
{uploadStage !== "completed" && currentFile && (
|
||
<FileProgress
|
||
fileName={currentFile.name}
|
||
fileSize={formatFileSize(currentFile.size)}
|
||
progress={uploadProgress}
|
||
speed={uploadSpeed}
|
||
/>
|
||
)}
|
||
|
||
{/* 处理步骤显示 */}
|
||
{(uploadStage === "processing" || uploadStage === "completed") && (
|
||
<div className="mt-4 mb-4">
|
||
<ProcessingSteps steps={processingSteps} />
|
||
</div>
|
||
)}
|
||
|
||
{/* 完成后的文件信息 */}
|
||
{uploadStage === "completed" && completedFile && (
|
||
<div className="mt-6">
|
||
<div className="bg-green-50 p-4 rounded-md mb-4 border border-green-100">
|
||
<div className="flex items-center text-green-800 mb-2">
|
||
<i className="ri-checkbox-circle-line text-xl mr-2"></i>
|
||
<span className="font-medium">评查成功</span>
|
||
</div>
|
||
<p className="text-sm text-green-700">文件已成功上传并评查完成,请查看结果</p>
|
||
</div>
|
||
|
||
<div className="file-info-grid">
|
||
<div>
|
||
<h4 className="font-medium mb-3">文件信息</h4>
|
||
<ul className="file-info-list">
|
||
<li className="file-info-item">
|
||
<span className="file-info-label">文件名:</span>
|
||
<span className="file-info-value">{completedFile.name}</span>
|
||
</li>
|
||
<li className="file-info-item">
|
||
<span className="file-info-label">文件大小:</span>
|
||
<span className="file-info-value">{formatFileSize(completedFile.size)}</span>
|
||
</li>
|
||
<li className="file-info-item">
|
||
<span className="file-info-label">上传时间:</span>
|
||
<span className="file-info-value">{completedFile.uploadTime}</span>
|
||
</li>
|
||
<li className="file-info-item">
|
||
<span className="file-info-label">文件类型:</span>
|
||
<span className="file-info-value">{FILE_TYPE_LABELS[completedFile.fileType]}</span>
|
||
</li>
|
||
<li className="file-info-item">
|
||
<span className="file-info-label">审核规则:</span>
|
||
<span className="file-info-value">系统自动选择</span>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div>
|
||
<h4 className="font-medium mb-3">解析结果预览</h4>
|
||
<div className="bg-gray-50 p-3 rounded-md border border-gray-200">
|
||
<div className="mb-2">
|
||
<span className="text-gray-500 text-sm">合同编号:</span>
|
||
<span className="pulse-animation">XS-2023-1025-001</span>
|
||
</div>
|
||
<div className="mb-2">
|
||
<span className="text-gray-500 text-sm">合同名称:</span>
|
||
<span className="pulse-animation">烟草制品销售合同</span>
|
||
</div>
|
||
<div className="mb-2">
|
||
<span className="text-gray-500 text-sm">签约日期:</span>
|
||
<span className="pulse-animation">2023年10月20日</span>
|
||
</div>
|
||
<div className="mb-2">
|
||
<span className="text-gray-500 text-sm">合同金额:</span>
|
||
<span className="pulse-animation">¥ 1,580,000.00</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-500 text-sm">当事人:</span>
|
||
<span className="pulse-animation">甲方:XX烟草公司,乙方:YY贸易有限公司</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-4 flex justify-between">
|
||
<Button
|
||
type="default"
|
||
icon="ri-refresh-line"
|
||
onClick={resetUpload}
|
||
>
|
||
上传新文件
|
||
</Button>
|
||
<Button
|
||
type="primary"
|
||
icon="ri-file-search-line"
|
||
>
|
||
查看详情并审核
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
|
||
{/* 上传队列 */}
|
||
<Card
|
||
title={
|
||
<div className="flex justify-between items-center">
|
||
<h3>上传队列</h3>
|
||
<span className="text-gray-500 text-sm">共 {queueFiles.length} 个文件</span>
|
||
</div>
|
||
}
|
||
>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={queueFiles}
|
||
rowKey="id"
|
||
emptyText="暂无上传文件"
|
||
/>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export function ErrorBoundary() {
|
||
return (
|
||
<div className="error-container p-6">
|
||
<h1 className="text-xl font-bold text-red-500 mb-4">出错了</h1>
|
||
<p className="mb-4">文件上传页面加载失败。请刷新页面或联系系统管理员。</p>
|
||
<Button type="primary" to="/">返回首页</Button>
|
||
</div>
|
||
);
|
||
}
|