Files
leaudit-platform-frontend/app/routes/files/_index.tsx
T

306 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React from 'react';
import { json, type MetaFunction } from '@remix-run/node';
import { useLoaderData, useSearchParams, Form } from '@remix-run/react';
import { Button } from '~/components/ui/Button';
import { Card } from '~/components/ui/Card';
import { Table } from '~/components/ui/Table';
import { Breadcrumb } from '~/components/layout/Breadcrumb';
import type { File } from '~/models/file';
import { REVIEW_STATUS_LABELS, REVIEW_STATUS_COLORS } from '~/models/file';
export const meta: MetaFunction = () => {
return [
{ title: "中国烟草AI合同及卷宗审核系统 - 文件列表" },
{ name: "description", content: "评查文件列表" }
];
};
export const handle = {
breadcrumb: '文件列表'
};
interface LoaderData {
files: File[];
documentTypes: {
id: string;
name: string;
}[];
totalCount: number;
}
export async function loader({ request }) {
const url = new URL(request.url);
const documentTypeId = url.searchParams.get("documentTypeId") || "";
const reviewStatus = url.searchParams.get("reviewStatus") || "";
const keyword = url.searchParams.get("keyword") || "";
// 模拟数据,实际项目中应从API获取
const files: File[] = [
{
id: "1",
fileName: "2023年度烟草专卖零售许可证.pdf",
fileType: "application/pdf",
documentTypeId: "2",
documentTypeName: "专卖许可证",
fileSize: 1024 * 1024 * 2.5, // 2.5MB
uploaderId: "1",
uploaderName: "张三",
status: "completed",
reviewStatus: "pass",
createdAt: "2023-12-24 14:30",
updatedAt: "2023-12-24 16:45"
},
{
id: "2",
fileName: "烟草制品购销合同(2023-12).docx",
fileType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
documentTypeId: "1",
documentTypeName: "合同文档",
fileSize: 1024 * 1024 * 1.2, // 1.2MB
uploaderId: "1",
uploaderName: "张三",
status: "completed",
reviewStatus: "warning",
createdAt: "2023-12-23 09:15",
updatedAt: "2023-12-23 10:30"
},
{
id: "3",
fileName: "专卖管理处罚决定书(2023-145).pdf",
fileType: "application/pdf",
documentTypeId: "3",
documentTypeName: "行政处罚决定书",
fileSize: 1024 * 1024 * 3.1, // 3.1MB
uploaderId: "2",
uploaderName: "李四",
status: "completed",
reviewStatus: "fail",
createdAt: "2023-12-22 16:45",
updatedAt: "2023-12-22 18:20"
},
{
id: "4",
fileName: "2023年第四季度采购合同.docx",
fileType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
documentTypeId: "1",
documentTypeName: "合同文档",
fileSize: 1024 * 1024 * 1.8, // 1.8MB
uploaderId: "3",
uploaderName: "王五",
status: "completed",
reviewStatus: "pass",
createdAt: "2023-12-20 11:20",
updatedAt: "2023-12-20 14:35"
},
{
id: "5",
fileName: "广告宣传协议书.pdf",
fileType: "application/pdf",
documentTypeId: "1",
documentTypeName: "合同文档",
fileSize: 1024 * 1024 * 0.9, // 0.9MB
uploaderId: "2",
uploaderName: "李四",
status: "pending",
reviewStatus: "pending",
createdAt: "2023-12-18 15:30",
updatedAt: "2023-12-18 15:30"
}
];
const documentTypes = [
{ id: "1", name: "合同文档" },
{ id: "2", name: "专卖许可证" },
{ id: "3", name: "行政处罚决定书" },
{ id: "4", name: "其他文档" }
];
// 过滤数据
let filteredFiles = [...files];
if (documentTypeId) {
filteredFiles = filteredFiles.filter(file => file.documentTypeId === documentTypeId);
}
if (reviewStatus) {
filteredFiles = filteredFiles.filter(file => file.reviewStatus === reviewStatus);
}
if (keyword) {
const lowerKeyword = keyword.toLowerCase();
filteredFiles = filteredFiles.filter(file =>
file.fileName.toLowerCase().includes(lowerKeyword)
);
}
return json<LoaderData>({
files: filteredFiles,
documentTypes,
totalCount: files.length
});
}
export default function FilesList() {
const { files, documentTypes } = useLoaderData<typeof loader>();
const [searchParams] = useSearchParams();
// 文件大小格式化
const formatFileSize = (bytes: number): string => {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
};
// 获取文件图标
const getFileIcon = (fileType: string): string => {
if (fileType.includes('pdf')) return 'ri-file-pdf-line';
if (fileType.includes('word')) return 'ri-file-word-line';
if (fileType.includes('excel') || fileType.includes('spreadsheet')) return 'ri-file-excel-line';
if (fileType.includes('image')) return 'ri-file-image-line';
return 'ri-file-text-line';
};
return (
<div>
<Breadcrumb
items={[
{ title: '文件管理', to: '/files' },
{ title: '文件列表', to: '/files' }
]}
/>
<div className="flex justify-between items-center mb-4">
<div className="flex items-center">
<h2 className="text-xl font-medium"></h2>
<div className="flex items-center ml-4 bg-white px-3 py-1 rounded-md">
<i className="ri-file-list-3-line text-primary text-lg mr-1"></i>
<span className="text-sm text-secondary"></span>
<span className="text-base font-bold text-primary ml-1">{files.length}</span>
</div>
</div>
<Button type="primary" icon="ri-file-upload-line" to="/files/new">
</Button>
</div>
<Card className="mb-5">
<Form method="get" className="flex flex-wrap items-end gap-3">
<div className="w-48">
<label className="form-label"></label>
<select
name="documentTypeId"
className="form-select w-full"
defaultValue={searchParams.get('documentTypeId') || ''}
>
<option value=""></option>
{documentTypes.map(type => (
<option key={type.id} value={type.id}>{type.name}</option>
))}
</select>
</div>
<div className="w-48">
<label className="form-label"></label>
<select
name="reviewStatus"
className="form-select w-full"
defaultValue={searchParams.get('reviewStatus') || ''}
>
<option value=""></option>
{Object.entries(REVIEW_STATUS_LABELS).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<div className="w-64">
<label className="form-label"></label>
<div className="search-box">
<input
type="text"
name="keyword"
className="form-input"
placeholder="搜索文件名称"
defaultValue={searchParams.get('keyword') || ''}
/>
<button type="submit" className="ant-btn ant-btn-primary">
<i className="ri-search-line"></i>
</button>
</div>
</div>
<Button type="default" className="ml-2"></Button>
</Form>
</Card>
<Table
columns={[
{
title: "文件名称",
render: (_, record: File) => (
<div className="flex items-center">
<i className={`${getFileIcon(record.fileType)} text-lg text-gray-500 mr-2`}></i>
<div>
<div className="font-medium">{record.fileName}</div>
<div className="text-xs text-gray-500">{formatFileSize(record.fileSize)}</div>
</div>
</div>
)
},
{ title: "文档类型", dataIndex: "documentTypeName" },
{
title: "评查状态",
dataIndex: "reviewStatus",
render: (value) => (
<span className={`status-badge status-${REVIEW_STATUS_COLORS[value]}`}>
<i className={`ri-${value === 'pass' ? 'checkbox-circle' : value === 'warning' ? 'error-warning' : value === 'fail' ? 'close-circle' : 'time'}-line mr-1`}></i>
{REVIEW_STATUS_LABELS[value]}
</span>
)
},
{
title: "上传人",
dataIndex: "uploaderName"
},
{
title: "上传时间",
dataIndex: "createdAt"
},
{
title: "操作",
render: (_, record: File) => (
<div className="space-x-2">
<Button
type="default"
size="small"
icon="ri-file-search-line"
to={`/reviews/${record.id}`}
>
</Button>
{record.status === 'pending' && (
<Button
type="primary"
size="small"
icon="ri-play-circle-line"
>
</Button>
)}
<Button
type="danger"
size="small"
icon="ri-delete-bin-line"
>
</Button>
</div>
)
}
]}
dataSource={files}
rowKey="id"
/>
</div>
);
}