import { json, type MetaFunction, type LoaderFunctionArgs } from "@remix-run/node"; import { useLoaderData, useSearchParams } from "@remix-run/react"; import { Button } from "~/components/ui/Button"; import { Card } from "~/components/ui/Card"; import { FileIcon } from "~/components/ui/FileIcon"; import { FilterPanel, FilterSelect, SearchFilter } from "~/components/ui/FilterPanel"; import { Pagination } from "~/components/ui/Pagination"; import { Table } from "~/components/ui/Table"; import { FileTypeTag } from "~/components/ui/FileTypeTag"; import { StatusBadge } from "~/components/ui/StatusBadge"; import rulesFilesStyles from "~/styles/pages/rules-files.css?url"; export const links = () => [ { rel: "stylesheet", href: rulesFilesStyles } ]; export const handle = { breadcrumb: "评查文件列表" }; export const meta: MetaFunction = () => { return [ { title: "评查文件列表 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "管理系统中所有上传的评查文件,支持按文件类型、评查状态进行筛选" }, { name: "keywords", content: "评查文件,合同审核,中国烟草,文件管理" } ]; }; // 评查文件类型枚举 export enum FileType { CONTRACT = 'contract', LICENSE = 'license', PUNISHMENT = 'punishment', REPORT = 'report', OTHER = 'other' } // 评查状态枚举 export enum ReviewStatus { PASS = 'pass', WARNING = 'warning', FAIL = 'fail', PENDING = 'pending' } // 日期范围枚举 export enum DateRange { ALL = 'all', TODAY = 'today', WEEK = 'week', MONTH = 'month', CUSTOM = 'custom' } // 文件类型标签映射 export const FILE_TYPE_LABELS: Record = { [FileType.CONTRACT]: '合同文档', [FileType.LICENSE]: '专卖许可证', [FileType.PUNISHMENT]: '行政处罚', [FileType.REPORT]: '报表文档', [FileType.OTHER]: '其他文档' }; // 评查状态标签映射 export const REVIEW_STATUS_LABELS: Record = { [ReviewStatus.PASS]: '通过', [ReviewStatus.WARNING]: '警告', [ReviewStatus.FAIL]: '不通过', [ReviewStatus.PENDING]: '待人工确认' }; // 评查文件模型 interface ReviewFile { id: string; fileName: string; fileCode: string; // 文件编号 fileType: FileType; fileSize: number; uploadTime: string; reviewStatus: ReviewStatus; issueCount: number; issues: Array<{ severity: 'info' | 'warning' | 'error' | 'critical'; message: string; }>; createdBy: string; } interface LoaderData { files: ReviewFile[]; totalCount: number; currentPage: number; pageSize: number; totalPages: number; } export async function loader({ request }: LoaderFunctionArgs) { const url = new URL(request.url); const fileType = url.searchParams.get("fileType") || ""; const reviewStatus = url.searchParams.get("reviewStatus") || ""; const dateRange = url.searchParams.get("dateRange") || ""; const keyword = url.searchParams.get("keyword") || ""; const sortOrder = url.searchParams.get("sortOrder") || "upload_time_desc"; const currentPage = parseInt(url.searchParams.get("page") || "1", 10); const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10); try { // 模拟数据,实际项目中应从API获取 const mockFiles: ReviewFile[] = [ { id: "1", fileName: "烟草产品销售合同(2023版).pdf", fileCode: "XS-2023-1025-001", fileType: FileType.CONTRACT, fileSize: 1024 * 1024 * 2.5, // 2.5MB uploadTime: "2023-10-25 14:30:45", reviewStatus: ReviewStatus.WARNING, issueCount: 3, issues: [ { severity: "warning", message: "付款条件描述不明确" }, { severity: "warning", message: "违约责任条款缺失" }, { severity: "warning", message: "签章不完整" } ], createdBy: "张三" }, { id: "2", fileName: "2023年度烟草专卖零售许可证.pdf", fileCode: "LS-2023-0058", fileType: FileType.LICENSE, fileSize: 1024 * 1024 * 1.2, // 1.2MB uploadTime: "2023-10-24 10:15:22", reviewStatus: ReviewStatus.PASS, issueCount: 0, issues: [], createdBy: "李四" }, { id: "3", fileName: "XX公司违规处罚决定书.pdf", fileCode: "处罚[2023]42号", fileType: FileType.PUNISHMENT, fileSize: 1024 * 1024 * 3.1, // 3.1MB uploadTime: "2023-10-23 16:45:30", reviewStatus: ReviewStatus.FAIL, issueCount: 2, issues: [ { severity: "error", message: "处罚依据条款引用错误" }, { severity: "error", message: "处罚金额超出规定范围" } ], createdBy: "王五" }, { id: "4", fileName: "烟草设备采购协议.docx", fileCode: "CG-2023-0089", fileType: FileType.CONTRACT, fileSize: 1024 * 1024 * 0.8, // 0.8MB uploadTime: "2023-10-22 09:22:15", reviewStatus: ReviewStatus.PENDING, issueCount: 1, issues: [ { severity: "warning", message: "交付日期条款存在歧义,需人工确认" } ], createdBy: "赵六" }, { id: "5", fileName: "2023年度销售额报表.xlsx", fileCode: "BB-2023-Q3", fileType: FileType.REPORT, fileSize: 1024 * 1024 * 0.5, // 0.5MB uploadTime: "2023-10-20 14:05:38", reviewStatus: ReviewStatus.PASS, issueCount: 0, issues: [], createdBy: "钱七" } ]; // 过滤数据 let filteredFiles = [...mockFiles]; if (fileType) { filteredFiles = filteredFiles.filter(file => file.fileType === fileType); } if (reviewStatus) { filteredFiles = filteredFiles.filter(file => file.reviewStatus === reviewStatus); } if (dateRange) { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); switch (dateRange) { case DateRange.TODAY: filteredFiles = filteredFiles.filter(file => { const fileDate = new Date(file.uploadTime.split(' ')[0]); return fileDate >= today; }); break; case DateRange.WEEK: { const weekStart = new Date(today); weekStart.setDate(today.getDate() - today.getDay()); filteredFiles = filteredFiles.filter(file => { const fileDate = new Date(file.uploadTime.split(' ')[0]); return fileDate >= weekStart; }); break; } case DateRange.MONTH: { const monthStart = new Date(today.getFullYear(), today.getMonth(), 1); filteredFiles = filteredFiles.filter(file => { const fileDate = new Date(file.uploadTime.split(' ')[0]); return fileDate >= monthStart; }); break; } } } if (keyword) { const lowerKeyword = keyword.toLowerCase(); filteredFiles = filteredFiles.filter(file => file.fileName.toLowerCase().includes(lowerKeyword) || file.fileCode.toLowerCase().includes(lowerKeyword) ); } // 排序 switch (sortOrder) { case "upload_time_desc": filteredFiles.sort((a, b) => new Date(b.uploadTime).getTime() - new Date(a.uploadTime).getTime()); break; case "upload_time_asc": filteredFiles.sort((a, b) => new Date(a.uploadTime).getTime() - new Date(b.uploadTime).getTime()); break; case "issue_count_desc": filteredFiles.sort((a, b) => b.issueCount - a.issueCount); break; case "issue_count_asc": filteredFiles.sort((a, b) => a.issueCount - b.issueCount); break; } // 计算分页信息 const totalCount = filteredFiles.length; const totalPages = Math.ceil(totalCount / pageSize); // 分页截取 const startIndex = (currentPage - 1) * pageSize; const endIndex = startIndex + pageSize; const paginatedFiles = filteredFiles.slice(startIndex, endIndex); return json({ files: paginatedFiles, totalCount, currentPage, pageSize, totalPages }, { headers: { "Cache-Control": "max-age=60, s-maxage=180" } }); } catch (error) { console.error('加载评查文件列表失败:', error); throw new Response('加载评查文件列表失败', { status: 500 }); } } // 提取renderErrorBoundary函数作为命名导出 export function ErrorBoundary() { return (

出错了

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

); } // 在文件中定义一个与路由文件名匹配的命名函数组件 export default function RulesFiles() { const { files, totalCount, currentPage, pageSize } = useLoaderData(); const [searchParams, setSearchParams] = useSearchParams(); 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 renderIssues = (issues: ReviewFile['issues']) => { if (issues.length === 0) { return (
所有评查点均通过
); } return (
{issues.slice(0, 3).map((issue, index) => (
{issue.message}
))}
); }; // 文件类型选项 const fileTypeOptions = Object.keys(FILE_TYPE_LABELS).map(type => ({ value: type, label: FILE_TYPE_LABELS[type as FileType] })); // 评查状态选项 const reviewStatusOptions = Object.keys(REVIEW_STATUS_LABELS).map(status => ({ value: status, label: REVIEW_STATUS_LABELS[status as ReviewStatus] })); // 时间范围选项 const dateRangeOptions = [ { value: DateRange.TODAY, label: '今天' }, { value: DateRange.WEEK, label: '本周' }, { value: DateRange.MONTH, label: '本月' }, { value: DateRange.CUSTOM, label: '自定义时间段' } ]; // 定义表格列配置 const columns = [ { title: "文件名称", key: "fileName", width: "30%", render: (_: unknown, file: ReviewFile) => (
{file.fileName}
{file.fileType === FileType.CONTRACT && "合同编号:"} {file.fileType === FileType.LICENSE && "许可证号:"} {file.fileType === FileType.PUNISHMENT && "文号:"} {file.fileType === FileType.REPORT && "报表编号:"} {file.fileCode}
) }, { title: "文件类型", key: "fileType", width: "12%", render: (_: unknown, file: ReviewFile) => ( ) }, { title: "上传时间", key: "uploadTime", width: "12%", render: (_: unknown, file: ReviewFile) => (
{file.uploadTime.split(' ')[0]}
{file.uploadTime.split(' ')[1]}
) }, { title: "评查状态", key: "reviewStatus", width: "12%", render: (_: unknown, file: ReviewFile) => ( 0 ? `${REVIEW_STATUS_LABELS[file.reviewStatus]} (${file.issueCount})` : REVIEW_STATUS_LABELS[file.reviewStatus]} showIcon={true} /> ) }, { title: "问题摘要", key: "issues", width: "20%", render: (_: unknown, file: ReviewFile) => renderIssues(file.issues) }, { title: "操作", key: "operation", width: "14%", render: (_: unknown, file: ReviewFile) => ( <> {file.reviewStatus === ReviewStatus.PENDING ? ( ) : ( )} ) } ]; return (
{/* 页面头部 */}

评查文件列表

总文件数: {totalCount}
{/* 筛选区域 */} {/* 文件列表 */} {/* 分页组件 */} {totalCount > 0 && ( )} ); }