1658bb1c6f
2.文档上传通过接口去查询是否存在同名的文件,做上传前拦截提示。 3.交叉评查的评查结果也同步添加企查查的企业信息查询模块。 4. 封装上传附件和上传模板的模态框的组件,在交叉评查的文档列表中引入显示。 5. 交叉评查的评查结果中关于合同类型的文档同步加入结构比对的功能。
324 lines
11 KiB
TypeScript
324 lines
11 KiB
TypeScript
import { useState } from "react";
|
||
import { messageService } from "./MessageModal";
|
||
|
||
// 格式化文件大小
|
||
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];
|
||
};
|
||
|
||
export interface AttachmentUploadModalProps {
|
||
/** 是否显示 */
|
||
isOpen: boolean;
|
||
/** 关闭回调 */
|
||
onClose: () => void;
|
||
/** 目标文档ID */
|
||
documentId: number | null;
|
||
/** 目标文档名称 */
|
||
documentName: string | null;
|
||
/** 目标文档版本号(可选) */
|
||
documentVersion?: number | null;
|
||
/** 主文件路径(用于判断是否为docx) */
|
||
mainFilePath?: string;
|
||
/** 上传回调 */
|
||
onUpload: (files: File[], mergeMode: 'overwrite' | 'new', remark: string) => Promise<void>;
|
||
/** 是否正在上传 */
|
||
uploading?: boolean;
|
||
/** 标题(可选,默认"追加合同附件") */
|
||
title?: string;
|
||
/** 支持的文件格式描述(可选) */
|
||
supportedFormatsDesc?: string;
|
||
/** 是否显示合并模式选择(可选,默认false,仅显示新建模式) */
|
||
showMergeMode?: boolean;
|
||
}
|
||
|
||
export function AttachmentUploadModal({
|
||
isOpen,
|
||
onClose,
|
||
documentId,
|
||
documentName,
|
||
documentVersion,
|
||
mainFilePath,
|
||
onUpload,
|
||
uploading = false,
|
||
title = "追加合同附件",
|
||
supportedFormatsDesc = "支持.pdf、.docx、ZIP、RAR格式。ZIP/RAR内需要保证文件格式一致,否则报错",
|
||
showMergeMode = false
|
||
}: AttachmentUploadModalProps) {
|
||
// 附件文件列表
|
||
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
|
||
// 合并模式
|
||
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('new');
|
||
// 备注
|
||
const [attachmentRemark, setAttachmentRemark] = useState<string>("");
|
||
// 拖拽状态
|
||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||
|
||
// 重置状态
|
||
const resetState = () => {
|
||
setAttachmentFiles([]);
|
||
setAttachmentRemark("");
|
||
setIsDragging(false);
|
||
};
|
||
|
||
// 关闭处理
|
||
const handleClose = () => {
|
||
resetState();
|
||
onClose();
|
||
};
|
||
|
||
// 处理文件选择
|
||
const handleFilesSelected = (files: FileList) => {
|
||
try {
|
||
if (files.length > 0) {
|
||
// 检查主文件类型
|
||
const isMainFileDocx = mainFilePath?.toLowerCase().endsWith('.docx');
|
||
|
||
// 验证文件类型
|
||
const validFiles: File[] = [];
|
||
let hasInvalidFiles = false;
|
||
let hasPdfForDocx = false;
|
||
|
||
Array.from(files).forEach(file => {
|
||
const fileName = file.name.toLowerCase();
|
||
const isPdf = file.type === 'application/pdf' || fileName.endsWith('.pdf');
|
||
const isValidType =
|
||
isPdf ||
|
||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || fileName.endsWith('.docx') ||
|
||
file.type === 'application/zip' || fileName.endsWith('.zip') ||
|
||
file.type === 'application/x-rar-compressed' || fileName.endsWith('.rar');
|
||
|
||
// 如果主文件是docx,不允许上传pdf附件
|
||
if (isMainFileDocx && isPdf) {
|
||
hasPdfForDocx = true;
|
||
return;
|
||
}
|
||
|
||
if (isValidType) {
|
||
validFiles.push(file);
|
||
} else {
|
||
hasInvalidFiles = true;
|
||
}
|
||
});
|
||
|
||
if (hasPdfForDocx) {
|
||
messageService.error('主文件为DOCX格式时,附件不可以是PDF格式', {
|
||
title: '文件类型限制',
|
||
confirmText: '确定',
|
||
cancelText: '',
|
||
});
|
||
} else if (hasInvalidFiles) {
|
||
messageService.error('只支持PDF、Word、ZIP、RAR格式的文件', {
|
||
title: '文件类型错误',
|
||
confirmText: '确定',
|
||
cancelText: '',
|
||
});
|
||
}
|
||
|
||
if (validFiles.length > 0) {
|
||
setAttachmentFiles(validFiles);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('处理文件选择时发生错误:', error);
|
||
}
|
||
};
|
||
|
||
// 处理上传
|
||
const handleUpload = async () => {
|
||
if (!documentId || attachmentFiles.length === 0) {
|
||
return;
|
||
}
|
||
await onUpload(attachmentFiles, attachmentMergeMode, attachmentRemark);
|
||
resetState();
|
||
};
|
||
|
||
// 拖拽事件处理
|
||
const handleDragEnter = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
setIsDragging(true);
|
||
};
|
||
|
||
const handleDragOver = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
};
|
||
|
||
const handleDragLeave = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
setIsDragging(false);
|
||
};
|
||
|
||
const handleDrop = (e: React.DragEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
setIsDragging(false);
|
||
|
||
const files = e.dataTransfer.files;
|
||
if (files && files.length > 0) {
|
||
handleFilesSelected(files);
|
||
}
|
||
};
|
||
|
||
if (!isOpen) return null;
|
||
|
||
return (
|
||
<div
|
||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]"
|
||
onClick={handleClose}
|
||
>
|
||
<div
|
||
className="bg-white rounded-lg p-6 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto"
|
||
onClick={(e) => e.stopPropagation()}
|
||
>
|
||
<div className="flex justify-between items-center mb-4">
|
||
<h3 className="text-lg font-semibold">{title}</h3>
|
||
<button
|
||
onClick={handleClose}
|
||
className="text-gray-400 hover:text-gray-600"
|
||
>
|
||
<i className="ri-close-line text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
{/* 文档信息 */}
|
||
<div className="bg-gray-50 p-3 rounded">
|
||
<p className="text-sm text-gray-600">
|
||
目标文档名称: <span className="font-medium">{documentName}</span>
|
||
{documentVersion && (
|
||
<span className="ml-2 text-xs bg-blue-100 text-blue-800 px-2 py-1 rounded">
|
||
v{documentVersion}
|
||
</span>
|
||
)}
|
||
</p>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
<i className="ri-information-line mr-1"></i>
|
||
{supportedFormatsDesc}
|
||
</p>
|
||
</div>
|
||
|
||
{/* 文件上传区域 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
选择附件文件 <span className="text-red-500">*</span>
|
||
</label>
|
||
<div
|
||
className={`border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
|
||
isDragging
|
||
? 'border-primary bg-primary-light'
|
||
: 'border-gray-300 hover:border-gray-400'
|
||
}`}
|
||
onDragEnter={handleDragEnter}
|
||
onDragOver={handleDragOver}
|
||
onDragLeave={handleDragLeave}
|
||
onDrop={handleDrop}
|
||
>
|
||
<input
|
||
type="file"
|
||
multiple
|
||
accept=".pdf,.doc,.docx,.zip,.rar"
|
||
onChange={(e) => e.target.files && handleFilesSelected(e.target.files)}
|
||
className="hidden"
|
||
id="attachment-file-input-modal"
|
||
/>
|
||
<label htmlFor="attachment-file-input-modal" className="cursor-pointer">
|
||
<i className={`ri-attachment-line text-3xl mb-2 block ${isDragging ? 'text-primary' : 'text-gray-400'}`}></i>
|
||
<p className={`text-sm ${isDragging ? 'text-primary font-medium' : 'text-gray-600'}`}>
|
||
{isDragging ? '松开鼠标上传文件' : '点击选择文件或拖拽文件到此处'}
|
||
</p>
|
||
<p className="text-xs text-gray-500 mt-1">支持.pdf、.docx、.zip、.rar格式,可多选</p>
|
||
</label>
|
||
</div>
|
||
{attachmentFiles.length > 0 && (
|
||
<div className="mt-2">
|
||
<p className="text-sm text-green-600 mb-2">
|
||
<i className="ri-checkbox-circle-line"></i> 已选择 {attachmentFiles.length} 个文件
|
||
</p>
|
||
<div className="space-y-1 max-h-32 overflow-y-auto">
|
||
{attachmentFiles.map((file, index) => (
|
||
<div key={index} className="text-xs text-gray-600 bg-gray-50 p-2 rounded">
|
||
<i className="ri-file-line mr-1"></i>
|
||
{file.name} ({formatFileSize(file.size)})
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 合并模式选择(可选) */}
|
||
{showMergeMode && (
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
合并模式
|
||
</label>
|
||
<div className="space-y-2">
|
||
<label className="flex items-center">
|
||
<input
|
||
type="radio"
|
||
name="mergeMode"
|
||
value="overwrite"
|
||
checked={attachmentMergeMode === 'overwrite'}
|
||
onChange={(e) => setAttachmentMergeMode(e.target.value as 'overwrite' | 'new')}
|
||
className="mr-2"
|
||
/>
|
||
<span className="text-sm">覆盖原文档</span>
|
||
</label>
|
||
<label className="flex items-center">
|
||
<input
|
||
type="radio"
|
||
name="mergeMode"
|
||
value="new"
|
||
checked={attachmentMergeMode === 'new'}
|
||
onChange={(e) => setAttachmentMergeMode(e.target.value as 'overwrite' | 'new')}
|
||
className="mr-2"
|
||
/>
|
||
<span className="text-sm">新建文档记录</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 备注 */}
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
备注(可选)
|
||
</label>
|
||
<textarea
|
||
value={attachmentRemark}
|
||
onChange={(e) => setAttachmentRemark(e.target.value)}
|
||
className="w-full p-2 border border-gray-300 rounded-md text-sm"
|
||
rows={3}
|
||
placeholder="请输入备注信息..."
|
||
/>
|
||
</div>
|
||
|
||
{/* 操作按钮 */}
|
||
<div className="flex justify-end gap-3 pt-4 border-t">
|
||
<button
|
||
className="px-4 py-2 text-sm border border-gray-300 rounded-md hover:bg-gray-50"
|
||
onClick={handleClose}
|
||
disabled={uploading}
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
className="px-4 py-2 text-sm bg-primary text-white rounded-md hover:bg-primary-dark disabled:opacity-50"
|
||
onClick={handleUpload}
|
||
disabled={attachmentFiles.length === 0 || uploading}
|
||
>
|
||
{uploading ? '上传中...' : '开始追加'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|