feat: wire real upload progress and subtype mapping
This commit is contained in:
@@ -147,8 +147,10 @@ export interface DocumentType {
|
|||||||
name: string;
|
name: string;
|
||||||
code?: string;
|
code?: string;
|
||||||
entryModuleId?: number;
|
entryModuleId?: number;
|
||||||
|
entryModuleName?: string | null;
|
||||||
isEnabled?: boolean;
|
isEnabled?: boolean;
|
||||||
ruleSetIds?: number[];
|
ruleSetIds?: number[];
|
||||||
|
childDocumentTypeIds?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DocumentSubtypeGroup {
|
export interface DocumentSubtypeGroup {
|
||||||
@@ -233,6 +235,12 @@ export interface UploadResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UploadProgressInfo {
|
||||||
|
loaded: number;
|
||||||
|
total: number;
|
||||||
|
percent: number;
|
||||||
|
}
|
||||||
|
|
||||||
// 旧接口上传响应(uploadContractTemplate / appendContractAttachments 仍在使用)
|
// 旧接口上传响应(uploadContractTemplate / appendContractAttachments 仍在使用)
|
||||||
interface LegacyUploadResponse {
|
interface LegacyUploadResponse {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@@ -684,6 +692,7 @@ export async function uploadDocumentToServer(
|
|||||||
autoRun: boolean = true,
|
autoRun: boolean = true,
|
||||||
speed: string = "normal",
|
speed: string = "normal",
|
||||||
jwtToken?: string,
|
jwtToken?: string,
|
||||||
|
onProgress?: (progress: UploadProgressInfo) => void,
|
||||||
): Promise<{ data: UploadResult } | { error: string; status?: number; payload?: UploadErrorPayload }> {
|
): Promise<{ data: UploadResult } | { error: string; status?: number; payload?: UploadErrorPayload }> {
|
||||||
try {
|
try {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -711,7 +720,23 @@ export async function uploadDocumentToServer(
|
|||||||
headers["Authorization"] = `Bearer ${jwtToken}`;
|
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;
|
const body = response.data;
|
||||||
|
|
||||||
// Result<DocumentUploadVO> envelope
|
// Result<DocumentUploadVO> envelope
|
||||||
@@ -826,8 +851,6 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT
|
|||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
if (selectedModuleId) {
|
if (selectedModuleId) {
|
||||||
params.entry_module_id = String(selectedModuleId);
|
params.entry_module_id = String(selectedModuleId);
|
||||||
} else if (documentTypeIds && documentTypeIds.length > 0) {
|
|
||||||
params.ids = documentTypeIds.join(",");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
@@ -835,18 +858,52 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT
|
|||||||
headers["Authorization"] = `Bearer ${token}`;
|
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;
|
const body = response.data;
|
||||||
|
|
||||||
if (body?.data && Array.isArray(body.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[] }) => ({
|
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,
|
id: item.id,
|
||||||
name: item.name,
|
name: item.name,
|
||||||
code: item.code,
|
code: item.code,
|
||||||
entryModuleId: item.entryModuleId,
|
entryModuleId: item.entryModuleId ?? null,
|
||||||
|
entryModuleName: item.entryModuleName ?? null,
|
||||||
isEnabled: item.isEnabled,
|
isEnabled: item.isEnabled,
|
||||||
ruleSetIds: item.ruleSetIds,
|
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 { data: types };
|
||||||
}
|
}
|
||||||
return { error: body?.message || "获取文档类型失败", status: response.status };
|
return { error: body?.message || "获取文档类型失败", status: response.status };
|
||||||
@@ -919,18 +976,26 @@ async function fetchAllEvaluationPointGroupRoots(token?: string): Promise<any[]>
|
|||||||
|
|
||||||
function collectSubtypeGroupsFromRoots(
|
function collectSubtypeGroupsFromRoots(
|
||||||
roots: any[],
|
roots: any[],
|
||||||
documentTypeId: number,
|
rootOrDocumentTypeId: number,
|
||||||
entryModuleId?: number | null,
|
entryModuleId?: number | null,
|
||||||
): DocumentSubtypeGroup[] {
|
): DocumentSubtypeGroup[] {
|
||||||
return dedupeSubtypeGroups(
|
const scopedRoots = roots.filter((root: any) => {
|
||||||
roots.flatMap((root: any) => {
|
|
||||||
if (!Array.isArray(root?.children)) return [];
|
|
||||||
if (entryModuleId && Number(root?.entry_module_id || 0) !== Number(entryModuleId)) {
|
if (entryModuleId && Number(root?.entry_module_id || 0) !== Number(entryModuleId)) {
|
||||||
return [];
|
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
|
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));
|
.map((child: any) => mapSubtypeChild(child, root));
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
+179
-165
@@ -25,6 +25,7 @@ import {
|
|||||||
type DocumentType,
|
type DocumentType,
|
||||||
type DocumentSubtypeGroup,
|
type DocumentSubtypeGroup,
|
||||||
type UploadErrorDetails,
|
type UploadErrorDetails,
|
||||||
|
type UploadProgressInfo,
|
||||||
type UploadResult,
|
type UploadResult,
|
||||||
DocumentStatus
|
DocumentStatus
|
||||||
} from "~/api/files/files-upload";
|
} from "~/api/files/files-upload";
|
||||||
@@ -139,6 +140,7 @@ async function handleFileUpload(
|
|||||||
createdBy?: number,
|
createdBy?: number,
|
||||||
attachments?: File[],
|
attachments?: File[],
|
||||||
jwtToken?: string,
|
jwtToken?: string,
|
||||||
|
onProgress?: (progress: UploadProgressInfo) => void,
|
||||||
): Promise<UploadResult> {
|
): Promise<UploadResult> {
|
||||||
const speed = priority === Priority.NORMAL ? "normal" : "urgent";
|
const speed = priority === Priority.NORMAL ? "normal" : "urgent";
|
||||||
|
|
||||||
@@ -154,6 +156,7 @@ async function handleFileUpload(
|
|||||||
true,
|
true,
|
||||||
speed,
|
speed,
|
||||||
jwtToken,
|
jwtToken,
|
||||||
|
onProgress,
|
||||||
);
|
);
|
||||||
|
|
||||||
if ("error" in response || !response.data) {
|
if ("error" in response || !response.data) {
|
||||||
@@ -172,6 +175,48 @@ async function handleFileUpload(
|
|||||||
return response.data;
|
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返回数据的类型
|
// 定义action返回数据的类型
|
||||||
type ActionData = {
|
type ActionData = {
|
||||||
errors?: {
|
errors?: {
|
||||||
@@ -199,7 +244,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
const errors: Record<string, string> = {};
|
const errors: Record<string, string> = {};
|
||||||
|
|
||||||
if (!fileType) {
|
if (!fileType) {
|
||||||
errors.fileType = "上传文件之前请选择文件类型";
|
errors.fileType = "上传文件之前请选择文档类型";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fileUpload) {
|
if (!fileUpload) {
|
||||||
@@ -408,6 +453,9 @@ export default function FilesUpload() {
|
|||||||
const hasMultipleSubtypeGroups = subtypeGroups.length > 1;
|
const hasMultipleSubtypeGroups = subtypeGroups.length > 1;
|
||||||
const singleSubtypeGroup = subtypeGroups.length === 1 ? subtypeGroups[0] : null;
|
const singleSubtypeGroup = subtypeGroups.length === 1 ? subtypeGroups[0] : null;
|
||||||
const isSingleDefaultSubtype = !!(singleSubtypeGroup && singleSubtypeGroup.isDefault);
|
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 selectedRootGroupName = selectedSubtypeGroup?.rootGroupName || singleSubtypeGroup?.rootGroupName || "";
|
||||||
const selectedEntryModuleName = selectedSubtypeGroup?.entryModuleName || singleSubtypeGroup?.entryModuleName || "";
|
const selectedEntryModuleName = selectedSubtypeGroup?.entryModuleName || singleSubtypeGroup?.entryModuleName || "";
|
||||||
|
|
||||||
@@ -476,7 +524,11 @@ export default function FilesUpload() {
|
|||||||
const scopedTypesResponse = await getDocumentTypes(loaderData.frontendJWT || undefined);
|
const scopedTypesResponse = await getDocumentTypes(loaderData.frontendJWT || undefined);
|
||||||
if (!cancelled && !scopedTypesResponse.error && scopedTypesResponse.data) {
|
if (!cancelled && !scopedTypesResponse.error && scopedTypesResponse.data) {
|
||||||
scopedTypes = 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);
|
filterDocumentTypes(effectiveTypeIds, scopedTypes, normalizedModuleId);
|
||||||
await filterDocuments(effectiveTypeIds);
|
await filterDocuments(effectiveTypeIds);
|
||||||
|
|
||||||
if (effectiveTypeIds && effectiveTypeIds.includes(1)) {
|
if (scopedTypes.length === 1) {
|
||||||
setIsContractType(true);
|
const onlyType = scopedTypes[0];
|
||||||
const contractType = scopedTypes.find(type => type.id === 1);
|
setFileType(onlyType.id.toString());
|
||||||
if (contractType) {
|
setIsContractType(onlyType.name.includes('合同'));
|
||||||
setFileType(contractType.id.toString());
|
|
||||||
setFileTypeError(null);
|
setFileTypeError(null);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
setIsContractType(false);
|
setIsContractType(false);
|
||||||
}
|
}
|
||||||
@@ -527,7 +577,10 @@ export default function FilesUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据 documentTypeIds 过滤文档类型
|
// 根据 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);
|
setDocumentTypesState(filteredTypes);
|
||||||
};
|
};
|
||||||
@@ -590,7 +643,6 @@ export default function FilesUpload() {
|
|||||||
const [completedFiles, setCompletedFiles] = useState<UploadedFile[]>([]);
|
const [completedFiles, setCompletedFiles] = useState<UploadedFile[]>([]);
|
||||||
|
|
||||||
// 计时器引用 - 分离为三个独立的定时器
|
// 计时器引用 - 分离为三个独立的定时器
|
||||||
const uploadProgressIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
const processingStatusIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const processingStatusIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const queueStatusIntervalRef = useRef<NodeJS.Timeout | null>(null); // 原 statusCheckIntervalRef
|
const queueStatusIntervalRef = useRef<NodeJS.Timeout | null>(null); // 原 statusCheckIntervalRef
|
||||||
|
|
||||||
@@ -828,7 +880,9 @@ export default function FilesUpload() {
|
|||||||
setFileTypeError(null);
|
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('合同'));
|
const isContract = !!(selectedType && selectedType.name.includes('合同'));
|
||||||
// console.log('【调试-handleFileTypeChange】文件类型检查:', {
|
// console.log('【调试-handleFileTypeChange】文件类型检查:', {
|
||||||
// selectedType,
|
// selectedType,
|
||||||
@@ -859,7 +913,7 @@ export default function FilesUpload() {
|
|||||||
setFileType("");
|
setFileType("");
|
||||||
setIsContractType(false);
|
setIsContractType(false);
|
||||||
// 如果用户选择了空选项,显示错误信息
|
// 如果用户选择了空选项,显示错误信息
|
||||||
setFileTypeError("上传文件之前请选择文件类型");
|
setFileTypeError("上传文件之前请选择文档类型");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1264,7 +1318,12 @@ export default function FilesUpload() {
|
|||||||
const mainFile = mainFiles[0];
|
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) {
|
if (duplicateResult.is_duplicate) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(
|
||||||
'存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。'
|
'存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。'
|
||||||
@@ -1291,31 +1350,8 @@ export default function FilesUpload() {
|
|||||||
setProcessingSteps(updatedSteps);
|
setProcessingSteps(updatedSteps);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 计算总大小并开启与旧逻辑一致的模拟进度(按时间推进到 95%)
|
|
||||||
const totalSize = filesForProgress.reduce((sum, f) => sum + (f?.size || 0), 0);
|
const totalSize = filesForProgress.reduce((sum, f) => sum + (f?.size || 0), 0);
|
||||||
const startTime = Date.now();
|
setUploadSpeed("上传中");
|
||||||
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);
|
|
||||||
|
|
||||||
// 转二进制
|
// 转二进制
|
||||||
const binaryData = await uploadFileToBinary(mainFile);
|
const binaryData = await uploadFileToBinary(mainFile);
|
||||||
@@ -1324,9 +1360,20 @@ export default function FilesUpload() {
|
|||||||
const region = (loaderData.userInfo?.area as string) || "default";
|
const region = (loaderData.userInfo?.area as string) || "default";
|
||||||
const createdBy = loaderData.userInfo?.user_id as number | undefined;
|
const createdBy = loaderData.userInfo?.user_id as number | undefined;
|
||||||
const uploadResp = await handleFileUpload(
|
const uploadResp = await handleFileUpload(
|
||||||
binaryData, mainFile.name, mainFile.type,
|
binaryData,
|
||||||
fileType as FileType, selectedGroupId ? Number(selectedGroupId) : null, priority,
|
mainFile.name,
|
||||||
region, createdBy, attachmentFiles, loaderData.frontendJWT || undefined,
|
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) {
|
if (!uploadResp.success) {
|
||||||
@@ -1350,10 +1397,6 @@ export default function FilesUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 完成:清理进度定时器并置满
|
// 完成:清理进度定时器并置满
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
uploadProgressIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
setUploadSpeed('完成');
|
setUploadSpeed('完成');
|
||||||
|
|
||||||
@@ -1378,11 +1421,6 @@ export default function FilesUpload() {
|
|||||||
await filterDocuments(documentTypeIds);
|
await filterDocuments(documentTypeIds);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('合同首传上传失败:', error);
|
console.error('合同首传上传失败:', error);
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
uploadProgressIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清空合同模板文件缓存
|
// 清空合同模板文件缓存
|
||||||
setContractTemplateFiles([]);
|
setContractTemplateFiles([]);
|
||||||
console.log('【合同上传失败】已清空合同模板文件缓存');
|
console.log('【合同上传失败】已清空合同模板文件缓存');
|
||||||
@@ -1412,7 +1450,7 @@ export default function FilesUpload() {
|
|||||||
// 检查是否选择了文件类型
|
// 检查是否选择了文件类型
|
||||||
if (!fileType) {
|
if (!fileType) {
|
||||||
console.error('【调试-checkAndPrepareUpload】未选择文件类型');
|
console.error('【调试-checkAndPrepareUpload】未选择文件类型');
|
||||||
toastService.error('请先选择文件类型');
|
toastService.error('请先选择文档类型');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (subtypeGroups.length > 1 && !selectedGroupId) {
|
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('合同'));
|
const isContract = !!(selectedType && selectedType.name.includes('合同'));
|
||||||
|
|
||||||
// console.log('【调试-checkAndPrepareUpload】文件类型检查', {
|
// console.log('【调试-checkAndPrepareUpload】文件类型检查', {
|
||||||
@@ -1454,7 +1492,12 @@ export default function FilesUpload() {
|
|||||||
|
|
||||||
// 检查主文件名称是否重复(在任何状态变化之前进行检查)
|
// 检查主文件名称是否重复(在任何状态变化之前进行检查)
|
||||||
const mainFile = allFiles[0];
|
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) {
|
if (duplicateResult.is_duplicate) {
|
||||||
const confirmed = window.confirm(
|
const confirmed = window.confirm(
|
||||||
'存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。'
|
'存在同名文件,会归纳到历史版本中。如需纳入历史版本请点击确定,否则点击取消并重新命名文档后再上传。'
|
||||||
@@ -1505,8 +1548,8 @@ export default function FilesUpload() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('【调试-checkAndPrepareUpload】未选择文件类型,无法上传');
|
console.error('【调试-checkAndPrepareUpload】未选择文档类型,无法上传');
|
||||||
toastService.error('请选择文件类型');
|
toastService.error('请选择文档类型');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error('【调试-checkAndPrepareUpload】没有文件可上传');
|
console.error('【调试-checkAndPrepareUpload】没有文件可上传');
|
||||||
@@ -1551,13 +1594,12 @@ export default function FilesUpload() {
|
|||||||
|
|
||||||
setUploadStage("uploading");
|
setUploadStage("uploading");
|
||||||
setUploadProgress(0);
|
setUploadProgress(0);
|
||||||
|
setUploadSpeed("上传中");
|
||||||
|
|
||||||
// 计算总文件大小
|
// 计算总文件大小
|
||||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||||
let uploadedSize = 0;
|
let uploadedSize = 0;
|
||||||
|
|
||||||
// console.log('【调试-startUpload】总文件大小:', formatFileSize(totalSize));
|
|
||||||
|
|
||||||
// 更新步骤状态
|
// 更新步骤状态
|
||||||
const updatedSteps = [...processingSteps];
|
const updatedSteps = [...processingSteps];
|
||||||
updatedSteps[0].status = "active";
|
updatedSteps[0].status = "active";
|
||||||
@@ -1571,33 +1613,6 @@ export default function FilesUpload() {
|
|||||||
return;
|
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[] = [];
|
const uploadedFiles: UploadedFile[] = [];
|
||||||
|
|
||||||
@@ -1629,44 +1644,35 @@ export default function FilesUpload() {
|
|||||||
|
|
||||||
// console.log(`【调试-startUpload】准备上传文件 ${file.name} 到服务器`);
|
// 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<void>((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添加超时处理
|
// 使用Promise.race添加超时处理
|
||||||
const region = (loaderData.userInfo?.area as string) || "default";
|
const region = (loaderData.userInfo?.area as string) || "default";
|
||||||
const createdBy = loaderData.userInfo?.user_id as number | undefined;
|
const createdBy = loaderData.userInfo?.user_id as number | undefined;
|
||||||
|
if (!effectiveDocumentTypeId) {
|
||||||
|
throw new Error("当前子类型缺少可提交的文档类型绑定,请先检查评查点分组配置");
|
||||||
|
}
|
||||||
const uploadPromise = handleFileUpload(
|
const uploadPromise = handleFileUpload(
|
||||||
binaryData, file.name, file.type,
|
binaryData,
|
||||||
fileType as FileType, selectedGroupId ? Number(selectedGroupId) : null, priority,
|
file.name,
|
||||||
region, createdBy, undefined, loaderData.frontendJWT || undefined,
|
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<UploadResult>((_, reject) => {
|
const timeoutPromise = new Promise<UploadResult>((_, reject) => {
|
||||||
@@ -1675,16 +1681,7 @@ export default function FilesUpload() {
|
|||||||
}, 600000);
|
}, 600000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 并行执行上传和进度更新
|
const uploadResult = await Promise.race([uploadPromise, timeoutPromise]);
|
||||||
const [uploadResult] = await Promise.all([
|
|
||||||
Promise.race([uploadPromise, timeoutPromise]),
|
|
||||||
progressPromise
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 清除进度定时器
|
|
||||||
if (progressInterval) {
|
|
||||||
clearInterval(progressInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再次检查组件是否已卸载
|
// 再次检查组件是否已卸载
|
||||||
if (!isMountedRef.current) {
|
if (!isMountedRef.current) {
|
||||||
@@ -1748,10 +1745,6 @@ export default function FilesUpload() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 清除进度定时器
|
// 清除进度定时器
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新上传状态
|
// 更新上传状态
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
setUploadSpeed("完成");
|
setUploadSpeed("完成");
|
||||||
@@ -1763,7 +1756,7 @@ export default function FilesUpload() {
|
|||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type_id: fileType ? parseInt(fileType) : 0,
|
type_id: effectiveDocumentTypeId || (fileType ? parseInt(fileType) : 0),
|
||||||
file_size: file.size,
|
file_size: file.size,
|
||||||
status: DocumentStatus.CUTTING,
|
status: DocumentStatus.CUTTING,
|
||||||
created_at: new Date().toISOString()
|
created_at: new Date().toISOString()
|
||||||
@@ -1789,11 +1782,6 @@ export default function FilesUpload() {
|
|||||||
|
|
||||||
setProcessingSteps(errorSteps);
|
setProcessingSteps(errorSteps);
|
||||||
|
|
||||||
// 清除进度定时器
|
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
showFriendlyUploadError(error, { titlePrefix: '文件上传失败' });
|
showFriendlyUploadError(error, { titlePrefix: '文件上传失败' });
|
||||||
resetUpload();
|
resetUpload();
|
||||||
|
|
||||||
@@ -1824,6 +1812,8 @@ export default function FilesUpload() {
|
|||||||
updatedSteps[1].description = "文档正在排队等待处理...";
|
updatedSteps[1].description = "文档正在排队等待处理...";
|
||||||
|
|
||||||
setProcessingSteps(updatedSteps);
|
setProcessingSteps(updatedSteps);
|
||||||
|
setUploadProgress(mapProcessingStatusToProgress(DocumentStatus.QUEUED));
|
||||||
|
setUploadSpeed(mapProcessingStatusToSpeed(DocumentStatus.QUEUED));
|
||||||
|
|
||||||
// 获取文件ID列表
|
// 获取文件ID列表
|
||||||
const fileIds = files.map(file => file.id).filter(id => id > 0);
|
const fileIds = files.map(file => file.id).filter(id => id > 0);
|
||||||
@@ -1926,6 +1916,17 @@ export default function FilesUpload() {
|
|||||||
return;
|
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);
|
const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED);
|
||||||
// console.log('【调试-checkProcessingStatus】文件处理状态:', { allCompleted, statusList: response.data.map(doc => doc.status) });
|
// console.log('【调试-checkProcessingStatus】文件处理状态:', { allCompleted, statusList: response.data.map(doc => doc.status) });
|
||||||
@@ -1955,6 +1956,8 @@ export default function FilesUpload() {
|
|||||||
completedSteps[5].description = "文档已准备就绪,可以查看";
|
completedSteps[5].description = "文档已准备就绪,可以查看";
|
||||||
|
|
||||||
setProcessingSteps(completedSteps);
|
setProcessingSteps(completedSteps);
|
||||||
|
setUploadProgress(100);
|
||||||
|
setUploadSpeed(mapProcessingStatusToSpeed(DocumentStatus.PROCESSED));
|
||||||
setUploadStage("completed");
|
setUploadStage("completed");
|
||||||
} else {
|
} else {
|
||||||
// 根据当前状态更新步骤
|
// 根据当前状态更新步骤
|
||||||
@@ -2036,9 +2039,18 @@ export default function FilesUpload() {
|
|||||||
updatedSteps[5].status = "done";
|
updatedSteps[5].status = "done";
|
||||||
updatedSteps[5].description = "文档已准备就绪,可以查看";
|
updatedSteps[5].description = "文档已准备就绪,可以查看";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DocumentStatus.FAILED:
|
||||||
|
updatedSteps[1].status = "done";
|
||||||
|
updatedSteps[1].description = "已进入处理队列";
|
||||||
|
updatedSteps[2].status = "error";
|
||||||
|
updatedSteps[2].description = "文档处理失败,请检查上传文件或稍后重试";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
setProcessingSteps(updatedSteps);
|
setProcessingSteps(updatedSteps);
|
||||||
|
setUploadProgress(mapProcessingStatusToProgress(status));
|
||||||
|
setUploadSpeed(mapProcessingStatusToSpeed(status));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新队列中文件的状态
|
// 更新队列中文件的状态
|
||||||
@@ -2063,12 +2075,6 @@ export default function FilesUpload() {
|
|||||||
|
|
||||||
// 重置上传状态 - 不清除队列状态检查定时器
|
// 重置上传状态 - 不清除队列状态检查定时器
|
||||||
const resetUpload = () => {
|
const resetUpload = () => {
|
||||||
// 清除上传和处理相关的定时器
|
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
uploadProgressIntervalRef.current = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (processingStatusIntervalRef.current) {
|
if (processingStatusIntervalRef.current) {
|
||||||
clearInterval(processingStatusIntervalRef.current);
|
clearInterval(processingStatusIntervalRef.current);
|
||||||
processingStatusIntervalRef.current = null;
|
processingStatusIntervalRef.current = null;
|
||||||
@@ -2136,7 +2142,11 @@ export default function FilesUpload() {
|
|||||||
// 获取文档类型名称
|
// 获取文档类型名称
|
||||||
const getDocumentTypeName = (codeId: number) => {
|
const getDocumentTypeName = (codeId: number) => {
|
||||||
const type = documentTypesState.find(t => t.id === codeId);
|
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() {
|
|||||||
{/* 文件类型选择和上传表单 */}
|
{/* 文件类型选择和上传表单 */}
|
||||||
<Form method="post" encType="multipart/form-data" ref={formRef}>
|
<Form method="post" encType="multipart/form-data" ref={formRef}>
|
||||||
{/* 文件类型选择 */}
|
{/* 文件类型选择 */}
|
||||||
<Card title={<h3>选择文件类型</h3>} className="mb-4">
|
<Card title={<h3>选择文档类型</h3>} className="mb-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="file-type-select" className="form-label">文件类型 <span className="text-red-500">*</span></label>
|
<label htmlFor="file-type-select" className="form-label">文档类型 <span className="text-red-500">*</span></label>
|
||||||
<select
|
<select
|
||||||
id="file-type-select"
|
id="file-type-select"
|
||||||
name="fileType"
|
name="fileType"
|
||||||
@@ -2402,7 +2412,7 @@ export default function FilesUpload() {
|
|||||||
onChange={handleFileTypeChange}
|
onChange={handleFileTypeChange}
|
||||||
disabled={uploadStage !== "idle"}
|
disabled={uploadStage !== "idle"}
|
||||||
>
|
>
|
||||||
<option value="">请选择文件类型</option>
|
<option value="">请选择文档类型</option>
|
||||||
{documentTypesState.map(type => (
|
{documentTypesState.map(type => (
|
||||||
<option key={type.id} value={type.id}>{type.name}</option>
|
<option key={type.id} value={type.id}>{type.name}</option>
|
||||||
))}
|
))}
|
||||||
@@ -2413,7 +2423,7 @@ export default function FilesUpload() {
|
|||||||
<div className="text-red-500 text-sm mt-1">{fileTypeError}</div>
|
<div className="text-red-500 text-sm mt-1">{fileTypeError}</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="form-tip">不同类型的文档将应用不同的审核规则</div>
|
<div className="form-tip">这里选择的是当前入口模块下允许上传的一级文档类型(业务大类)。</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="priority-select" className="form-label">审核优先级</label>
|
<label htmlFor="priority-select" className="form-label">审核优先级</label>
|
||||||
@@ -2446,11 +2456,11 @@ export default function FilesUpload() {
|
|||||||
required={subtypeGroups.length > 1}
|
required={subtypeGroups.length > 1}
|
||||||
>
|
>
|
||||||
{!fileType ? (
|
{!fileType ? (
|
||||||
<option value="">请先选择文件类型</option>
|
<option value="">请先选择文档类型</option>
|
||||||
) : groupOptionsLoading ? (
|
) : groupOptionsLoading ? (
|
||||||
<option value="">子类型加载中...</option>
|
<option value="">子类型加载中...</option>
|
||||||
) : subtypeGroups.length === 0 ? (
|
) : subtypeGroups.length === 0 ? (
|
||||||
<option value="">当前文档类型暂无二级分组</option>
|
<option value="">当前文档类型暂无子类型</option>
|
||||||
) : subtypeGroups.length === 1 ? (
|
) : subtypeGroups.length === 1 ? (
|
||||||
<option value={String(subtypeGroups[0].id)}>
|
<option value={String(subtypeGroups[0].id)}>
|
||||||
{getSubtypeDisplayName(subtypeGroups[0])}
|
{getSubtypeDisplayName(subtypeGroups[0])}
|
||||||
@@ -2468,27 +2478,35 @@ export default function FilesUpload() {
|
|||||||
</select>
|
</select>
|
||||||
<div className="form-tip">
|
<div className="form-tip">
|
||||||
{!fileType
|
{!fileType
|
||||||
? "请先选择文件类型,再确定本次上传实际命中的子类型。"
|
? "请先选择一级文档类型,再确定本次上传实际命中的子类型。"
|
||||||
: groupOptionsLoading
|
: groupOptionsLoading
|
||||||
? "正在加载当前文档类型下可用的子类型配置。"
|
? "正在加载当前一级文档类型下可用的子类型配置。"
|
||||||
: subtypeGroups.length === 0
|
: subtypeGroups.length === 0
|
||||||
? "当前文档类型在当前入口下还没有可用子类型,请先到评查点分组管理补齐“一级分组 / 二级分组 / 规则集”绑定。"
|
? "当前一级文档类型在当前入口下还没有可用子类型,请先到评查点分组管理补齐“一级分组 / 二级分组 / 规则集”绑定。"
|
||||||
: hasMultipleSubtypeGroups
|
: hasMultipleSubtypeGroups
|
||||||
? "同一文档类型在当前入口下已拆分多个子类型,请选择本次上传实际命中的子类型。"
|
? "当前一级文档类型在当前入口下已拆分多个子类型,请选择本次上传实际命中的子类型。"
|
||||||
: isSingleDefaultSubtype
|
: isSingleDefaultSubtype
|
||||||
? "当前文档类型在当前入口下尚未拆分业务子类型,系统将按默认子类型处理;后续可在评查点分组管理中继续细分。"
|
? "当前一级文档类型在当前入口下尚未拆分业务子类型,系统将按默认子类型处理;后续可在评查点分组管理中继续细分。"
|
||||||
: "当前文档类型在当前入口下仅配置了一个子类型,系统会自动带出该子类型。"}
|
: "当前一级文档类型在当前入口下仅配置了 1 个子类型,系统已自动带出。"}
|
||||||
</div>
|
</div>
|
||||||
{selectedRootGroupName ? (
|
{selectedRootGroupName ? (
|
||||||
<div className="form-tip">
|
<div className="form-tip">
|
||||||
所属一级分组:{selectedRootGroupName}
|
所属一级分组:{selectedRootGroupName}
|
||||||
{selectedEntryModuleName ? ` · 入口模块:${selectedEntryModuleName}` : ""}
|
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{selectedSubtypeGroup ? (
|
{selectedEntryModuleName ? (
|
||||||
<div className="form-tip">
|
<div className="form-tip">
|
||||||
当前命中:{getSubtypeDisplayName(selectedSubtypeGroup)}
|
所属入口模块:{selectedEntryModuleName}
|
||||||
{selectedSubtypeGroup.displayHint ? ` · ${selectedSubtypeGroup.displayHint}` : ""}
|
</div>
|
||||||
|
) : null}
|
||||||
|
{effectiveSubtypeGroup ? (
|
||||||
|
<div className="form-tip">
|
||||||
|
当前子类型:{getSubtypeDisplayName(effectiveSubtypeGroup)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{effectiveSubtypeGroup?.code ? (
|
||||||
|
<div className="form-tip">
|
||||||
|
当前命中规则集:{effectiveSubtypeGroup.code}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
@@ -2802,7 +2820,7 @@ export default function FilesUpload() {
|
|||||||
fileName={`${currentFiles.length}个文件`}
|
fileName={`${currentFiles.length}个文件`}
|
||||||
fileSize={formatFileSize(currentFiles.reduce((sum, file) => sum + file.size, 0))}
|
fileSize={formatFileSize(currentFiles.reduce((sum, file) => sum + file.size, 0))}
|
||||||
progress={uploadProgress}
|
progress={uploadProgress}
|
||||||
speed={''}
|
speed={uploadSpeed}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -2867,10 +2885,6 @@ export default function FilesUpload() {
|
|||||||
type="default"
|
type="default"
|
||||||
icon="ri-refresh-line"
|
icon="ri-refresh-line"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
// 清除所有定时器
|
|
||||||
if (uploadProgressIntervalRef.current) {
|
|
||||||
clearInterval(uploadProgressIntervalRef.current);
|
|
||||||
}
|
|
||||||
if (processingStatusIntervalRef.current) {
|
if (processingStatusIntervalRef.current) {
|
||||||
clearInterval(processingStatusIntervalRef.current);
|
clearInterval(processingStatusIntervalRef.current);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user