import React, { useState, useEffect } from 'react'; import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs } from "@remix-run/node"; import { useLoaderData, useSearchParams, useNavigate, useFetcher } from "@remix-run/react"; import { Button } from '~/components/ui/Button'; import { Card } from '~/components/ui/Card'; import { Tag } from '~/components/ui/Tag'; import { DocumentListModal } from '~/components/cross-checking'; import { usePermission } from '~/hooks/usePermission'; import crossCheckingStyles from "~/styles/pages/cross-checking_index.css?url"; import { Table } from '~/components/ui/Table'; import { FilterPanel, FilterSelect, SearchFilter, DateRangeFilter } from '~/components/ui/FilterPanel'; import { Pagination } from '~/components/ui/Pagination'; import { toastService } from '~/components/ui/Toast'; import { getCrossCheckingTasks, getCrossCheckingStats, deleteCrossCheckingTask, getCrossCheckingTaskDetail, getCrossCheckingDocumentTypes, getTaskDocumentsWithVersions, type CrossCheckingTask, type TaskDocument, type CrossReviewDocumentWithVersion, type CrossReviewDocumentListResponse, type TaskListParams, type DocumentType, CrossCheckingTaskStatus, CrossCheckingTaskType, CrossCheckingDocType } from '~/api/cross-checking/cross-files'; import { findIsProposer } from '~/api/cross-checking/cross-file-result'; export const links = () => [ { rel: "stylesheet", href: crossCheckingStyles } ]; export const meta: MetaFunction = () => { return [ { title: "中国烟草AI合同及卷宗审核系统 - 交叉评查" }, { name: "cross-checking", content: "交叉评查任务管理,支持根据类型、状态和时间进行筛选" }, { name: "keywords", content: "交叉评查,任务管理,合同审核,中国烟草" } ]; }; // 声明loader返回的数据类型 export type LoaderData = { tasks: CrossCheckingTask[]; totalCount: number; currentPage: number; pageSize: number; totalPages: number; stats: { totalTasks: number; pendingTasks: number; inProgressTasks: number; completedTasks: number; }; initialLoad?: boolean; frontendJWT?: string; // 新增JWT documentTypes: DocumentType[]; // 新增:文档类型列表 documentTypesError?: string; // 新增:文档类型加载错误 }; export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); // 从 URL 参数中提取查询条件 const params: TaskListParams = { page: parseInt(url.searchParams.get("page") || "1", 10), pageSize: parseInt(url.searchParams.get("pageSize") || "10", 10), taskType: url.searchParams.get("taskType") || undefined, docType: url.searchParams.get("docType") || undefined, status: url.searchParams.get("status") || undefined, keyword: url.searchParams.get("keyword") || undefined, dateFrom: url.searchParams.get("dateFrom") || undefined, dateTo: url.searchParams.get("dateTo") || undefined }; try { // 获取用户会话信息 const { getUserSession } = await import("~/api/login/auth.server"); const { userInfo, frontendJWT } = await getUserSession(request); // console.log('frontendJWT', frontendJWT); // 获取任务列表、统计数据和文档类型,传递用户信息和JWT const [tasksResponse, statsResponse, documentTypesResponse] = await Promise.all([ getCrossCheckingTasks(params, userInfo, frontendJWT), getCrossCheckingStats(userInfo, frontendJWT), getCrossCheckingDocumentTypes(frontendJWT) ]); // console.log('tasksResponse', tasksResponse.data?.tasks); if (!tasksResponse.success) { console.error('获取任务列表失败:', tasksResponse.error); return Response.json({ tasks: [], totalCount: 0, currentPage: params.page, pageSize: params.pageSize, totalPages: 0, stats: { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 }, frontendJWT, documentTypes: [], error: tasksResponse.error || '获取任务列表失败' }, { status: 500 }); } if (!statsResponse.success) { console.error('获取统计数据失败:', statsResponse.error); } return Response.json({ tasks: tasksResponse.data?.tasks || [], totalCount: tasksResponse.data?.totalCount || 0, currentPage: tasksResponse.data?.currentPage || params.page, pageSize: tasksResponse.data?.pageSize || params.pageSize, totalPages: tasksResponse.data?.totalPages || 0, stats: statsResponse.data || { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 }, frontendJWT, // 新增:返回JWT给客户端 documentTypes: documentTypesResponse.success ? documentTypesResponse.data || [] : [], // 新增:返回文档类型列表 documentTypesError: documentTypesResponse.error // 新增:返回文档类型加载错误 }, { headers: { "Cache-Control": "max-age=60, s-maxage=180" } }); } catch (error) { console.error('加载交叉评查任务列表失败:', error); return Response.json({ tasks: [], totalCount: 0, currentPage: params.page, pageSize: params.pageSize, totalPages: 0, stats: { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 }, frontendJWT: undefined, documentTypes: [], error: error instanceof Error ? error.message : '加载任务列表失败' }, { status: 500 }); } } export async function action({ request }: ActionFunctionArgs) { const formData = await request.formData(); const _action = formData.get('_action'); const taskId = formData.get('taskId'); if (_action === 'delete' && taskId) { try { const deleteResponse = await deleteCrossCheckingTask(Number(taskId)); if (!deleteResponse.success) { return Response.json({ result: false, message: deleteResponse.error || '删除任务失败' }, { status: 500 }); } return Response.json({ result: true, message: "任务删除成功" }, { status: 200 }); } catch (error) { console.error('操作任务失败:', error); return Response.json({ result: false, message: error instanceof Error ? error.message : "操作失败" }, { status: 500 }); } } if (_action === 'getTaskDetail') { try { // 获取用户会话信息 const { getUserSession } = await import("~/api/login/auth.server"); const { frontendJWT } = await getUserSession(request); const page = parseInt(formData.get('page') as string || '1', 10); const pageSize = parseInt(formData.get('pageSize') as string || '10', 10); if (!taskId) { return Response.json({ success: false, error: "缺少必要参数" }, { status: 400 }); } const response = await getCrossCheckingTaskDetail( Number(taskId), page, pageSize, frontendJWT ); if (response.error) { return Response.json({ success: false, error: response.error }, { status: 500 }); } // console.log('用户任务详情返回:', response.data); return Response.json({ success: true, data: response.data }); } catch (error) { console.error('获取任务详情失败:', error); return Response.json({ success: false, error: error instanceof Error ? error.message : "获取任务详情失败" }, { status: 500 }); } } return Response.json({ result: false, message: "无效的操作" }, { status: 400 }); } // 状态标签配置 const statusConfig = { [CrossCheckingTaskStatus.PENDING]: { label: '未开始', color: 'yellow' as const }, [CrossCheckingTaskStatus.IN_PROGRESS]: { label: '进行中', color: 'blue' as const }, [CrossCheckingTaskStatus.COMPLETED]: { label: '已完成', color: 'green' as const } }; // 任务类型标签配置 const taskTypeConfig: Record = { [CrossCheckingTaskType.CITY]: { label: '市局间交叉评查', color: 'green' as const }, [CrossCheckingTaskType.DISTRICT]: { label: '区局间交叉评查', color: 'orange' as const } }; // 案卷类型标签配置 const docTypeConfig = { [CrossCheckingDocType.PENALTY]: { label: '行政处罚', color: 'blue' as const }, [CrossCheckingDocType.PERMIT]: { label: '行政许可', color: 'purple' as const } }; export default function CrossCheckingIndex() { const loaderData = useLoaderData(); const { tasks = [], totalCount = 0, currentPage = 1, pageSize = 10, stats = { totalTasks: 0, pendingTasks: 0, inProgressTasks: 0, completedTasks: 0 }, frontendJWT, documentTypes = [], documentTypesError } = loaderData || {}; const [searchParams, setSearchParams] = useSearchParams(); const dateFrom = searchParams.get('dateFrom') || ''; const dateTo = searchParams.get('dateTo') || ''; const navigate = useNavigate(); const fetcher = useFetcher(); // 权限控制 const { hasPermission } = usePermission(); const canCreateTask = hasPermission('cross_review:task:create'); const canViewTask = hasPermission('cross_review:task:read'); // 状态管理 const [isDeleting, setIsDeleting] = useState(false); const [hasAutoOpened, setHasAutoOpened] = useState(false); // 标记是否已自动打开模态框 const [isProposer, setIsProposer] = useState(false); // 是否是负责人 const [isProposerLoading, setIsProposerLoading] = useState(false); // 负责人状态加载中 const [modalState, setModalState] = useState<{ isOpen: boolean; title: string; documents: CrossReviewDocumentWithVersion[]; loading: boolean; // 分页相关状态 currentPage: number; pageSize: number; total: number; // 搜索关键词 keyword: string; }>({ isOpen: false, title: '', documents: [], loading: false, currentPage: 1, pageSize: 10, total: 0, keyword: '' }); // 客户端调式日志 // useEffect(()=>{ // console.log('[CrossCheckingIndex] loaderData.tasks', loaderData.tasks) // },[loaderData]) // 获取进度条样式类 const getProgressClass = (progress: number) => { if (progress === 0) return 'low'; if (progress < 70) return 'medium'; return 'high'; }; // 处理查看结果 - 打开文档列表模态框 const handleViewResult = async (taskId: number, taskName: string) => { // 存储任务信息用于分页 setCurrentTaskInfo({ taskId, taskName }); // 重置负责人状态 setIsProposer(false); setIsProposerLoading(true); // 打开模态框,同时设置标题 setModalState(prev => ({ ...prev, isOpen: true, title: `${taskName} - 文档列表`, currentPage: 1, pageSize: 10 })); // 并行加载:文档列表和负责人状态 const [, isProposerResult] = await Promise.all([ loadModalData(taskId, 1, 10, undefined, taskName), findIsProposer(taskId, undefined, frontendJWT) ]); // 设置负责人状态 setIsProposer(isProposerResult); setIsProposerLoading(false); }; // 关闭模态框 const handleCloseModal = () => { setModalState({ isOpen: false, title: '', documents: [], loading: false, currentPage: 1, pageSize: 10, total: 0, keyword: '' }); setCurrentTaskInfo(null); // 重置负责人状态 setIsProposer(false); setIsProposerLoading(false); }; // 处理文档查看 - 导航到评查详情页 const handleViewFile = (fileId: string) => { const params = new URLSearchParams({ id: fileId, tId: currentTaskInfo?.taskId?.toString() || '', tName: currentTaskInfo?.taskName || '', previousRoute: 'crossChecking' }); navigate(`/cross-checking/result?${params.toString()}`); }; // 存储当前任务信息用于分页 const [currentTaskInfo, setCurrentTaskInfo] = useState<{ taskId: number; taskName: string; } | null>(null); // 加载分页数据(使用新版接口,支持版本归纳) const loadModalData = async (taskId: number, page: number = 1, pageSize: number = 10, keyword?: string, taskName?: string) => { try { setModalState(prev => ({ ...prev, loading: true, currentPage: page, pageSize: pageSize })); // 直接调用新版 API(支持版本归纳) const response = await getTaskDocumentsWithVersions({ taskId, page, pageSize, keyword, jwtToken: frontendJWT }); if (!response.success || !response.data) { throw new Error(response.error || '获取任务文档列表失败'); } const { documents, total, page: returnedPage, page_size: returnedPageSize, total_pages } = response.data; // 使用传入的 taskName 或 currentTaskInfo 中的 taskName const displayTaskName = taskName || currentTaskInfo?.taskName || ''; setModalState(prev => ({ ...prev, loading: false, title: displayTaskName ? `${displayTaskName} - 文档列表` : prev.title, documents: documents || [], total: total || 0, currentPage: returnedPage || page, pageSize: returnedPageSize || pageSize })); } catch (error) { console.error('获取任务文档列表失败:', error); toastService.error(`获取任务文档列表失败: ${error instanceof Error ? error.message : '未知错误'}`); setModalState(prev => ({ ...prev, loading: false })); } }; // 处理模态框分页变化 const handleModalPageChange = (page: number) => { if (currentTaskInfo) { loadModalData(currentTaskInfo.taskId, page, modalState.pageSize, modalState.keyword); } }; // 处理模态框每页大小变化 const handleModalPageSizeChange = (size: number) => { if (currentTaskInfo) { loadModalData(currentTaskInfo.taskId, 1, size, modalState.keyword); } }; // 处理模态框搜索 const handleModalSearch = (keyword: string) => { if (currentTaskInfo) { setModalState(prev => ({ ...prev, keyword })); loadModalData(currentTaskInfo.taskId, 1, modalState.pageSize, keyword); } }; // 渲染进度条 const renderProgress = (progress: number) => (
{progress}%
); // 渲染操作按钮 const renderOperation = (task: CrossCheckingTask) => { // 无权限时不显示操作按钮 if (!canViewTask) { return -; } switch (task.status) { case CrossCheckingTaskStatus.PENDING: return ( ); case CrossCheckingTaskStatus.IN_PROGRESS: return ( ); case CrossCheckingTaskStatus.COMPLETED: return ( ); default: return -; } }; // 处理筛选变化 const handleFilterChange = (e: React.ChangeEvent) => { const { name, value } = e.target; const newParams = new URLSearchParams(searchParams); if (value) { newParams.set(name, value); } else { newParams.delete(name); } // 切换筛选条件时,重置到第一页 newParams.set('page', '1'); setSearchParams(newParams); }; // 处理搜索 const handleSearch = (keyword: string) => { const newParams = new URLSearchParams(searchParams); if (keyword) { newParams.set('keyword', keyword); } else { newParams.delete('keyword'); } newParams.set('page', '1'); setSearchParams(newParams); }; // 处理分页变化 const handlePageChange = (page: number) => { const newParams = new URLSearchParams(searchParams); newParams.set('page', page.toString()); setSearchParams(newParams); }; // 处理每页条数变化 const handlePageSizeChange = (size: number) => { const newParams = new URLSearchParams(searchParams); newParams.set('pageSize', size.toString()); newParams.set('page', '1'); setSearchParams(newParams); }; // 处理重置筛选 const handleReset = () => { const input = document.querySelector('input[placeholder="输入任务名称或评查地区"]'); if (input) { (input as HTMLInputElement).value = ''; } const dateFromInput = document.querySelector('input[name="dateFrom"]'); const dateToInput = document.querySelector('input[name="dateTo"]'); if(dateFromInput) { (dateFromInput as HTMLInputElement).value = ''; } if(dateToInput) { (dateToInput as HTMLInputElement).value = ''; } setSearchParams(new URLSearchParams()); }; // 处理时间范围变更 const handleDateChange = (field: 'dateFrom' | 'dateTo', value: string) => { const newParams = new URLSearchParams(searchParams); if(value) { newParams.set(field, value); } else { newParams.delete(field); } newParams.set('page', '1'); setSearchParams(newParams); }; // 检测URL参数,自动打开模态框 useEffect(() => { const openModal = searchParams.get('openModal'); const taskId = searchParams.get('taskId'); const taskName = searchParams.get('taskName'); if (openModal === 'true' && taskId && !hasAutoOpened) { console.log('[自动打开模态框] taskId:', taskId, 'taskName:', taskName); // 标记已自动打开,防止重复触发 setHasAutoOpened(true); // 清除URL参数,避免刷新页面时再次打开 const newParams = new URLSearchParams(searchParams); newParams.delete('openModal'); newParams.delete('taskId'); newParams.delete('taskName'); setSearchParams(newParams, { replace: true }); // 延迟自动打开模态框,确保状态已更新 setTimeout(() => { handleViewResult(Number(taskId), taskName || '任务详情'); }, 100); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParams, hasAutoOpened]); // 监听fetcher状态变化 - 删除操作 useEffect(() => { if (fetcher.data && fetcher.state === 'idle' && isDeleting) { setIsDeleting(false); const data = fetcher.data as { result?: boolean; message?: string }; if (data.result) { toastService.success(data.message || '操作成功'); // 删除成功后刷新页面 window.location.reload(); } else { toastService.error(data.message || '操作失败'); } } }, [fetcher.data, fetcher.state, isDeleting]); // 定义表格列配置 const columns = [ { title: "序号", dataIndex: "sequence" as keyof CrossCheckingTask, key: "sequence", align: "center" as const, width: "2%" }, { title: "任务名称", dataIndex: "taskName" as keyof CrossCheckingTask, key: "taskName", align: "left" as const, width: "16%" }, { title: "评查开始时间", dataIndex: "startDate" as keyof CrossCheckingTask, key: "startDate", align: "center" as const, width: "10%" }, { title: "案卷类型", key: "docType", align: "center" as const, width: "8%", render: (_: unknown, record: CrossCheckingTask) => { const config = docTypeConfig[record.docType as keyof typeof docTypeConfig] || { label: record.docType, color: 'gray' as const }; return ( {config.label} ); } }, { title: "任务类型", key: "taskType", align: "center" as const, width: "10%", render: (_: unknown, record: CrossCheckingTask) => { const config = taskTypeConfig[record.taskType] || { label: record.taskType, color: 'gray' as const }; return ( {config.label} ); } }, { title: "评查地区", key: "evaluationRegion", align: "left" as const, 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: "评查进度", key: "progress", align: "left" as const, width: "12%", render: (_: unknown, record: CrossCheckingTask) => renderProgress(record.progress) }, { title: "评查状态", key: "status", align: "center" as const, width: "auto", render: (_: unknown, record: CrossCheckingTask) => { const config = statusConfig[record.status as keyof typeof statusConfig] || { label: record.status, color: 'gray' as const }; return ( {config.label} ); } }, // { // title: "评查分数", // key: "score", // align: "center" as const, // width: "5%", // render: (_: unknown, record: CrossCheckingTask) => // record.status === CrossCheckingTaskStatus.COMPLETED ? record.score : '-' // }, { title: "操作", key: "operation", align: "center" as const, width: "auto", render: (_: unknown, record: CrossCheckingTask) => renderOperation(record) } ]; return (
{/* 页面头部 */}

评查任务

总任务数: {stats.totalTasks}
待开始: {stats.pendingTasks}
进行中: {stats.inProgressTasks}
已完成: {stats.completedTasks}
{canCreateTask && ( )}
{/* 筛选区域 */} } > 0 ? documentTypes.map((docType: DocumentType) => ({ value: docType.code, label: docType.name })) : [] } onChange={handleFilterChange} className="mr-4 w-[15%]" /> handleDateChange('dateFrom', value)} onEndDateChange={(value) => handleDateChange('dateTo', value)} simple={true} colorMode="light" /> {/* 任务列表 */}
{/* 分页 */} {totalCount > 0 && ( )} {/* 文档列表模态框 */} ); } // 错误边界 export function ErrorBoundary() { return (

出错了

加载交叉评查任务列表时发生错误。请稍后再试,或联系管理员。

); }