diff --git a/app/api/client.ts b/app/api/client.ts index 66263e5..a4607d3 100644 --- a/app/api/client.ts +++ b/app/api/client.ts @@ -14,9 +14,9 @@ export type ApiResponse = { export type QueryParams = Record; // 获取 API 基础 URL -// const API_BASE_URL = '172.18.0.100:3000'; -// const API_BASE_URL = '172.16.0.119:9000/admin'; -const API_BASE_URL = 'http://nas.7bm.co:3000'; +const API_BASE_URL = 'http://172.18.0.100:3000'; +// const API_BASE_URL = 'http://172.16.0.119:9000/admin'; +// export const API_BASE_URL = 'http://nas.7bm.co:3000'; // 是否使用模拟数据(开发环境使用) const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题 diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts new file mode 100644 index 0000000..b196a4d --- /dev/null +++ b/app/api/evaluation_points/reviews.ts @@ -0,0 +1,237 @@ +import { postgrestGet, type PostgrestParams } from "../postgrest-client"; +import dayjs from 'dayjs'; + +/** + * 格式化日期 + * @param dateString 日期字符串 + * @returns 格式化后的日期字符串 + */ +function formatDate(dateString: string): string { + if (!dateString) return ''; + try { + return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); + } catch (error) { + console.error('日期格式化失败:', error); + return dateString; + } + } + +/** + * 从不同格式的 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; +} + +// 定义评查结果类型 +interface EvaluationResult { + id: string | number; + document_id: string | number; + evaluation_point_id: string | number; + evaluated_results?: { + result?: boolean; + message?: string; + data?: string; + [key: string]: unknown; + }; + [key: string]: unknown; +} + +// 定义评查点类型 +interface EvaluationPoint { + id: string | number; + evaluation_point_groups_id: string | number; + suggestion_message_type?: string; + suggestion_message?: string; + score?: number; + [key: string]: unknown; +} + +// 定义评查点组类型 +interface EvaluationPointGroup { + id: string | number; + name?: string; + [key: string]: unknown; +} + +// 定义前端使用的评查点结果类型 +interface ReviewPointResult { + id: string | number; + title: string; + groupName: string; + status: string; + content: string; + suggestion: string; + result?: boolean; + score: number; +} + +// 定义统计数据类型 +interface StatsData { + total: number; + success: number; + warning: number; + error: number; + score: number; +} + +/** + * 获取当前评查文件的所有评查点结果 + * @param fileId 评查文件ID + * @returns 评查点结果列表和统计数据 + */ +export async function getReviewPoints(fileId: string) { + // 步骤1:根据fileId查询evaluation_results表 + const evaluationResultsParams: PostgrestParams = { + select: '*', + filter: { + 'document_id': `eq.${fileId}` + } + }; + const evaluationResultsResponse = await postgrestGet('evaluation_results', evaluationResultsParams); + + if (evaluationResultsResponse.error) { + return { error: evaluationResultsResponse.error, status: evaluationResultsResponse.status }; + } + + const evaluationResultsData = extractApiData(evaluationResultsResponse.data); + + if (!evaluationResultsData || !Array.isArray(evaluationResultsData)) { + return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; + } + + // 收集所有评查点ID,用于查询评查点详情 + const evaluationPointIds = evaluationResultsData.map(item => item.evaluation_point_id).filter(Boolean); + + if (evaluationPointIds.length === 0) { + return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; + } + + // 步骤2:根据evaluation_point_id查询evaluation_points表 + const evaluationPointsParams: PostgrestParams = { + select: '*', + filter: { + 'id': `in.(${evaluationPointIds.join(',')})` + } + }; + const evaluationPointsResponse = await postgrestGet('evaluation_points', evaluationPointsParams); + + if (evaluationPointsResponse.error) { + return { error: evaluationPointsResponse.error, status: evaluationPointsResponse.status }; + } + + const evaluationPointsData = extractApiData(evaluationPointsResponse.data); + + if (!evaluationPointsData || !Array.isArray(evaluationPointsData)) { + return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; + } + + // 收集所有评查点组ID,用于查询评查点组详情 + const groupIds = evaluationPointsData.map(item => item.evaluation_point_groups_id).filter(Boolean); + + if (groupIds.length === 0) { + return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; + } + + // 查询评查点组 + const groupsParams: PostgrestParams = { + select: '*', + filter: { + 'id': `in.(${groupIds.join(',')})` + } + }; + const groupsResponse = await postgrestGet('evaluation_point_groups', groupsParams); + + if (groupsResponse.error) { + return { error: groupsResponse.error, status: groupsResponse.status }; + } + + const groupsData = extractApiData(groupsResponse.data); + + if (!groupsData || !Array.isArray(groupsData)) { + return { data: [], stats: { total: 0, success: 0, warning: 0, error: 0, score: 0 } }; + } + + // 创建映射关系以便快速查找 + const pointsMap = new Map(); + evaluationPointsData.forEach(point => { + pointsMap.set(point.id, point); + }); + + // console.log('pointsMap-------', pointsMap); + + const groupsMap = new Map(); + groupsData.forEach(group => { + groupsMap.set(group.id, group); + }); + + // console.log('groupsMap-------', groupsMap); + + // 构建前端所需的数据格式 + const resultData: ReviewPointResult[] = evaluationResultsData.map(result => { + const point = pointsMap.get(result.evaluation_point_id) || {} as EvaluationPoint; + const group = groupsMap.get(point.evaluation_point_groups_id || 0) || {} as EvaluationPointGroup; + + // 从 evaluated_results 中提取数据 + let message = ''; + let data = ''; + + if (result.evaluated_results && typeof result.evaluated_results === 'object') { + message = result.evaluated_results.message || ''; + data = result.evaluated_results.data || ''; + } + + return { + id: result.id, + title: message, + groupName: group.name || '', + status: point.suggestion_message_type || '', + content: data, + suggestion: point.suggestion_message || '', + result: result.evaluated_results?.result, // 记录评查结果,用于统计 + score: point.score || 0 + }; + }); + + // 统计数据 + const stats: StatsData = { + total: evaluationResultsData.length, + success: 0, + warning: 0, + error: 0, + score: 0 + }; + + // 计算统计数据 + resultData.forEach(item => { + // 成功数量统计 + if (item.result === true) { + stats.success += 1; + } else if (item.result === false) { + // 警告和错误数量统计 + if (item.status === 'warning') { + stats.warning += 1; + } else if (item.status === 'error') { + stats.error += 1; + } + } + + // 分数统计 + stats.score += item.score || 0; + }); + + return { data: resultData, stats }; +} diff --git a/app/api/evaluation_points/rules-files.ts b/app/api/evaluation_points/rules-files.ts new file mode 100644 index 0000000..78df771 --- /dev/null +++ b/app/api/evaluation_points/rules-files.ts @@ -0,0 +1,371 @@ +import { postgrestGet, postgrestPut, type PostgrestParams } from '../postgrest-client'; +import dayjs from 'dayjs'; +import { getDocumentTypes } from '../document-types/document-types'; +import type { DocumentTypeUI } from '../document-types/document-types'; +import weekday from 'dayjs/plugin/weekday'; +import updateLocale from 'dayjs/plugin/updateLocale'; + +// 配置 dayjs +dayjs.extend(weekday); +dayjs.extend(updateLocale); +// 设置一周的第一天为周一 +dayjs.updateLocale('en', { + weekStart: 1 +}); + +// 文档数据库表接口 +export interface Document { + id: number; + user_id: number | null; + type_id: number; + name: string; + document_number: string; + path: string; + storage_type: string; + file_size: number; + upload_time: string; + is_test_document: boolean; + evaluation_level: string; + status: string; + ocr_result: Record; + extracted_results: Record | null; + sumary: string | null; + remark: string; + created_at: string; + updated_at: string; + evaluations_status: number | null; + audit_status: number | null; +} + +// 文档类型接口 +export interface DocumentType { + id: number; + name: string; + description: string | null; + status: number; + created_at: string; + updated_at: string; +} + +// 评查文件UI接口 +export interface ReviewFileUI { + id: string; + fileName: string; + fileCode: string; + fileType: string; + fileTypeId: number; + fileSize: number; + uploadTime: string; + reviewStatus: string; + reviewStatusCode: number; + issueCount: number; + issues: Array<{ + severity: 'info' | 'warning' | 'error' | 'critical'; + message: string; + }>; + createdBy: string; +} + +// 文件列表搜索参数 +export interface DocumentSearchParams { + fileType?: string; // 文件类型ID + reviewStatus?: string; // 评查状态 + dateRange?: string; // 日期范围 + keyword?: string; // 搜索关键字 + sortOrder?: string; // 排序方式 + page?: number; // 当前页码 + pageSize?: number; // 每页条数 +} + +/** + * 格式化日期 + * @param dateString 日期字符串 + * @returns 格式化后的日期字符串 + */ +function formatDate(dateString: string): string { + if (!dateString) return ''; + try { + return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); + } catch (error) { + console.error('日期格式化失败:', error); + return dateString; + } +} + +/** + * 从不同格式的 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; +} + +/** + * 将评查状态代码映射到UI状态 + * @param status 评查状态代码 + * @returns UI状态 + */ +export function mapReviewStatusToUI(status: number | null): string { + switch(status) { + case 1: return 'pass'; + case 2: return 'warning'; + case -1: return 'fail'; + case 0: return 'pending'; + default: return 'pending'; + } +} + +/** + * 将UI状态映射到评查状态代码 + * @param status UI状态 + * @returns 评查状态代码 + */ +export function mapUIToReviewStatus(status: string): number { + switch(status) { + case 'pass': return 1; + case 'warning': return 2; + case 'fail': return -1; + case 'pending': return 0; + default: return 0; + } +} + +/** + * 获取文件扩展名 + * @param fileName 文件名 + * @returns 文件扩展名 + */ +export function getFileExtension(fileName: string): string { + return fileName.split('.').pop()?.toLowerCase() || ''; +} + +/** + * 将数据库文档转换为UI文件对象 + * @param document 数据库文档 + * @param documentTypeName 文档类型名称 + * @returns UI文件对象 + */ +export function convertToReviewFileUI(document: Document, documentTypeName: string): ReviewFileUI { + const reviewStatus = mapReviewStatusToUI(document.evaluations_status); + + const reviewFileUI: ReviewFileUI = { + id: document.id.toString(), + fileName: document.name, + fileCode: document.document_number, + fileType: documentTypeName, + fileTypeId: document.type_id, + fileSize: document.file_size, + uploadTime: formatDate(document.created_at), + reviewStatus: reviewStatus, + reviewStatusCode: document.evaluations_status || 0, + issueCount: 0, + issues: [], + createdBy: document.user_id?.toString() || '系统' + }; + +// console.log('reviewFileUI-----',reviewFileUI); + + return reviewFileUI; +} + +/** + * 获取评查文件列表 + * @param searchParams 搜索参数 + * @returns 评查文件列表和总数 + */ +export async function getReviewFiles(searchParams: DocumentSearchParams = {}): Promise<{ + data?: { files: ReviewFileUI[], total: number }; + error?: string; + status?: number; +}> { + try { + const page = searchParams.page || 1; + const pageSize = searchParams.pageSize || 10; + + // 构建查询参数 + const params: PostgrestParams = { + select: '*', + order: 'created_at.desc', + headers: { + 'Prefer': 'count=exact' + }, + limit: pageSize, + offset: (page - 1) * pageSize, + filter: {} as Record + }; + + // 根据排序方式设置排序 + if (searchParams.sortOrder) { + switch (searchParams.sortOrder) { + case 'upload_time_desc': + params.order = 'created_at.desc'; + break; + case 'upload_time_asc': + params.order = 'created_at.asc'; + break; + // 其他排序方式可以在这里添加 + } + } + + // 添加筛选条件 + const filter: Record = {}; + + if (searchParams.fileType) { + filter['type_id'] = `eq.${searchParams.fileType}`; + } + + if (searchParams.reviewStatus) { + const statusValue = mapUIToReviewStatus(searchParams.reviewStatus); + filter['evaluations_status'] = `eq.${statusValue}`; + } + + if (searchParams.keyword) { + filter['or'] = `(name.ilike.%${searchParams.keyword}%,document_number.ilike.%${searchParams.keyword}%)`; + } + + // 处理日期范围筛选 + if (searchParams.dateRange) { + const now = dayjs(); + const today = now.startOf('day').format('YYYY-MM-DD HH:mm:ss'); + switch (searchParams.dateRange) { + case 'today': + filter['created_at'] = `gte.${today}`; + break; + case 'week': { + const weekStart = now.startOf('week').format('YYYY-MM-DD HH:mm:ss'); + filter['created_at'] = `gte.${weekStart}`; + break; + } + case 'month': { + const monthStart = now.startOf('month').format('YYYY-MM-DD HH:mm:ss'); + filter['created_at'] = `gte.${monthStart}`; + break; + } + } + } + + // console.log('filter-----',filter); + params.filter = filter; + + // 发送API请求获取文档列表 + const response = await postgrestGet('documents', params); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + // 提取API返回的数据 + const extractedDocuments = extractApiData(response.data); + + if (!extractedDocuments) { + return { error: '获取评查文件数据失败', status: 500 }; + } + + // 从响应头中获取总数 + let totalCount = 0; + const responseWithHeaders = response as { data: Document[]; headers: Record }; + if(responseWithHeaders.headers){ + const rangeHeader = responseWithHeaders.headers['content-range']; + if(rangeHeader){ + const total = rangeHeader.split('/')[1]; + if(total !== '*'){ + totalCount = parseInt(total, 10); + } + } + } + + // 获取文档类型数据,用于查找文档类型名称 + const documentTypesResponse = await getDocumentTypes({pageSize: 500}); + const documentTypes = documentTypesResponse.data?.types || []; + + // 创建文档类型ID到名称的映射 + const typeNameMap: Record = {}; + documentTypes.forEach((type: DocumentTypeUI) => { + typeNameMap[type.id] = type.name; + }); + + // 将文档数据转换为UI文件对象 + const reviewFiles = extractedDocuments.map(doc => { + const typeName = typeNameMap[doc.type_id] || '未知类型'; + return convertToReviewFileUI(doc, typeName); + }); + + return { + data: { + files: reviewFiles, + total: totalCount + } + }; + } catch (error) { + console.error('获取评查文件列表失败:', error); + return { + error: error instanceof Error ? error.message : '获取评查文件列表失败', + status: 500 + }; + } +} + +/** + * 更新文件的评查状态 + * @param id 文件ID + * @param status 评查状态 + * @returns 更新后的文件信息 + */ +export async function updateReviewStatus(id: string, status: string): Promise<{ + data?: ReviewFileUI; + error?: string; + status?: number; +}> { + try { + if (!id) { + return { error: '文件ID不能为空', status: 400 }; + } + + const statusValue = mapUIToReviewStatus(status); + + const response = await postgrestPut>( + 'documents', + { evaluations_status: statusValue }, + { id: parseInt(id) } + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + const extractedData = extractApiData(response.data); + + if (!extractedData) { + return { error: '更新评查状态失败', status: 500 }; + } + + // 获取文档类型,用于查找文档类型名称 + const documentTypesResponse = await getDocumentTypes({pageSize: 500}); + const documentTypes = documentTypesResponse.data?.types || []; + + // 查找文档类型名称 + const docType = documentTypes.find((type: DocumentTypeUI) => type.id === extractedData.type_id); + const typeName = docType ? docType.name : '未知类型'; + + return { data: convertToReviewFileUI(extractedData, typeName) }; + } catch (error) { + console.error('更新评查状态失败:', error); + return { + error: error instanceof Error ? error.message : '更新评查状态失败', + status: 500 + }; + } +} + diff --git a/app/api/files.ts b/app/api/files.ts deleted file mode 100644 index a699beb..0000000 --- a/app/api/files.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { apiRequest } from './client'; -import { FileType, Priority } from '~/types/enums'; - -/** - * 将文件转换为二进制数据 - */ -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); - }); -} - -/** - * 上传文件到服务器 - */ -export async function uploadFileToServer( - binaryData: ArrayBuffer, - fileName: string, - fileType: string, - documentType: FileType, - priority: Priority -): Promise<{ success: boolean; fileId?: string; message?: string; error?: string }> { - try { - const formData = new FormData(); - formData.append('file', new Blob([binaryData], { type: fileType }), fileName); - formData.append('documentType', documentType); - formData.append('priority', priority); - - const response = await apiRequest<{ success: boolean; fileId?: string; message?: string }>( - '/api/files/upload', - { - method: 'POST', - body: formData - } - ); - - if (response.error) { - return { success: false, error: response.error }; - } - - return { success: true, fileId: response.data?.fileId, message: response.data?.message }; - } catch (error) { - return { success: false, 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 -): Promise<{ - 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; -}> { - try { - // 创建FormData对象 - const formData = new FormData(); - - // 将二进制数据转换为Blob并添加到FormData - const blob = new Blob([binaryData], { type: fileType }); - formData.append('file', blob, fileName); - - // 将信息添加到一个JSON对象中 - const uploadInfo = { - type_id: Number(typeId), - evaluation_level: priority, - document_number: documentNumber || null, - remark: remark || null, - is_test_document: isTestDocument - }; - - // 添加JSON字符串到FormData - formData.append('upload_info', JSON.stringify(uploadInfo)); - - // 发送请求 - const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', { - method: 'POST', - headers: { - 'X-File-Name': encodeURIComponent(fileName) - }, - body: formData - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error(`上传失败 (${response.status}): ${errorText}`); - return { - success: false, - error: `上传失败: ${response.status} ${response.statusText}` - }; - } - - const data = await response.json(); - return data; - } catch (error) { - console.error('上传错误:', error); - return { - success: false, - error: error instanceof Error ? error.message : '上传失败' - }; - } -} \ No newline at end of file diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 74ccb9c..4b7a02f 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -45,6 +45,8 @@ export interface DocumentSearchParams { documentNumber?: string; documentType?: string; status?: string; + auditStatus?: string; + fileStatus?: string; dateFrom?: string; dateTo?: string; page?: number; @@ -67,10 +69,16 @@ export interface Document { is_test_document: boolean; evaluation_level: string; status: 'pass' | 'warning' | 'waiting' | 'processing' | 'fail'; - ocr_result: unknown; - extracted_results: unknown; - summary: unknown; - remark: string; + file_status: 'Waiting' | 'Cutting' | 'Extractioning' | 'Evaluationing' | 'Processed'; + audit_status: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中 + ocr_result?: { + __meta?: { + page_count?: number; + } + }; + extracted_results?: unknown; + summary?: unknown; + remark?: string; created_at: string; updated_at: string; } @@ -85,12 +93,15 @@ export interface DocumentUI { type: string; typeName: string; size: number; - status: string; + auditStatus: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中 + fileStatus: string; // Waiting, Cutting, Extractioning, Evaluationing, Processed issues: number | null; uploadTime: string; fileType: string; path: string; isTest: boolean; + updatedAt?: string; + pageCount?: number; } /** @@ -119,12 +130,15 @@ async function convertToUIDocument(doc: Document): Promise { type: doc.type_id.toString(), typeName: docType?.name || '未知类型', size: doc.file_size, - status: doc.status, + auditStatus: doc.audit_status || 0, + fileStatus: doc.status || '', // 默认为'' issues: 0, // 固定为0 uploadTime: formatDate(doc.updated_at), fileType: getFileExtension(doc.name), path: doc.path, - isTest: doc.is_test_document + isTest: doc.is_test_document, + updatedAt: formatDate(doc.updated_at), + pageCount: doc.ocr_result?.__meta?.page_count || 0 }; } @@ -169,20 +183,36 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro filter['type_id'] = `eq.${searchParams.documentType}`; } - if (searchParams.status) { - filter['status'] = `eq.${searchParams.status}`; + if (searchParams.auditStatus) { + filter['audit_status'] = `eq.${searchParams.auditStatus}`; + } + + if (searchParams.fileStatus) { + filter['status'] = `eq.${searchParams.fileStatus}`; } // 处理日期范围 if (searchParams.dateFrom) { - filter['updated_at'] = `gte.${searchParams.dateFrom}`; + // 添加当天开始时间 00:00:00 + filter['created_at'] = `gte.'${dayjs(`${searchParams.dateFrom} 00:00:00`).format()}'`; } if (searchParams.dateTo) { - const dateToKey = searchParams.dateFrom ? 'and.updated_at.lte' : 'updated_at'; - filter[dateToKey] = `lte.${searchParams.dateTo}`; + // 如果有开始日期,使用and条件;否则直接设置结束日期 + const dateToKey = searchParams.dateFrom ? 'and' : 'created_at'; + const newDateFrom = dayjs(`${searchParams.dateFrom} 00:00:00`).format(); + const newDateTo = dayjs(`${searchParams.dateTo} 23:59:59`).format(); + // 添加当天结束时间 23:59:59 + if (dateToKey === 'and') { + delete filter['created_at']; + // 使用OR操作符连接两个条件 + filter[dateToKey] = `(created_at.gte.'${newDateFrom}',created_at.lte.'${newDateTo}')`; + } else { + filter['created_at'] = `lte.'${newDateTo}'`; + } } + // console.log('filter-----', filter); params.filter = filter; // 发送请求 @@ -200,7 +230,7 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro // 转换为UI格式 const documents = await Promise.all(extractedData.map(convertToUIDocument)); - + // console.log('documentsItem',documents) // 获取总数 let totalCount = 0; const responseWithHeaders = response as { @@ -332,13 +362,6 @@ export async function getFileDownloadUrl(filePath: string): Promise<{ return { error: '文件路径不能为空', status: 400 }; } - // 构建API请求参数 - const params: PostgrestParams = { - filter: { - 'path': `eq.${filePath}` - } - }; - // 这里应该调用获取文件下载链接的API // 假设后端有这样的端点:/api/files/generate-download-url?path=xxx // 实际项目中需要根据你的后端API调整 @@ -387,8 +410,8 @@ export async function updateDocument(id: string, document: Partial & apiDocument.type_id = parseInt(document.type); } - if (document.status !== undefined) { - apiDocument.status = document.status as 'pass' | 'warning' | 'waiting' | 'processing' | 'fail'; + if (document.auditStatus !== undefined) { + apiDocument.audit_status = document.auditStatus; } if (document.isTest !== undefined) { diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts index c317975..0f9e0d6 100644 --- a/app/api/files/files-upload.ts +++ b/app/api/files/files-upload.ts @@ -1,5 +1,6 @@ import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client'; import dayjs from 'dayjs'; +// import { API_BASE_URL } from '../client'; /** * 格式化日期 @@ -39,10 +40,12 @@ function extractApiData(responseData: unknown): T | null { // 文档状态枚举 export enum DocumentStatus { + WAITING = "Waiting", CUTTING = "Cutting", - EXTRACTIONING = "extractioning", - REVIEWING = "reviewing", - COMPLETED = "completed" + EXTRACTIONING = "Extractioning", + EVALUATIONING = "Evaluationing", + FAILED = "Failed", + PROCESSED = "Processed" } // 文档类型接口 @@ -80,6 +83,132 @@ export interface Document { remark?: string; } +// 文件上传响应接口 +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 是否为测试文档 + * @returns 上传结果 + */ +export async function uploadDocumentToServer( + binaryData: ArrayBuffer, + fileName: string, + fileType: string, + typeId: string | number, + priority: string, + documentNumber?: string | null, + remark?: string | null, + isTestDocument: boolean = false +): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { + try { + // 创建FormData对象 + const formData = new FormData(); + + // 将二进制数据转换为Blob并添加到FormData + const blob = new Blob([binaryData], { type: fileType }); + formData.append('file', blob, fileName); + + // 将信息添加到一个JSON对象中 + const uploadInfo = { + type_id: Number(typeId), + evaluation_level: priority, + document_number: documentNumber || null, + remark: remark || null, + is_test_document: isTestDocument + }; + + // 添加JSON字符串到FormData + formData.append('upload_info', JSON.stringify(uploadInfo)); + + console.log('上传信息:', { + fileName, + fileType, + typeId: Number(typeId), + priority, + documentNumber: documentNumber || null, + remark: remark || null, + isTestDocument + }); + + // 发送请求 + // const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, { + const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', { + method: 'POST', + headers: { + 'X-File-Name': encodeURIComponent(fileName) + }, + body: formData + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`上传失败 (${response.status}): ${errorText}`); + return { + error: `上传失败: ${response.status} ${response.statusText}`, + status: response.status + }; + } + + const responseData = await response.json(); + const extractedData = extractApiData(responseData); + + if (!extractedData) { + return { error: '处理上传响应失败', status: 500 }; + } + + return { data: extractedData }; + } catch (error) { + console.error('上传错误:', error); + return { + error: error instanceof Error ? error.message : '上传失败', + status: 500 + }; + } +} + /** * 获取当天的文档列表 * @returns 文档列表 diff --git a/app/api/filesApi.ts b/app/api/filesApi.ts deleted file mode 100644 index b7f2340..0000000 --- a/app/api/filesApi.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { apiRequest, buildUrl, type PaginatedResponse } from './base'; -import type { File, DocumentType } from '~/models/file'; - -/** - * 文件API服务 - */ - -interface FileFilterParams { - documentTypeId?: string; - status?: string; - reviewStatus?: string; - keyword?: string; - startDate?: string; - endDate?: string; - page?: number; - pageSize?: number; -} - -// 获取文件列表 -export async function getFiles(params?: FileFilterParams): Promise> { - const url = buildUrl('/api/files', params); - return apiRequest>(url); -} - -// 获取单个文件 -export async function getFile(id: string): Promise { - const url = buildUrl(`/api/files/${id}`); - return apiRequest(url); -} - -// 上传文件 -export async function uploadFile(formData: FormData): Promise { - const url = buildUrl('/api/files/upload'); - - const response = await fetch(url, { - method: 'POST', - body: formData, - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || '文件上传失败'); - } - - return response.json().then(data => data.data); -} - -// 删除文件 -export async function deleteFile(id: string): Promise { - const url = buildUrl(`/api/files/${id}`); - return apiRequest(url, 'DELETE'); -} - -// 获取文档类型列表 -export async function getDocumentTypes(): Promise { - const url = buildUrl('/api/document-types'); - return apiRequest(url); -} - -// 获取单个文档类型 -export async function getDocumentType(id: string): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url); -} - -// 创建文档类型 -// 请使用 ~/api/document-types/document-types.ts 中的实现 -/* -export async function createDocumentType(documentType: Omit): Promise { - const url = buildUrl('/api/document-types'); - return apiRequest(url, 'POST', documentType); -} -*/ - -// 更新文档类型 -// 请使用 ~/api/document-types/document-types.ts 中的实现 -/* -export async function updateDocumentType(id: string, documentType: Partial>): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url, 'PUT', documentType); -} -*/ - -// 删除文档类型 -export async function deleteDocumentType(id: string): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url, 'DELETE'); -} \ No newline at end of file diff --git a/app/api/rulesApi.ts b/app/api/rulesApi.ts deleted file mode 100644 index 8b8d3ed..0000000 --- a/app/api/rulesApi.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { apiRequest, buildUrl, type PaginatedResponse } from './base'; -import type { Rule, RuleGroup } from '~/models/rule'; - -/** - * 评查规则API服务 - */ - -interface RuleFilterParams { - ruleType?: string; - groupId?: string; - isActive?: boolean; - keyword?: string; - page?: number; - pageSize?: number; -} - -// 获取评查点列表 -export async function getRules(params?: RuleFilterParams): Promise> { - const url = buildUrl('/api/rules', params); - return apiRequest>(url); -} - -// 获取单个评查点 -export async function getRule(id: string): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url); -} - -// 创建评查点 -export async function createRule(rule: Omit): Promise { - const url = buildUrl('/api/rules'); - return apiRequest(url, 'POST', rule); -} - -// 更新评查点 -export async function updateRule(id: string, rule: Partial>): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url, 'PUT', rule); -} - -// 删除评查点 -export async function deleteRule(id: string): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url, 'DELETE'); -} - -// 获取评查点分组列表 -export async function getRuleGroups(): Promise { - const url = buildUrl('/api/rule-groups'); - return apiRequest(url); -} - -// 获取单个评查点分组 -export async function getRuleGroup(id: string): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url); -} - -// 创建评查点分组 -export async function createRuleGroup(group: Omit): Promise { - const url = buildUrl('/api/rule-groups'); - return apiRequest(url, 'POST', group); -} - -// 更新评查点分组 -export async function updateRuleGroup(id: string, group: Partial>): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url, 'PUT', group); -} - -// 删除评查点分组 -export async function deleteRuleGroup(id: string): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url, 'DELETE'); -} \ No newline at end of file diff --git a/app/components/reviews/FileInfo.tsx b/app/components/reviews/FileInfo.tsx index ecb8546..55abbb2 100644 --- a/app/components/reviews/FileInfo.tsx +++ b/app/components/reviews/FileInfo.tsx @@ -1,17 +1,14 @@ -/** - * 文件信息组件 - * 显示文件名称、状态信息以及操作按钮(下载原文件、导出评查报告、确认评查结果) - */ interface FileInfoProps { fileInfo: { fileName: string; contractNumber: string; - fileSize?: string; + fileSize?: string; fileFormat?: string; pageCount?: number; uploadTime?: string; uploadUser?: string; + auditStatus?: number; }; onConfirmResults: () => void; } diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index 065d71c..335ee25 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -1,16 +1,34 @@ /** * 评查点列表组件 - * 显示评查结果统计和所有评查点列表 + * + * 功能概述: + * - 展示评查结果统计信息(总计、通过、警告、错误数量) + * - 提供评查点过滤功能(按状态和搜索文本) + * - 显示评查点详细信息(标题、状态、内容、建议修改等) + * - 支持评查点操作(一键替换、人工审核等) + * + * 组件结构: + * - 统计区域: 显示评查点数量统计 + * - 搜索区域: 提供文本搜索功能 + * - 评查点列表: 展示所有评查点 + * - 评查点卡片: 展示单个评查点详情 + * - 评查点头部: 显示标题和状态 + * - 评查点内容: 显示当前内容和问题 + * - 建议修改区域: 显示建议的修改内容 + * - 操作按钮: 提供一键替换和人工审核功能 */ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; -// 评查点类型定义 -interface ReviewPoint { +/** + * 评查点类型定义 + * 用于展示单个评查结果 + */ +export interface ReviewPoint { id: string; title: string; - location: string; + groupName: string; status: string; - content: string; + content: string | Record; suggestion: string; needsHumanReview?: boolean; humanReviewNote?: string; @@ -20,6 +38,7 @@ interface ReviewPoint { section: string; index: number; }; + result?: boolean; } // 统计数据类型 @@ -41,29 +60,75 @@ interface ReviewPointsListProps { export function ReviewPointsList({ reviewPoints, - statistics, + statistics, activeReviewPointId, onReviewPointSelect, onStatusChange }: ReviewPointsListProps) { - const [editingReviewPoint, setEditingReviewPoint] = useState(null); - const [userInputText, setUserInputText] = useState(''); - const [searchText, setSearchText] = useState(''); - const [statusFilter, setStatusFilter] = useState(null); + // 状态管理 + const [editingReviewPoint, setEditingReviewPoint] = useState(null); // 当前正在编辑的评查点ID + const [userInputText, setUserInputText] = useState(''); // 用户输入的审核意见文本 + const [searchText, setSearchText] = useState(''); // 搜索文本 + const [statusFilter, setStatusFilter] = useState(null); // 状态过滤 + const [suggestionTexts, setSuggestionTexts] = useState>({}); // 存储每个评查点的建议文本 - // 过滤评查点 + // 初始化建议文本 + useEffect(() => { + // 将所有评查点的建议文本存储到状态中 + const suggestions: Record = {}; + reviewPoints.forEach(point => { + suggestions[point.id] = point.suggestion || ''; + }); + setSuggestionTexts(suggestions); + }, [reviewPoints]); + + // 处理建议文本变更 + const handleSuggestionChange = (reviewPointId: string, text: string) => { + setSuggestionTexts(prev => ({ + ...prev, + [reviewPointId]: text + })); + }; + + /** + * 过滤评查点 + * 根据搜索文本和状态过滤条件筛选评查点 + */ const filteredReviewPoints = reviewPoints.filter(point => { + // 匹配搜索文本 const matchesSearch = searchText === '' || point.title.toLowerCase().includes(searchText.toLowerCase()) || - point.location.toLowerCase().includes(searchText.toLowerCase()) || - point.content.toLowerCase().includes(searchText.toLowerCase()); + point.groupName.toLowerCase().includes(searchText.toLowerCase()) || + (typeof point.content === 'string' && point.content.toLowerCase().includes(searchText.toLowerCase())) || + (typeof point.content === 'object' && point.content !== null && + Object.values(point.content).some(value => + typeof value === 'string' && value.toLowerCase().includes(searchText.toLowerCase()) + )); - const matchesStatus = statusFilter === null || point.status === statusFilter; + // 处理状态过滤 + let matchesStatus = false; + + if (statusFilter === null) { + // 未选择过滤条件时显示所有 + matchesStatus = true; + } else if (statusFilter === 'success') { + // 过滤"通过"状态 + matchesStatus = point.result === true || (point.result === undefined && point.status === 'success'); + } else if (statusFilter === 'warning') { + // 过滤"警告"状态 + matchesStatus = point.result === false && point.status === 'warning'; + } else if (statusFilter === 'error') { + // 过滤"错误"状态 + matchesStatus = point.result === false && point.status === 'error'; + } return matchesSearch && matchesStatus; }); - // 处理点击"一键替换"按钮 + /** + * 处理一键替换操作 + * @param reviewPointId 评查点ID + */ const handleReplace = (reviewPointId: string) => { // 在实际应用中,这里应该调用API进行内容替换 // 模拟替换操作 @@ -73,7 +138,11 @@ export function ReviewPointsList({ onStatusChange(reviewPointId, 'success'); }; - // 处理评查点审核操作 + /** + * 处理评查点审核操作 + * @param reviewPointId 评查点ID + * @param action 操作类型: 'approve' 通过 / 'reject' 不通过 + */ const handleReviewAction = (reviewPointId: string, action: 'approve' | 'reject') => { // 更新评查点状态 onStatusChange(reviewPointId, action === 'approve' ? 'success' : 'error'); @@ -85,7 +154,10 @@ export function ReviewPointsList({ alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointId}`); }; - // 显示评查点详情编辑界面 + /** + * 显示评查点详情编辑界面 + * @param reviewPointId 评查点ID + */ const handleEditReviewPoint = (reviewPointId: string) => { setEditingReviewPoint(reviewPointId); @@ -96,18 +168,51 @@ export function ReviewPointsList({ } }; - // 渲染评查统计信息 + /** + * 渲染评查统计信息 + * 显示总计、通过、警告、错误数量 + */ const renderStatistics = () => { + // 确保传入的statistics存在,否则使用计算值 + const statsToUse = statistics || { + total: reviewPoints.length, + success: 0, + warning: 0, + error: 0, + score: 0 + }; + + // 计算各个状态的评查点数量 + const successCount = reviewPoints.filter( + point => point.result === true || (point.result === undefined && point.status === 'success') + ).length; + + const warningCount = reviewPoints.filter( + point => point.result === false && point.status === 'warning' + ).length; + + const errorCount = reviewPoints.filter( + point => point.result === false && point.status === 'error' + ).length; + + // 如果没有计算值,则使用传入的统计值 + const totalToShow = statsToUse.total === 0 ? reviewPoints.length : statsToUse.total; + const successToShow = successCount || statsToUse.success; + const warningToShow = warningCount || statsToUse.warning; + const errorToShow = errorCount || statsToUse.error; + return (
+ {/* 总计数量 */}
- {statistics.total} + {totalToShow}
总计
+ {/* 通过数量 */}
通过
+ {/* 警告数量 */}
警告
+ {/* 错误数量 */}
错误
@@ -148,7 +255,10 @@ export function ReviewPointsList({ ); }; - // 渲染搜索框 + /** + * 渲染搜索框 + * 用于按文本搜索评查点 + */ const renderSearchBar = () => { return (
@@ -174,8 +284,40 @@ export function ReviewPointsList({ ); }; - // 渲染评查点状态标签 - const renderStatusBadge = (status: string) => { + /** + * 渲染评查点状态标签 + * @param status 状态文本 + * @param result 评查结果 + * @returns 状态标签组件 + */ + const renderStatusBadge = (status: string, result?: boolean) => { + // 优先根据result判断是否通过 + if (result === true) { + return ( + + 通过 + + ); + } + + // 当result为false时,根据status决定显示警告还是错误 + if (result === false) { + if (status === 'warning') { + return ( + + 警告 + + ); + } else if (status === 'error') { + return ( + + 不通过 + + ); + } + } + + // 兼容旧版逻辑,当没有result时,仍按status判断 switch (status) { case 'success': return ( @@ -210,7 +352,11 @@ export function ReviewPointsList({ } }; - // 渲染人工审核标记 + /** + * 渲染人工审核标记 + * @param reviewPoint 评查点 + * @returns 人工审核标记组件 + */ const renderHumanReviewBadge = (reviewPoint: ReviewPoint) => { if (reviewPoint.needsHumanReview) { return ( @@ -222,7 +368,11 @@ export function ReviewPointsList({ return null; }; - // 渲染人工审核注释 + /** + * 渲染人工审核注释 + * @param reviewPoint 评查点 + * @returns 人工审核注释组件 + */ const renderHumanReviewNote = (reviewPoint: ReviewPoint) => { if (reviewPoint.needsHumanReview && reviewPoint.humanReviewNote) { return ( @@ -239,11 +389,16 @@ export function ReviewPointsList({ return null; }; - // 渲染评查点内容与建议 + /** + * 渲染评查点内容与建议 + * @param reviewPoint 评查点 + * @returns 评查点内容组件 + */ const renderReviewPointContent = (reviewPoint: ReviewPoint) => { // 如果当前评查点不处于编辑状态,只显示简单信息 if (editingReviewPoint !== reviewPoint.id) { - if (reviewPoint.status === 'success') { + // 根据result和status决定渲染哪种样式 + if (reviewPoint.result === true || (reviewPoint.result === undefined && reviewPoint.status === 'success')) { // 已通过的评查点只显示基本信息和人工审核注释 if (reviewPoint.needsHumanReview && reviewPoint.humanReviewNote) { return ( @@ -262,39 +417,62 @@ export function ReviewPointsList({ return null; } + // 非通过状态,显示内容和修改建议 + const isErrorStatus = reviewPoint.result === false && reviewPoint.status === 'error'; + return (
-
-
- 当前值 - - {reviewPoint.status === 'error' ? '不符合规范' : '需优化'} - -
-

{reviewPoint.content || '(内容为空)'}

- - {reviewPoint.suggestion && ( -
+ {/* 内容显示区域 */} +
+ {/* 移除顶部的"当前值"标题,在每个内容项中显示 */} + {typeof reviewPoint.content === 'object' && reviewPoint.content !== null ? ( + // 当 content 是对象时的渲染方式 +
+ {Object.entries(reviewPoint.content).map(([key, value], index) => ( +
+ {/* 使用flex布局使key和状态标签左右对齐 */} +
+ {key} + + {isErrorStatus ? '不符合规范' : '需优化'} + +
+

{value || '(内容为空)'}

+
+ ))} +
+ ) : ( + // 当 content 是字符串时的渲染方式 + <> + {/* 为字符串内容也添加标题和状态 */}
- 建议修改为 - - 符合规范 + 当前值 + + {isErrorStatus ? '不符合规范' : '需优化'}
-