Files
leaudit-platform-frontend/app/api/cross-checking/cross-files-upload.ts
T
LiangShiyong d4000cd292 fix: 1. 继续对齐交叉评查的接口,完善创建交叉评查的逻辑 和 相关组件的渲染布局。
2. 文档的基本信息修改改用接口。      3. 重新完善角色权限管理的页面逻辑。     4.将评查点列表中的返回逻辑改用浏览器的记忆返回。
2025-12-12 12:00:36 +08:00

372 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { UPLOAD_URL, API_BASE_URL } from '../../config/api-config';
import axios from 'axios';
/**
* 从不同格式的 API 响应中提取数据
* @param responseData API 响应数据
* @returns 提取后的数据或 null
*/
function extractApiData<T>(responseData: unknown): T | null {
if (!responseData) return null;
// 格式1: { code: number, msg: string, data: T }
if (typeof responseData === 'object' && responseData !== null &&
'code' in responseData &&
'data' in responseData &&
(responseData as { data: unknown }).data) {
return (responseData as { data: T }).data;
}
// 格式2: 直接是数据对象
return responseData as T;
}
// 案卷类型枚举
export enum CaseType {
ADMINISTRATIVE_PENALTY = "administrative_penalty",
ADMINISTRATIVE_PERMIT = "administrative_permit"
}
// 案卷类型到type_id的映射
export const CASE_TYPE_TO_TYPE_ID: Record<CaseType, number> = {
[CaseType.ADMINISTRATIVE_PENALTY]: 3, // 行政处罚
[CaseType.ADMINISTRATIVE_PERMIT]: 2, // 行政许可
};
// 文件上传响应接口
export interface CrossCheckingFileUploadResponse {
success: boolean;
result?: {
id: number;
file_name: string;
file_size: number;
file_url: string;
type_id: number;
type_description: string;
document_number: string | null;
storage_type: string;
is_test_document: boolean;
remark: string | null;
background_processing: boolean;
evaluation_level: string;
};
error: string | null;
}
// 上传的文件接口
export interface CrossCheckingUploadedFile {
id: string; // 本地生成的唯一ID
file: File;
name: string;
size: number;
type: string;
uploadType: 'single' | 'multiple';
}
/**
* 将文件转换为二进制数据
*/
export async function uploadFileToBinary(file: File | Blob): Promise<ArrayBuffer> {
return new Promise((resolve, reject) => {
// 只保留简单类型检查和调试
if (!(file instanceof File) && !(file instanceof Blob)) {
reject(new Error(`参数必须是File或Blob对象,当前类型: ${typeof file}`));
return;
}
const reader = new FileReader();
reader.onload = () => {
if (reader.result instanceof ArrayBuffer) {
resolve(reader.result);
} else {
reject(new Error('文件读取失败'));
}
};
reader.onerror = () => reject(new Error('文件读取失败'));
reader.readAsArrayBuffer(file);
});
}
/**
* 上传交叉评查文件到服务器
* @param binaryData 文件的二进制数据
* @param fileName 文件名
* @param fileType 文件类型
* @param typeId 文档类型ID
* @param priority 优先级
* @param documentNumber 文档编号(可选)
* @param remark 备注信息(可选)
* @param isTestDocument 是否为测试文档
* @param documentId 关联的文档ID(用于附件上传)
* @param isReupload 是否为重新上传
* @returns 上传结果
*/
export async function uploadCrossCheckingDocument(
binaryData: ArrayBuffer,
fileName: string,
fileType: string,
typeId: number,
priority: string = 'normal',
documentNumber: string = '',
remark: string = '',
isTestDocument: boolean = false,
documentId: number | null = null,
isReupload: boolean = false,
token: string | null = null
): Promise<{data: CrossCheckingFileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> {
try {
console.log('【交叉评查上传】开始上传文档:', { fileName, fileSize: binaryData.byteLength, typeId });
// 创建FormData对象
const formData = new FormData();
// 将二进制数据转换为Blob并添加到FormData
const blob = new Blob([binaryData], { type: fileType });
formData.append('file', blob, fileName);
console.log('【交叉评查上传】Blob已创建,文件大小:', blob.size);
// 将信息添加到一个JSON对象中
const uploadInfo = {
type_id: typeId,
evaluation_level: priority,
document_number: documentNumber || null,
remark: remark || null,
is_test_document: isTestDocument,
document_id: documentId || null,
is_reupload: isReupload
};
// 添加JSON字符串到FormData
formData.append('upload_info', JSON.stringify(uploadInfo));
console.log('【交叉评查上传】FormData准备完成:', JSON.stringify(uploadInfo));
// 根据是否有documentId决定使用哪个接口
const uploadEndpoint = '/batch_upload';
const uploadUrl = UPLOAD_URL + uploadEndpoint;
console.log('【交叉评查上传】准备发送请求到服务器:', uploadUrl);
// 发送请求
try {
console.log('【交叉评查上传】开始axios请求...');
const headers: Record<string, string> = {
'X-File-Name': encodeURIComponent(fileName),
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await axios.post(uploadUrl, formData, {
headers
});
console.log('【交叉评查上传】收到服务器响应:', { status: response.status, statusText: response.statusText });
console.log('【交叉评查上传】JSON响应解析成功:', response.data);
const extractedData = extractApiData<CrossCheckingFileUploadResponse>(response.data);
console.log('【交叉评查上传】提取的数据:', extractedData);
if (!extractedData) {
console.error('【交叉评查上传】无法提取数据');
return { error: '处理上传响应失败', status: 500 };
}
console.log('【交叉评查上传】上传成功,返回数据');
return { data: extractedData as CrossCheckingFileUploadResponse };
} catch (axiosError) {
console.error('【交叉评查上传】axios请求失败:', axiosError);
if (axios.isAxiosError(axiosError)) {
const errorText = axiosError.response?.data || axiosError.message;
return {
error: `上传失败: ${axiosError.response?.status || 500} ${axiosError.response?.statusText || ''} - ${errorText}`,
status: axiosError.response?.status || 500
};
}
return {
error: `axios请求错误: ${axiosError instanceof Error ? axiosError.message : '未知错误'}`,
status: 500
};
}
} catch (error) {
console.error('【交叉评查上传】上传过程中发生错误:', error);
return {
error: error instanceof Error ? error.message : '上传失败',
status: 500
};
}
}
/**
* 批量上传并自动分配交叉评查任务(新接口适配)
* @param files 文件列表
* @param typeId 文档类型ID
* @param priority 优先级
* @param documentNumber 文档编号
* @param remark 备注
* @param isTestDocument 是否为测试文档
* @param assignUserIds 需要分配的用户ID数组
* @param taskName 任务名称
* @param docType 文档类型(如 XZCF、XZXK
* @param taskType 任务类型(如 市局间交叉评查、区局间交叉评查)
* @param token JWT Token
* @param principalUserIds 负责人ID数组(包含主要负责人和额外负责人)
*/
export async function batchUploadAndAssignCrossCheckingFiles(
files: CrossCheckingUploadedFile[],
typeId: number,
priority: string = 'normal',
documentNumber: string = '',
remark: string = '',
isTestDocument: boolean = false,
assignUserIds: number[],
taskName: string,
docType: string,
taskType: string = '市局间交叉评查',
token: string | null = null,
principalUserIds: number[] = []
): Promise<{
successes: Array<{file: CrossCheckingUploadedFile; result: Record<string, unknown>}>;
failures: Array<{file: CrossCheckingUploadedFile; error: string}>;
}> {
const successes: Array<{file: CrossCheckingUploadedFile; result: Record<string, unknown>}> = [];
const failures: Array<{file: CrossCheckingUploadedFile; error: string}> = [];
const uploadEndpoint = '/admin/v2/documents/cross_review/documents/upload_and_assign';
const uploadUrl = API_BASE_URL + uploadEndpoint;
// console.log('[批量上传] 任务类型:', taskType, '文档类型:', docType, '负责人ID:', leaderId);
for (const fileInfo of files) {
try {
const formData = new FormData();
formData.append('file', fileInfo.file, fileInfo.name);
const uploadInfo = {
type_id: typeof typeId === 'string' ? parseInt(typeId, 10) : typeId,
evaluation_level: priority,
document_number: documentNumber || null,
remark: remark || null,
is_test_document: isTestDocument,
task_name: taskName,
doc_type: typeof docType === 'string' ? docType.toUpperCase() : docType,
task_type: taskType
};
// console.log('fileInfo', fileInfo)
formData.append('upload_info', JSON.stringify(uploadInfo));
formData.append('assign_user_ids', JSON.stringify(assignUserIds));
// 添加负责人ID数组(包含主要负责人和额外负责人)
if (principalUserIds.length > 0) {
formData.append('principal_user_ids', JSON.stringify(principalUserIds));
}
const headers: Record<string, string> = {};
if (token) headers['Authorization'] = `Bearer ${token}`;
const response = await axios.post(uploadUrl, formData, {
headers
});
const result = response.data;
if (result && result.success) {
successes.push({ file: fileInfo, result });
} else {
failures.push({ file: fileInfo, error: result.error || '未知错误' });
}
} catch (error) {
failures.push({ file: fileInfo, error: error instanceof Error ? error.message : '上传失败' });
}
}
return { successes, failures };
}
/**
* 创建交叉评查任务
* @param taskData 任务数据
* @param token JWT Token
* @returns 创建结果
*/
export async function createCrossReviewTask(taskData: {
documentIds: number[];
userIds: number[];
assignerId: number;
taskName: string;
docType: string;
taskType?: string;
}, token: string | null = null): Promise<{
success: boolean;
data?: unknown;
error?: string;
}> {
try {
const requestBody = {
document_ids: taskData.documentIds,
user_ids: taskData.userIds,
assigner_id: taskData.assignerId,
task_name: taskData.taskName,
doc_type: taskData.docType,
task_type: taskData.taskType || '市局间交叉评查'
};
// console.log('[创建任务] 请求数据:', requestBody);
const headers: Record<string, string> = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await axios.post(
`${API_BASE_URL}/admin/cross_review/tasks/assign`,
requestBody,
{ headers }
);
console.log('[创建任务] 成功:', response.data);
return {
success: true,
data: response.data
};
} catch (error) {
console.error('[创建任务] 失败:', error);
let errorMessage = '创建任务失败';
if (axios.isAxiosError(error) && error.response?.data) {
errorMessage = error.response.data.msg || errorMessage;
} else if (error instanceof Error) {
errorMessage = error.message || errorMessage;
}
return {
success: false,
error: errorMessage
};
}
}
/**
* 生成唯一文件ID
* @returns 唯一ID字符串
*/
export function generateFileId(): string {
return `cross_checking_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
}
/**
* 格式化文件大小显示
* @param bytes 字节数
* @returns 格式化后的文件大小字符串
*/
export function formatFileSize(bytes: number): string {
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];
}