新增附件追加和合同模板上传功能,支持文件选择、验证及上传逻辑,优化用户界面和操作体验。
This commit is contained in:
@@ -13,6 +13,7 @@ import documentsIndexStyles from "~/styles/pages/documents_index.css?url";
|
||||
import { getDocuments, deleteDocument, type DocumentUI } from "~/api/files/documents";
|
||||
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||
import { updateDocumentAuditStatus } from "~/api/evaluation_points/rules-files";
|
||||
import { appendContractAttachments, uploadContractTemplate } from "~/api/files/files-upload";
|
||||
import { toastService } from "~/components/ui/Toast";
|
||||
import { messageService } from "~/components/ui/MessageModal";
|
||||
import { loadingBarService } from "~/components/ui/LoadingBar";
|
||||
@@ -192,6 +193,17 @@ export default function DocumentsIndex() {
|
||||
// 添加一个状态来跟踪是否执行了删除操作
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
// 附件追加和模板上传状态
|
||||
const [showAttachmentUpload, setShowAttachmentUpload] = useState<boolean>(false);
|
||||
const [showTemplateUpload, setShowTemplateUpload] = useState<boolean>(false);
|
||||
const [selectedDocumentId, setSelectedDocumentId] = useState<number | null>(null);
|
||||
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]);
|
||||
const [templateFile, setTemplateFile] = useState<File | null>(null);
|
||||
const [attachmentMergeMode, setAttachmentMergeMode] = useState<'overwrite' | 'new'>('overwrite');
|
||||
const [attachmentRemark, setAttachmentRemark] = useState<string>("");
|
||||
const [attachmentUploading, setAttachmentUploading] = useState<boolean>(false);
|
||||
const [templateUploading, setTemplateUploading] = useState<boolean>(false);
|
||||
|
||||
// 查询参数记忆 key 与保存/恢复方法
|
||||
const SEARCH_PARAMS_STORAGE_KEY = 'documents.searchParams';
|
||||
const persistSearchParams = (params: URLSearchParams) => {
|
||||
@@ -720,6 +732,166 @@ export default function DocumentsIndex() {
|
||||
navigate(`/reviews?id=${fileId}&previousRoute=documents`);
|
||||
};
|
||||
|
||||
// 处理附件追加文件选择
|
||||
const handleAttachmentFilesSelected = (files: FileList) => {
|
||||
try {
|
||||
console.log('【附件追加】开始处理附件文件选择, 文件数量:', files.length);
|
||||
|
||||
if (files.length > 0) {
|
||||
// 验证文件类型,支持PDF、Word、ZIP、RAR
|
||||
const validFiles: File[] = [];
|
||||
let hasInvalidFiles = false;
|
||||
|
||||
Array.from(files).forEach(file => {
|
||||
const fileName = file.name.toLowerCase();
|
||||
const isValidType =
|
||||
file.type === 'application/pdf' || fileName.endsWith('.pdf') ||
|
||||
file.type === 'application/msword' || fileName.endsWith('.doc') ||
|
||||
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');
|
||||
|
||||
if (isValidType) {
|
||||
validFiles.push(file);
|
||||
} else {
|
||||
hasInvalidFiles = true;
|
||||
console.error(`【附件追加】无效的文件类型: ${file.name}, 类型: ${file.type}`);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasInvalidFiles) {
|
||||
messageService.error('只支持PDF、Word、ZIP、RAR格式的文件', {
|
||||
title: '文件类型错误',
|
||||
confirmText: '确定',
|
||||
cancelText: '',
|
||||
});
|
||||
}
|
||||
|
||||
if (validFiles.length > 0) {
|
||||
setAttachmentFiles(validFiles);
|
||||
console.log('【附件追加】有效文件数量:', validFiles.length);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('【附件追加】处理文件选择时发生错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理附件追加上传
|
||||
const handleAttachmentUpload = async () => {
|
||||
if (!selectedDocumentId || attachmentFiles.length === 0) {
|
||||
toastService.error('请选择文档和附件文件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setAttachmentUploading(true);
|
||||
|
||||
const result = await appendContractAttachments(
|
||||
selectedDocumentId,
|
||||
attachmentFiles,
|
||||
attachmentMergeMode,
|
||||
true, // isReprocess
|
||||
attachmentRemark || undefined,
|
||||
loaderData.userInfo?.token as string | undefined
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
toastService.success('附件追加成功!');
|
||||
|
||||
// 重置状态
|
||||
setAttachmentFiles([]);
|
||||
setAttachmentRemark("");
|
||||
setShowAttachmentUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
|
||||
// 刷新文档列表
|
||||
if (reviewType) {
|
||||
fetchData(reviewType);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('【附件追加】上传失败:', error);
|
||||
toastService.error(error instanceof Error ? error.message : '附件追加失败');
|
||||
} finally {
|
||||
setAttachmentUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理合同模板文件选择
|
||||
const handleTemplateFileSelected = (files: FileList) => {
|
||||
try {
|
||||
console.log('【合同模板上传】开始处理模板文件选择, 文件数量:', files.length);
|
||||
|
||||
if (files.length > 0) {
|
||||
const file = files[0];
|
||||
// 验证文件类型,支持PDF和Word
|
||||
const fileName = file.name.toLowerCase();
|
||||
const isValidType =
|
||||
file.type === 'application/pdf' || fileName.endsWith('.pdf') ||
|
||||
file.type === 'application/msword' || fileName.endsWith('.doc') ||
|
||||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || fileName.endsWith('.docx');
|
||||
|
||||
if (isValidType) {
|
||||
setTemplateFile(file);
|
||||
console.log('【合同模板上传】有效文件:', file.name);
|
||||
} else {
|
||||
messageService.error('只支持PDF、Word格式的文件', {
|
||||
title: '文件类型错误',
|
||||
confirmText: '确定',
|
||||
cancelText: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('【合同模板上传】处理文件选择时发生错误:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理合同模板上传
|
||||
const handleTemplateUpload = async () => {
|
||||
if (!selectedDocumentId || !templateFile) {
|
||||
toastService.error('请选择文档和模板文件');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setTemplateUploading(true);
|
||||
|
||||
const result = await uploadContractTemplate(
|
||||
templateFile,
|
||||
selectedDocumentId,
|
||||
undefined, // comparisonId
|
||||
loaderData.userInfo?.token as string | undefined
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
|
||||
toastService.success('合同模板上传成功!');
|
||||
|
||||
// 重置状态
|
||||
setTemplateFile(null);
|
||||
setShowTemplateUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
|
||||
// 刷新文档列表
|
||||
if (reviewType) {
|
||||
fetchData(reviewType);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('【合同模板上传】上传失败:', error);
|
||||
toastService.error(error instanceof Error ? error.message : '合同模板上传失败');
|
||||
} finally {
|
||||
setTemplateUploading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
@@ -842,14 +1014,14 @@ export default function DocumentsIndex() {
|
||||
{
|
||||
title: "操作",
|
||||
key: "actions",
|
||||
width: "20%",
|
||||
width: "25%",
|
||||
render: (_: unknown, record: DocumentUI) => (
|
||||
<div className="operations-cell">
|
||||
<div className="operations-cell flex flex-wrap gap-1">
|
||||
{(record.auditStatus === 0 || record.auditStatus == null) ? (
|
||||
<button
|
||||
onClick={() => handleReviewFileClick(record.id, record.auditStatus)}
|
||||
disabled={record.fileStatus !== 'Processed'}
|
||||
className={`mr-1 ${
|
||||
className={`text-xs px-2 py-1 h-7 mr-1 ${
|
||||
record.fileStatus === 'Processed'
|
||||
? 'hover:underline hover:cursor-pointer text-primary'
|
||||
: 'text-gray-400 cursor-not-allowed opacity-60'
|
||||
@@ -862,7 +1034,7 @@ export default function DocumentsIndex() {
|
||||
//record.auditStatus === 3 目前这个状态不存在,所以除了待审核(0)-开始审核,其他都是审核中(2)-查看
|
||||
<Link
|
||||
to={`/documents/${record.id}/progress`}
|
||||
className="mr-1 hover:underline"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 hover:underline"
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
查看进度
|
||||
@@ -870,7 +1042,7 @@ export default function DocumentsIndex() {
|
||||
) : (
|
||||
<Link
|
||||
to={`/reviews?id=${record.id}&previousRoute=documents`}
|
||||
className="mr-1 hover:underline"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 hover:underline"
|
||||
>
|
||||
<i className="ri-eye-line"></i>
|
||||
查看
|
||||
@@ -878,22 +1050,48 @@ export default function DocumentsIndex() {
|
||||
)}
|
||||
<Link
|
||||
to={`/documents/edit?id=${record.id}`}
|
||||
className="mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||
>
|
||||
<i className="ri-edit-line"></i>
|
||||
修改
|
||||
</Link>
|
||||
<button
|
||||
type="button"
|
||||
className="mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 text-gray-500 hover:underline hover:text-gray-700"
|
||||
onClick={() => handleDownload(record.path)}
|
||||
>
|
||||
<i className="ri-download-line"></i>
|
||||
下载
|
||||
</button>
|
||||
{record.type === 1 && record.fileStatus === 'Processed' && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 bg-primary text-white hover:bg-primary-dark rounded"
|
||||
onClick={() => {
|
||||
setSelectedDocumentId(record.id);
|
||||
setShowAttachmentUpload(true);
|
||||
}}
|
||||
>
|
||||
<i className="ri-attachment-line"></i>
|
||||
追加附件
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="text-xs px-2 py-1 h-7 mr-1 bg-gray-100 text-gray-700 hover:bg-gray-200 rounded"
|
||||
onClick={() => {
|
||||
setSelectedDocumentId(record.id);
|
||||
setShowTemplateUpload(true);
|
||||
}}
|
||||
>
|
||||
<i className="ri-file-copy-line"></i>
|
||||
上传模板
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="text-error hover:underline hover:text-red-700"
|
||||
className="text-xs px-2 py-1 h-7 text-error hover:underline hover:text-red-700"
|
||||
onClick={() => handleDelete(record.id.toString(), record.name, record.fileStatus)}
|
||||
>
|
||||
<i className="ri-delete-bin-line"></i>
|
||||
@@ -1055,6 +1253,232 @@ export default function DocumentsIndex() {
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* 附件追加模态框 */}
|
||||
{showAttachmentUpload && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-2xl mx-4 max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">追加合同附件</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowAttachmentUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
setAttachmentFiles([]);
|
||||
setAttachmentRemark("");
|
||||
}}
|
||||
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">
|
||||
目标文档ID: <span className="font-medium">{selectedDocumentId}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
支持PDF、Word、ZIP、RAR格式,ZIP/RAR内仅合并其中的PDF文件
|
||||
</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 border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors">
|
||||
<input
|
||||
type="file"
|
||||
multiple
|
||||
accept=".pdf,.doc,.docx,.zip,.rar"
|
||||
onChange={(e) => e.target.files && handleAttachmentFilesSelected(e.target.files)}
|
||||
className="hidden"
|
||||
id="attachment-file-input"
|
||||
/>
|
||||
<label htmlFor="attachment-file-input" className="cursor-pointer">
|
||||
<i className="ri-attachment-line text-3xl text-gray-400 mb-2 block"></i>
|
||||
<p className="text-sm text-gray-600">点击选择文件或拖拽文件到此处</p>
|
||||
<p className="text-xs text-gray-500 mt-1">支持PDF、Word、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>
|
||||
|
||||
{/* 合并模式选择 */}
|
||||
<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={() => {
|
||||
setShowAttachmentUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
setAttachmentFiles([]);
|
||||
setAttachmentRemark("");
|
||||
}}
|
||||
disabled={attachmentUploading}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 text-sm bg-primary text-white rounded-md hover:bg-primary-dark disabled:opacity-50"
|
||||
onClick={handleAttachmentUpload}
|
||||
disabled={attachmentFiles.length === 0 || attachmentUploading}
|
||||
>
|
||||
{attachmentUploading ? '上传中...' : '开始追加'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 合同模板上传模态框 */}
|
||||
{showTemplateUpload && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-lg p-6 w-full max-w-lg mx-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold">上传合同模板</h3>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowTemplateUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
setTemplateFile(null);
|
||||
}}
|
||||
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">
|
||||
目标文档ID: <span className="font-medium">{selectedDocumentId}</span>
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
支持PDF、Word格式,用于与合同文档进行结构对比
|
||||
</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 border-gray-300 rounded-lg p-6 text-center hover:border-gray-400 transition-colors">
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.doc,.docx"
|
||||
onChange={(e) => e.target.files && handleTemplateFileSelected(e.target.files)}
|
||||
className="hidden"
|
||||
id="template-file-input"
|
||||
/>
|
||||
<label htmlFor="template-file-input" className="cursor-pointer">
|
||||
<i className="ri-file-copy-line text-3xl text-gray-400 mb-2 block"></i>
|
||||
<p className="text-sm text-gray-600">点击选择文件或拖拽文件到此处</p>
|
||||
<p className="text-xs text-gray-500 mt-1">支持PDF、Word格式</p>
|
||||
</label>
|
||||
</div>
|
||||
{templateFile && (
|
||||
<div className="mt-2">
|
||||
<p className="text-sm text-green-600 mb-2">
|
||||
<i className="ri-checkbox-circle-line"></i> 已选择模板文件
|
||||
</p>
|
||||
<div className="text-xs text-gray-600 bg-gray-50 p-2 rounded">
|
||||
<i className="ri-file-line mr-1"></i>
|
||||
{templateFile.name} ({formatFileSize(templateFile.size)})
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</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={() => {
|
||||
setShowTemplateUpload(false);
|
||||
setSelectedDocumentId(null);
|
||||
setTemplateFile(null);
|
||||
}}
|
||||
disabled={templateUploading}
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 text-sm bg-primary text-white rounded-md hover:bg-primary-dark disabled:opacity-50"
|
||||
onClick={handleTemplateUpload}
|
||||
disabled={!templateFile || templateUploading}
|
||||
>
|
||||
{templateUploading ? '上传中...' : '开始上传'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1939,15 +1939,16 @@ export default function FilesUpload() {
|
||||
{
|
||||
title: "操作",
|
||||
key: "operation",
|
||||
width: "20%",
|
||||
width: "25%",
|
||||
render: (_: unknown, record: Document) => (
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Button
|
||||
type="default"
|
||||
size="small"
|
||||
disabled={record.status !== DocumentStatus.PROCESSED}
|
||||
icon="ri-eye-line"
|
||||
onClick={() => handleViewFile(record)}
|
||||
className="text-xs px-2 py-1 h-7"
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
@@ -1961,6 +1962,7 @@ export default function FilesUpload() {
|
||||
setSelectedDocumentId(record.id);
|
||||
setShowAttachmentUpload(true);
|
||||
}}
|
||||
className="text-xs px-2 py-1 h-7"
|
||||
>
|
||||
追加附件
|
||||
</Button>
|
||||
@@ -1972,6 +1974,7 @@ export default function FilesUpload() {
|
||||
setSelectedDocumentId(record.id);
|
||||
setShowTemplateUpload(true);
|
||||
}}
|
||||
className="text-xs px-2 py-1 h-7"
|
||||
>
|
||||
上传模板
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user