import { postgrestGet, type PostgrestParams } from '../postgrest-client'; import dayjs from 'dayjs'; import { UPLOAD_URL } from '../../config/api-config'; import axios from 'axios'; // import { API_BASE_URL } from '../client'; /** * 从不同格式的 API 响应中提取数据 * @param responseData API 响应数据 * @returns 提取后的数据或 null */ function extractApiData(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; } /** * 从 sessionStorage 获取文档类型 ID 列表(客户端专用) * @returns 文档类型 ID 数组,如果不存在则返回 null */ function getDocumentTypeIdsFromSession(): number[] | null { if (typeof window === 'undefined') { return null; // 服务端环境返回 null } try { const typeIdsStr = sessionStorage.getItem('documentTypeIds'); if (!typeIdsStr) { return null; } const typeIds = JSON.parse(typeIdsStr); if (Array.isArray(typeIds) && typeIds.every(id => typeof id === 'number')) { return typeIds; } console.warn('⚠️ [getDocumentTypeIds] documentTypeIds 格式不正确:', typeIds); return null; } catch (error) { console.error('❌ [getDocumentTypeIds] 解析 documentTypeIds 失败:', error); return null; } } // 文档状态枚举 export enum DocumentStatus { waiting = 'waiting', WAITING = "Waiting", CUTTING = "Cutting", EXTRACTIONING = "Extractioning", EVALUATIONING = "Evaluationing", FAILED = "Failed", PROCESSED = "Processed" } // 文档类型接口 export interface DocumentType { id: number; name: string; } // 提取结果接口 interface ExtractedResult { [key: string]: unknown; } // 摘要接口 interface Summary { [key: string]: unknown; } // 文档接口 export interface Document { id: number; name: string; type_id: number; file_size: number; status: DocumentStatus; created_at: string; document_number?: string; path?: string; storage_type?: string; is_test_document?: boolean; evaluation_level?: string; ocr_result?: Record; extracted_results?: ExtractedResult; sumary?: Summary; remark?: string; audit_status?: number; } // 合同结构比较表接口 export interface ContractStructureComparison { id: number; template_contract_name: string; file_size: number; status: DocumentStatus; created_at: string; document_id?: number; template_contract_path?: string; ocr_results?: Record; comparison_results?: Record; } // 文件上传响应接口 export interface FileUploadResponse { 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 async function uploadFileToBinary(file: File): Promise { return new Promise((resolve, reject) => { 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 是否为重新上传 * @param jwtToken JWT token * @returns 上传结果 */ /** * 上传合同模板(用于与合同文档结构对比) * @param file 模板文件 * @param documentId 源合同文档ID * @param comparisonId 已有对比记录ID(可选) * @param jwtToken JWT token * @returns 上传结果 */ export async function uploadContractTemplate( file: File, documentId: number, comparisonId?: number, jwtToken?: string ): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { try { console.log('【合同模板上传】开始上传模板:', { fileName: file.name, documentId, comparisonId }); // 创建FormData对象 const formData = new FormData(); // 添加文件 formData.append('file', file); // 添加上传信息 const uploadInfo = { document_id: documentId, ...(comparisonId && { comparison_id: comparisonId }) }; formData.append('upload_info', JSON.stringify(uploadInfo)); // 构建请求URL const uploadUrl = `${UPLOAD_URL}/upload_contract_template`; console.log('【合同模板上传】准备发送请求到服务器:', uploadUrl); // 设置请求头 const headers: HeadersInit = { 'Accept': 'application/json' }; // 从 localStorage 获取 token if (typeof window !== 'undefined') { const token = localStorage.getItem('access_token'); if (token) { headers['Authorization'] = `Bearer ${token}`; } } // 发送请求 const response = await axios.post(uploadUrl, formData, { headers }); console.log('【合同模板上传】服务器响应状态:', response.status); const result = response.data; console.log('【合同模板上传】服务器返回结果:', result); if (result.success) { return { data: result.result }; } else { return { error: result.error || '合同模板上传失败' }; } } catch (error) { console.error('【合同模板上传】上传过程中发生错误:', error); return { error: error instanceof Error ? error.message : '合同模板上传过程中发生未知错误' }; } } /** * 合同文档追加附件并合并 * @param documentId 合同文档ID * @param files 附件文件列表 * @param mergeMode 合并模式:'overwrite'(覆盖原文档)或 'new'(新建文档记录) * @param isReprocess 是否触发重新处理 * @param remark 备注 * @param token JWT token(可选) * @returns 上传结果 */ export async function appendContractAttachments( documentId: number, files: File[], mergeMode: 'overwrite' | 'new' = 'overwrite', isReprocess: boolean = true, remark?: string, token?: string ): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { try { console.log('【合同附件追加】开始追加附件:', { documentId, fileCount: files.length, mergeMode }); // 创建FormData对象 const formData = new FormData(); // 添加多个文件 files.forEach(file => { formData.append('files', file); }); // 添加其他参数 formData.append('merge_mode', mergeMode); formData.append('is_reprocess', isReprocess.toString()); if (remark) { formData.append('remark', remark); } // 构建请求URL const uploadUrl = `${UPLOAD_URL}/contracts/${documentId}/append_attachments`; console.log('【合同附件追加】准备发送请求到服务器:', uploadUrl); // 设置请求头 const headers: HeadersInit = { 'Accept': 'application/json' }; // 使用传入的 token 或从 localStorage 获取 const authToken = token || (typeof window !== 'undefined' ? localStorage.getItem('access_token') : null); if (authToken) { headers['Authorization'] = `Bearer ${authToken}`; } // 发送请求 const response = await axios.post(uploadUrl, formData, { headers }); console.log('【合同附件追加】服务器响应状态:', response.status); const result = response.data; console.log('【合同附件追加】服务器返回结果:', result); if (result.success) { return { data: result.result }; } else { return { error: result.error || '附件追加失败' }; } } catch (error) { console.error('【合同附件追加】上传过程中发生错误:', error); return { error: error instanceof Error ? error.message : '附件追加过程中发生未知错误' }; } } export async function uploadDocumentToServer( binaryData: ArrayBuffer, fileName: string, fileType: string, typeId: string | number, priority: string, documentNumber?: string | null, remark?: string | null, isTestDocument: boolean = false, documentId?: number | null, isReupload: boolean = false, jwtToken?: string, attachments?: File[] ): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { try { // console.log('【调试】开始上传文档:', { fileName, fileSize: binaryData.byteLength }); // 创建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: Number(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)); // 如果提供了附件(仅当后端支持首传合并时使用),按照后端规范追加到 FormData if (attachments && attachments.length > 0) { attachments.forEach(att => { formData.append('attachments', att); }); } // 根据是否有documentId决定使用哪个接口 // 首传合并:无论是否有附件,都应走 /upload; // 仅当要上传“合同模板”时,使用独立的 uploadContractTemplate 接口(不在本函数中处理)。 const uploadEndpoint = '/upload'; const uploadUrl = UPLOAD_URL + uploadEndpoint; // console.log('【调试】准备发送请求到服务器:', uploadUrl); // 发送请求 // const response = await axios.post(`${API_BASE_URL}/admin/documents/upload`, ... try { // console.log('【调试】开始axios请求...'); // 构建请求头,只在有JWT token时添加Authorization const headers: Record = { 'X-File-Name': encodeURIComponent(fileName) }; if (jwtToken) { headers['Authorization'] = `Bearer ${jwtToken}`; } const response = await axios.post(uploadUrl, formData, { headers }); // console.log('【调试】收到服务器响应:', { status: response.status, statusText: response.statusText }); const responseData = response.data; // console.log('【上传调试】服务器原始JSON响应:', responseData); // console.log('【上传调试】响应类型:', typeof responseData); // console.log('【上传调试】响应keys:', Object.keys(responseData)); const extractedData = extractApiData(responseData); // console.log('【上传调试】提取后的数据:', extractedData); // console.log('【上传调试】提取数据详情:', { // hasData: !!extractedData, // success: extractedData?.success, // hasResult: !!extractedData?.result, // error: extractedData?.error // }); if (!extractedData) { console.error('【调试】无法提取数据,原始响应:', JSON.stringify(responseData)); return { error: '处理上传响应失败', status: 500 }; } // console.log('【调试】上传成功,返回数据'); return { data: extractedData }; } 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 userInfo 用户信息(必需) * @param token JWT token * @param documentTypeIds 文档类型 ID 列表(可选) * @returns 文档列表 */ export async function getTodayDocuments( userInfo?: { user_id?: number; [key: string]: unknown }, token?: string, documentTypeIds?: number[] ): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> { try { // 检查用户信息是否存在 if (!userInfo?.user_id) { return { error: '没有找到用户信息,请刷新重试', status: 401 }; } const today = dayjs().startOf('day').format('YYYY-MM-DD'); // console.log('查询当天文档,日期范围:', today); // 🔑 优先使用传入的 documentTypeIds,否则从 sessionStorage 读取(客户端) const typeIds = documentTypeIds || getDocumentTypeIdsFromSession(); // console.log('📋 [getTodayDocuments] 文档类型 IDs:', typeIds, '来源:', documentTypeIds ? 'URL参数' : 'sessionStorage'); const params: PostgrestParams = { select: ` id, name, type_id, file_size, status, created_at, document_number, path, storage_type, is_test_document, evaluation_level, ocr_result, extracted_results, sumary, remark, audit_status `, order: 'created_at.desc', filter: { 'created_at': `gte.${today}`, 'user_id': `eq.${userInfo.user_id}` } }; // 🔑 根据 documentTypeIds 添加过滤条件 if (typeIds && typeIds.length > 0) { // 使用动态的文档类型 ID 列表 const typeIdsStr = typeIds.join(','); if (params.filter) { params.filter['type_id'] = `in.(${typeIdsStr})`; } else { params.filter = { 'type_id': `in.(${typeIdsStr})` }; } console.log('📋 [getTodayDocuments] 使用文档类型 IDs 查询:', typeIdsStr); } // console.log('发送请求参数:', params); const response = await postgrestGet('/api/postgrest/proxy/documents', { ...params, token }); // console.log('API 响应:', response); if (response.error) { console.error('API 返回错误:', response.error); return { error: response.error, status: response.status }; } const extractedData = extractApiData(response.data); // console.log('提取后的数据:', extractedData); if (!extractedData) { console.error('数据提取失败'); return { error: '获取数据失败', status: 500 }; } return { data: extractedData }; } catch (error) { console.error('获取当天文档列表失败:', error); return { error: error instanceof Error ? error.message : '获取当天文档列表失败', status: 500 }; } } /** * 获取文档类型列表 * @param token JWT token (可选) * @returns 文档类型列表 */ export async function getDocumentTypes(token?: string): Promise<{data: DocumentType[]; error?: never} | {data?: never; error: string; status?: number}> { try { const params: PostgrestParams = { select: 'id, name', filter: {} // 初始化为空对象 }; // 🔑 从 sessionStorage 获取文档类型 ID 列表(动态方式) const documentTypeIds = getDocumentTypeIdsFromSession(); // console.log('📋 [getDocumentTypes] 文档类型 IDs:', documentTypeIds); // 根据 documentTypeIds 添加过滤条件 if (documentTypeIds && documentTypeIds.length > 0) { // 使用动态的文档类型 ID 列表 const typeIdsStr = documentTypeIds.join(','); if (params.filter) { params.filter['id'] = `in.(${typeIdsStr})`; } else { params.filter = { 'id': `in.(${typeIdsStr})` }; } console.log('📋 [getDocumentTypes] 使用动态类型 IDs 查询:', typeIdsStr); } // 如果没有 documentTypeIds,返回所有文档类型(不添加过滤条件) const response = await postgrestGet('/api/postgrest/proxy/document_types', { ...params, token }); if (response.error) { return { error: response.error, status: response.status }; } const extractedData = extractApiData(response.data); if (!extractedData) { return { error: '获取数据失败', status: 500 }; } return { data: extractedData }; } catch (error) { console.error('获取文档类型列表失败:', error); return { error: error instanceof Error ? error.message : '获取文档类型列表失败', status: 500 }; } } /** * 获取指定文档的状态 * @param documentIds 文档ID列表 * @param attachmentIds 合同附件ID列表(可选) * @param token JWT token (可选) * @returns 文档状态列表 */ export async function getDocumentsStatus( documentIds: number[], attachmentIds?: number[], token?: string ): Promise<{data: Document[]; error?: never} | {data?: never; error: string; status?: number}> { try { if ((!documentIds || documentIds.length === 0) && (!attachmentIds || attachmentIds.length === 0)) { return { data: [] }; } // 查询主文档状态 // eslint-disable-next-line @typescript-eslint/no-explicit-any let documentsResponse: any = { data: [], error: undefined, status: undefined }; if (documentIds && documentIds.length > 0) { const documentsParams: PostgrestParams = { select: 'id, status', filter: { 'id': `in.(${documentIds.join(',')})` } }; documentsResponse = await postgrestGet('/api/postgrest/proxy/documents', { ...documentsParams, token }); } // 查询合同附件状态 // eslint-disable-next-line @typescript-eslint/no-explicit-any let attachmentResponse: any = { data: [], error: undefined, status: undefined }; if (attachmentIds && attachmentIds.length > 0) { const attachmentParams: PostgrestParams = { select: 'id, status', filter: { 'id': `in.(${attachmentIds.join(',')})` } }; attachmentResponse = await postgrestGet('/api/postgrest/proxy/contract_structure_comparison', { ...attachmentParams, token }); } if (documentsResponse.error && attachmentResponse.error) { return { error: documentsResponse.error || attachmentResponse.error, status: documentsResponse.status || attachmentResponse.status }; } let allData: Document[] = []; // 处理主文档数据 if (!documentsResponse.error && documentsResponse.data) { const extractedDocuments = extractApiData(documentsResponse.data); if (extractedDocuments) { allData = [...allData, ...extractedDocuments]; } } // 处理合同附件数据 if (!attachmentResponse.error && attachmentResponse.data) { const extractedAttachments = extractApiData(attachmentResponse.data); if (extractedAttachments) { // 将ContractStructureComparison转换为Document格式 const convertedAttachments: Document[] = extractedAttachments.map(item => ({ id: item.id, name: item.template_contract_name || `合同结构比较记录_${item.id}`, type_id: 1, file_size: item.file_size || 0, status: item.status, created_at: item.created_at })); allData = [...allData, ...convertedAttachments]; } } return { data: allData }; } catch (error) { console.error('获取文档状态失败:', error); return { error: error instanceof Error ? error.message : '获取文档状态失败', status: 500 }; } }