import { API_BASE_URL } from '../../config/api-config'; import { postgrestPut, postgrestGet } from '../postgrest-client'; import axios from 'axios'; // 交叉评查任务状态枚举 export enum CrossCheckingTaskStatus { PENDING = 'pending', IN_PROGRESS = 'in_progress', COMPLETED = 'completed' } // 交叉评查任务类型枚举 export enum CrossCheckingTaskType { CITY = 'CITY', DISTRICT = 'DISTRICT' } // 案卷类型枚举 export enum CrossCheckingDocType { PENALTY = 'penalty', // 行政处罚 PERMIT = 'permit' // 行政许可 } // 文档类型接口(用于交叉评查案卷类型选项) export interface DocumentType { id: number; name: string; code: string; evaluation_point_groups_ids?: number[]; } // 交叉评查任务接口 export interface CrossCheckingTask { id: number; sequence: number; taskName: string; startDate: string; taskType: CrossCheckingTaskType; docType: string; // 改为直接使用返回的 doc_type 字符串 evaluationRegion: string[]; progress: number; status: string; // 改为直接使用返回的 task_status 字符串 score: number; operation: string; documents: UserTaskDocument[]; // 改为 documents 数组,包含完整的文档信息 totalDocuments?: number; // 新增:任务包含的文档总数 } // 用户任务文档接口类型定义 export interface UserTaskDocument { document_id: number; document_name: string; document_type_id: number; document_type_name: string; } // 新的用户任务信息接口(根据新的API格式) export interface UserTaskInfo { task_id: number; task_name?: string; task_status: string; doc_type?: string; task_created_at?: string; evaluation_region?: string[]; task_type?: string; progress?: number; total_documents?: number; // 新增:任务包含的文档总数 } // 用户任务API响应格式(新格式) export interface UserTaskApiResponse { total: number; page: number; page_size: number; items: UserTaskInfo[]; } // 任务文档接口类型定义(旧版,保留兼容) export interface TaskDocument { document_id: number; file_name: string; status: string; path: string; file_code: string; file_type_name: string; file_type_id: number; file_size: number; upload_time: string; created_at: string; evaluations_status: number; audit_status: number; created_by_user_id: number; issues: Array<{ severity: string; message: string; }>; final_score: number; score_summary: string ; score_percent?: number | null; pass_count: number; warning_count: number; fail_count: number; manual_count: number; } // ==================== 新版接口类型定义(支持版本归纳)==================== /** * 历史版本信息 * 每个历史版本都有独立的评查统计、消息列表、分数信息 */ export interface CrossReviewHistoryVersion { /** 历史版本的文档ID */ id: number; /** 版本号(从1开始,数字越小越旧) */ version_number: number; /** 创建时间(ISO 8601格式) */ created_at: string; /** 文件大小(字节) */ file_size: number; /** 文件存储路径 */ path: string; /** 文档处理状态 */ status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed"; /** 文书号/文档编号(可为null) */ document_number: string | null; /** 文档类型ID */ type_id: number; /** 文档类型名称 */ type_name: string; /** 上传时间(ISO 8601格式) */ upload_time: string; /** 任务内评查完成状态:0=未评查, 1=已评查 */ audit_status: 0 | 1; /** 总评查点数 */ total_evaluation_points: number; /** 通过的评查点数量 */ pass_count: number; /** 警告的评查点数量 */ warning_count: number; /** 错误的评查点数量 */ error_count: number; /** 需人工审核的评查点数量 */ manual_count: number; /** 问题总数 */ issue_count: number; /** 警告消息列表 */ warning_messages: string[]; /** 错误消息列表 */ error_messages: string[]; /** 问题消息列表(综合:警告+错误) */ issue_messages: string[]; /** 需人工确认的消息列表 */ manual_messages: string[]; /** 最终得分 */ final_score: number; /** 满分 */ full_score: number; /** 得分摘要(如 "85.5/100") */ score_summary: string; /** 得分百分比(0-100) */ score_percent: number; } /** * 文档信息(含版本和评查统计)- 新版接口 */ export interface CrossReviewDocumentWithVersion { // ========== 基本信息 ========== /** 当前版本的文档ID */ id: number; /** 文档名称 */ name: string; /** 文件存储路径 */ path: string; /** 当前版本号(最大值,从1开始) */ version_number: number; /** 创建时间(ISO 8601格式) */ created_at: string; /** 文档处理状态 */ status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed"; /** 文件大小(字节) */ file_size: number; /** 文书号/文档编号(可为null) */ document_number: string | null; /** 文档类型ID */ type_id: number; /** 文档类型名称 */ type_name: string; /** 上传时间(ISO 8601格式) */ upload_time: string; // ========== 任务内评查状态 ========== /** 任务内评查完成状态:0=未评查, 1=已评查 */ audit_status: 0 | 1; // ========== 评查统计 ========== /** 总评查点数 */ total_evaluation_points: number; /** 通过的评查点数量 */ pass_count: number; /** 警告的评查点数量 */ warning_count: number; /** 错误的评查点数量 */ error_count: number; /** 需人工审核的评查点数量 */ manual_count: number; /** 问题总数 */ issue_count: number; // ========== 评查消息列表 ========== /** 警告消息列表 */ warning_messages: string[]; /** 错误消息列表 */ error_messages: string[]; /** 问题消息列表(综合) */ issue_messages: string[]; /** 需人工确认的消息列表 */ manual_messages: string[]; // ========== 交叉评查特有:分数信息 ========== /** 最终得分 */ final_score: number; /** 满分 */ full_score: number; /** 得分摘要(如 "85.5/100") */ score_summary: string; /** 得分百分比(0-100) */ score_percent: number; // ========== 版本信息 ========== /** 总版本数 */ total_versions: number; /** 历史版本列表(按created_at降序,不包含当前版本) */ history_versions: CrossReviewHistoryVersion[]; // ========== 前端扩展字段 ========== /** 是否已展开历史版本(前端状态) */ isExpanded?: boolean; } /** * 交叉评查任务文档列表响应 */ export interface CrossReviewDocumentListResponse { /** 总文档数(按版本分组后的唯一文档数) */ total: number; /** 当前页码 */ page: number; /** 每页数量 */ page_size: number; /** 总页数 */ total_pages: number; /** 文档列表 */ documents: CrossReviewDocumentWithVersion[]; } /** * 获取任务文档列表请求参数(支持版本归纳) */ export interface GetTaskDocumentsWithVersionsParams { /** 任务ID */ taskId: number; /** 页码(从1开始) */ page?: number; /** 每页数量(最大100) */ pageSize?: number; /** 模糊搜索关键字(匹配文件名称或文档编号) */ keyword?: string; /** JWT token */ jwtToken?: string; } // 任务文档API响应格式(新增) export interface TaskDocumentApiResponse { total: number; page: number; page_size: number; items: TaskDocument[]; } // API响应格式 export interface ApiResponse { success: boolean; data?: T; error?: string; message?: string; status?: number; } // 任务列表查询参数 export interface TaskListParams { page?: number; pageSize?: number; taskType?: string; docType?: string; status?: string; keyword?: string; dateFrom?: string; dateTo?: string; } // 任务列表响应数据 export interface TaskListResponse { tasks: CrossCheckingTask[]; totalCount: number; currentPage: number; pageSize: number; totalPages: number; } /** * 获取交叉评查任务列表 * @param params 查询参数 * @param userInfo 用户信息 * @param jwtToken JWT token * @returns 任务列表响应 */ export async function getCrossCheckingTasks(params: TaskListParams = {}, userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise> { try { // console.log('开始调用getCrossCheckingTasks,参数:', params); // 调用用户任务API,获取当前用户参与的任务 const userTasksResponse = await getUserTaskDocuments(params.page || 1, params.pageSize || 10, jwtToken); // console.log('getUserTaskDocuments响应:', JSON.stringify(userTasksResponse,null,2)); if (!userTasksResponse.success || !userTasksResponse.data) { console.error('获取用户任务失败:', userTasksResponse.error); return { success: false, error: userTasksResponse.error || '获取用户任务失败' }; } // 将用户任务数据转换为CrossCheckingTask格式 const userTasks = userTasksResponse.data.items; const convertedTasks: CrossCheckingTask[] = userTasks.map((userTask: UserTaskInfo, index: number) => { // 从用户任务中提取任务信息,使用API返回的实际数据 const task: CrossCheckingTask = { id: userTask.task_id, sequence: index + 1, taskName: userTask.task_name || `任务 ${userTask.task_id}`, // 使用API返回的任务名称 startDate: userTask.task_created_at ? new Date(userTask.task_created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], taskType: userTask.task_type, // 保持默认任务类型 docType: userTask.doc_type || '未知类型', // 使用API返回的文档类型 evaluationRegion: userTask.evaluation_region || [], // 保持默认评查地区 progress: userTask.progress || 0, // 使用API返回的进度 status: userTask.task_status || 'pending', // 使用API返回的任务状态 score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数 operation: userTask.task_status === 'completed' ? '查看结果' : userTask.task_status === 'in_progress' ? '进行中' : '去评查', documents: [], // 暂时为空数组,因为新API格式中任务列表不包含具体文档信息 totalDocuments: userTask.total_documents || 0 // 使用API返回的文档总数 }; return task; }); const { taskType, docType, status, keyword, dateFrom, dateTo } = params; // 筛选数据 let filteredTasks = [...convertedTasks]; // 按任务类型筛选 if (taskType && taskType !== 'all') { filteredTasks = filteredTasks.filter(task => task.taskType === taskType); } // 按案卷类型筛选 if (docType && docType !== 'all') { filteredTasks = filteredTasks.filter(task => task.docType === docType); } // 按状态筛选 if (status && status !== 'all') { filteredTasks = filteredTasks.filter(task => task.status === status); } // 按关键词搜索 if (keyword) { const lowerKeyword = keyword.toLowerCase(); filteredTasks = filteredTasks.filter(task => task.taskName.toLowerCase().includes(lowerKeyword) || task.evaluationRegion.toLowerCase().includes(lowerKeyword) ); } // 按日期范围筛选 if (dateFrom || dateTo) { filteredTasks = filteredTasks.filter(task => { const taskDate = new Date(task.startDate); if (dateFrom && new Date(dateFrom) > taskDate) return false; if (dateTo && new Date(dateTo) < taskDate) return false; return true; }); } return { success: true, data: { tasks: filteredTasks, totalCount: userTasksResponse.data.total, currentPage: userTasksResponse.data.page, pageSize: userTasksResponse.data.page_size, totalPages: Math.ceil(userTasksResponse.data.total / userTasksResponse.data.page_size) } }; } catch (error) { console.error('获取交叉评查任务列表失败:', error); return { success: false, error: error instanceof Error ? error.message : '获取任务列表失败' }; } } /** * 删除交叉评查任务 * @param taskId 任务ID * @returns 删除结果 */ export async function deleteCrossCheckingTask(taskId: number): Promise> { try { // 模拟API延迟 await new Promise(resolve => setTimeout(resolve, 500)); // 这里应该调用实际的API来删除任务 // 目前暂时返回成功,因为没有实际的删除API console.log(`尝试删除任务ID: ${taskId}`); return { success: true, data: true, message: '删除任务成功' }; } catch (error) { console.error('删除交叉评查任务失败:', error); return { success: false, error: error instanceof Error ? error.message : '删除任务失败' }; } } /** * 获取任务详情及相关文档 * @param taskId 任务ID * @param page 页码,默认为1 * @param pageSize 每页大小,默认为10 * @param jwtToken JWT token * @returns 任务详情和文档列表 */ export async function getCrossCheckingTaskDetail( taskId: number, page: number = 1, pageSize: number = 10, jwtToken?: string ): Promise> { try { // console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize }); // 获取任务的文档列表 const taskDocumentsResponse = await getTaskDocuments(taskId, page, pageSize, jwtToken); if (!taskDocumentsResponse.success || !taskDocumentsResponse.data) { console.error('获取任务文档失败:', taskDocumentsResponse.error); return { success: false, error: taskDocumentsResponse.error || '获取任务文档失败' }; } const documentsData = taskDocumentsResponse.data; const result = { success: true, data: { task: null, // 暂时不返回任务详情,因为新接口主要关注文档列表 files: documentsData.items, total: documentsData.total, currentPage: documentsData.page, pageSize: documentsData.page_size } }; return result; } catch (error) { console.error('获取任务详情失败:', error); return { success: false, error: error instanceof Error ? error.message : '获取任务详情失败' }; } } /** * 获取统计数据 * @param userInfo 用户信息 * @param jwtToken JWT token * @returns 统计数据 */ export async function getCrossCheckingStats(userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise> { try { console.log('开始调用getCrossCheckingStats'); // 获取用户任务数据来计算统计(默认获取第一页数据进行统计) const userTasksResponse = await getUserTaskDocuments(1, 100, jwtToken); // 获取前100个任务用于统计 if (!userTasksResponse.success || !userTasksResponse.data) { console.error('获取用户任务失败:', userTasksResponse.error); return { success: false, error: userTasksResponse.error || '获取用户任务失败' }; } const userTasks = userTasksResponse.data.items; const totalTasks = userTasksResponse.data.total; const pendingTasks = userTasks.filter(t => t.task_status === 'pending').length; const inProgressTasks = userTasks.filter(t => t.task_status === 'in_progress').length; const completedTasks = userTasks.filter(t => t.task_status === 'completed').length; return { success: true, data: { totalTasks, pendingTasks, inProgressTasks, completedTasks } }; } catch (error) { console.error('获取统计数据失败:', error); return { success: false, error: error instanceof Error ? error.message : '获取统计数据失败' }; } } // ==================== 更新:用户任务文档相关接口 ==================== /** * 获取用户参与的所有任务列表(更新为新的API格式) * @param page 页码 * @param pageSize 每页大小 * @param jwtToken JWT token * @returns 用户任务列表 */ export async function getUserTaskDocuments(page: number = 1, pageSize: number = 10, jwtToken?: string): Promise> { try { // 拼接绝对路径,去除多余斜杠 const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; const url = `${base}/admin/v2/cross_review/tasks/user_tasks`; const response = await axios.post(url, { page: page, page_size: pageSize }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwtToken || ''}` } }); return { success: true, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { return { success: false, error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}` }; } return { success: false, error: error instanceof Error ? error.message : '获取用户任务列表失败' }; } } /** * 获取指定任务的文档列表(旧版接口,保留兼容) * @param taskId 任务ID * @param page 页码 * @param pageSize 每页大小 * @param jwtToken JWT token * @returns 任务文档列表 */ export async function getTaskDocuments(taskId: number, page: number = 1, pageSize: number = 10, jwtToken?: string): Promise> { try { // 拼接绝对路径,去除多余斜杠 const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; const url = `${base}/admin/v2/cross_review/tasks/${taskId}/documents`; // console.log('最终请求URL:', url); const response = await axios.post(url, { page: page, page_size: pageSize }, { headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${jwtToken || ''}` } }); return { success: true, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { return { success: false, error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}` }; } return { success: false, error: error instanceof Error ? error.message : '获取任务文档列表失败' }; } } /** * 获取任务下文档列表(支持版本归纳)- 新版接口 * * POST /api/v2/cross_review/tasks/{task_id}/documents * * 同一任务内同名且同类型的文档会被归纳为版本组,最新上传的为当前版本,其余为历史版本。 * * @param params 请求参数 * @returns 文档列表响应(含版本信息) */ export async function getTaskDocumentsWithVersions( params: GetTaskDocumentsWithVersionsParams ): Promise> { const { taskId, page = 1, pageSize = 10, keyword, jwtToken } = params; try { // 拼接绝对路径,去除多余斜杠 const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; const url = `${base}/api/v2/cross_review/tasks/${taskId}/documents`; // 构建请求体 const queryParams: { page: number; page_size: number; keyword?: string; } = { page, page_size: pageSize }; // 只有当 keyword 有值时才添加 if (keyword && keyword.trim()) { queryParams.keyword = keyword.trim(); } const response = await axios.get(url, { params: queryParams, headers: { 'Authorization': `Bearer ${jwtToken || ''}` } }); return { success: true, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { // 处理特定错误码 if (error.response?.status === 403) { return { success: false, error: '无权访问任务:您不是该任务的参与者', status: 403 }; } return { success: false, error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`, status: error.response?.status }; } return { success: false, error: error instanceof Error ? error.message : '获取任务文档列表失败' }; } } /** * 更新文件的审核状态 * @param id 文件ID * @param auditStatus 审核状态 * @returns 更新结果 */ export async function updateDocumentAuditStatus(id: string, auditStatus: number, frontendJWT?: string): Promise<{ success?: boolean; error?: string; status?: number; }> { try { if (!id) { return { error: '文件ID不能为空', status: 400 }; } const response = await postgrestPut>( '/api/postgrest/proxy/documents', { audit_status: auditStatus }, { id: parseInt(id) }, frontendJWT ); if (response.error) { return { error: response.error, status: response.status }; } return { success: true }; } catch (error) { console.error('更新文件审核状态失败:', error); return { error: error instanceof Error ? error.message : '更新文件审核状态失败', status: 500 }; } } /** * 获取可用于交叉评查的文档类型列表 * 条件:evaluation_point_groups_ids 不为空 * @param jwtToken JWT token * @returns 文档类型列表 */ export async function getCrossCheckingDocumentTypes(jwtToken?: string): Promise> { try { // console.log('[getCrossCheckingDocumentTypes] 开始获取交叉评查文档类型'); const response = await postgrestGet('/api/postgrest/proxy/document_types',{ select: 'id,name,code,evaluation_point_groups_ids', filter: { evaluation_point_groups_ids: 'not.is.null' }, token: jwtToken }); if (response.error) { console.error('[getCrossCheckingDocumentTypes] 获取失败:', response.error); return { success: false, error: response.error }; } // 进一步过滤,确保 evaluation_point_groups_ids 是非空数组 const dataArray = Array.isArray(response.data) ? response.data : []; const filteredData = dataArray.filter( (item: DocumentType) => item.evaluation_point_groups_ids && Array.isArray(item.evaluation_point_groups_ids) && item.evaluation_point_groups_ids.length > 0 ); // console.log('[getCrossCheckingDocumentTypes] 获取成功,共', filteredData.length, '个文档类型'); return { success: true, data: filteredData }; } catch (error) { console.error('[getCrossCheckingDocumentTypes] 获取交叉评查文档类型失败:', error); return { success: false, error: error instanceof Error ? error.message : '获取文档类型失败' }; } } // ==================== 追加附件 API ==================== /** * 追加附件响应接口 */ export interface AppendAttachmentsResponse { success: boolean; result?: { original_document_id: number; new_document_id: number; new_version_number: number; task_id: number; message: string; background_processing: boolean; }; error?: string; } /** * 追加附件参数接口 */ export interface AppendAttachmentsParams { /** 任务ID */ taskId: number; /** 原文档ID */ documentId: number; /** 附件文件列表 */ files: File[]; /** 备注说明(可选) */ remark?: string; /** Word附件是否使用Markdown处理(可选,默认false) */ useMarkdown?: boolean; /** JWT Token */ jwtToken?: string; } /** * 为交叉评查任务文档追加附件 * * POST /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/append_attachments * * 追加附件后创建新文档(新版本),原文档保留。 * 新文档自动关联到当前任务,audit_status = 0(需重新评查) * * @param params 追加附件参数 * @returns 追加结果 */ export async function appendTaskDocumentAttachments( params: AppendAttachmentsParams ): Promise> { const { taskId, documentId, files, remark, useMarkdown = false, jwtToken } = params; try { if (!taskId || taskId <= 0) { return { success: false, error: '任务ID无效' }; } if (!documentId || documentId <= 0) { return { success: false, error: '文档ID无效' }; } if (!files || files.length === 0) { return { success: false, error: '请选择附件文件' }; } // 构建 FormData const formData = new FormData(); files.forEach(file => { formData.append('files', file); }); if (remark) { formData.append('remark', remark); } formData.append('use_markdown', useMarkdown.toString()); // 构建请求 URL const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL; const url = `${base}/api/v2/cross_review/tasks/${taskId}/documents/${documentId}/append_attachments`; const response = await axios.post(url, formData, { headers: { 'Content-Type': 'multipart/form-data', 'Authorization': `Bearer ${jwtToken || ''}` } }); return { success: true, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { if (error.response?.status === 403) { return { success: false, error: '无权追加附件:您不是任务创建者或负责人', status: 403 }; } if (error.response?.status === 400) { return { success: false, error: error.response.data?.error || '请求参数错误', status: 400 }; } return { success: false, error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`, status: error.response?.status }; } return { success: false, error: error instanceof Error ? error.message : '追加附件失败' }; } } // ==================== 上传模板 API ==================== /** * 上传模板响应接口(文档级别) */ export interface UploadDocumentTemplateResponse { success: boolean; result?: { document_id: number; comparison_id: number; template_name: string; template_path: string; status: string; message: string; }; error?: string; } /** * 上传模板参数接口(文档级别) */ export interface UploadDocumentTemplateParams { /** 文档ID */ documentId: number; /** 模板文件 */ file: File; /** 对比记录ID(可选,用于更新已有模板) */ comparisonId?: number; /** JWT Token */ jwtToken?: string; } /** * 为交叉评查任务中的文档上传模板(文档级别) * * 复用现有的 /upload_contract_template 接口,与 files-upload.ts 中的 uploadContractTemplate 保持一致 * * @param params 上传模板参数 * @returns 上传结果 */ export async function uploadCrossReviewDocumentTemplate( params: UploadDocumentTemplateParams ): Promise> { const { documentId, file, comparisonId, jwtToken } = params; try { if (!documentId || documentId <= 0) { return { success: false, error: '文档ID无效' }; } if (!file) { return { success: false, error: '请选择模板文件' }; } // 构建 FormData,与 files-upload.ts 中的 uploadContractTemplate 保持一致 const formData = new FormData(); formData.append('file', file); // upload_info 作为 JSON 字符串 const uploadInfo: { document_id: number; comparison_id?: number } = { document_id: documentId }; if (comparisonId) { uploadInfo.comparison_id = comparisonId; } formData.append('upload_info', JSON.stringify(uploadInfo)); // 使用与 files-upload.ts 相同的上传接口 const { UPLOAD_URL } = await import('~/config/api-config'); const url = `${UPLOAD_URL}/upload_contract_template`; const response = await axios.post(url, formData, { headers: { 'Accept': 'application/json', 'Authorization': `Bearer ${jwtToken || ''}` } }); return { success: true, data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { if (error.response?.status === 403) { return { success: false, error: '无权上传模板', status: 403 }; } if (error.response?.status === 400) { return { success: false, error: error.response.data?.error || '请求参数错误', status: 400 }; } return { success: false, error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`, status: error.response?.status }; } return { success: false, error: error instanceof Error ? error.message : '上传模板失败' }; } }