From 88466b7a8b56b42f74bf0a8fa8d2c41d2f6764cd Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Tue, 2 Dec 2025 10:10:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=201.=20=E5=AE=8C=E5=96=84=E4=BA=A4?= =?UTF-8?q?=E5=8F=89=E8=AF=84=E6=9F=A5=E4=B8=8A=E4=BC=A0=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=EF=BC=8C=E6=94=B9=E6=88=90=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E6=96=87=E6=A1=A3=E7=B1=BB=E5=9E=8B=E3=80=82?= =?UTF-8?q?=202.=20=E9=87=8D=E6=96=B0=E5=AF=B9=E9=BD=90=E4=BA=A4=E5=8F=89?= =?UTF-8?q?=E8=AF=84=E6=9F=A5=E7=9A=84=E6=8E=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/cross-checking/cross-file-result.ts | 69 ++++++-- app/api/cross-checking/cross-files-upload.ts | 82 ++++++++- app/api/cross-checking/cross-files.ts | 16 +- .../cross-checking/verify-document-access.ts | 143 ++++++++++++++++ app/api/document-types/document-types.ts | 3 + app/api/evaluation_points/reviews.ts | 2 +- .../cross-checking/DocumentListModal.tsx | 2 +- .../cross-checking/ReviewPointsList.tsx | 159 ++++++++++-------- app/components/layout/Sidebar.tsx | 3 +- app/components/reviews/FilePreview.tsx | 1 + app/config/api-config.ts | 18 +- app/routes/_index.tsx | 7 +- app/routes/cross-checking._index.tsx | 43 ++++- app/routes/cross-checking.result.tsx | 56 ++++-- app/routes/cross-checking.upload.tsx | 75 +++------ app/routes/document-types._index.tsx | 2 +- app/routes/document-types.new.tsx | 23 +++ app/routes/role-permissions._index.tsx | 20 ++- app/styles/cross-checking-result.css | 5 + app/styles/pages/document-types_index.css | 4 + vite.config.ts | 2 +- 21 files changed, 561 insertions(+), 174 deletions(-) create mode 100644 app/api/cross-checking/verify-document-access.ts diff --git a/app/api/cross-checking/cross-file-result.ts b/app/api/cross-checking/cross-file-result.ts index 8bc781a..b3cc2fb 100644 --- a/app/api/cross-checking/cross-file-result.ts +++ b/app/api/cross-checking/cross-file-result.ts @@ -102,10 +102,10 @@ export async function findIsProposer(taskId: string | number, userId: number | u return false; } const data = extractApiData<{assigner_id: number}[]>(response.data); - // console.log('data', data); + // console.log('data', data, userId); if (data && data.length > 0) { - return data[0].assigner_id === userId; + return data[0].assigner_id.toString() === userId?.toString(); } return false; @@ -124,6 +124,7 @@ export async function submitCrossCheckingOpinion( ): Promise> { try { // 获取JWT token + console.log('jwtToken', jwtToken) const token = await safeGetJWT(jwtToken); const requestData = { @@ -151,9 +152,21 @@ export async function submitCrossCheckingOpinion( }; } catch (error) { console.error('提交交叉评查意见失败:', error); + + // 正确处理 axios 错误响应 + let errorMessage = '提交意见失败'; + + if (axios.isAxiosError(error) && error.response?.data) { + // 从 axios 错误响应中提取 msg 字段 + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + // 处理普通 Error 对象 + errorMessage = error.message || errorMessage; + } + return { - error: error instanceof Error ? error.message : '提交意见失败', - status: 500 + error: errorMessage, + status: axios.isAxiosError(error) ? error.response?.status || 500 : 500 }; } } @@ -245,9 +258,21 @@ export async function getCrossCheckingOpinions( }; } catch (error) { console.error('获取交叉评查意见失败:', error); + + // 正确处理 axios 错误响应 + let errorMessage = '获取意见列表失败'; + + if (axios.isAxiosError(error) && error.response?.data) { + // 从 axios 错误响应中提取 msg 字段 + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + // 处理普通 Error 对象 + errorMessage = error.message || errorMessage; + } + return { - error: error instanceof Error ? error.message : '获取意见列表失败', - status: 500 + error: errorMessage, + status: axios.isAxiosError(error) ? error.response?.status || 500 : 500 }; } } @@ -343,9 +368,21 @@ export async function performOpinionAction( }; } catch (error) { console.error('执行意见操作失败:', error); + + // 正确处理 axios 错误响应 + let errorMessage = '操作失败'; + + if (axios.isAxiosError(error) && error.response?.data) { + // 从 axios 错误响应中提取 msg 字段 + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + // 处理普通 Error 对象 + errorMessage = error.message || errorMessage; + } + return { - error: error instanceof Error ? error.message : '操作失败', - status: 500 + error: errorMessage, + status: axios.isAxiosError(error) ? error.response?.status || 500 : 500 }; } } @@ -427,9 +464,21 @@ export async function checkProposalVotes( }; } catch (error) { console.error('检查失败:', error); + + // 正确处理 axios 错误响应 + let errorMessage = '检查失败'; + + if (axios.isAxiosError(error) && error.response?.data) { + // 从 axios 错误响应中提取 msg 字段 + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + // 处理普通 Error 对象 + errorMessage = error.message || errorMessage; + } + return { - error: error instanceof Error ? error.message : '检查失败', - status: 500 + error: errorMessage, + status: axios.isAxiosError(error) ? error.response?.status || 500 : 500 }; } } diff --git a/app/api/cross-checking/cross-files-upload.ts b/app/api/cross-checking/cross-files-upload.ts index fd7e583..f3111e6 100644 --- a/app/api/cross-checking/cross-files-upload.ts +++ b/app/api/cross-checking/cross-files-upload.ts @@ -1,4 +1,4 @@ -import { UPLOAD_URL } from '../../config/api-config'; +import { UPLOAD_URL, API_BASE_URL } from '../../config/api-config'; import axios from 'axios'; /** @@ -208,6 +208,7 @@ export async function uploadCrossCheckingDocument( * @param assignUserIds 需要分配的用户ID数组 * @param taskName 任务名称 * @param docType 文档类型(如 XZCF、XZXK) + * @param taskType 任务类型(如 市局间交叉评查、区局间交叉评查) * @param token JWT Token */ export async function batchUploadAndAssignCrossCheckingFiles( @@ -220,6 +221,7 @@ export async function batchUploadAndAssignCrossCheckingFiles( assignUserIds: number[], taskName: string, docType: string, + taskType: string = '市局间交叉评查', token: string | null = null ): Promise<{ successes: Array<{file: CrossCheckingUploadedFile; result: Record}>; @@ -229,6 +231,10 @@ export async function batchUploadAndAssignCrossCheckingFiles( const failures: Array<{file: CrossCheckingUploadedFile; error: string}> = []; const uploadEndpoint = '/cross_review/documents/upload_and_assign'; const uploadUrl = UPLOAD_URL + uploadEndpoint; + + // console.log('[批量上传] 任务类型:', taskType, '文档类型:', docType); + + for (const fileInfo of files) { try { const formData = new FormData(); @@ -240,15 +246,22 @@ export async function batchUploadAndAssignCrossCheckingFiles( remark: remark || null, is_test_document: isTestDocument, task_name: taskName, - doc_type: typeof docType === 'string' ? docType.toUpperCase() : docType + doc_type: typeof docType === 'string' ? docType.toUpperCase() : docType, + task_type: taskType }; + // console.log('fileInfo', fileInfo) + formData.append('upload_info', JSON.stringify(uploadInfo)); formData.append('assign_user_ids', JSON.stringify(assignUserIds)); + const headers: Record = {}; + if (token) headers['Authorization'] = `Bearer ${token}`; + const response = await axios.post(uploadUrl, formData, { headers }); + const result = response.data; if (result && result.success) { successes.push({ file: fileInfo, result }); @@ -262,6 +275,71 @@ export async function batchUploadAndAssignCrossCheckingFiles( return { successes, failures }; } +/** + * 创建交叉评查任务 + * @param taskData 任务数据 + * @param token JWT Token + * @returns 创建结果 + */ +export async function createCrossReviewTask(taskData: { + documentIds: number[]; + userIds: number[]; + assignerId: number; + taskName: string; + docType: string; + taskType?: string; +}, token: string | null = null): Promise<{ + success: boolean; + data?: unknown; + error?: string; +}> { + try { + const requestBody = { + document_ids: taskData.documentIds, + user_ids: taskData.userIds, + assigner_id: taskData.assignerId, + task_name: taskData.taskName, + doc_type: taskData.docType, + task_type: taskData.taskType || '市局间交叉评查' + }; + + // console.log('[创建任务] 请求数据:', requestBody); + + const headers: Record = { + 'Content-Type': 'application/json' + }; + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } + + const response = await axios.post( + `${API_BASE_URL}/admin/cross_review/tasks/assign`, + requestBody, + { headers } + ); + + console.log('[创建任务] 成功:', response.data); + return { + success: true, + data: response.data + }; + } catch (error) { + console.error('[创建任务] 失败:', error); + + let errorMessage = '创建任务失败'; + if (axios.isAxiosError(error) && error.response?.data) { + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + errorMessage = error.message || errorMessage; + } + + return { + success: false, + error: errorMessage + }; + } +} + /** * 生成唯一文件ID * @returns 唯一ID字符串 diff --git a/app/api/cross-checking/cross-files.ts b/app/api/cross-checking/cross-files.ts index 44c2ef3..90f6070 100644 --- a/app/api/cross-checking/cross-files.ts +++ b/app/api/cross-checking/cross-files.ts @@ -11,8 +11,8 @@ export enum CrossCheckingTaskStatus { // 交叉评查任务类型枚举 export enum CrossCheckingTaskType { - CITY = 'city', - COUNTY = 'county' + CITY = 'CITY', + DISTRICT = 'DISTRICT' } // 案卷类型枚举 @@ -37,7 +37,7 @@ export interface CrossCheckingTask { startDate: string; taskType: CrossCheckingTaskType; docType: string; // 改为直接使用返回的 doc_type 字符串 - evaluationRegion: string; + evaluationRegion: string[]; progress: number; status: string; // 改为直接使用返回的 task_status 字符串 score: number; @@ -61,6 +61,8 @@ export interface UserTaskInfo { task_status: string; doc_type?: string; task_created_at?: string; + evaluation_region?: string[]; + task_type?: string; progress?: number; total_documents?: number; // 新增:任务包含的文档总数 } @@ -150,7 +152,7 @@ export interface TaskListResponse { */ export async function getCrossCheckingTasks(params: TaskListParams = {}, userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise> { try { - console.log('开始调用getCrossCheckingTasks,参数:', params); + // console.log('开始调用getCrossCheckingTasks,参数:', params); // 调用用户任务API,获取当前用户参与的任务 const userTasksResponse = await getUserTaskDocuments(params.page || 1, params.pageSize || 10, jwtToken); @@ -174,9 +176,9 @@ export async function getCrossCheckingTasks(params: TaskListParams = {}, userInf 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: CrossCheckingTaskType.CITY, // 保持默认任务类型 + taskType: userTask.task_type, // 保持默认任务类型 docType: userTask.doc_type || '未知类型', // 使用API返回的文档类型 - evaluationRegion: '待定', // 保持默认评查地区 + evaluationRegion: userTask.evaluation_region || [], // 保持默认评查地区 progress: userTask.progress || 0, // 使用API返回的进度 status: userTask.task_status || 'pending', // 使用API返回的任务状态 score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数 @@ -302,7 +304,7 @@ export async function getCrossCheckingTaskDetail( pageSize: number; }>> { try { - console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize }); + // console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize }); // 获取任务的文档列表 const taskDocumentsResponse = await getTaskDocuments(taskId, page, pageSize, jwtToken); diff --git a/app/api/cross-checking/verify-document-access.ts b/app/api/cross-checking/verify-document-access.ts new file mode 100644 index 0000000..9078979 --- /dev/null +++ b/app/api/cross-checking/verify-document-access.ts @@ -0,0 +1,143 @@ +/** + * 验证用户是否有权访问指定文档 + * + * 权限验证逻辑: + * 1. 检查文档是否属于指定的任务 + * 2. 检查用户是否是该任务的参与者(评审员或发起人) + * 3. 防止用户通过修改 URL 参数访问未授权的文档 + */ + +import { postgrestGet } from "../postgrest-client"; + +interface DocumentAccessCheckParams { + documentId: string | number; + taskId: string | number; + userId: number; + jwtToken?: string; +} + +interface DocumentAccessCheckResult { + hasAccess: boolean; + reason?: string; + userRole?: 'assigner' | 'assignee' | 'none'; +} + +/** + * 验证文档访问权限 + * @param params 验证参数 + * @returns 验证结果 + */ +export async function verifyDocumentAccess( + params: DocumentAccessCheckParams +): Promise { + const { documentId, taskId, userId, jwtToken } = params; + + try { + // 1. 检查文档是否属于该任务(通过 cross_task_document_mapping 表) + const documentMappingResponse = await postgrestGet('cross_task_document_mapping', { + select: 'task_id,document_id', + filter: { + task_id: `eq.${taskId}`, + document_id: `eq.${documentId}` + }, + token: jwtToken + }); + + if (documentMappingResponse.error) { + console.error('❌ [verifyDocumentAccess] 查询文档-任务映射失败:', documentMappingResponse.error); + return { + hasAccess: false, + reason: '查询文档-任务映射失败' + }; + } + + // 提取数据 + const mappingData = Array.isArray(documentMappingResponse.data) + ? documentMappingResponse.data + : []; + + // 文档不属于该任务 + if (mappingData.length === 0) { + console.warn(`⚠️ [verifyDocumentAccess] 文档 ${documentId} 不属于任务 ${taskId}`); + return { + hasAccess: false, + reason: '文档不属于该任务' + }; + } + + // 2. 检查用户是否是该任务的参与者 + const taskResponse = await postgrestGet('cross_examination_tasks', { + select: 'assigner_id,assignee_ids', + filter: { + id: `eq.${taskId}` + }, + token: jwtToken + }); + + if (taskResponse.error) { + console.error('❌ [verifyDocumentAccess] 查询任务信息失败:', taskResponse.error); + return { + hasAccess: false, + reason: '查询任务信息失败' + }; + } + + const taskData = Array.isArray(taskResponse.data) ? taskResponse.data[0] : null; + + if (!taskData) { + console.warn(`⚠️ [verifyDocumentAccess] 任务 ${taskId} 不存在`); + return { + hasAccess: false, + reason: '任务不存在' + }; + } + + // 3. 验证用户身份 + const isAssigner = taskData.assigner_id === userId; + const assigneeIds = Array.isArray(taskData.assignee_ids) ? taskData.assignee_ids : []; + const isAssignee = assigneeIds.includes(userId); + + if (isAssigner) { + console.log(`✅ [verifyDocumentAccess] 用户 ${userId} 是任务 ${taskId} 的发起人,允许访问文档 ${documentId}`); + return { + hasAccess: true, + userRole: 'assigner' + }; + } + + if (isAssignee) { + console.log(`✅ [verifyDocumentAccess] 用户 ${userId} 是任务 ${taskId} 的评审员,允许访问文档 ${documentId}`); + return { + hasAccess: true, + userRole: 'assignee' + }; + } + + // 用户既不是发起人也不是评审员 + console.warn(`⚠️ [verifyDocumentAccess] 用户 ${userId} 无权访问任务 ${taskId} 的文档 ${documentId}`); + return { + hasAccess: false, + reason: '您没有权限访问该文档', + userRole: 'none' + }; + + } catch (error) { + console.error('❌ [verifyDocumentAccess] 验证失败:', error); + return { + hasAccess: false, + reason: error instanceof Error ? error.message : '权限验证失败' + }; + } +} + +/** + * 快速验证文档访问权限(仅返回布尔值) + * @param params 验证参数 + * @returns 是否有权限访问 + */ +export async function canAccessDocument( + params: DocumentAccessCheckParams +): Promise { + const result = await verifyDocumentAccess(params); + return result.hasAccess; +} diff --git a/app/api/document-types/document-types.ts b/app/api/document-types/document-types.ts index e3d8c37..5717158 100644 --- a/app/api/document-types/document-types.ts +++ b/app/api/document-types/document-types.ts @@ -43,6 +43,7 @@ export interface DocumentTypeCreateDTO { name: string; description?: string; group_ids: number[]; // 改为 number[] 数组 + code: string | null; entry_module_id?: number | null; llm_extraction_template_id?: number | null; vlm_extraction_template_id?: number | null; @@ -236,6 +237,7 @@ export async function createDocumentType( name: documentType.name.trim(), description: documentType.description || '', entry_module_id: documentType.entry_module_id || null, + code: documentType.code || null, group_ids: documentType.group_ids, llm_extraction_template_id: documentType.llm_extraction_template_id || null, vlm_extraction_template_id: documentType.vlm_extraction_template_id || null, @@ -305,6 +307,7 @@ export async function updateDocumentType( name: documentType.name.trim(), description: documentType.description || '', entry_module_id: documentType.entry_module_id || null, + code: documentType.code || null, group_ids: documentType.group_ids, llm_extraction_template_id: documentType.llm_extraction_template_id || null, vlm_extraction_template_id: documentType.vlm_extraction_template_id || null, diff --git a/app/api/evaluation_points/reviews.ts b/app/api/evaluation_points/reviews.ts index 851cf89..2929201 100644 --- a/app/api/evaluation_points/reviews.ts +++ b/app/api/evaluation_points/reviews.ts @@ -1023,7 +1023,7 @@ export async function getReviewPoints_fromApi(fileId: string, request: Request) // 成功响应 if (response.data) { // console.log('✅ [getReviewPoints_fromApi] API调用成功,返回数据结构:', JSON.stringify({ - // // 评查点数量: response.data.data?.length || 0, + // 评查点数量: response.data.data?.length || 0, // // 统计信息: response.data.stats, // // 评查信息: response.data.reviewInfo, // 有文档数据: response.data.document, diff --git a/app/components/cross-checking/DocumentListModal.tsx b/app/components/cross-checking/DocumentListModal.tsx index 07425b6..2b62899 100644 --- a/app/components/cross-checking/DocumentListModal.tsx +++ b/app/components/cross-checking/DocumentListModal.tsx @@ -62,7 +62,7 @@ export function DocumentListModal({ try { // 更新文档状态,传递JWT const updatedFile = await updateDocumentAuditStatus(fileId, 2, frontendJWT); - console.log('更新后的文档状态:', updatedFile); + // console.log('更新后的文档状态:', updatedFile); } catch (error) { console.error('更新文件审核状态时出错:', error); toastService.error(`更新文件审核状态时出错:${error instanceof Error ? error.message : '未知错误'}`); diff --git a/app/components/cross-checking/ReviewPointsList.tsx b/app/components/cross-checking/ReviewPointsList.tsx index 8f0c453..4a8a571 100644 --- a/app/components/cross-checking/ReviewPointsList.tsx +++ b/app/components/cross-checking/ReviewPointsList.tsx @@ -182,7 +182,7 @@ interface ReviewPointsListProps { reviewPoints: ReviewPoint[]; statistics: Statistics; activeReviewPointResultId: string | null; - onReviewPointSelect: (id: string, page?: number, charPositions?: CharPosition[]) => void; + onReviewPointSelect: (id: string, page?: number, charPositions?: CharPosition[], value?: string) => void; onStatusChange?: (id: string, editAuditStatusId: string | number, status: string, message: string) => void; scoringProposals?: ScoringProposal[]; jwtToken?: string; // 添加JWT token参数 @@ -779,16 +779,17 @@ export function ReviewPointsList({ } // 打印最终请求体 // console.log('最终请求体:', data); + // console.log('jwtToken:', jwtToken); // 用 axios + application/json 提交 try { const response = await axios.post(`${API_BASE_URL.replace(/\/$/, '')}/admin/cross_review/proposals`, data, { headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${userInfo.frontend_jwt}`, + 'Authorization': `Bearer ${jwtToken}`, } }); const result = response.data; - if (response.status === 200) { + if (result.code === 200 || result.code === 0) { toastService.success('意见提交成功'); // 创建新的提案对象 @@ -814,11 +815,24 @@ export function ReviewPointsList({ handleCloseOpinionModal(); } else { - toastService.error(result.detail || '提交意见失败'); + throw new Error(result.msg || '提交意见失败') + // toastService.error(result.msg || '提交意见失败'); } } catch (error) { console.error('提交意见失败:', error); - toastService.error('提交意见失败,请稍后重试'); + + // 正确处理 axios 错误响应 + let errorMessage = '提交意见失败,请稍后重试'; + + if (axios.isAxiosError(error) && error.response?.data) { + // 从 axios 错误响应中提取 msg 字段 + errorMessage = error.response.data.msg || errorMessage; + } else if (error instanceof Error) { + // 处理普通 Error 对象 + errorMessage = error.message || errorMessage; + } + + toastService.error(errorMessage); } setIsSubmittingOpinion(false); }; @@ -1407,7 +1421,7 @@ export function ReviewPointsList({ for (const item of chain) { if (item.data.page && typeof onReviewPointSelect === 'function') { hasPage = true; - onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions); + onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions, item.data.value); break; } } @@ -1421,7 +1435,7 @@ export function ReviewPointsList({ // 遍历chain找到第一个有效的page for (const item of chain) { if (item.data.page && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions); + onReviewPointSelect(reviewPoint.id, Number(item.data.page), item.data.char_positions, item.data.value); break; } } @@ -1461,11 +1475,11 @@ export function ReviewPointsList({ // 假设onReviewPointSelect在作用域内可用 const reviewPointId = reviewPoint.id as string; if (reviewPointId && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPointId, Number(item.data.page), item.data.char_positions); + onReviewPointSelect(reviewPointId, Number(item.data.page), item.data.char_positions, item.data.value); } } else if(reviewPoint.contentPage && reviewPoint.contentPage[item.field]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[item.field])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[item.field]), item.data.char_positions, item.data.value); } else{ toastService.error(`没有找到${item.field}对应的索引内容`); @@ -1544,11 +1558,11 @@ export function ReviewPointsList({ if (chain[0].data.page) { const reviewPointId = reviewPoint.id as string; if (reviewPointId && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPointId, chain[0].data.page, chain[0].data.char_positions); + onReviewPointSelect(reviewPointId, chain[0].data.page, chain[0].data.char_positions, chain[0].data.value); } } else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[0].field]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[chain[0].field])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[chain[0].field]), chain[0].data.char_positions, chain[0].data.value); } else{ toastService.error(`没有找到${chain[0].field}对应的索引内容`); @@ -1570,11 +1584,11 @@ export function ReviewPointsList({ if (chain[1].data.page) { const reviewPointId = reviewPoint.id as string; if (reviewPointId && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPointId, chain[1].data.page, chain[1].data.char_positions); + onReviewPointSelect(reviewPointId, chain[1].data.page, chain[1].data.char_positions, chain[1].data.value); } } else if(reviewPoint.contentPage && reviewPoint.contentPage[chain[1].field]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[chain[1].field])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[chain[1].field]), chain[1].data.char_positions, chain[1].data.value); } else{ toastService.error(`没有找到${chain[1].field}对应的索引内容`); @@ -1710,9 +1724,9 @@ export function ReviewPointsList({ onClick={(e) => { e.stopPropagation(); if (mainTypeValue.page && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions); + onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions, mainTypeValue.value); }else if(reviewPoint.contentPage && reviewPoint.contentPage[fieldKey]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey]), mainTypeValue.char_positions, mainTypeValue.value); }else{ toastService.error(`没有找到${fieldKey}对应的索引内容`); } @@ -1721,7 +1735,9 @@ export function ReviewPointsList({ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (mainTypeValue.page && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions); + onReviewPointSelect(reviewPoint.id, Number(mainTypeValue.page), mainTypeValue.char_positions, mainTypeValue.value); + }else if(reviewPoint.contentPage && reviewPoint.contentPage[fieldKey]){ + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[fieldKey]), mainTypeValue.char_positions, mainTypeValue.value); }else{ toastService.error(`没有找到${fieldKey}对应的索引内容`); } @@ -1832,9 +1848,9 @@ export function ReviewPointsList({ onClick={(e) => { e.stopPropagation(); if (value.page && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions); + onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions, value.value); }else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key]), value.char_positions, value.value); }else{ toastService.error(`没有找到${key}对应的索引内容`); } @@ -1844,9 +1860,9 @@ export function ReviewPointsList({ if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (value.page && typeof onReviewPointSelect === 'function') { - onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions); + onReviewPointSelect(reviewPoint.id, Number(value.page), value.char_positions, value.value); }else if(reviewPoint.contentPage && reviewPoint.contentPage[key]){ - onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key])); + onReviewPointSelect(reviewPoint.id, Number(reviewPoint.contentPage[key]), value.char_positions, value.value); }else{ toastService.error(`没有找到${key}对应的索引内容`); } @@ -2404,29 +2420,26 @@ export function ReviewPointsList({ <>
{/* 悬浮的意见数量显示 - 固定在左侧 */} - @@ -2656,14 +2669,16 @@ export function ReviewPointsList({
) : ( <> - +
+
( -
{record.evaluation_point_name}
+
{record.evaluation_point_name}
) }, { @@ -2671,7 +2686,7 @@ export function ReviewPointsList({ key: "problem_message", width: "18%", render: (_: unknown, record: CrossCheckingOpinion) => ( -
{record.problem_message}
+
{record.problem_message}
) }, { @@ -2682,14 +2697,14 @@ export function ReviewPointsList({ const reason = record.reason || ''; const display = reason.length > 20 ? reason.slice(0, 20) + '...' : reason; return ( - {display} +
{display}
); } }, { title: "调整分数", key: "proposed_score", - width: "5%", + width: "8%", align: "center" as const, render: (_: unknown, record: CrossCheckingOpinion) => ( = 0 ? 'text-green-600' : 'text-red-600'}`}> @@ -2700,8 +2715,7 @@ export function ReviewPointsList({ { title: "投票人", key: "votes", - width: "22%", - align: "center" as const, + width: "24%", render: (_: unknown, record: CrossCheckingOpinion) => { // 投票类型配置 const voterGroups = [ @@ -2728,19 +2742,20 @@ export function ReviewPointsList({ } ]; return ( -
+
{voterGroups.map((group) => ( Array.isArray(group.voters) && group.voters.length > 0 && ( -
+
{group.voters.map((name, idx) => ( {name} @@ -2757,9 +2772,10 @@ export function ReviewPointsList({ key: "proposer", width: "8%", render: (_: unknown, record: CrossCheckingOpinion) => ( -
+
{record.proposer} @@ -2769,15 +2785,16 @@ export function ReviewPointsList({ { title: "发起时间", key: "created_at", - width: "12%", + width: "8%", render: (_: unknown, record: CrossCheckingOpinion) => ( -
{record.created_at}
+
{record.created_at}
) }, { title: "投票状态", key: "opinion_status", width: "12%", + align: "center" as const, render: (_: unknown, record: CrossCheckingOpinion) => { let label = ''; let color = ''; @@ -2796,7 +2813,7 @@ export function ReviewPointsList({ color = 'text-yellow-600'; break; } - return {label}; + return {label}; } }, { @@ -2812,25 +2829,27 @@ export function ReviewPointsList({ } } ]} - dataSource={opinionListData} - rowKey="proposal_id" - emptyText="暂无意见数据" - className="opinion-list-table" - /> + dataSource={opinionListData} + rowKey="proposal_id" + emptyText="暂无意见数据" + className="opinion-list-table" + /> +
- {/* 分页组件 */} - {opinionListTotal > 0 && ( - 0 && ( + - )} + onChange={handleOpinionListPageChange} + onPageSizeChange={handleOpinionListPageSizeChange} + showTotal={true} + showPageSizeChanger={true} + pageSizeOptions={[5,10, 20, 30, 50]} + /> + )} +
)}
diff --git a/app/components/layout/Sidebar.tsx b/app/components/layout/Sidebar.tsx index 78d8004..b4b2617 100644 --- a/app/components/layout/Sidebar.tsx +++ b/app/components/layout/Sidebar.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { Link, useLocation, useNavigate } from '@remix-run/react'; import type { UserRole } from '~/root'; import { getUserRoutesByRole, mapUserRoleToRoleKey, type MenuItem } from '~/api/auth/user-routes'; +import { DOCUMENT_URL } from '~/config/api-config'; interface SidebarProps { onToggle: () => void; @@ -320,7 +321,7 @@ export function Sidebar({ onToggle, collapsed, userRole, frontendJWT = '' }: Sid
{selectedModulePicPath && ( {selectedModuleName} diff --git a/app/components/reviews/FilePreview.tsx b/app/components/reviews/FilePreview.tsx index cbd1ca3..1d931b4 100644 --- a/app/components/reviews/FilePreview.tsx +++ b/app/components/reviews/FilePreview.tsx @@ -211,6 +211,7 @@ export function FilePreview({ fileContent, activeReviewPointResultId, targetPage // console.log('[FilePreview] 渲染PDF预览', { real_path, targetPage, charPositions }); // console.log('[FilePreview] 渲染PDF预览', { fileContent }); const pageOffset = fileContent.ocrResult?.__meta?.page_offset || fileContent.ocr_result?.__meta?.page_offset || 0; + // console.log('pageOffset', pageOffset) return ( > = { // 主要 // 梅州 '51703': { - baseUrl: 'http://172.16.0.55:8073', - documentUrl: 'http://172.16.0.55:8073/docauditai/', - uploadUrl: 'http://172.16.0.55:8073/admin/documents', + // baseUrl: 'http://172.16.0.55:8073', + // documentUrl: 'http://172.16.0.55:8073/docauditai/', + // uploadUrl: 'http://172.16.0.55:8073/admin/documents', + // collaboraUrl: 'http://172.16.0.81:9980', + // appUrl: 'http://172.16.0.34:51703', - collaboraUrl: 'http://172.16.0.81:9980', - appUrl: 'http://172.16.0.34:51703', + baseUrl: 'http://10.79.97.17:8000', + documentUrl: 'http://10.79.97.17:8000/docauditai/', + uploadUrl: 'http://10.79.97.17:8000/admin/documents', + collaboraUrl: 'http://10.79.97.17:9980', + appUrl: 'http://10.79.97.17:51703', oauth: { redirectUri: 'http://10.79.97.17:51703/callback' @@ -119,7 +124,8 @@ const configs: Record = { uploadUrl: 'http://172.16.0.55:8073/admin/documents', collaboraUrl: 'http://172.16.0.81:9980', - appUrl: 'http://172.16.0.34:51703', + // appUrl: 'http://172.16.0.34:51709', + appUrl: 'http://172.16.0.34:5173', oauth: { serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index ec29f1b..e618665 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -210,6 +210,7 @@ export default function Index() { } else if (module.name === '智慧法务大模型') { // 智慧法务大模型 → 跳转到 AI 对话 targetPath = '/chat-with-llm/chat'; + sessionStorage.setItem('selectedModulePicPath', '/images/icon_assistant.png') // console.log('📌 [Index] 智慧法务大模型,跳转到:', targetPath); } else { // console.log('📌 [Index] 其他模块,跳转到:', targetPath); @@ -291,10 +292,12 @@ export default function Index() { if (typeof window !== 'undefined') { // 🔑 设置标志:表示用户通过交叉评查入口进入 sessionStorage.setItem('crossCheckingMode', 'true'); + sessionStorage.setItem('selectedModuleName', '交叉评查') + sessionStorage.setItem('selectedModulePicPath', '/images/icon_cross@2x.png') // 清除模块相关的标志(因为不是从入口模块进入) sessionStorage.removeItem('selectedModuleId'); - sessionStorage.removeItem('selectedModuleName'); - sessionStorage.removeItem('selectedModulePicPath'); + // sessionStorage.removeItem('selectedModuleName'); + // sessionStorage.removeItem('selectedModulePicPath'); // 清除系统设置模式标志 sessionStorage.removeItem('settingsMode'); } diff --git a/app/routes/cross-checking._index.tsx b/app/routes/cross-checking._index.tsx index b641f49..7cbc911 100644 --- a/app/routes/cross-checking._index.tsx +++ b/app/routes/cross-checking._index.tsx @@ -206,9 +206,9 @@ const statusConfig = { }; // 任务类型标签配置 -const taskTypeConfig = { - [CrossCheckingTaskType.CITY]: { label: '市级交叉评查', color: 'green' as const }, - [CrossCheckingTaskType.COUNTY]: { label: '下级交叉评查', color: 'orange' as const } +const taskTypeConfig: Record = { + [CrossCheckingTaskType.CITY]: { label: '市局间交叉评查', color: 'green' as const }, + [CrossCheckingTaskType.DISTRICT]: { label: '区局间交叉评查', color: 'orange' as const } }; // 案卷类型标签配置 @@ -248,6 +248,11 @@ export default function CrossCheckingIndex() { total: 0 }); + // 客户端调式日志 + // useEffect(()=>{ + // console.log('[CrossCheckingIndex] loaderData.tasks', loaderData.tasks) + // },[loaderData]) + // 获取进度条样式类 const getProgressClass = (progress: number) => { if (progress === 0) return 'low'; @@ -603,7 +608,7 @@ export default function CrossCheckingIndex() { align: "center" as const, width: "10%", render: (_: unknown, record: CrossCheckingTask) => { - const config = taskTypeConfig[record.taskType]; + const config = taskTypeConfig[record.taskType] || { label: record.taskType, color: 'gray' as const }; return ( {config.label} @@ -613,10 +618,36 @@ export default function CrossCheckingIndex() { }, { title: "评查地区", - dataIndex: "evaluationRegion" as keyof CrossCheckingTask, key: "evaluationRegion", align: "left" as const, - width: "16%" + width: "16%", + render: (_: unknown, record: CrossCheckingTask) => { + const regions = record.evaluationRegion; + + // 如果不是数组,直接显示字符串 + if (!Array.isArray(regions)) { + return {regions || '-'}; + } + + // 如果是空数组 + if (regions.length === 0) { + return -; + } + + // 渲染为标签列表 + return ( +
+ {regions.map((region, index) => ( + + {region} + + ))} +
+ ); + } }, { title: "评查进度", diff --git a/app/routes/cross-checking.result.tsx b/app/routes/cross-checking.result.tsx index f086e00..1c55b17 100644 --- a/app/routes/cross-checking.result.tsx +++ b/app/routes/cross-checking.result.tsx @@ -26,7 +26,7 @@ import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } f import React, { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { useNavigate, useLoaderData } from "@remix-run/react"; import crossCheckingStyles from "~/styles/cross-checking-result.css?url"; -import { getReviewPoints, updateReviewResult} from "~/api/evaluation_points/reviews"; +import { getReviewPoints, updateReviewResult, getReviewPoints_fromApi} from "~/api/evaluation_points/reviews"; import { toastService } from "~/components/ui/Toast"; import { confirmReviewResults, checkProposalVotes, findIsProposer } from "~/api/cross-checking/cross-file-result"; @@ -199,8 +199,37 @@ export async function loader({ request }: LoaderFunctionArgs) { const { getUserSession } = await import("~/api/login/auth.server"); const { userInfo, frontendJWT } = await getUserSession(request); - // 获取评查点数据,传递request对象 - const reviewData = await getReviewPoints(id, request); + // 🔒 安全验证:检查用户是否有权限访问该文档 + if (!userInfo?.user_id) { + return Response.json({ + result: false, + message: '用户身份验证失败,请重新登录' + }, { status: 401 }); + } + + // const { verifyDocumentAccess } = await import("~/api/cross-checking/verify-document-access"); + // const accessCheck = await verifyDocumentAccess({ + // documentId: id, + // taskId: taskId, + // userId: userInfo.user_id, + // jwtToken: frontendJWT + // }); + + // if (!accessCheck.hasAccess) { + // console.warn(`⚠️ [Loader] 用户 ${userInfo.user_id} 尝试访问未授权文档 ${id},原因: ${accessCheck.reason}`); + // return Response.json({ + // result: false, + // message: accessCheck.reason || '您没有权限访问该文档' + // }, { status: 403 }); + // } + + // console.log(`✅ [Loader] 用户 ${userInfo.user_id} (${accessCheck.userRole}) 访问文档 ${id} - 权限验证通过`); + + // 对接接口,新的获取评查点结果的方法 + const reviewData = await getReviewPoints_fromApi(id, request) + + // 获取评查点数据,传递request对象 旧获取评查点结果的方法 + // const reviewData = await getReviewPoints(id, request); // 获取当前登录用户是否是发起人 const isProposer = await findIsProposer(taskId, userInfo?.user_id, frontendJWT); @@ -313,12 +342,9 @@ export default function CrossCheckingResult() { const isProcessingRef = useRef(false); // 添加组件挂载/卸载日志 - // useEffect(() => { - // console.log('[组件] CrossCheckingResult 挂载'); - // return () => { - // console.log('[组件] CrossCheckingResult 卸载'); - // }; - // }, []); + useEffect(() => { + console.log('[组件] CrossCheckingResult', isProposer); + }, [isProposer]); // 同步外部scoring_proposals到本地状态 useEffect(() => { @@ -536,18 +562,18 @@ export default function CrossCheckingResult() { // 使用ref防止重复点击,避免触发状态更新 if (isProcessingRef.current) { - console.log('[完成评查] 正在处理中,跳过'); + // console.log('[完成评查] 正在处理中,跳过'); return; } try { - console.log('[完成评查] 标记为处理中'); + // console.log('[完成评查] 标记为处理中'); isProcessingRef.current = true; // 1. 先检查未投票(不触发loading状态更新,避免重新渲染) - console.log('[完成评查] 开始检查未投票提案'); + // console.log('[完成评查] 开始检查未投票提案'); const checkRes = await checkProposalVotes(document.id, jwtToken); - console.log("[完成评查] 检查结果:", checkRes); + // console.log("[完成评查] 检查结果:", checkRes); if (checkRes.error) { toastService.error(checkRes.error); @@ -582,11 +608,11 @@ export default function CrossCheckingResult() { } // 4. 重置处理状态标记,准备显示模态框(不触发状态更新) - console.log('[完成评查] 重置处理标记,准备显示模态框'); + // console.log('[完成评查] 重置处理标记,准备显示模态框'); isProcessingRef.current = false; // 5. 弹出模态框 - console.log('[完成评查] 显示确认模态框'); + // console.log('[完成评查] 显示确认模态框'); messageService.show({ title: '提示', message: modalMessage, diff --git a/app/routes/cross-checking.upload.tsx b/app/routes/cross-checking.upload.tsx index c3bf32e..5aadb7c 100644 --- a/app/routes/cross-checking.upload.tsx +++ b/app/routes/cross-checking.upload.tsx @@ -14,7 +14,8 @@ import { type CrossCheckingUploadedFile, generateFileId, formatFileSize, - batchUploadAndAssignCrossCheckingFiles + batchUploadAndAssignCrossCheckingFiles, + createCrossReviewTask } from "~/api/cross-checking/cross-files-upload"; import { getCrossCheckingDocumentTypes, @@ -147,48 +148,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => { }); }; -/** - * 创建交叉评查任务 - * @param taskData 任务数据 - * @param token JWT Token - * @returns 创建结果 - */ -export async function createCrossReviewTask(taskData: { - documentIds: number[]; - userIds: number[]; - assignerId: number; - taskName: string; - docType: string; -}, token: string) { - try { - const response = await fetch(`${API_BASE_URL}/admin/cross_review/tasks/assign`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` - }, - body: JSON.stringify({ - document_ids: taskData.documentIds, - user_ids: taskData.userIds, - assigner_id: taskData.assignerId, - task_name: taskData.taskName, - doc_type: taskData.docType - }) - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - console.log('任务创建成功:', result); - return result; - } catch (error) { - console.error('创建任务失败:', error); - throw error; - } -} - export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const caseType = formData.get("caseType") as string; @@ -217,7 +176,7 @@ export default function CrossCheckingUpload() { const [taskInfo, setTaskInfo] = useState({ name: '', date: '', - type: '市局间交叉评查', + type: 'CITY', // 使用枚举值,默认为市局间交叉评查 }); // 步骤2状态 const [groupChecked, setGroupChecked] = useState(userInfo?.user_id ? [`user_${userInfo.user_id}`] : []); @@ -441,6 +400,7 @@ export default function CrossCheckingUpload() { try { // 获取选中的文档类型信息 const selectedDocType = documentTypes?.find((dt: DocumentType) => dt.id === selectedDocTypeId); + if (!selectedDocType) { toastService.error("无效的案卷类型"); return; @@ -460,6 +420,23 @@ export default function CrossCheckingUpload() { return; } + // const requireParam = { + // filesToUpload: filesToUpload, + // selectedDocTypeId: selectedDocTypeId, + // priority: priority, + // documentNumber: documentNumber, + // remark: remark, + // isTestDocument: isTestDocument, + // userIds: userIds, + // taskInfo_name: taskInfo.name, + // selectedDocType_name: selectedDocType.code, + // taskInfo_type: taskInfo.type, + // frontendJWT + // } + + // console.log("requireParam", requireParam) + // return; + // 使用文档类型名称作为 doc_type const uploadResult = await batchUploadAndAssignCrossCheckingFiles( filesToUpload, @@ -470,10 +447,14 @@ export default function CrossCheckingUpload() { isTestDocument, userIds, taskInfo.name, - selectedDocType.name, // 使用文档类型名称 + selectedDocType.code, // 使用文档类型code + taskInfo.type, // 使用任务类型(市局间交叉评查 或 区局间交叉评查) frontendJWT ); + + // return; + const { successes, failures } = uploadResult; if (failures.length > 0) { @@ -710,8 +691,8 @@ export default function CrossCheckingUpload() { value={taskInfo.type} onChange={e => setTaskInfo({ ...taskInfo, type: e.target.value })} > - - + +
diff --git a/app/routes/document-types._index.tsx b/app/routes/document-types._index.tsx index 37bf8f3..895edbf 100644 --- a/app/routes/document-types._index.tsx +++ b/app/routes/document-types._index.tsx @@ -324,7 +324,7 @@ export default function DocumentTypesList() { render: (_: unknown, record: DocumentTypeUI) => (
{record.entry_module ? ( - {record.entry_module.name} + {record.entry_module.name} ) : ( 暂无关联入口 )} diff --git a/app/routes/document-types.new.tsx b/app/routes/document-types.new.tsx index 89776f9..8b5e777 100644 --- a/app/routes/document-types.new.tsx +++ b/app/routes/document-types.new.tsx @@ -134,6 +134,7 @@ export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const id = formData.get("id") as string | null; const name = formData.get("name") as string; + const code = formData.get("code") as string; const description = formData.get("description") as string; const entryModuleId = formData.get("entry_module_id") as string; const llmExtractionTemplateId = formData.get("llm_extraction_template") as string; @@ -171,6 +172,7 @@ export async function action({ request }: ActionFunctionArgs) { // 构建文档类型数据 - group_ids 转换为 number[] const documentTypeData = { name, + code: code || null, description, group_ids: selectedGroups.map(id => parseInt(id, 10)), entry_module_id: entryModuleId ? parseInt(entryModuleId) : null, @@ -239,6 +241,7 @@ export default function DocumentTypeNew() { const [formData, setFormData] = useState({ id: documentType?.id || "", name: documentType?.name || "", + code: documentType?.code || "", description: documentType?.description || "", entryModuleId: documentType?.entry_module?.id?.toString() || "", llmExtractionTemplateId: documentType?.llm_extraction_template_id?.toString() || "", @@ -287,9 +290,11 @@ export default function DocumentTypeNew() { // 当文档类型数据加载完成时更新表单 useEffect(() => { if (documentType) { + console.log('documentType', documentType) setFormData({ id: documentType.id, name: documentType.name, + code: documentType.code || "", description: documentType.description, entryModuleId: documentType.entry_module?.id?.toString() || "", llmExtractionTemplateId: documentType.llm_extraction_template_id?.toString() || "", @@ -592,6 +597,24 @@ export default function DocumentTypeNew() {
例如:销售合同、采购合同、专卖许可证等
+ {/* 文档类型编码 */} +
+ + +
用于系统内部识别的唯一编码(可选)
+
+ {/* 入口模块 */}
diff --git a/app/styles/cross-checking-result.css b/app/styles/cross-checking-result.css index 78c62c7..e373971 100644 --- a/app/styles/cross-checking-result.css +++ b/app/styles/cross-checking-result.css @@ -336,3 +336,8 @@ width: 100%; } } + +/* 意见列表表格样式 */ +.opinion-list-table { + width: 100%; +} diff --git a/app/styles/pages/document-types_index.css b/app/styles/pages/document-types_index.css index 7c7832e..0bd1f35 100644 --- a/app/styles/pages/document-types_index.css +++ b/app/styles/pages/document-types_index.css @@ -14,6 +14,10 @@ @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-primary-50 text-primary-600 mr-1 mb-1; } +.document-types-page .entry-module-badge { + @apply inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-blue-50 text-blue-600 mr-1 mb-1; +} + .document-types-page .groups-container { @apply flex flex-wrap gap-1 max-w-md; } diff --git a/vite.config.ts b/vite.config.ts index 579eb6f..75d6bfe 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -53,7 +53,7 @@ export default defineConfig({ server: { host: '0.0.0.0', // port: 5173, - port: Number(process.env.PORT) || 51709, + port: Number(process.env.PORT) || 5173, open: true, // open: false, allowedHosts: ['nas.7bm.co', 'localhost', '127.0.0.1'], // 允许的主机名列表1