From 5c2c36785640603665fb820a7748cfcaab2d9205 Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Mon, 21 Apr 2025 09:22:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8F=90=E7=A4=BAToast?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/client.ts | 14 +- app/api/document-types/document-types.ts | 16 +- app/api/evaluation_points/reviews.ts | 86 +++- app/api/evaluation_points/rule-groups.ts | 17 +- app/api/evaluation_points/rules-files.ts | 25 +- app/api/evaluation_points/rules.ts | 7 +- app/api/files/documents.ts | 28 +- app/api/files/files-upload.ts | 1 + app/api/home/home.ts | 222 +++++++---- app/api/prompts/prompts.ts | 16 +- app/api/system_setting/config-lists.ts | 17 +- app/components/layout/Breadcrumb.tsx | 49 ++- app/components/reviews/FileInfo.tsx | 39 +- app/components/reviews/FilePreview.tsx | 139 ++++--- app/components/reviews/ReviewPointsList.tsx | 414 ++++++++++++++------ app/components/rules/new/ReviewSettings.tsx | 73 +++- app/components/ui/MessageModal.tsx | 303 ++++++++++++++ app/components/ui/Toast.tsx | 257 ++++++++++++ app/root.tsx | 16 +- app/routes/_index.tsx | 50 +-- app/routes/document-types._index.tsx | 30 ++ app/routes/documents._index.tsx | 81 ++-- app/routes/documents.edit.tsx | 9 +- app/routes/examples/message-modal.tsx | 204 ++++++++++ app/routes/examples/toast.tsx | 164 ++++++++ app/routes/files.upload.tsx | 21 +- app/routes/reviews.tsx | 88 ++++- app/routes/rule-groups._index.tsx | 11 +- app/routes/rules-files.tsx | 4 +- app/routes/rules-new.tsx | 107 ++++- app/routes/rules._index.tsx | 12 +- app/styles/components/message-modal.css | 282 +++++++++++++ app/styles/components/toast.css | 244 ++++++++++++ app/styles/main.css | 2 + app/styles/reviews.css | 20 + app/utils.ts | 19 +- 36 files changed, 2609 insertions(+), 478 deletions(-) create mode 100644 app/components/ui/MessageModal.tsx create mode 100644 app/components/ui/Toast.tsx create mode 100644 app/routes/examples/message-modal.tsx create mode 100644 app/routes/examples/toast.tsx create mode 100644 app/styles/components/message-modal.css create mode 100644 app/styles/components/toast.css diff --git a/app/api/client.ts b/app/api/client.ts index a51b872..02c65b2 100644 --- a/app/api/client.ts +++ b/app/api/client.ts @@ -219,35 +219,35 @@ export async function apiRequest( if (response.status === 400) { console.error('PostgREST 错误 - 无效请求:', data || responseText); return { - error: '无效的请求格式,请检查数据格式是否正确', + error: data?.message || data?.msg || '无效的请求格式,请检查数据格式是否正确', status: response.status, headers: responseHeaders }; } else if (response.status === 401) { console.error('PostgREST 错误 - 未授权:', data || responseText); return { - error: '未授权,请检查认证信息', + error: data?.message || data?.msg || '未授权,请检查认证信息', status: response.status, headers: responseHeaders }; } else if (response.status === 403) { console.error('PostgREST 错误 - 禁止访问:', data || responseText); return { - error: '没有权限执行此操作', + error: data?.message || data?.msg || '没有权限执行此操作', status: response.status, headers: responseHeaders }; } else if (response.status === 404) { console.error('PostgREST 错误 - 资源不存在:', data || responseText); return { - error: '请求的资源不存在', + error: data?.message || data?.msg || '请求的资源不存在', status: response.status, headers: responseHeaders }; } else { console.error(`HTTP请求失败: ${response.status} - ${url}`, data || responseText); return { - error: data?.msg || `请求失败: ${response.status}`, + error: data?.message || data?.msg || `请求失败: ${response.status}`, status: response.status, headers: responseHeaders }; @@ -256,9 +256,9 @@ export async function apiRequest( // 检查API返回的状态码 if (data && 'code' in data && data.code !== 0) { - console.error(`API请求失败: ${data.msg || '未知错误'} - ${url}`); + console.error(`API请求失败: ${data.message || data.msg || '未知错误'} - ${url}`); return { - error: data.msg || '请求失败', + error: data.message || data.msg || '请求失败', status: response.status, headers: responseHeaders }; diff --git a/app/api/document-types/document-types.ts b/app/api/document-types/document-types.ts index 7b46861..ed2fd1c 100644 --- a/app/api/document-types/document-types.ts +++ b/app/api/document-types/document-types.ts @@ -1,5 +1,5 @@ import { postgrestGet, postgrestDelete, postgrestPost, postgrestPut, type PostgrestParams } from '../postgrest-client'; -import dayjs from 'dayjs'; +import { formatDate } from '../../utils'; // 定义文档类型接口 export interface DocumentType { @@ -65,20 +65,6 @@ export interface DocumentTypeSearchParams { 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 响应中提取数据 diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index 20d48ea..b65f6e5 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -90,6 +90,17 @@ interface StatsData { score: number; } +// 在文件顶部添加的类型定义,在interface区块前添加 +interface OcrDataResult { + pages?: number[]; + [key: string]: unknown; +} + +interface OcrData { + [key: string]: OcrDataResult | unknown; + ocr_result?: Record; +} + /** * 获取当前评查文件的所有评查点结果 * @param fileId 评查文件ID @@ -204,8 +215,9 @@ export async function getReviewPoints(fileId: string) { } // 提取页码数组 - let contentPage: number[] = []; - console.log('datacontent-------', data); + let contentPage: Record = {}; + // console.log('result-------', result.evaluated_results?.result); + // console.log('datacontent-------', data); if (data && typeof data === 'object') { try { const dataObj = data as Record; @@ -214,19 +226,19 @@ export async function getReviewPoints(fileId: string) { if (Object.prototype.hasOwnProperty.call(dataObj, key)) { // 使用'-'分割获取前缀(如'立案报告表') const prefix = key.split('-')[0]; - console.log('prefix-------', prefix); + // console.log('prefix-------', prefix); // 检查document.data中的ocrResult是否存在这个key if (documentData.data?.ocrResult && typeof documentData.data.ocrResult === 'object') { // ocrResult可能有嵌套的ocr_result属性 - let ocrData: Record = documentData.data.ocrResult as Record; + let ocrData: OcrData = documentData.data.ocrResult as OcrData; // 检查是否有嵌套的ocr_result对象 if ('ocr_result' in ocrData && ocrData.ocr_result && typeof ocrData.ocr_result === 'object') { - ocrData = ocrData.ocr_result as Record; + ocrData = ocrData.ocr_result as OcrData; } for (const ocrKey in ocrData) { @@ -239,7 +251,8 @@ export async function getReviewPoints(fileId: string) { // 获取pages数组 const pages = ocrData[ocrKey].pages; if (Array.isArray(pages)) { - contentPage = pages; + // 存储每个key对应的页码数组 + contentPage[key] = pages; } break; } @@ -249,7 +262,7 @@ export async function getReviewPoints(fileId: string) { } } catch (e) { console.error('解析评查点data失败:', e); - contentPage = []; + contentPage = {}; } } @@ -420,3 +433,62 @@ export async function updateReviewResult(resultId: string, result: boolean, mess }; } } + +/** + * 确认评查结果并更新文档审核状态 + * @param documentId 文档ID + * @returns 更新结果 + */ +export async function confirmReviewResults(documentId: string): Promise<{ + data?: { auditStatus: number; score: number }; + error?: string; + status?: number; +}> { + try { + if (!documentId) { + return { error: '文档ID不能为空', status: 400 }; + } + + // 获取该文档的所有评查点结果 + const reviewPointsResponse = await getReviewPoints(documentId); + + if ('error' in reviewPointsResponse && reviewPointsResponse.error) { + return { error: reviewPointsResponse.error, status: reviewPointsResponse.status }; + } + + if (!('data' in reviewPointsResponse) || !reviewPointsResponse.data || !Array.isArray(reviewPointsResponse.data)) { + return { error: '获取评查点数据失败', status: 500 }; + } + + // 计算总分数 + const totalScore = reviewPointsResponse.stats?.score || 0; + + // 根据总分确定审核状态 + // <80分:不通过(-1),>=80分:通过(1) + const auditStatus = totalScore < 80 ? -1 : 1; + + // 更新文档的审核状态 + const updateDocumentParams = { + audit_status: auditStatus + }; + + // 调用API更新文档审核状态 + const response = await postgrestPut<{ id: string }, typeof updateDocumentParams>( + 'documents', + updateDocumentParams, + { id: documentId } + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + return { data: { auditStatus, score: totalScore } }; + } catch (error) { + console.error('确认评查结果失败:', error); + return { + error: error instanceof Error ? error.message : '确认评查结果失败', + status: 500 + }; + } +} diff --git a/app/api/evaluation_points/rule-groups.ts b/app/api/evaluation_points/rule-groups.ts index ac6a8db..c1f87f5 100644 --- a/app/api/evaluation_points/rule-groups.ts +++ b/app/api/evaluation_points/rule-groups.ts @@ -1,5 +1,5 @@ import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client'; -import dayjs from 'dayjs'; +import { formatDate } from '../../utils'; /** * 评查点分组接口 @@ -44,20 +44,7 @@ interface ApiResponse { data: T; } -/** - * 格式化日期 - * @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 响应中提取数据 diff --git a/app/api/evaluation_points/rules-files.ts b/app/api/evaluation_points/rules-files.ts index 11982d9..64dfeb3 100644 --- a/app/api/evaluation_points/rules-files.ts +++ b/app/api/evaluation_points/rules-files.ts @@ -4,6 +4,7 @@ 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'; +import { formatDate } from '../../utils'; // 配置 dayjs dayjs.extend(weekday); @@ -110,21 +111,6 @@ interface DocumentReviewResult { issueCount: number; } -/** - * 格式化日期 - * @param dateString 日期字符串 - * @returns 格式化后的日期字符串 - */ -function formatDate(dateString: string): string { - if (!dateString) return ''; - try { - return dayjs(dateString).format('YYYY-MM-DD'); - } catch (error) { - console.error('日期格式化失败:', error); - return dateString; - } -} - /** * 从不同格式的 API 响应中提取数据 * @param responseData API 响应数据 @@ -409,6 +395,7 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}): P let hasFailResult = false; let totalScore = 0; let totalPoints = 0; + let totalPassPoints = 0; // 存储该文档的问题消息 const issuesList: Array<{severity: 'info' | 'warning' | 'error' | 'critical', message: string}> = []; @@ -425,7 +412,7 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}): P } // 检查是否有不通过的结果 - if (resultValue === false) { + if (!resultValue) { hasFailResult = true; // 收集问题消息 @@ -435,6 +422,8 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}): P message: evaluatedResults.message as string }); } + }else{ + totalPassPoints++; } // 计算总分 @@ -467,7 +456,9 @@ export async function getReviewFiles(searchParams: DocumentSearchParams = {}): P // 如果没有评查点,默认为通过 if (totalPoints > 0) { // 通过分数线为80分 - status = totalScore >= 80 ? 1 : -1; // 通过或不通过 + // status = totalScore >= 80 ? 1 : -1; // 通过或不通过 + // 通过率为80% + status = parseFloat((totalPassPoints/totalPoints).toFixed(1)) >= 0.8 ? 1 : -1; // 通过或不通过 } } diff --git a/app/api/evaluation_points/rules.ts b/app/api/evaluation_points/rules.ts index 558ee9d..8220005 100644 --- a/app/api/evaluation_points/rules.ts +++ b/app/api/evaluation_points/rules.ts @@ -1,5 +1,5 @@ import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client'; -import dayjs from 'dayjs'; +import { formatDate } from '../../utils'; /** * 从不同格式的 API 响应中提取数据 @@ -146,11 +146,6 @@ function mapApiRuleToFrontendModel(apiRule: ApiRule): Rule { }; } -// 格式化日期的辅助函数 -function formatDate(dateString: string): string { - return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss'); -} - /** * 获取评查点列表 * @param params 查询参数 diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 99ca0e8..222d15d 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -1,6 +1,7 @@ import { postgrestGet, postgrestDelete, postgrestPut, type PostgrestParams } from '../postgrest-client'; import dayjs from 'dayjs'; import { getDocumentTypes } from '../document-types/document-types'; +import { formatDate } from '../../utils'; /** * 从不同格式的 API 响应中提取数据 @@ -22,20 +23,6 @@ function extractApiData(responseData: unknown): T | null { return responseData as T; } -/** - * 格式化日期 - * @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; - } - } /** * 查询参数 @@ -94,7 +81,7 @@ export interface DocumentUI { typeName: string; size: number; auditStatus: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中 - fileStatus: string; // Waiting, Cutting, Extractioning, Evaluationing, Processed + fileStatus: string; // Waiting, Cutting, Extractioning, Failed, Evaluationing, Processed issues: number | null; uploadTime: string; fileType: string; @@ -161,6 +148,7 @@ async function convertToUIDocument(doc: Document): Promise { }); } + return { id: doc.id, name: doc.name, @@ -233,21 +221,19 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro // 处理日期范围 if (searchParams.dateFrom) { // 添加当天开始时间 00:00:00 - filter['created_at'] = `gte.'${dayjs(`${searchParams.dateFrom} 00:00:00`).format()}'`; + filter['created_at'] = `gte.${searchParams.dateFrom + ' 00:00:00'}`; } if (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}')`; + filter[dateToKey] = `(created_at.gte.${searchParams.dateFrom+' 00:00:00'},created_at.lte.${searchParams.dateTo+' 23:59:59'})`; } else { - filter['created_at'] = `lte.'${newDateTo}'`; + filter['created_at'] = `lte.${searchParams.dateTo+' 23:59:59'}`; } } @@ -267,6 +253,8 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro return { error: '获取文档数据失败', status: 500 }; } + // console.log('extractedData---1--',extractedData[0]); + // 转换为UI格式 const documents = await Promise.all(extractedData.map(convertToUIDocument)); // console.log('documentsItem',documents) diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts index 7b54623..e50b6f2 100644 --- a/app/api/files/files-upload.ts +++ b/app/api/files/files-upload.ts @@ -82,6 +82,7 @@ export interface Document { extracted_results?: ExtractedResult; sumary?: Summary; remark?: string; + audit_status?: number; } // 文件上传响应接口 diff --git a/app/api/home/home.ts b/app/api/home/home.ts index 9eefad2..58329a3 100644 --- a/app/api/home/home.ts +++ b/app/api/home/home.ts @@ -7,29 +7,51 @@ import dayjs from 'dayjs'; * @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; + if (!responseData) { + console.warn('API响应数据为空'); + return null; } - // 格式2: 直接是数据对象 - return responseData as T; -} + try { + // 检查是否有错误信息 + if (typeof responseData === 'object' && responseData !== null) { + // 错误检查: 检查错误码,一般成功的错误码是0或200 + if ('code' in responseData) { + const code = (responseData as { code: number }).code; + // 如果有错误码且不是成功状态 + if (code !== 0 && code !== 200) { + const errorMsg = 'msg' in responseData + ? (responseData as { msg: string }).msg + : '未知错误'; + console.error(`API响应错误: [${code}] ${errorMsg}`); + return null; + } + } -/** - * 评估结果类型定义 - */ -interface EvaluationResult { - result: boolean; - rule_id?: string; - rule_name?: string; - description?: string; - [key: string]: unknown; + // 错误检查: 检查是否包含错误消息但没有数据 + if ('error' in responseData && (responseData as { error: unknown }).error) { + const error = (responseData as { error: unknown }).error; + console.error(`API响应包含错误: ${typeof error === 'string' ? error : JSON.stringify(error)}`); + return null; + } + + // 格式1: { code: number, msg: string, data: T } + if ('data' in responseData) { + const data = (responseData as { data: unknown }).data; + if (!data) { + console.warn('API响应中的data字段为空'); + return null; + } + return data as T; + } + } + + // 格式2: 直接是数据对象 + return responseData as T; + } catch (error) { + console.error('处理API响应数据时出错:', error); + return null; + } } /** @@ -67,43 +89,81 @@ export async function getHomeData(): Promise { const startOfLastMonth = dayjs().subtract(1, 'month').startOf('month').format('YYYY-MM-DD HH:mm:ss'); const endOfLastMonth = dayjs().subtract(1, 'month').endOf('month').format('YYYY-MM-DD HH:mm:ss'); + // 通用API响应处理函数 + const handleApiResponse = async ( + apiCall: Promise<{ + data?: unknown; + headers?: Record; + error?: string; + status?: number + }>, + errorMessage: string, + defaultValue: T + ): Promise => { + try { + const response = await apiCall; + if (response.error) { + console.error(`${errorMessage}: ${response.error}`); + return defaultValue; + } + const data = extractApiData(response.data); + if (!data) { + console.warn(`${errorMessage}: 无法提取有效数据`); + return defaultValue; + } + return data; + } catch (error) { + console.error(`${errorMessage}: ${error instanceof Error ? error.message : '未知错误'}`); + return defaultValue; + } + }; + // 1. 今日待审核文件 - 获取今天的待审核文件数量 (audit_status = 0 或 2) const todayPendingParams: PostgrestParams = { select: 'count', filter: { - or: `(audit_status.eq.0,audit_status.eq.2)`, + or: `(audit_status.eq.0,audit_status.eq.2,audit_status.is.null)`, created_at: `gte.${startOfToday}` } }; - const todayPendingResponse = await postgrestGet('documents', todayPendingParams); - const todayPendingCount = extractApiData<{count: number}[]>(todayPendingResponse.data); - const todayPendingFiles = todayPendingCount?.[0]?.count || 0; + const todayPendingCount = await handleApiResponse<{count: number}[]>( + postgrestGet('documents', todayPendingParams), + '获取今日待审核文件数量失败', + [] + ); + const todayPendingFiles = todayPendingCount[0]?.count || 0; // 2. 本月已审核文件 - 获取本月已审核文件数量 (audit_status != 0 且 != 2) const thisMonthReviewedParams: PostgrestParams = { select: 'count', filter: { and: `(audit_status.neq.0,audit_status.neq.2)`, - created_at: `gte.${startOfThisMonth}`, - created_at_lte: `lte.${endOfThisMonth}` + created_at: `gte.${startOfThisMonth}` } }; - const thisMonthReviewedResponse = await postgrestGet('documents', thisMonthReviewedParams); - const thisMonthReviewedCount = extractApiData<{count: number}[]>(thisMonthReviewedResponse.data); - const monthlyReviewedFiles = thisMonthReviewedCount?.[0]?.count || 0; + const thisMonthReviewedCount = await handleApiResponse<{count: number}[]>( + postgrestGet('documents', thisMonthReviewedParams), + '获取本月已审核文件数量失败', + [] + ); + // 本月已审核文件数量 + const monthlyReviewedFiles = thisMonthReviewedCount[0]?.count || 0; // 上月已审核文件 const lastMonthReviewedParams: PostgrestParams = { select: 'count', filter: { - and: `(audit_status.neq.0,audit_status.neq.2)`, - created_at: `gte.${startOfLastMonth}`, - created_at_lte: `lte.${endOfLastMonth}` + or: `(audit_status.eq.1,audit_status.eq.-1)`, + and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})` } }; - const lastMonthReviewedResponse = await postgrestGet('documents', lastMonthReviewedParams); - const lastMonthReviewedCount = extractApiData<{count: number}[]>(lastMonthReviewedResponse.data); - const lastMonthReviewed = lastMonthReviewedCount?.[0]?.count || 0; + const lastMonthReviewedCount = await handleApiResponse<{count: number}[]>( + postgrestGet('documents', lastMonthReviewedParams), + '获取上月已审核文件数量失败', + [] + ); + // 上月已审核文件数量 + const lastMonthReviewed = lastMonthReviewedCount[0]?.count || 0; // 计算同比增长 let reviewGrowthValue = 0; @@ -115,37 +175,46 @@ export async function getHomeData(): Promise { reviewGrowthIsUp = growthRate >= 0; } - // 3. 审核通过率 - 本月审核通过率 (已审核文件 / 总待审核文件 + 已审核文件) + // 3. 审核通过率 - 本月审核通过率 const thisMonthTotalParams: PostgrestParams = { select: 'count', filter: { - created_at: `gte.${startOfThisMonth}`, - created_at_lte: `lte.${endOfThisMonth}` + audit_status: `eq.1`, + created_at: `gte.${startOfThisMonth}` } }; - const thisMonthTotalResponse = await postgrestGet('documents', thisMonthTotalParams); - const thisMonthTotalCount = extractApiData<{count: number}[]>(thisMonthTotalResponse.data); - const thisMonthTotal = thisMonthTotalCount?.[0]?.count || 0; + const thisMonthTotalCount = await handleApiResponse<{count: number}[]>( + postgrestGet('documents', thisMonthTotalParams), + '获取本月审核通过数量失败', + [] + ); + // 本月审核通过数量 + const thisMonthPassTotal = thisMonthTotalCount[0]?.count || 0; // 本月审核通过率 - const monthlyPassRate = thisMonthTotal > 0 - ? parseFloat(((monthlyReviewedFiles / thisMonthTotal) * 100).toFixed(1)) + const monthlyPassRate = (thisMonthPassTotal > 0 && monthlyReviewedFiles > 0) + ? parseFloat(((thisMonthPassTotal / monthlyReviewedFiles) * 100).toFixed(1)) : 0; // 上月审核通过率 const lastMonthTotalParams: PostgrestParams = { select: 'count', filter: { - created_at: `gte.${startOfLastMonth}`, - created_at_lte: `lte.${endOfLastMonth}` + audit_status: `eq.1`, + and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})` } }; - const lastMonthTotalResponse = await postgrestGet('documents', lastMonthTotalParams); - const lastMonthTotalCount = extractApiData<{count: number}[]>(lastMonthTotalResponse.data); - const lastMonthTotal = lastMonthTotalCount?.[0]?.count || 0; + const lastMonthTotalCount = await handleApiResponse<{count: number}[]>( + postgrestGet('documents', lastMonthTotalParams), + '获取上月审核通过数量失败', + [] + ); + // 上月审核通过数量 + const lastMonthTotal = lastMonthTotalCount[0]?.count || 0; + // 上月审核通过率 const lastMonthPassRate = (lastMonthTotal > 0 && lastMonthReviewed > 0) - ? (lastMonthReviewed / lastMonthTotal) * 100 + ? parseFloat(((lastMonthTotal / lastMonthReviewed) * 100).toFixed(1)) : 0; // 计算通过率同比增长 @@ -160,48 +229,35 @@ export async function getHomeData(): Promise { // 4. 检查出的问题总数(从评估结果表中统计) const thisMonthIssuesParams: PostgrestParams = { - select: 'evaluated_results', + select: 'count', filter: { - created_at: `gte.${startOfThisMonth}`, - created_at_lte: `lte.${endOfThisMonth}` + and: `(created_at.gte.${startOfThisMonth},created_at.lte.${endOfThisMonth})`, + 'evaluated_results->result': 'eq.false' // 使用->操作符访问JSONB字段 } }; - const thisMonthIssuesResponse = await postgrestGet('evaluation_results', thisMonthIssuesParams); - const thisMonthIssuesData = extractApiData<{ evaluated_results: EvaluationResult[] }[]>(thisMonthIssuesResponse.data); - - // 累计本月问题数量 - let thisMonthIssuesCount = 0; - if (thisMonthIssuesData && thisMonthIssuesData.length > 0) { - thisMonthIssuesData.forEach(row => { - if (row.evaluated_results && Array.isArray(row.evaluated_results)) { - thisMonthIssuesCount += row.evaluated_results.filter((result: EvaluationResult) => - result && result.result === false - ).length; - } - }); - } + const thisMonthIssuesResponse = await handleApiResponse<{count: number}[]>( + postgrestGet('evaluation_results', thisMonthIssuesParams), + '获取本月问题数据失败', + [] + ); + // 本月问题数量 + const thisMonthIssuesCount = thisMonthIssuesResponse[0]?.count || 0; // 上月问题数量 const lastMonthIssuesParams: PostgrestParams = { - select: 'evaluated_results', + select: 'count', filter: { - created_at: `gte.${startOfLastMonth}`, - created_at_lte: `lte.${endOfLastMonth}` + and: `(created_at.gte.${startOfLastMonth},created_at.lte.${endOfLastMonth})`, + 'evaluated_results->result': 'eq.false' // 使用->操作符访问JSONB字段 } }; - const lastMonthIssuesResponse = await postgrestGet('evaluation_results', lastMonthIssuesParams); - const lastMonthIssuesData = extractApiData<{ evaluated_results: EvaluationResult[] }[]>(lastMonthIssuesResponse.data); - - let lastMonthIssuesCount = 0; - if (lastMonthIssuesData && lastMonthIssuesData.length > 0) { - lastMonthIssuesData.forEach(row => { - if (row.evaluated_results && Array.isArray(row.evaluated_results)) { - lastMonthIssuesCount += row.evaluated_results.filter((result: EvaluationResult) => - result && result.result === false - ).length; - } - }); - } + const lastMonthIssuesResponse = await handleApiResponse<{count: number}[]>( + postgrestGet('evaluation_results', lastMonthIssuesParams), + '获取上月问题数据失败', + [] + ); + // 上月问题数量 + const lastMonthIssuesCount = lastMonthIssuesResponse[0]?.count || 0; // 计算问题数量同比增长 let issuesGrowthValue = 0; @@ -233,7 +289,7 @@ export async function getHomeData(): Promise { } }; } catch (error) { - console.error('获取首页数据失败:', error); + console.error('获取首页数据失败:', error instanceof Error ? error.message : String(error)); // 返回默认值以防止页面崩溃 return { todayPendingFiles: 0, diff --git a/app/api/prompts/prompts.ts b/app/api/prompts/prompts.ts index 58b0277..ca720c8 100644 --- a/app/api/prompts/prompts.ts +++ b/app/api/prompts/prompts.ts @@ -1,5 +1,5 @@ import { postgrestGet, postgrestPut, postgrestPost, postgrestDelete, type PostgrestParams } from '../postgrest-client'; -import dayjs from 'dayjs'; +import { formatDate } from '../../utils'; // 提示词模板接口 export interface PromptTemplate { @@ -40,20 +40,6 @@ export interface PromptSearchParams { 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 响应中提取数据 diff --git a/app/api/system_setting/config-lists.ts b/app/api/system_setting/config-lists.ts index cf90cb2..5bc8b10 100644 --- a/app/api/system_setting/config-lists.ts +++ b/app/api/system_setting/config-lists.ts @@ -1,5 +1,5 @@ import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client'; -import dayjs from 'dayjs'; +import { formatDate } from '../../utils'; // 配置项接口 export interface ConfigItem { id: number; @@ -15,20 +15,7 @@ export interface ConfigItem { created_at: string; updated_at: string; } -/** - * 格式化日期 - * @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; - } -} + /** diff --git a/app/components/layout/Breadcrumb.tsx b/app/components/layout/Breadcrumb.tsx index 12e3e7c..1985936 100644 --- a/app/components/layout/Breadcrumb.tsx +++ b/app/components/layout/Breadcrumb.tsx @@ -10,26 +10,59 @@ interface BreadcrumbProps { className?: string; } +interface PreviousRouteData { + title: string; + to: string; +} + interface Handle { - breadcrumb: string | ((data: any) => string); + breadcrumb: string | ((data: unknown) => string); + previousRoute?: PreviousRouteData | ((data: unknown) => PreviousRouteData | undefined); } interface Match { handle?: Handle; pathname: string; - data: any; + data: unknown; } export function Breadcrumb({ items = [], className = '' }: BreadcrumbProps) { const matches = useMatches() as Match[]; + + // 构建面包屑数据 const breadcrumbs = items.length > 0 ? items : matches .filter(match => match.handle?.breadcrumb) - .map(match => ({ - title: typeof match.handle?.breadcrumb === 'function' - ? match.handle.breadcrumb(match.data) - : match.handle?.breadcrumb, - to: match.pathname - })); + .map((match, index, array) => { + // 当前路由的面包屑 + const current = { + title: typeof match.handle?.breadcrumb === 'function' + ? match.handle.breadcrumb(match.data) + : match.handle?.breadcrumb as string, + to: match.pathname + }; + + // 如果当前路由有previousRoute属性且该路由是数组中的最后一个 + if (match.handle?.previousRoute && index === array.length - 1) { + // 获取previousRoute数据,支持函数形式 + const prevRouteData = typeof match.handle.previousRoute === 'function' + ? match.handle.previousRoute(match.data) + : match.handle.previousRoute; + + // 如果previousRoute存在,添加到面包屑中 + if (prevRouteData) { + return [ + { + title: prevRouteData.title, + to: prevRouteData.to + }, + current + ]; + } + } + + return [current]; + }) + .flat(); // 扁平化数组 if (breadcrumbs.length === 0) { return null; diff --git a/app/components/reviews/FileInfo.tsx b/app/components/reviews/FileInfo.tsx index ee7c5c4..966fde1 100644 --- a/app/components/reviews/FileInfo.tsx +++ b/app/components/reviews/FileInfo.tsx @@ -9,13 +9,48 @@ interface FileInfoProps { uploadTime?: string; uploadUser?: string; auditStatus?: number; + path?: string; }; onConfirmResults: () => void; } export function FileInfo({ fileInfo, onConfirmResults }: FileInfoProps) { - const handleDownloadFile = () => { - alert('下载原文件功能'); + const handleDownloadFile = async () => { + try { + const urlBefore = 'http://172.18.0.100:9000/docauditai/'; + const downloadUrl = `${urlBefore}${fileInfo.path}`; + + // 使用fetch获取文件内容 + const response = await fetch(downloadUrl); + if (!response.ok) { + throw new Error(`下载失败: ${response.status} ${response.statusText}`); + } + + // 将响应转换为Blob + const blob = await response.blob(); + + // 创建Blob URL + const blobUrl = URL.createObjectURL(blob); + + // 创建一个隐藏的a标签并点击它 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + // 从路径中获取文件名 + const fileName = fileInfo.path?.split('/').pop() || 'document'; + a.download = decodeURIComponent(fileName); + document.body.appendChild(a); + a.click(); + + // 清理 + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }, 100); + } catch (error) { + console.error('下载文件失败:', error); + alert(`下载文件失败: ${error instanceof Error ? error.message : '未知错误'}`); + } }; const handleExportReport = () => { diff --git a/app/components/reviews/FilePreview.tsx b/app/components/reviews/FilePreview.tsx index 63656f4..fe413c7 100644 --- a/app/components/reviews/FilePreview.tsx +++ b/app/components/reviews/FilePreview.tsx @@ -12,6 +12,27 @@ pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js'; // 导入统一的ReviewPoint类型 import { type ReviewPoint } from './'; +/** + * 自定义样式 + * 这些样式解决了PDF页面在放大时互相重叠的问题 + */ +const styles = { + pdfContainer: { + display: 'flex', + flexDirection: 'column' as const, + alignItems: 'center', + width: '100%', + position: 'relative' as const, + }, + pageContainer: { + display: 'flex', + flexDirection: 'column' as const, + alignItems: 'center', + width: '100%', + position: 'relative' as const, + } +}; + // 定义文档内容类型 interface FileContent { title: string; @@ -76,24 +97,28 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta }; // 当选中的评查点变化时,滚动到对应位置 - useEffect(() => { - if (activeReviewPointId && contentRef.current) { - const highlightElement = contentRef.current.querySelector(`[data-review-id="${activeReviewPointId}"]`); - if (highlightElement) { - highlightElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + // useEffect(() => { + // if (activeReviewPointId && contentRef.current) { + // const highlightElement = contentRef.current.querySelector(`[data-review-id="${activeReviewPointId}"]`); + // if (highlightElement) { + // highlightElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + // // highlightElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); - // 添加临时突出显示效果 - highlightElement.classList.add('highlight-focus'); - setTimeout(() => { - highlightElement.classList.remove('highlight-focus'); - }, 1500); - } - } - }, [activeReviewPointId]); + // // 添加临时突出显示效果 + // highlightElement.classList.add('highlight-focus'); + // setTimeout(() => { + // highlightElement.classList.remove('highlight-focus'); + // }, 1500); + // } + // } + // }, [activeReviewPointId]); // 处理页面跳转 + const prevTargetPageRef = useRef(undefined); useEffect(() => { - if (targetPage && numPages && targetPage <= numPages) { + // 如果有目标页码,并且与上次不同或activeReviewPointId变化了,则执行跳转 + if (targetPage && numPages && targetPage <= numPages && (targetPage !== prevTargetPageRef.current || activeReviewPointId)) { + prevTargetPageRef.current = targetPage; let newTargetPage = targetPage; try { // 安全地访问ocrResult @@ -107,11 +132,11 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta const pageElement = document.getElementById(`page-${newTargetPage}`); if (pageElement) { - console.log(`跳转到第${newTargetPage}页`); - pageElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + console.log(`跳转到第${newTargetPage}页,对应评查点ID: ${activeReviewPointId}`); + pageElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } - }, [targetPage, numPages, fileContent]); + }, [targetPage, numPages, fileContent, activeReviewPointId]); // 获取评查点对应的样式类 const getHighlightClass = (status: string) => { @@ -133,6 +158,15 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta console.log("PDF加载成功,页数:", numPages); } + // 计算页面在缩放后的实际间距 + const calculatePageMargin = (zoomFactor: number) => { + // 基础间距为30px,随着缩放倍数线性增加 + const baseMargin = 30; + // 页面缩放后,需要额外添加的间距 = (缩放倍数 - 1) * 页面高度 + const additionalMargin = Math.max(0, (zoomFactor - 1) * 800); // 800是估计的页面高度 + return baseMargin + additionalMargin; + }; + /** * 渲染PDF文档的所有页面 * @@ -157,25 +191,35 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta const pageReviewPoints = reviewPoints.filter(point => point.position && point.position.section === `page-${i}` ); - // console.log("pageReviewPoints-------",pageReviewPoints); + + // 计算当前缩放级别下的页面容器样式 + const zoomFactor = zoomLevel / 100; + const pageContainerStyle = { + ...styles.pageContainer, + marginBottom: `${calculatePageMargin(zoomFactor)}px`, // 动态计算页面间距 + }; // 为每一页创建组件 pages.push( -
+
{/* 页码标识,显示在页面上方 */}
第 {i} 页
{/* 页面容器,应用缩放变换,设置相对定位用于放置评查点高亮 */} -
+
{/* 渲染PDF页面组件 */} @@ -218,20 +262,22 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta // 渲染文档内容 const renderDocumentContent = () => { return ( - { - console.error("PDF加载错误:", error); - setLoadError("PDF文档加载失败:" + (error.message || "未知错误")); - }} - className="flex flex-col items-center" - error={
PDF文档加载失败,请检查链接或网络连接。
} - noData={
无数据
} - loading={
PDF加载中...
} - > - {renderAllPages()} -
+
+ { + console.error("PDF加载错误:", error); + setLoadError("PDF文档加载失败:" + (error.message || "未知错误")); + }} + className="flex flex-col items-center w-full" + error={
PDF文档加载失败,请检查链接或网络连接。
} + noData={
无数据
} + loading={
PDF加载中...
} + > + {renderAllPages()} +
+
); }; @@ -255,15 +301,20 @@ export function FilePreview({ fileContent, reviewPoints, activeReviewPointId, ta > - + */} + {"比例:"+zoomLevel+"%"}
-
+
{loadError ? (

{loadError}

diff --git a/app/components/reviews/ReviewPointsList.tsx b/app/components/reviews/ReviewPointsList.tsx index 7492927..88ca6e5 100644 --- a/app/components/reviews/ReviewPointsList.tsx +++ b/app/components/reviews/ReviewPointsList.tsx @@ -25,6 +25,7 @@ import { useState, useEffect } from 'react'; */ export interface ReviewPoint { id: string; + pointName: string; title: string; groupName: string; status: string; @@ -34,7 +35,7 @@ export interface ReviewPoint { humanReviewNote?: string; humanReviewBy?: string; humanReviewTime?: string; - contentPage?: number[]; + contentPage?: Record; position?: { section: string; index: number; @@ -133,7 +134,7 @@ export function ReviewPointsList({ // 清除编辑状态 setEditingReviewPoint(null); - alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointResultId},审核内容: ${message}`); + // alert(`${action === 'approve' ? '通过' : '不通过'}了评查点 ${reviewPointResultId},审核内容: ${message}`); }; /** @@ -143,6 +144,7 @@ export function ReviewPointsList({ const filteredReviewPoints = reviewPoints.filter(point => { // 匹配搜索文本 const matchesSearch = searchText === '' || + point.pointName.toLowerCase().includes(searchText.toLowerCase()) || point.title.toLowerCase().includes(searchText.toLowerCase()) || point.groupName.toLowerCase().includes(searchText.toLowerCase()) || (typeof point.content === 'string' && point.content.toLowerCase().includes(searchText.toLowerCase())) || @@ -297,7 +299,7 @@ export function ReviewPointsList({ {searchText && (