From 61bbf6907bce0f81131e365909bd53e04c6b1811 Mon Sep 17 00:00:00 2001 From: wren <“porlong@qq.com”> Date: Wed, 6 May 2026 18:33:53 +0800 Subject: [PATCH] feat: wire real upload progress and subtype mapping --- app/api/files/files-upload.ts | 105 +++++++++-- app/routes/files.upload.tsx | 346 ++++++++++++++++++---------------- 2 files changed, 265 insertions(+), 186 deletions(-) diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts index be57e86..dd62a53 100644 --- a/app/api/files/files-upload.ts +++ b/app/api/files/files-upload.ts @@ -147,8 +147,10 @@ export interface DocumentType { name: string; code?: string; entryModuleId?: number; + entryModuleName?: string | null; isEnabled?: boolean; ruleSetIds?: number[]; + childDocumentTypeIds?: number[]; } export interface DocumentSubtypeGroup { @@ -233,6 +235,12 @@ export interface UploadResult { error?: string; } +export interface UploadProgressInfo { + loaded: number; + total: number; + percent: number; +} + // 旧接口上传响应(uploadContractTemplate / appendContractAttachments 仍在使用) interface LegacyUploadResponse { success: boolean; @@ -684,6 +692,7 @@ export async function uploadDocumentToServer( autoRun: boolean = true, speed: string = "normal", jwtToken?: string, + onProgress?: (progress: UploadProgressInfo) => void, ): Promise<{ data: UploadResult } | { error: string; status?: number; payload?: UploadErrorPayload }> { try { const formData = new FormData(); @@ -711,7 +720,23 @@ export async function uploadDocumentToServer( headers["Authorization"] = `Bearer ${jwtToken}`; } - const response = await axios.post(`${API_BASE_URL}/api/upload`, formData, { headers }); + const response = await axios.post(`${API_BASE_URL}/api/upload`, formData, { + headers, + onUploadProgress: (event) => { + const fileBlob = formData.get("file"); + const fallbackTotal = fileBlob instanceof Blob ? fileBlob.size : binaryData.byteLength; + const total = Number(event.total || fallbackTotal); + const loaded = Number(event.loaded || 0); + if (!total || !onProgress) { + return; + } + onProgress({ + loaded, + total, + percent: Math.min(100, Math.max(0, Number(((loaded / total) * 100).toFixed(2)))), + }); + }, + }); const body = response.data; // Result envelope @@ -826,8 +851,6 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT const params: Record = {}; if (selectedModuleId) { params.entry_module_id = String(selectedModuleId); - } else if (documentTypeIds && documentTypeIds.length > 0) { - params.ids = documentTypeIds.join(","); } const headers: Record = {}; @@ -835,18 +858,52 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT headers["Authorization"] = `Bearer ${token}`; } - const response = await axios.get(`${API_BASE_URL}/api/document-types`, { params, headers }); + const [response, groupRoots] = await Promise.all([ + axios.get(`${API_BASE_URL}/api/v3/document-type-roots`, { params, headers }), + fetchAllEvaluationPointGroupRoots(token), + ]); const body = response.data; if (body?.data && Array.isArray(body.data)) { - const types: DocumentType[] = body.data.map((item: { id: number; name: string; code?: string; entryModuleId?: number; isEnabled?: boolean; ruleSetIds?: number[] }) => ({ - id: item.id, - name: item.name, - code: item.code, - entryModuleId: item.entryModuleId, - isEnabled: item.isEnabled, - ruleSetIds: item.ruleSetIds, - })); + let types: DocumentType[] = body.data.map((item: { + id: number; + name: string; + code?: string; + entryModuleId?: number | null; + entryModuleName?: string | null; + isEnabled?: boolean; + ruleSetIds?: number[]; + }) => { + const matchedRoot = groupRoots.find((root: any) => Number(root?.id || 0) === Number(item.id)); + const childDocumentTypeIds = Array.isArray(matchedRoot?.children) + ? Array.from( + new Set( + matchedRoot.children + .map((child: any) => Number(child?.document_type_id || 0)) + .filter((childId: number) => childId > 0), + ), + ) + : []; + + return { + id: item.id, + name: item.name, + code: item.code, + entryModuleId: item.entryModuleId ?? null, + entryModuleName: item.entryModuleName ?? null, + isEnabled: item.isEnabled, + ruleSetIds: item.ruleSetIds, + childDocumentTypeIds, + }; + }); + + if (!selectedModuleId && documentTypeIds && documentTypeIds.length > 0) { + types = types.filter((item) => + documentTypeIds.includes(item.id) || + (item.childDocumentTypeIds || []).some((childId) => documentTypeIds.includes(childId)), + ); + } + return { data: types }; } return { error: body?.message || "获取文档类型失败", status: response.status }; @@ -919,18 +976,26 @@ async function fetchAllEvaluationPointGroupRoots(token?: string): Promise function collectSubtypeGroupsFromRoots( roots: any[], - documentTypeId: number, + rootOrDocumentTypeId: number, entryModuleId?: number | null, ): DocumentSubtypeGroup[] { - return dedupeSubtypeGroups( - roots.flatMap((root: any) => { - if (!Array.isArray(root?.children)) return []; - if (entryModuleId && Number(root?.entry_module_id || 0) !== Number(entryModuleId)) { - return []; - } + const scopedRoots = roots.filter((root: any) => { + if (entryModuleId && Number(root?.entry_module_id || 0) !== Number(entryModuleId)) { + return false; + } + return true; + }); + const matchedRoot = scopedRoots.find((root: any) => Number(root?.id || 0) === Number(rootOrDocumentTypeId)); + if (matchedRoot && Array.isArray(matchedRoot.children)) { + return dedupeSubtypeGroups(matchedRoot.children.map((child: any) => mapSubtypeChild(child, matchedRoot))); + } + + return dedupeSubtypeGroups( + scopedRoots.flatMap((root: any) => { + if (!Array.isArray(root?.children)) return []; return root.children - .filter((child: any) => Number(child?.document_type_id || 0) === Number(documentTypeId)) + .filter((child: any) => Number(child?.document_type_id || 0) === Number(rootOrDocumentTypeId)) .map((child: any) => mapSubtypeChild(child, root)); }), ); diff --git a/app/routes/files.upload.tsx b/app/routes/files.upload.tsx index 3df8bbb..e0b955b 100644 --- a/app/routes/files.upload.tsx +++ b/app/routes/files.upload.tsx @@ -25,6 +25,7 @@ import { type DocumentType, type DocumentSubtypeGroup, type UploadErrorDetails, + type UploadProgressInfo, type UploadResult, DocumentStatus } from "~/api/files/files-upload"; @@ -139,6 +140,7 @@ async function handleFileUpload( createdBy?: number, attachments?: File[], jwtToken?: string, + onProgress?: (progress: UploadProgressInfo) => void, ): Promise { const speed = priority === Priority.NORMAL ? "normal" : "urgent"; @@ -154,6 +156,7 @@ async function handleFileUpload( true, speed, jwtToken, + onProgress, ); if ("error" in response || !response.data) { @@ -172,6 +175,48 @@ async function handleFileUpload( return response.data; } +function mapProcessingStatusToProgress(status: DocumentStatus): number { + switch (status) { + case DocumentStatus.QUEUED: + case DocumentStatus.WAITING: + case DocumentStatus.waiting: + return 15; + case DocumentStatus.CUTTING: + return 35; + case DocumentStatus.EXTRACTIONING: + return 60; + case DocumentStatus.EVALUATIONING: + return 85; + case DocumentStatus.PROCESSED: + return 100; + case DocumentStatus.FAILED: + return 0; + default: + return 15; + } +} + +function mapProcessingStatusToSpeed(status: DocumentStatus): string { + switch (status) { + case DocumentStatus.QUEUED: + case DocumentStatus.WAITING: + case DocumentStatus.waiting: + return "排队中"; + case DocumentStatus.CUTTING: + return "切分中"; + case DocumentStatus.EXTRACTIONING: + return "抽取中"; + case DocumentStatus.EVALUATIONING: + return "评查中"; + case DocumentStatus.PROCESSED: + return "已完成"; + case DocumentStatus.FAILED: + return "处理失败"; + default: + return "处理中"; + } +} + // 定义action返回数据的类型 type ActionData = { errors?: { @@ -199,7 +244,7 @@ export async function action({ request }: ActionFunctionArgs) { const errors: Record = {}; if (!fileType) { - errors.fileType = "上传文件之前请选择文件类型"; + errors.fileType = "上传文件之前请选择文档类型"; } if (!fileUpload) { @@ -408,6 +453,9 @@ export default function FilesUpload() { const hasMultipleSubtypeGroups = subtypeGroups.length > 1; const singleSubtypeGroup = subtypeGroups.length === 1 ? subtypeGroups[0] : null; const isSingleDefaultSubtype = !!(singleSubtypeGroup && singleSubtypeGroup.isDefault); + const effectiveSubtypeGroup = selectedSubtypeGroup || singleSubtypeGroup; + const effectiveDocumentTypeId = effectiveSubtypeGroup?.documentTypeId || null; + const effectiveGroupId = effectiveSubtypeGroup?.id || null; const selectedRootGroupName = selectedSubtypeGroup?.rootGroupName || singleSubtypeGroup?.rootGroupName || ""; const selectedEntryModuleName = selectedSubtypeGroup?.entryModuleName || singleSubtypeGroup?.entryModuleName || ""; @@ -476,7 +524,11 @@ export default function FilesUpload() { const scopedTypesResponse = await getDocumentTypes(loaderData.frontendJWT || undefined); if (!cancelled && !scopedTypesResponse.error && scopedTypesResponse.data) { scopedTypes = scopedTypesResponse.data; - effectiveTypeIds = scopedTypes.map(type => type.id); + effectiveTypeIds = Array.from( + new Set( + scopedTypes.flatMap((type) => type.childDocumentTypeIds || []), + ), + ); } } @@ -488,13 +540,11 @@ export default function FilesUpload() { filterDocumentTypes(effectiveTypeIds, scopedTypes, normalizedModuleId); await filterDocuments(effectiveTypeIds); - if (effectiveTypeIds && effectiveTypeIds.includes(1)) { - setIsContractType(true); - const contractType = scopedTypes.find(type => type.id === 1); - if (contractType) { - setFileType(contractType.id.toString()); - setFileTypeError(null); - } + if (scopedTypes.length === 1) { + const onlyType = scopedTypes[0]; + setFileType(onlyType.id.toString()); + setIsContractType(onlyType.name.includes('合同')); + setFileTypeError(null); } else { setIsContractType(false); } @@ -527,7 +577,10 @@ export default function FilesUpload() { } // 根据 documentTypeIds 过滤文档类型 - const filteredTypes = types.filter(type => documentTypeIds.includes(type.id)); + const filteredTypes = types.filter(type => + documentTypeIds.includes(type.id) || + (type.childDocumentTypeIds || []).some((childId) => documentTypeIds.includes(childId)) + ); setDocumentTypesState(filteredTypes); }; @@ -590,7 +643,6 @@ export default function FilesUpload() { const [completedFiles, setCompletedFiles] = useState([]); // 计时器引用 - 分离为三个独立的定时器 - const uploadProgressIntervalRef = useRef(null); const processingStatusIntervalRef = useRef(null); const queueStatusIntervalRef = useRef(null); // 原 statusCheckIntervalRef @@ -828,7 +880,9 @@ export default function FilesUpload() { setFileTypeError(null); // 检查是否选择了合同类型 - const selectedType = loaderData.documentTypes.find(t => t.id.toString() === value); + const selectedType = + documentTypesState.find(t => t.id.toString() === value) || + loaderData.documentTypes.find(t => t.id.toString() === value); const isContract = !!(selectedType && selectedType.name.includes('合同')); // console.log('【调试-handleFileTypeChange】文件类型检查:', { // selectedType, @@ -859,7 +913,7 @@ export default function FilesUpload() { setFileType(""); setIsContractType(false); // 如果用户选择了空选项,显示错误信息 - setFileTypeError("上传文件之前请选择文件类型"); + setFileTypeError("上传文件之前请选择文档类型"); } }; @@ -1264,7 +1318,12 @@ export default function FilesUpload() { const mainFile = mainFiles[0]; // 检查文档名称是否重复 - const duplicateResult = await checkDocumentDuplicate(mainFile.name, Number(fileType)); + if (!effectiveDocumentTypeId) { + toastService.error('当前子类型缺少可提交的文档类型绑定,请先检查评查点分组配置'); + return; + } + + const duplicateResult = await checkDocumentDuplicate(mainFile.name, effectiveDocumentTypeId); if (duplicateResult.is_duplicate) { const confirmed = window.confirm( '存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。' @@ -1291,31 +1350,8 @@ export default function FilesUpload() { setProcessingSteps(updatedSteps); } - // 计算总大小并开启与旧逻辑一致的模拟进度(按时间推进到 95%) const totalSize = filesForProgress.reduce((sum, f) => sum + (f?.size || 0), 0); - const startTime = Date.now(); - let lastUpdateTime = startTime; - let lastRatio = 0; - const estimatedUploadTime = Math.max( - (totalSize / (1024 * 1024)) / 3 * 1000, // 3MB/s 估算 - 1000 - ); - - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - } - uploadProgressIntervalRef.current = setInterval(() => { - const now = Date.now(); - const deltaSec = (now - lastUpdateTime) / 1000; - const ratio = Math.min((now - startTime) / estimatedUploadTime, 0.95); - // 计算瞬时速度(基于比例变化) - const deltaRatio = Math.max(ratio - lastRatio, 0); - const bytesPerSec = deltaSec > 0 ? (totalSize * deltaRatio) / deltaSec : 0; - lastRatio = ratio; - lastUpdateTime = now; - setUploadSpeed(`${formatFileSize(bytesPerSec)}/s`); - setUploadProgress(parseFloat((ratio * 100).toFixed(2))); - }, 200); + setUploadSpeed("上传中"); // 转二进制 const binaryData = await uploadFileToBinary(mainFile); @@ -1324,9 +1360,20 @@ export default function FilesUpload() { const region = (loaderData.userInfo?.area as string) || "default"; const createdBy = loaderData.userInfo?.user_id as number | undefined; const uploadResp = await handleFileUpload( - binaryData, mainFile.name, mainFile.type, - fileType as FileType, selectedGroupId ? Number(selectedGroupId) : null, priority, - region, createdBy, attachmentFiles, loaderData.frontendJWT || undefined, + binaryData, + mainFile.name, + mainFile.type, + String(effectiveDocumentTypeId), + effectiveGroupId, + priority, + region, + createdBy, + attachmentFiles, + loaderData.frontendJWT || undefined, + (progress) => { + setUploadProgress(progress.percent); + setUploadSpeed(`${formatFileSize(progress.loaded)}/${formatFileSize(progress.total)}`); + }, ); if (!uploadResp.success) { @@ -1350,10 +1397,6 @@ export default function FilesUpload() { } // 完成:清理进度定时器并置满 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - uploadProgressIntervalRef.current = null; - } setUploadProgress(100); setUploadSpeed('完成'); @@ -1378,11 +1421,6 @@ export default function FilesUpload() { await filterDocuments(documentTypeIds); } catch (error) { console.error('合同首传上传失败:', error); - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - uploadProgressIntervalRef.current = null; - } - // 清空合同模板文件缓存 setContractTemplateFiles([]); console.log('【合同上传失败】已清空合同模板文件缓存'); @@ -1412,7 +1450,7 @@ export default function FilesUpload() { // 检查是否选择了文件类型 if (!fileType) { console.error('【调试-checkAndPrepareUpload】未选择文件类型'); - toastService.error('请先选择文件类型'); + toastService.error('请先选择文档类型'); return; } if (subtypeGroups.length > 1 && !selectedGroupId) { @@ -1421,7 +1459,7 @@ export default function FilesUpload() { } // 检查是否为合同类型 - const selectedType = loaderData.documentTypes.find(t => t.id.toString() === fileType); + const selectedType = getSelectedDocumentType(); const isContract = !!(selectedType && selectedType.name.includes('合同')); // console.log('【调试-checkAndPrepareUpload】文件类型检查', { @@ -1454,7 +1492,12 @@ export default function FilesUpload() { // 检查主文件名称是否重复(在任何状态变化之前进行检查) const mainFile = allFiles[0]; - const duplicateResult = await checkDocumentDuplicate(mainFile.name, Number(fileType)); + if (!effectiveDocumentTypeId) { + toastService.error('当前子类型缺少可提交的文档类型绑定,请先检查评查点分组配置'); + return; + } + + const duplicateResult = await checkDocumentDuplicate(mainFile.name, effectiveDocumentTypeId); if (duplicateResult.is_duplicate) { const confirmed = window.confirm( '存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。' @@ -1505,8 +1548,8 @@ export default function FilesUpload() { } } } else { - console.error('【调试-checkAndPrepareUpload】未选择文件类型,无法上传'); - toastService.error('请选择文件类型'); + console.error('【调试-checkAndPrepareUpload】未选择文档类型,无法上传'); + toastService.error('请选择文档类型'); } } else { console.error('【调试-checkAndPrepareUpload】没有文件可上传'); @@ -1551,13 +1594,12 @@ export default function FilesUpload() { setUploadStage("uploading"); setUploadProgress(0); + setUploadSpeed("上传中"); // 计算总文件大小 const totalSize = files.reduce((sum, file) => sum + file.size, 0); let uploadedSize = 0; - // console.log('【调试-startUpload】总文件大小:', formatFileSize(totalSize)); - // 更新步骤状态 const updatedSteps = [...processingSteps]; updatedSteps[0].status = "active"; @@ -1571,33 +1613,6 @@ export default function FilesUpload() { return; } - // 转换文件为二进制格式 - // console.log("【调试-startUpload】开始转换文件到二进制格式..."); - - // 模拟上传进度 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - } - - const startTime = Date.now(); - let lastUploadedSize = 0; - let lastUpdateTime = startTime; - - uploadProgressIntervalRef.current = setInterval(() => { - const currentTime = Date.now(); - const timeElapsed = (currentTime - lastUpdateTime) / 1000; // 使用最近一次更新的时间间隔 - const currentSpeed = timeElapsed > 0 ? (uploadedSize - lastUploadedSize) / timeElapsed : 0; // 字节/秒 - lastUploadedSize = uploadedSize; - lastUpdateTime = currentTime; - - // 更新上传速度显示 - setUploadSpeed(`${formatFileSize(currentSpeed)}/s`); - - // 更新进度 - 保留2位小数 - const progress = Math.min((uploadedSize / totalSize) * 100, 99.99); - setUploadProgress(parseFloat(progress.toFixed(2))); - }, 200); // 改为200ms更新一次,提供更准确的速度计算 - // 上传所有文件 const uploadedFiles: UploadedFile[] = []; @@ -1629,44 +1644,35 @@ export default function FilesUpload() { // console.log(`【调试-startUpload】准备上传文件 ${file.name} 到服务器`); - // 创建基于时间的渐进式进度模拟 - const startUploadTime = Date.now(); - // 根据文件大小动态估算上传时间,考虑网络速度 - const estimatedUploadTime = Math.max( - file.size / (1024 * 1024) / 3 * 1000, // 假设3MB/s的速度,1MB需要1/3秒 - 1000 // 最小1秒 - ); - let progressInterval: NodeJS.Timeout | null = null; - - // 开始渐进式进度更新 - const progressPromise = new Promise((resolve) => { - progressInterval = setInterval(() => { - const elapsed = Date.now() - startUploadTime; - const progressRatio = Math.min(elapsed / estimatedUploadTime, 0.95); // 最大95% - - // 计算当前文件的进度贡献 - const fileProgress = progressRatio * file.size; - const previousFilesSize = files.slice(0, temp_n - 1).reduce((sum, f) => sum + f.size, 0); - uploadedSize = previousFilesSize + fileProgress; - - // 如果接近完成,停止进度更新并resolve - if (progressRatio >= 0.95) { - if (progressInterval) { - clearInterval(progressInterval); - progressInterval = null; - } - resolve(); - } - }, 100); // 改为100ms更新一次,提供更流畅的进度 - }); - // 使用Promise.race添加超时处理 const region = (loaderData.userInfo?.area as string) || "default"; const createdBy = loaderData.userInfo?.user_id as number | undefined; + if (!effectiveDocumentTypeId) { + throw new Error("当前子类型缺少可提交的文档类型绑定,请先检查评查点分组配置"); + } const uploadPromise = handleFileUpload( - binaryData, file.name, file.type, - fileType as FileType, selectedGroupId ? Number(selectedGroupId) : null, priority, - region, createdBy, undefined, loaderData.frontendJWT || undefined, + binaryData, + file.name, + file.type, + String(effectiveDocumentTypeId), + effectiveGroupId, + priority, + region, + createdBy, + undefined, + loaderData.frontendJWT || undefined, + (progress) => { + const previousFilesSize = files + .slice(0, temp_n - 1) + .reduce((sum, currentFile) => sum + currentFile.size, 0); + const currentLoaded = Math.min(progress.loaded, file.size); + uploadedSize = previousFilesSize + currentLoaded; + const overallProgress = totalSize > 0 ? (uploadedSize / totalSize) * 100 : 0; + setUploadProgress(parseFloat(Math.min(overallProgress, 100).toFixed(2))); + setUploadSpeed( + `${formatFileSize(previousFilesSize + currentLoaded)}/${formatFileSize(totalSize)}`, + ); + }, ); const timeoutPromise = new Promise((_, reject) => { @@ -1675,16 +1681,7 @@ export default function FilesUpload() { }, 600000); }); - // 并行执行上传和进度更新 - const [uploadResult] = await Promise.all([ - Promise.race([uploadPromise, timeoutPromise]), - progressPromise - ]); - - // 清除进度定时器 - if (progressInterval) { - clearInterval(progressInterval); - } + const uploadResult = await Promise.race([uploadPromise, timeoutPromise]); // 再次检查组件是否已卸载 if (!isMountedRef.current) { @@ -1748,10 +1745,6 @@ export default function FilesUpload() { } // 清除进度定时器 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - } - // 更新上传状态 setUploadProgress(100); setUploadSpeed("完成"); @@ -1763,7 +1756,7 @@ export default function FilesUpload() { return { id, name: file.name, - type_id: fileType ? parseInt(fileType) : 0, + type_id: effectiveDocumentTypeId || (fileType ? parseInt(fileType) : 0), file_size: file.size, status: DocumentStatus.CUTTING, created_at: new Date().toISOString() @@ -1789,11 +1782,6 @@ export default function FilesUpload() { setProcessingSteps(errorSteps); - // 清除进度定时器 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - } - showFriendlyUploadError(error, { titlePrefix: '文件上传失败' }); resetUpload(); @@ -1824,6 +1812,8 @@ export default function FilesUpload() { updatedSteps[1].description = "文档正在排队等待处理..."; setProcessingSteps(updatedSteps); + setUploadProgress(mapProcessingStatusToProgress(DocumentStatus.QUEUED)); + setUploadSpeed(mapProcessingStatusToSpeed(DocumentStatus.QUEUED)); // 获取文件ID列表 const fileIds = files.map(file => file.id).filter(id => id > 0); @@ -1925,6 +1915,17 @@ export default function FilesUpload() { // console.log('【调试-checkProcessingStatus】没有返回文件状态数据'); return; } + + const hasFailed = response.data.some(doc => doc.status === DocumentStatus.FAILED); + if (hasFailed) { + if (processingStatusIntervalRef.current) { + clearInterval(processingStatusIntervalRef.current); + processingStatusIntervalRef.current = null; + } + updateProcessingSteps(DocumentStatus.FAILED); + updateQueueFilesStatus(response.data); + return; + } // 检查是否所有文件都已完成处理 const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED); @@ -1955,6 +1956,8 @@ export default function FilesUpload() { completedSteps[5].description = "文档已准备就绪,可以查看"; setProcessingSteps(completedSteps); + setUploadProgress(100); + setUploadSpeed(mapProcessingStatusToSpeed(DocumentStatus.PROCESSED)); setUploadStage("completed"); } else { // 根据当前状态更新步骤 @@ -2036,9 +2039,18 @@ export default function FilesUpload() { updatedSteps[5].status = "done"; updatedSteps[5].description = "文档已准备就绪,可以查看"; break; + + case DocumentStatus.FAILED: + updatedSteps[1].status = "done"; + updatedSteps[1].description = "已进入处理队列"; + updatedSteps[2].status = "error"; + updatedSteps[2].description = "文档处理失败,请检查上传文件或稍后重试"; + break; } setProcessingSteps(updatedSteps); + setUploadProgress(mapProcessingStatusToProgress(status)); + setUploadSpeed(mapProcessingStatusToSpeed(status)); }; // 更新队列中文件的状态 @@ -2063,12 +2075,6 @@ export default function FilesUpload() { // 重置上传状态 - 不清除队列状态检查定时器 const resetUpload = () => { - // 清除上传和处理相关的定时器 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - uploadProgressIntervalRef.current = null; - } - if (processingStatusIntervalRef.current) { clearInterval(processingStatusIntervalRef.current); processingStatusIntervalRef.current = null; @@ -2136,7 +2142,11 @@ export default function FilesUpload() { // 获取文档类型名称 const getDocumentTypeName = (codeId: number) => { const type = documentTypesState.find(t => t.id === codeId); - return type ? type.name : '未知类型'; + if (type) { + return type.name; + } + const matchedRoot = documentTypesState.find((item) => (item.childDocumentTypeIds || []).includes(codeId)); + return matchedRoot ? matchedRoot.name : '未知类型'; }; // 处理查看文件 @@ -2390,10 +2400,10 @@ export default function FilesUpload() { {/* 文件类型选择和上传表单 */}
{/* 文件类型选择 */} - 选择文件类型} className="mb-4"> + 选择文档类型} className="mb-4">
- +
{!fileType - ? "请先选择文件类型,再确定本次上传实际命中的子类型。" + ? "请先选择一级文档类型,再确定本次上传实际命中的子类型。" : groupOptionsLoading - ? "正在加载当前文档类型下可用的子类型配置。" + ? "正在加载当前一级文档类型下可用的子类型配置。" : subtypeGroups.length === 0 - ? "当前文档类型在当前入口下还没有可用子类型,请先到评查点分组管理补齐“一级分组 / 二级分组 / 规则集”绑定。" + ? "当前一级文档类型在当前入口下还没有可用子类型,请先到评查点分组管理补齐“一级分组 / 二级分组 / 规则集”绑定。" : hasMultipleSubtypeGroups - ? "同一文档类型在当前入口下已拆分多个子类型,请选择本次上传实际命中的子类型。" + ? "当前一级文档类型在当前入口下已拆分多个子类型,请选择本次上传实际命中的子类型。" : isSingleDefaultSubtype - ? "当前文档类型在当前入口下尚未拆分业务子类型,系统将按默认子类型处理;后续可在评查点分组管理中继续细分。" - : "当前文档类型在当前入口下仅配置了一个子类型,系统会自动带出该子类型。"} + ? "当前一级文档类型在当前入口下尚未拆分业务子类型,系统将按默认子类型处理;后续可在评查点分组管理中继续细分。" + : "当前一级文档类型在当前入口下仅配置了 1 个子类型,系统已自动带出。"}
{selectedRootGroupName ? (
所属一级分组:{selectedRootGroupName} - {selectedEntryModuleName ? ` · 入口模块:${selectedEntryModuleName}` : ""}
) : null} - {selectedSubtypeGroup ? ( + {selectedEntryModuleName ? (
- 当前命中:{getSubtypeDisplayName(selectedSubtypeGroup)} - {selectedSubtypeGroup.displayHint ? ` · ${selectedSubtypeGroup.displayHint}` : ""} + 所属入口模块:{selectedEntryModuleName} +
+ ) : null} + {effectiveSubtypeGroup ? ( +
+ 当前子类型:{getSubtypeDisplayName(effectiveSubtypeGroup)} +
+ ) : null} + {effectiveSubtypeGroup?.code ? ( +
+ 当前命中规则集:{effectiveSubtypeGroup.code}
) : null}
@@ -2802,7 +2820,7 @@ export default function FilesUpload() { fileName={`${currentFiles.length}个文件`} fileSize={formatFileSize(currentFiles.reduce((sum, file) => sum + file.size, 0))} progress={uploadProgress} - speed={''} + speed={uploadSpeed} /> )} @@ -2867,10 +2885,6 @@ export default function FilesUpload() { type="default" icon="ri-refresh-line" onClick={() => { - // 清除所有定时器 - if (uploadProgressIntervalRef.current) { - clearInterval(uploadProgressIntervalRef.current); - } if (processingStatusIntervalRef.current) { clearInterval(processingStatusIntervalRef.current); }