重新构建路由和配置样式文件
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user