feat: 1. 接入CollaboraViewer选中的高亮效果,清除高亮功能,页面销毁自动清除高亮。

2. 合同模板对比接入monaco editor的效果。
3. 添加交叉评查的案卷类型的数据查询。

fix: 1. 修复文档列表的打开模态框蒙板层显示效果。
This commit is contained in:
2025-11-30 19:33:05 +08:00
parent fb67f138dc
commit 4fcc92a381
14 changed files with 1263 additions and 286 deletions
+97 -60
View File
@@ -16,6 +16,10 @@ import {
formatFileSize,
batchUploadAndAssignCrossCheckingFiles
} from "~/api/cross-checking/cross-files-upload";
import {
getCrossCheckingDocumentTypes,
type DocumentType
} from "~/api/cross-checking/cross-files";
import {
getOrganizationTree,
convertToTreeData
@@ -125,16 +129,21 @@ const TreeNodeCheckbox: React.FC<{
);
};
/**
* 获取用户会话和前端JWT
* 获取用户会话和前端JWT,以及文档类型列表
*/
export const loader = async ({ request }: LoaderFunctionArgs) => {
// 获取用户会话信息
const { getUserSession } = await import("~/api/login/auth.server");
const { userInfo, frontendJWT } = await getUserSession(request);
// 获取可用于交叉评查的文档类型列表
const documentTypesResponse = await getCrossCheckingDocumentTypes(frontendJWT);
return Response.json({
userInfo,
frontendJWT
frontendJWT,
documentTypes: documentTypesResponse.success ? documentTypesResponse.data : [],
documentTypesError: documentTypesResponse.error
});
};
@@ -194,10 +203,12 @@ export const action = async ({ request }: ActionFunctionArgs) => {
export default function CrossCheckingUpload() {
// 获取loader数据
const { userInfo, frontendJWT } = useLoaderData<typeof loader>();
// 基础状态
const [caseType, setCaseType] = useState<CaseType>(CaseType.ADMINISTRATIVE_PENALTY);
const { userInfo, frontendJWT, documentTypes, documentTypesError } = useLoaderData<typeof loader>();
// 基础状态 - 使用第一个文档类型的ID作为默认值
const [selectedDocTypeId, setSelectedDocTypeId] = useState<number | null>(
documentTypes && documentTypes.length > 0 ? documentTypes[0].id : null
);
// 步骤状态
const [currentStep, setCurrentStep] = useState(1);
// 任务创建状态
@@ -235,16 +246,17 @@ export default function CrossCheckingUpload() {
// 处理案卷类型切换
const handleCaseTypeChange = (type: CaseType) => {
const handleDocTypeChange = (docTypeId: number) => {
if (isUploading) {
toastService.warning("上传进行中,无法切换案卷类型");
return;
}
setCaseType(type);
setSelectedDocTypeId(docTypeId);
// 清空已选择的文件和重置上传方式
clearAllFiles();
console.log("案卷类型切换为:", type, "typeId:", CASE_TYPE_TO_TYPE_ID[type]);
const selectedType = documentTypes?.find((dt: DocumentType) => dt.id === docTypeId);
console.log("案卷类型切换为:", selectedType?.name, "ID:", docTypeId);
};
// 清空所有文件
@@ -268,7 +280,11 @@ export default function CrossCheckingUpload() {
let hasInvalidFiles = false;
Array.from(files).forEach(file => {
if (file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf')) {
const isPdf = file.type === 'application/pdf' || file.name.toLowerCase().endsWith('.pdf');
const isDocx = file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
file.name.toLowerCase().endsWith('.docx');
if (isPdf || isDocx) {
validFiles.push({
id: generateFileId(),
file,
@@ -283,7 +299,7 @@ export default function CrossCheckingUpload() {
});
if (hasInvalidFiles) {
messageService.error('只能上传PDF格式的文件', {
messageService.error('只能上传PDF或DOCX格式的文件', {
title: '文件类型错误',
confirmText: '确定',
});
@@ -413,12 +429,25 @@ export default function CrossCheckingUpload() {
return;
}
// 验证选择了案卷类型
if (!selectedDocTypeId) {
toastService.error("请选择案卷类型");
return;
}
setIsCreatingTask(true);
setIsUploading(true);
try {
// 获取选中的文档类型信息
const selectedDocType = documentTypes?.find((dt: DocumentType) => dt.id === selectedDocTypeId);
if (!selectedDocType) {
toastService.error("无效的案卷类型");
return;
}
// 第一步:上传文件并自动分配任务(新接口)
console.log("开始批量上传文件并分配任务:", filesToUpload.length, "个,案卷类型:", caseType);
console.log("开始批量上传文件并分配任务:", filesToUpload.length, "个,案卷类型:", selectedDocType.name);
// 提取用户ID(从选中的组织架构中获取用户)
const userIds = groupChecked.filter(id => {
@@ -431,22 +460,17 @@ export default function CrossCheckingUpload() {
return;
}
// 创建任务数据
const docTypeMap = {
[CaseType.ADMINISTRATIVE_PENALTY]: 'XZCF',
[CaseType.ADMINISTRATIVE_PERMIT]: 'XZXK'
};
// 使用文档类型名称作为 doc_type
const uploadResult = await batchUploadAndAssignCrossCheckingFiles(
filesToUpload,
CASE_TYPE_TO_TYPE_ID[caseType],
selectedDocTypeId, // 使用选中的文档类型ID
priority,
documentNumber,
remark,
isTestDocument,
userIds,
taskInfo.name,
docTypeMap[caseType] || 'XZCF',
selectedDocType.name, // 使用文档类型名称
frontendJWT
);
@@ -814,29 +838,36 @@ export default function CrossCheckingUpload() {
<div className="flex justify-center mb-6">
<div>
<div className="text-sm font-medium text-gray-700 mb-3 text-center"></div>
<div className="case-type-options">
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PENALTY ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PENALTY)}
disabled={isUploading}
>
</button>
<button
type="button"
className={`case-type-option ${caseType === CaseType.ADMINISTRATIVE_PERMIT ? 'active' : 'inactive'}`}
onClick={() => handleCaseTypeChange(CaseType.ADMINISTRATIVE_PERMIT)}
disabled={isUploading}
>
</button>
</div>
{documentTypesError ? (
<div className="text-red-500 text-sm text-center p-4 border border-red-200 rounded-md bg-red-50">
<i className="ri-error-warning-line mr-2"></i>
: {documentTypesError}
</div>
) : documentTypes && documentTypes.length > 0 ? (
<div className="case-type-options">
{documentTypes.map((docType: DocumentType) => (
<button
key={docType.id}
type="button"
className={`case-type-option ${selectedDocTypeId === docType.id ? 'active' : 'inactive'}`}
onClick={() => handleDocTypeChange(docType.id)}
disabled={isUploading}
>
{docType.name}
</button>
))}
</div>
) : (
<div className="text-gray-500 text-sm text-center p-4 border border-gray-200 rounded-md bg-gray-50">
<i className="ri-information-line mr-2"></i>
</div>
)}
</div>
</div>
{/* 文件上传区域 */}
<input type="hidden" name="caseType" value={caseType} />
<input type="hidden" name="selectedDocTypeId" value={selectedDocTypeId || ''} />
<input type="hidden" name="uploadType" value={uploadType} />
{/* 上传框区域 */}
@@ -851,14 +882,14 @@ export default function CrossCheckingUpload() {
ref={singleUploadRef}
onFilesSelected={handleSingleFilesSelected}
className="custom-upload-area"
accept=".pdf"
accept=".pdf,.docx"
multiple={true}
icon="ri-file-upload-line"
buttonText="选择文件"
mainText="点击或拖拽文件到此区域上传"
tipText={
<div className="upload-tip-error">
PDF文件
PDF或DOCX文件
</div>
}
disabled={uploadType === 'multiple' || isUploading}
@@ -911,23 +942,29 @@ export default function CrossCheckingUpload() {
{/* 单案件文件列表 */}
{uploadType === 'single' && singleFiles.length > 0 && (
<div className="max-h-32 overflow-y-auto space-y-1">
{singleFiles.map((file) => (
<div key={file.id} className="flex items-center justify-between bg-white p-2 rounded border">
<div className="flex items-center space-x-2 flex-1 min-w-0">
<i className="ri-file-pdf-line text-red-500"></i>
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
{singleFiles.map((file) => {
const isDocx = file.name.toLowerCase().endsWith('.docx');
const isPdf = file.name.toLowerCase().endsWith('.pdf');
return (
<div key={file.id} className="flex items-center justify-between bg-white p-2 rounded border">
<div className="flex items-center space-x-2 flex-1 min-w-0">
{isPdf && <i className="ri-file-pdf-line text-red-500"></i>}
{isDocx && <i className="ri-file-word-2-line text-blue-500"></i>}
{!isPdf && !isDocx && <i className="ri-file-line text-gray-500"></i>}
<span className="text-sm truncate">{file.name}</span>
<span className="text-xs text-gray-500">{formatFileSize(file.size)}</span>
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'single')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
<button
type="button"
onClick={() => handleRemoveFile(file.id, 'single')}
className="text-red-500 hover:text-red-700 p-1"
disabled={isUploading}
>
<i className="ri-close-line"></i>
</button>
</div>
))}
);
})}
</div>
)}