Files
leaudit-platform-frontend/app/components/cross-checking/DocumentListModal.tsx
T
2025-12-02 10:10:03 +08:00

331 lines
11 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 { Modal } from '../ui/Modal';
import { Table } from '../ui/Table';
import { Button } from '../ui/Button';
import { FileIcon } from '../ui/FileIcon';
import { FileTypeTag } from '../ui/FileTypeTag';
import { StatusBadge } from '../ui/StatusBadge';
import { Pagination } from '../ui/Pagination';
import { LoadingIndicator } from '../ui/SkeletonScreen';
import { updateDocumentAuditStatus, type TaskDocument } from '~/api/cross-checking/cross-files'; // 更新导入
import { toastService } from '../ui/Toast';
import { formatDate } from '~/utils';
import {useRef, useState} from "react";
// 导出样式链接
export const links = () => [];
interface DocumentListModalProps {
isOpen: boolean;
onClose: () => void;
title: string;
files: TaskDocument[]; // 更新类型
onViewFile?: (fileId: string) => void;
loading?: boolean;
// 分页相关属性
currentPage?: number;
pageSize?: number;
total?: number;
onPageChange?: (page: number) => void;
onPageSizeChange?: (size: number) => void;
frontendJWT?: string; // 新增JWT参数
}
export function DocumentListModal({
isOpen,
onClose,
title,
files,
onViewFile,
loading = false,
// 分页属性,使用默认值
currentPage = 1,
pageSize = 10,
total = 0,
onPageChange,
onPageSizeChange,
frontendJWT
}: DocumentListModalProps) {
// 查看按钮防抖
const [isnavigating,setIsnavigating] = useState(false)
const viewDebounceRef = useRef<number | null>(null)
const handleViewClickDebounced = (fileId: string, auditStatus: number | null) => {
if(viewDebounceRef.current) return;
viewDebounceRef.current = window.setTimeout(()=>{
viewDebounceRef.current = null;
},1000);
void handleReviewFileClick(fileId, auditStatus);
}
// 查看评查文件
const handleReviewFileClick = async (fileId: string, auditStatus: number | null) => {
// 检查audit_status是否为0,如果是则更新为2
if (auditStatus === 0 || auditStatus === null) {
try {
// 更新文档状态,传递JWT
const updatedFile = await updateDocumentAuditStatus(fileId, 2, frontendJWT);
// console.log('更新后的文档状态:', updatedFile);
} catch (error) {
console.error('更新文件审核状态时出错:', error);
toastService.error(`更新文件审核状态时出错:${error instanceof Error ? error.message : '未知错误'}`);
return;
}
}
// 如果有自定义的查看处理函数,则调用它
if (onViewFile) {
setIsnavigating(true)
onViewFile(fileId);
}
};
// 审核状态选项及样式 - 与documents._index.tsx保持一致
const auditStatusMapping: Record<string, { label: string; color: string; icon: string }> = {
"-1": { label: "不通过", color: "red", icon: "ri-close-line" },
"-2": { label: "警告", color: "yellow", icon: "ri-alert-line" },
"0": { label: "待审核", color: "blue", icon: "ri-time-line" },
"1": { label: "通过", color: "green", icon: "ri-check-line" },
"2": { label: "审核中", color: "purple", icon: "ri-search-line" },
};
// 渲染审核状态
const renderAuditStatus = (file: TaskDocument) => {
// 处理audit_status为null或undefined的情况,默认为0(待审核)
const auditStatus = file.audit_status != null ? file.audit_status : 0;
const statusKey = auditStatus.toString();
const statusInfo = auditStatusMapping[statusKey] || auditStatusMapping["0"];
return (
<div className={`inline-flex items-center px-2 py-1 rounded-full text-xs bg-${statusInfo.color}-100 text-${statusInfo.color}-800`}>
<i className={`${statusInfo.icon} mr-1`}></i>
<span>{statusInfo.label}</span>
</div>
);
};
// 获取文件大小的友好显示
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
// 定义表格列配置
const columns = [
{
title: "文件名称",
key: "fileName",
width: "30%",
render: (_: unknown, file: TaskDocument) => (
<div className="flex">
<div className="flex-shrink-0 flex items-center self-center">
<FileIcon fileName={file.file_name} className="text-lg w-10 h-10" />
</div>
<div className="min-w-0 flex-1 flex flex-col py-2 ml-2">
<div className="font-normal text-base break-words whitespace-normal leading-normal" title={file.file_name}>{file.file_name}</div>
<div className="text-xs text-secondary mt-2">
{file.file_code}
</div>
<div className="text-xs text-secondary mt-1">
{formatFileSize(file.file_size)}
</div>
</div>
</div>
)
},
{
title: "文件类型",
key: "fileType",
width: "8%",
render: (_: unknown, file: TaskDocument) => (
<FileTypeTag
type="other"
typeName={file.file_type_name}
text={file.file_type_name}
size="sm"
showIcon={false}
colorMode="light"
/>
)
},
{
title: "上传时间",
key: "uploadTime",
width: "8%",
render: (_: unknown, file: TaskDocument) => {
const uploadTime = formatDate(file.upload_time).split(' ');
const date = uploadTime[0];
const time = uploadTime[1];
return (
<div>
<span className="text-base">{date}</span> {/* 2025-07-22 */}
<br />
<span className="text-xs text-secondary">{time}</span> {/* 10:00:00 */}
</div>
);
}
},
{
title: "评查统计",
key: "reviewStatus",
width: "10%",
render: (_: unknown, file: TaskDocument) =>
// 要文件切分处理完之后,再显示评查统计
file.status === 'Processed' ? (
<div>
{file.pass_count > 0 && (
<StatusBadge
status="pass"
text={`通过(${file.pass_count})`}
showIcon={true}
className="my-2"
/>
)}
{file.warning_count > 0 && (
<StatusBadge
status="warning"
text={`警告(${file.warning_count})`}
showIcon={true}
className="my-2"
/>
)}
{file.fail_count > 0 && (
<StatusBadge
status="fail"
text={`不通过(${file.fail_count})`}
showIcon={true}
className="my-2"
/>
)}
{/* {file.manual_count > 0 && (
<StatusBadge
status="pending"
text={`需人工(${file.manual_count})`}
showIcon={true}
className="my-2"
/>
)} */}
</div>
) : (
<div className="text-sm">
-
</div>
)
},
{
title: "评查分数",
key: "score",
width: "8%",
render: (_: unknown, file: TaskDocument) => (
<div className="text-left">
{file.final_score ? (
<span>
{file.score_summary}
</span>
) : (
<span className="text-gray-400">-</span>
)}
</div>
)
},
{
title: "评查分数百分化",
key: "scorePercent",
width: "10%",
render: (_: unknown, file: TaskDocument) => {
const value: number | null | undefined = file.score_percent as number | null | undefined;
if (value === null || value === undefined || Number.isNaN(value)) {
return <span className="text-gray-400">-</span>;
}
const numericValue = typeof value === 'string' ? Number(value) : value;
const normalized = numericValue <= 1 ? numericValue * 100 : numericValue;
const display = `${Number(normalized.toFixed(1))}%`;
return <span>{display}</span>;
}
},
{
title: '审核状态',
key: 'auditStatus',
width: '8%',
render: (_: unknown, file: TaskDocument) => renderAuditStatus(file)
},
{
title: "操作",
key: "operation",
width: "auto",
render: (_: unknown, file: TaskDocument) => (
<>
<Button
type="default"
size="small"
icon="ri-eye-line"
onClick={() => handleViewClickDebounced(file.document_id.toString(), file.audit_status)}
disabled={file.status !== 'Processed'}
className="mr-2"
>
{isnavigating ? '跳转中...' : '查看'}
</Button>
</>
)
}
];
return (
<Modal
isOpen={isOpen}
onClose={onClose}
title={title}
size="full"
className="document-list-modal"
>
<div className="px-6 py-4">
{loading ? (
// 显示loading状态
<div className="py-8">
<LoadingIndicator text="正在加载文档列表..." />
</div>
) : files.length === 0 ? (
// 无数据状态
<div className="text-center py-8 text-gray-500">
</div>
) : (
// 有数据时显示表格和分页
<>
<div className="mb-4 flex items-center">
<i className="ri-file-list-3-line text-primary text-lg mr-2"></i>
<span className="text-sm text-secondary"></span>
<span className="text-base font-normal text-primary ml-1 mr-1">{total || files.length}</span>
<span className="text-sm text-secondary"></span>
</div>
<Table
columns={columns}
dataSource={files}
rowKey="document_id"
emptyText="暂无文件数据"
className="files-table table-auto-height"
/>
{/* 分页组件 - 只有在提供了分页回调函数且总数大于每页大小时才显示 */}
{onPageChange && total > 0 && (
<Pagination
currentPage={currentPage}
total={total}
pageSize={pageSize}
onChange={onPageChange || (() => {})}
onPageSizeChange={onPageSizeChange}
showTotal={true}
showPageSizeChanger={!!onPageSizeChange}
pageSizeOptions={[10, 20, 30, 50]}
/>
)}
</>
)}
</div>
</Modal>
);
}