import { useState } from "react"; import { useSearchParams, Link, useLoaderData, useFetcher } from "@remix-run/react"; import { type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node"; import { Card } from "~/components/ui/Card"; import { Button } from "~/components/ui/Button"; import { Table } from "~/components/ui/Table"; import { Pagination } from "~/components/ui/Pagination"; import { StatusBadge } from "~/components/ui/StatusBadge"; import { FileTypeTag } from "~/components/ui/FileTypeTag"; import { FileTag } from "~/components/ui/FileTag"; import { FilterPanel, FilterSelect, SearchFilter, DateRangeFilter } from "~/components/ui/FilterPanel"; import documentsIndexStyles from "~/styles/pages/documents_index.css?url"; import { getDocuments, deleteDocument, type DocumentUI, getFileDownloadUrl } from "~/api/files/documents"; import { getDocumentTypes } from "~/api/document-types/document-types"; // 导入样式 export function links() { return [ { rel: "stylesheet", href: documentsIndexStyles } ]; } // 元数据 export const meta: MetaFunction = () => { return [ { title: "文档列表 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "查看和管理系统中的所有文档,包括合同、许可证和行政处罚决定书等" }, ]; }; // 数据加载器 export const loader = async ({ request }: LoaderFunctionArgs) => { // 获取URL查询参数 const url = new URL(request.url); const search = url.searchParams.get("search") || ""; const documentType = url.searchParams.get("documentType") || ""; const status = url.searchParams.get("status") || ""; const documentNumber = url.searchParams.get("documentNumber") || ""; const dateFrom = url.searchParams.get("dateFrom") || ""; const dateTo = url.searchParams.get("dateTo") || ""; const page = parseInt(url.searchParams.get("page") || "1", 10); const pageSize = parseInt(url.searchParams.get("pageSize") || "10", 10); // 构建搜索参数 const searchParams = { name: search || undefined, documentNumber: documentNumber || undefined, documentType: documentType || undefined, status: status || undefined, dateFrom: dateFrom || undefined, dateTo: dateTo || undefined, page, pageSize }; // 获取文档列表 const documentsResponse = await getDocuments(searchParams); if (documentsResponse.error) { throw new Error(documentsResponse.error); } // 获取文档类型列表,用于筛选条件 const typesResponse = await getDocumentTypes(); const documentTypes = typesResponse.data?.types || []; const documentTypeOptions = documentTypes.map(type => ({ value: type.id, label: type.name })); return Response.json({ documents: documentsResponse.data?.documents || [], total: documentsResponse.data?.total || 0, page, pageSize, documentTypeOptions }); }; // 处理表单提交和删除等操作 export const action = async ({ request }: ActionFunctionArgs) => { const formData = await request.formData(); const action = formData.get("_action"); if (action === "delete") { const id = formData.get("id") as string; const response = await deleteDocument(id); if (response.error) { return Response.json({ success: false, message: response.error }, { status: response.status || 500 }); } return Response.json({ success: true, message: "文档已成功删除" }); } if (action === "batchDelete") { const ids = formData.getAll("ids") as string[]; // 批量删除处理 const results = await Promise.all(ids.map(id => deleteDocument(id))); const failures = results.filter(r => r.error); if (failures.length > 0) { return Response.json({ success: false, message: `删除失败: ${failures.map(f => f.error).join(', ')}` }, { status: 400 }); } return Response.json({ success: true, message: `已成功删除${ids.length}个文档` }); } // 未知操作 return Response.json({ success: false, message: "未知操作" }, { status: 400 }); }; // 文档状态选项 const documentStatusOptions = [ { value: "waiting", label: "待审核" }, { value: "processing", label: "审核中" }, { value: "pass", label: "通过" }, { value: "warning", label: "警告" }, { value: "fail", label: "不通过" }, ]; // 格式化文件大小 const formatFileSize = (bytes: number) => { if (bytes === 0) return "0 Bytes"; const k = 1024; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i]; }; // 获取文档类型标签背景颜色 // 此函数已不再需要,改用 FileTypeTag 组件 // const getDocumentTypeTagColor = (type: string): string => { // const colorMap: Record = { // "sales-contract": "blue", // "purchase-contract": "green", // "license": "purple", // "punishment": "yellow", // "agreement": "orange", // "default": "gray" // }; // return colorMap[type] || colorMap.default; // }; export default function DocumentsIndex() { const [searchParams, setSearchParams] = useSearchParams(); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const loaderData = useLoaderData(); const fetcher = useFetcher(); // 从URL获取当前筛选条件 const search = searchParams.get("search") || ""; const documentType = searchParams.get("documentType") || ""; const status = searchParams.get("status") || ""; const documentNumber = searchParams.get("documentNumber") || ""; const dateFrom = searchParams.get("dateFrom") || ""; const dateTo = searchParams.get("dateTo") || ""; const currentPage = parseInt(searchParams.get("page") || "1", 10); const pageSize = parseInt(searchParams.get("pageSize") || "10", 10); // 获取API返回的数据 const { documents, total, documentTypeOptions } = loaderData; // 分页处理函数 const handlePageChange = (page: number) => { searchParams.set("page", page.toString()); setSearchParams(searchParams); }; // 每页条数变更处理函数 const handlePageSizeChange = (size: number) => { searchParams.set("pageSize", size.toString()); searchParams.set("page", "1"); // 重置到第一页 setSearchParams(searchParams); }; // 处理文档名称搜索 const handleNameSearch = (value: string) => { const params = new URLSearchParams(searchParams); if (value) { params.set("search", value); } else { params.delete("search"); } params.set("page", "1"); // 重置页码 setSearchParams(params); }; // 处理文档编号变更 const handleDocumentNumberChange = (value: string) => { const params = new URLSearchParams(searchParams); if (value) { params.set("documentNumber", value); } else { params.delete("documentNumber"); } params.set("page", "1"); // 重置页码 setSearchParams(params); }; // 处理文档类型变更 const handleDocumentTypeChange = (e: React.ChangeEvent) => { const params = new URLSearchParams(searchParams); if (e.target.value) { params.set("documentType", e.target.value); } else { params.delete("documentType"); } params.set("page", "1"); // 重置页码 setSearchParams(params); }; // 处理状态变更 const handleStatusChange = (e: React.ChangeEvent) => { const params = new URLSearchParams(searchParams); if (e.target.value) { params.set("status", e.target.value); } else { params.delete("status"); } params.set("page", "1"); // 重置页码 setSearchParams(params); }; // 处理日期范围变更 const handleDateChange = (field: 'dateFrom' | 'dateTo', value: string) => { const params = new URLSearchParams(searchParams); if (value) { params.set(field, value); } else { params.delete(field); } params.set("page", "1"); // 重置页码 setSearchParams(params); }; // 重置搜索条件 const handleReset = () => { // 直接重置所有筛选条件的DOM值 const resetInput = (selector: string, value: string = "") => { const element = document.querySelector(selector); if (element) { element.value = value; // 对于搜索框,触发其input事件以激活搜索 if (element instanceof HTMLInputElement && element.type === "text") { // 创建一个input事件 const event = new Event('input', { bubbles: true }); element.dispatchEvent(event); } } }; // 重置所有搜索字段 resetInput('input[placeholder="请输入文档名称"]'); resetInput('input[placeholder="请输入文档编号"]'); resetInput('select[name="documentType"]'); resetInput('select[name="status"]'); resetInput('input[name="dateFrom"]'); resetInput('input[name="dateTo"]'); // 重置URL参数 setSearchParams(new URLSearchParams({ page: "1", pageSize: pageSize.toString() })); }; // 行选择变更处理 const handleRowSelectionChange = (id: string) => { if (selectedRowKeys.includes(id)) { setSelectedRowKeys(selectedRowKeys.filter(key => key !== id)); } else { setSelectedRowKeys([...selectedRowKeys, id]); } }; // 全选处理 const handleSelectAll = (checked: boolean) => { if (checked) { setSelectedRowKeys(documents.map((doc: DocumentUI) => doc.id.toString())); } else { setSelectedRowKeys([]); } }; // 下载文档 const handleDownload = async (path: string, fileName: string) => { console.log('handleDownload',path,fileName) try { // 使用API获取授权的下载链接 // const { data, error } = await getFileDownloadUrl(path); // if (error || !data?.downloadUrl) { // console.error('获取下载链接失败:', error); // alert('获取下载链接失败: ' + (error || '未知错误')); // return; // } // 创建一个隐藏的a标签并点击它 const a = document.createElement('a'); // a.href = data.downloadUrl; a.href = path; a.download = fileName; // 设置下载的文件名 document.body.appendChild(a); a.click(); document.body.removeChild(a); } catch (err) { console.error('下载文件失败:', err); alert('下载文件失败: ' + (err instanceof Error ? err.message : '未知错误')); } }; // 删除文档 const handleDelete = (id: string, name: string) => { if (window.confirm(`确认删除文档 "${name}"?`)) { // 使用fetcher提交表单 const formData = new FormData(); formData.append('_action', 'delete'); formData.append('id', id); fetcher.submit(formData, { method: 'post' }); // 更新选中行 setSelectedRowKeys(selectedRowKeys.filter(key => key !== id)); } }; // 批量删除 const handleBatchDelete = () => { if (selectedRowKeys.length === 0) { alert('请至少选择一个文档'); return; } if (window.confirm(`确认删除选中的 ${selectedRowKeys.length} 个文档?`)) { // 使用fetcher提交表单 const formData = new FormData(); formData.append('_action', 'batchDelete'); // 添加所有选中的ID selectedRowKeys.forEach(id => { formData.append('ids', id); }); fetcher.submit(formData, { method: 'post' }); // 清空选中行 setSelectedRowKeys([]); } }; // 表格列定义 const columns = [ { title: ( handleSelectAll(e.target.checked)} /> ), key: "selection", width: "50px", render: (_: unknown, record: DocumentUI) => ( handleRowSelectionChange(record.id.toString())} /> ) }, { title: "文档名称", key: "name", render: (_: unknown, record: DocumentUI) => (
{record.name}
{record.isTest && ( 测试 )}
) }, { title: "文档编号", key: "documentNumber", render: (_: unknown, record: DocumentUI) => ( {record.documentNumber} ) }, { title: "文件大小", key: "size", width: "100px", render: (_: unknown, record: DocumentUI) => formatFileSize(record.size) }, { title: "审核状态", key: "status", render: (_: unknown, record: DocumentUI) => ( ) }, { title: "问题数量", key: "issues", width:"60px", render: (_: unknown, record: DocumentUI) => ( record.issues === null ? "-" : record.issues ) }, { title: "上传时间", key: "uploadTime", render: (_: unknown, record: DocumentUI) => record.uploadTime }, { title: "操作", key: "actions", width: "280px", render: (_: unknown, record: DocumentUI) => (
{record.status === "waiting" ? ( 开始审核 ) : record.status === "processing" ? ( 查看进度 ) : ( 查看 )} 修改
) } ]; return (
{/* 页面头部 */}

文档列表

{/* 搜索筛选区 */} {/* */} } noActionDivider={true} > handleDateChange('dateFrom', value)} onEndDateChange={(value) => handleDateChange('dateTo', value)} className="flex-1" simple={true} /> {/* 数据表格 */}
{total} 条记录
{/* 分页 */} ); } // 错误边界处理 export function ErrorBoundary() { return (

出错了

加载文档列表时出现错误。请刷新页面或联系系统管理员。

); }