1.同步包版本。

2.交叉评查的任务中上传文件。
3.添加dify库名解决保存配置失败的问题。
This commit is contained in:
2026-01-20 17:29:55 +08:00
parent 1fca1a2e2e
commit 9951f16e50
11 changed files with 394 additions and 82 deletions
@@ -8,13 +8,16 @@ import { ResultStats } from '../ui/ResultStats';
import { toastService } from '../ui/Toast';
import { AttachmentUploadModal } from '../ui/AttachmentUploadModal';
import { TemplateUploadModal } from '../ui/TemplateUploadModal';
import { UploadArea, type UploadAreaRef } from '../ui/UploadArea';
import { formatDate } from '~/utils';
import {
type CrossReviewDocumentWithVersion,
type CrossReviewHistoryVersion,
appendTaskDocumentAttachments,
uploadCrossReviewDocumentTemplate,
uploadDocumentToTask,
} from '~/api/cross-checking/cross-files';
import { formatFileSize } from '~/api/cross-checking/cross-files-upload';
// 导出样式链接
export const links = () => [];
@@ -69,15 +72,6 @@ const crossReviewAuditStatusMapping: Record<string, { label: string; color: stri
"1": { label: "已评查", color: "green", icon: "ri-check-line" },
};
// 格式化文件大小
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 function DocumentListModal({
isOpen,
onClose,
@@ -124,6 +118,12 @@ export function DocumentListModal({
const [showTemplateUpload, setShowTemplateUpload] = useState(false);
const [templateUploading, setTemplateUploading] = useState(false);
// 上传文件模态框状态
const [showUploadModal, setShowUploadModal] = useState(false);
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
const [isFileUploading, setIsFileUploading] = useState(false);
const uploadAreaRef = useRef<UploadAreaRef>(null);
// 同步外部文档数据到本地
useEffect(() => {
setLocalDocuments(documents.map(doc => ({ ...doc, isExpanded: expandedRows.has(doc.id) })));
@@ -217,6 +217,78 @@ export function DocumentListModal({
setSelectedDocumentPath(null);
};
// 打开上传文件模态框
const handleOpenUploadModal = () => {
setShowUploadModal(true);
setUploadedFile(null);
};
// 关闭上传文件模态框
const handleCloseUploadModal = () => {
setShowUploadModal(false);
setUploadedFile(null);
uploadAreaRef.current?.resetFileInput();
};
// 处理文件选择
const handleFileSelected = (files: FileList) => {
if (files.length === 0) return;
const file = files[0];
const fileName = file.name.toLowerCase();
const isValidType = file.type === 'application/pdf' || fileName.endsWith('.pdf') ||
file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || fileName.endsWith('.docx') ||
file.type === 'application/zip' || fileName.endsWith('.zip') ||
file.type === 'application/x-zip-compressed';
// || file.type === 'application/x-7z-compressed' || fileName.endsWith('.7z');
if (!isValidType) {
// toastService.error('只能上传 PDF、DOCX 文件或 ZIP、7Z 压缩包');
toastService.error('只能上传 PDF、DOCX 文件或 ZIP 压缩包');
return;
}
setUploadedFile(file);
};
// 删除选中的文件
const handleRemoveFile = () => {
setUploadedFile(null);
uploadAreaRef.current?.resetFileInput();
};
// 确认上传文件
const handleUploadFile = async () => {
if (!uploadedFile || !taskId) {
toastService.error('缺少必要参数');
return;
}
setIsFileUploading(true);
try {
const result = await uploadDocumentToTask({
taskId,
file: uploadedFile,
jwtToken: frontendJWT
});
if (!result.success || result.error) {
throw new Error(result.error || '上传文件失败');
}
toastService.success('文件上传成功!正在后台处理中');
handleCloseUploadModal();
// 刷新文档列表
if (onSearch) {
onSearch(searchKeyword);
}
} catch (error) {
console.error('上传文件失败:', error);
toastService.error(error instanceof Error ? error.message : '上传文件失败');
} finally {
setIsFileUploading(false);
}
};
// 处理追加附件上传
const handleAttachmentUpload = async (files: File[], _mergeMode: 'overwrite' | 'new', remark: string) => {
if (!taskId || !selectedDocumentId) {
@@ -642,30 +714,43 @@ export function DocumentListModal({
</div>
</div>
{/* 右侧:搜索框 */}
{onSearch && (
<div className="flex items-center">
<div className="relative">
<input
type="text"
placeholder="搜索文件名称或文档编号"
value={searchKeyword}
onChange={(e) => handleSearchChange(e.target.value)}
className="pl-9 pr-4 py-2 border border-gray-300 rounded-md text-sm w-64 focus:outline-none focus:ring-0"
/>
<i className="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
{searchKeyword && (
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
onClick={() => handleSearchChange('')}
>
<i className="ri-close-line"></i>
</button>
)}
{/* 右侧:搜索框 + 上传按钮 */}
<div className="flex items-center gap-3">
{/* 上传文件按钮 - 仅负责人可见 */}
{isProposer && (
<button
type="button"
className="flex items-center gap-2 px-4 py-2 bg-green-800 text-white rounded-md text-sm hover:bg-green-700 transition-colors"
onClick={handleOpenUploadModal}
>
<i className="ri-upload-cloud-2-line"></i>
</button>
)}
{onSearch && (
<div className="flex items-center">
<div className="relative">
<input
type="text"
placeholder="搜索文件名称或文档编号"
value={searchKeyword}
onChange={(e) => handleSearchChange(e.target.value)}
className="pl-9 pr-4 py-2 border border-gray-300 rounded-md text-sm w-64 focus:outline-none focus:ring-0"
/>
<i className="ri-search-line absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
{searchKeyword && (
<button
type="button"
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
onClick={() => handleSearchChange('')}
>
<i className="ri-close-line"></i>
</button>
)}
</div>
</div>
</div>
)}
)}
</div>
</div>
{loading ? (
@@ -780,6 +865,150 @@ export function DocumentListModal({
title="上传合同模板"
supportedFormatsDesc="支持.pdf、.docx格式,用于与合同文档进行结构对比"
/>
{/* 上传文件模态框 */}
<Modal
isOpen={showUploadModal}
onClose={handleCloseUploadModal}
title="上传文档到任务"
size="medium"
>
<div className="p-6">
<div className="flex gap-6">
{/* 左侧:上传区域 */}
<div className="flex-1">
<div className="text-sm font-medium text-gray-700 mb-3"></div>
<UploadArea
ref={uploadAreaRef}
onFilesSelected={handleFileSelected}
className="custom-upload-area"
// accept=".pdf,.docx,.zip,.7z"
accept=".pdf,.docx,.zip"
multiple={false}
icon="ri-upload-cloud-2-line"
buttonText="选择文件"
mainText="点击或拖拽文件到此区域上传"
tipText={
<div className="text-gray-500 text-xs mt-2">
<div></div>
<div className="flex flex-wrap gap-2 mt-1 justify-center">
<span className="inline-flex items-center px-2 py-0.5 rounded bg-red-50 text-red-600">
<i className="ri-file-pdf-line mr-1"></i>PDF
</span>
<span className="inline-flex items-center px-2 py-0.5 rounded bg-blue-50 text-blue-600">
<i className="ri-file-word-2-line mr-1"></i>DOCX
</span>
<span className="inline-flex items-center px-2 py-0.5 rounded bg-orange-50 text-orange-600">
<i className="ri-folder-zip-line mr-1"></i>ZIP
</span>
</div>
</div>
}
disabled={isFileUploading || uploadedFile !== null}
/>
</div>
{/* 右侧:文件信息展示 */}
<div className="flex-1">
<div className="text-sm font-medium text-gray-700 mb-3"></div>
<div className="border border-gray-200 rounded-lg bg-gray-50 min-h-[200px] p-4">
{uploadedFile ? (
<div className="h-full flex flex-col">
{/* 文件图标和类型 */}
<div className="flex items-center justify-center mb-4">
{(() => {
const fileName = uploadedFile.name.toLowerCase();
if (fileName.endsWith('.pdf')) return <i className="ri-file-pdf-line text-5xl text-red-500"></i>;
if (fileName.endsWith('.docx')) return <i className="ri-file-word-2-line text-5xl text-blue-500"></i>;
if (fileName.endsWith('.zip') || fileName.endsWith('.7z')) return <i className="ri-folder-zip-line text-5xl text-orange-500"></i>;
return <i className="ri-file-line text-5xl text-gray-500"></i>;
})()}
</div>
{/* 文件详情 */}
<div className="flex-1 space-y-3">
<div className="bg-white rounded-md p-3 border border-gray-200">
<div className="text-xs text-gray-500 mb-1"></div>
<div className="text-sm font-medium text-gray-800 truncate" title={uploadedFile.name}>
{uploadedFile.name}
</div>
</div>
<div className="bg-white rounded-md p-3 border border-gray-200">
<div className="text-xs text-gray-500 mb-1"></div>
<div className="text-sm font-medium text-gray-800">
{formatFileSize(uploadedFile.size)}
</div>
</div>
</div>
{/* 删除按钮 */}
<div className="mt-4 pt-3 border-t border-gray-200">
<button
type="button"
onClick={handleRemoveFile}
disabled={isFileUploading}
className="w-full flex items-center justify-center gap-2 px-4 py-2 text-sm text-red-600 bg-red-50 hover:bg-red-100 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<i className="ri-delete-bin-line"></i>
</button>
</div>
</div>
) : (
<div className="h-full flex flex-col items-center justify-center text-gray-400">
<i className="ri-file-line text-4xl mb-2"></i>
<span className="text-sm"></span>
<span className="text-xs mt-1"></span>
</div>
)}
</div>
</div>
</div>
{/* 按钮区域 */}
<div className="flex justify-end gap-3 mt-6 pt-4 border-t border-gray-200">
<button
type="button"
onClick={handleCloseUploadModal}
disabled={isFileUploading}
className="px-4 py-2 text-sm text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
</button>
<button
type="button"
onClick={handleUploadFile}
disabled={!uploadedFile || isFileUploading}
className="px-4 py-2 text-sm text-white bg-green-800 rounded-md hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
>
{isFileUploading ? (
<>
<i className="ri-loader-4-line animate-spin"></i>
...
</>
) : (
<>
<i className="ri-upload-line"></i>
</>
)}
</button>
</div>
{/* 上传进度提示 */}
{isFileUploading && (
<div className="mt-4">
<div className="bg-blue-50 p-4 rounded-md border border-blue-100">
<div className="flex items-center justify-center text-blue-800">
<i className="ri-loader-4-line animate-spin text-xl mr-2"></i>
<span className="font-medium">...</span>
</div>
</div>
</div>
)}
</div>
</Modal>
</Modal>
);
}