优化文档列表

This commit is contained in:
2025-04-14 16:47:26 +08:00
parent cb52bf8179
commit 5573724390
5 changed files with 182 additions and 115 deletions
+24 -7
View File
@@ -45,6 +45,8 @@ export interface DocumentSearchParams {
documentNumber?: string;
documentType?: string;
status?: string;
auditStatus?: string;
fileStatus?: string;
dateFrom?: string;
dateTo?: string;
page?: number;
@@ -123,7 +125,7 @@ async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
typeName: docType?.name || '未知类型',
size: doc.file_size,
auditStatus: doc.audit_status,
fileStatus: doc.file_status || 'Processed', // 默认为已处理
fileStatus: doc.status || '', // 默认为''
issues: 0, // 固定为0
uploadTime: formatDate(doc.updated_at),
fileType: getFileExtension(doc.name),
@@ -173,20 +175,35 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
filter['type_id'] = `eq.${searchParams.documentType}`;
}
if (searchParams.status) {
filter['status'] = `eq.${searchParams.status}`;
if (searchParams.auditStatus) {
filter['audit_status'] = `eq.${searchParams.auditStatus}`;
}
if (searchParams.fileStatus) {
filter['status'] = `eq.${searchParams.fileStatus}`;
}
// 处理日期范围
if (searchParams.dateFrom) {
filter['updated_at'] = `gte.${searchParams.dateFrom}`;
// 添加当天开始时间 00:00:00
filter['created_at'] = `gte.'${searchParams.dateFrom} 00:00:00'`;
}
if (searchParams.dateTo) {
const dateToKey = searchParams.dateFrom ? 'and.updated_at.lte' : 'updated_at';
filter[dateToKey] = `lte.${searchParams.dateTo}`;
// 如果有开始日期,使用and条件;否则直接设置结束日期
const dateToKey = searchParams.dateFrom ? 'and' : 'created_at';
// 添加当天结束时间 23:59:59
if (dateToKey === 'and') {
delete filter['created_at'];
// 使用OR操作符连接两个条件
filter[dateToKey] = `(created_at.gte.'${dayjs(searchParams.dateFrom).format()}',created_at.lte.'${dayjs(searchParams.dateTo).format()}')`;
} else {
filter['created_at'] = `lte.'${dayjs(searchParams.dateTo).format()}'`;
}
}
console.log('filter-----', filter);
params.filter = filter;
// 发送请求
@@ -204,7 +221,7 @@ export async function getDocuments(searchParams: DocumentSearchParams = {}): Pro
// 转换为UI格式
const documents = await Promise.all(extractedData.map(convertToUIDocument));
// console.log('documentsItem',documents)
// 获取总数
let totalCount = 0;
const responseWithHeaders = response as {
+5 -4
View File
@@ -40,11 +40,12 @@ function extractApiData<T>(responseData: unknown): T | null {
// 文档状态枚举
export enum DocumentStatus {
WAITING = "waiting",
WAITING = "Waiting",
CUTTING = "Cutting",
EXTRACTIONING = "extractioning",
REVIEWING = "reviewing",
COMPLETED = "completed"
EXTRACTIONING = "Extractioning",
EVALUATIONING = "Evaluationing",
FAILED = "Failed",
PROCESSED = "Processed"
}
// 文档类型接口
+95 -40
View File
@@ -33,8 +33,9 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
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 auditStatus = url.searchParams.get("auditStatus") || "";
const documentNumber = url.searchParams.get("documentNumber") || "";
const fileStatus = url.searchParams.get("fileStatus") || "";
const dateFrom = url.searchParams.get("dateFrom") || "";
const dateTo = url.searchParams.get("dateTo") || "";
const page = parseInt(url.searchParams.get("page") || "1", 10);
@@ -45,7 +46,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
name: search || undefined,
documentNumber: documentNumber || undefined,
documentType: documentType || undefined,
status: status || undefined,
auditStatus: auditStatus || undefined,
fileStatus: fileStatus || undefined,
dateFrom: dateFrom || undefined,
dateTo: dateTo || undefined,
page,
@@ -58,8 +60,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
throw new Error(documentsResponse.error);
}
// 获取文档类型列表,用于筛选条件
const typesResponse = await getDocumentTypes();
// 获取文档类型列表,用于筛选条件,设置较大的pageSize确保获取所有数据
const typesResponse = await getDocumentTypes({ pageSize: 500 });
const documentTypes = typesResponse.data?.types || [];
const documentTypeOptions = documentTypes.map(type => ({
value: type.id,
@@ -112,22 +114,33 @@ export const action = async ({ request }: ActionFunctionArgs) => {
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 auditStatusOptions = [
// { value: "", label: "全部" },
{ value: "-1", label: "不通过" },
{ value: "0", label: "待审核" },
{ value: "1", label: "通过" },
{ value: "2", label: "警告" },
{ value: "3", label: "审核中" },
];
// 文件处理状态选项
const fileProcessingStatusOptions = [
{ value: "Waiting", label: "上传中", icon: "ri-loader-line" },
{ value: "Cutting", label: "切分中", icon: "ri-loader-line" },
{ value: "Extractioning", label: "抽取中", icon: "ri-loader-line" },
{ value: "Evaluationing", label: "评查中", icon: "ri-loader-line" },
{ value: "Processed", label: "已完成", icon: "ri-check-line" },
{ value: "Waiting", label: "上传中", icon: "ri-loader-line", color: "blue" },
{ value: "Cutting", label: "切分中", icon: "ri-loader-line", color: "purple" },
{ value: "Extractioning", label: "抽取中", icon: "ri-loader-line", color: "cyan" },
{ value: "Evaluationing", label: "评查中", icon: "ri-loader-line", color: "teal" },
{ value: "Processed", label: "已完成", icon: "ri-check-line", color: "green" },
];
// 文件状态筛选选项
const fileStatusOptions = [
// { value: "", label: "全部" },
{ value: "Waiting", label: "上传中" },
{ value: "Cutting", label: "切分中" },
{ value: "Extractioning", label: "抽取中" },
{ value: "Evaluationing", label: "评查中" },
{ value: "Processed", label: "已完成" },
];
// 审核状态选项及样式
@@ -171,8 +184,9 @@ export default function DocumentsIndex() {
// 从URL获取当前筛选条件
const search = searchParams.get("search") || "";
const documentType = searchParams.get("documentType") || "";
const status = searchParams.get("status") || "";
const auditStatus = searchParams.get("auditStatus") || "";
const documentNumber = searchParams.get("documentNumber") || "";
const fileStatus = searchParams.get("fileStatus") || "";
const dateFrom = searchParams.get("dateFrom") || "";
const dateTo = searchParams.get("dateTo") || "";
const currentPage = parseInt(searchParams.get("page") || "1", 10);
@@ -234,9 +248,9 @@ export default function DocumentsIndex() {
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const params = new URLSearchParams(searchParams);
if (e.target.value) {
params.set("status", e.target.value);
params.set("auditStatus", e.target.value);
} else {
params.delete("status");
params.delete("auditStatus");
}
params.set("page", "1"); // 重置页码
setSearchParams(params);
@@ -332,7 +346,11 @@ export default function DocumentsIndex() {
};
// 删除文档
const handleDelete = (id: string, name: string) => {
const handleDelete = (id: string, name: string, fileStatus: string) => {
if (fileStatus !== 'Processed') {
alert('文档正在处理中,不能删除');
return;
}
if (window.confirm(`确认删除文档 "${name}"`)) {
// 使用fetcher提交表单
const formData = new FormData();
@@ -353,6 +371,16 @@ export default function DocumentsIndex() {
return;
}
// 检查是否有正在处理中的文件
const hasProcessingFiles = documents.some((doc: DocumentUI) =>
selectedRowKeys.includes(doc.id.toString()) && doc.fileStatus !== 'Processed'
);
if (hasProcessingFiles) {
alert('存在服务器未处理完成的文件,请重新选择需要删除的文件');
return;
}
if (window.confirm(`确认删除选中的 ${selectedRowKeys.length} 个文档?`)) {
// 使用fetcher提交表单
const formData = new FormData();
@@ -370,6 +398,18 @@ export default function DocumentsIndex() {
}
};
// 处理文件状态变更
const handleFileStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const params = new URLSearchParams(searchParams);
if (e.target.value) {
params.set("fileStatus", e.target.value);
} else {
params.delete("fileStatus");
}
params.set("page", "1"); // 重置页码
setSearchParams(params);
};
// 表格列定义
const columns = [
{
@@ -393,6 +433,7 @@ export default function DocumentsIndex() {
{
title: "文档名称",
key: "name",
width:'20%',
render: (_: unknown, record: DocumentUI) => (
<div className="flex items-center m-1">
<FileTag
@@ -401,10 +442,10 @@ export default function DocumentsIndex() {
showText={false}
showBackground={false}
size="lg"
className="mr-2"
className="mr-2 flex-shrink-0"
/>
<div>
<span className="file-name" title={record.name}>{record.name}</span>
<div className="overflow-hidden">
<span className="file-name truncate block" title={record.name}>{record.name}</span>
<div className="mt-2 flex inline-block">
<FileTypeTag
type={record.type}
@@ -424,6 +465,7 @@ export default function DocumentsIndex() {
{
title: "文档编号",
key: "documentNumber",
width:'10%',
render: (_: unknown, record: DocumentUI) => (
<span className="document-number">{record.documentNumber}</span>
)
@@ -431,18 +473,22 @@ export default function DocumentsIndex() {
{
title: "文件大小",
key: "size",
width: "100px",
width: "10%",
render: (_: unknown, record: DocumentUI) => formatFileSize(record.size)
},
{
title: "文件状态",
key: "fileStatus",
width:'10%',
render: (_: unknown, record: DocumentUI) => {
const status = fileProcessingStatusOptions.find(s => s.value === record.fileStatus) ||
// 处理fileStatus为null或undefined的情况
// console.log('fileStatus',record.fileStatus)
const fileStatus = record.fileStatus || "Processed";
const status = fileProcessingStatusOptions.find(s => s.value === fileStatus) ||
fileProcessingStatusOptions[0];
const isSpinning = record.fileStatus !== "Processed";
const isSpinning = fileStatus !== "Processed";
return (
<div className="flex items-center">
<div className={`inline-flex items-center px-2 py-1 rounded-full text-xs bg-${status.color}-100 text-${status.color}-800`}>
<i className={`${status.icon} ${isSpinning ? "animate-spin" : ""} mr-1`}></i>
<span>{status.label}</span>
</div>
@@ -452,8 +498,11 @@ export default function DocumentsIndex() {
{
title: "审核状态",
key: "auditStatus",
width:"10%",
render: (_: unknown, record: DocumentUI) => {
const statusKey = record.auditStatus.toString();
// 处理auditStatus为null或undefined的情况,默认为0(待审核)
const auditStatus = record.auditStatus != null ? record.auditStatus : 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`}>
@@ -466,7 +515,7 @@ export default function DocumentsIndex() {
{
title: "问题数量",
key: "issues",
width:"60px",
width:"10%",
render: (_: unknown, record: DocumentUI) => (
record.issues === null ? "-" : record.issues
)
@@ -474,15 +523,16 @@ export default function DocumentsIndex() {
{
title: "上传时间",
key: "uploadTime",
width:"10%",
render: (_: unknown, record: DocumentUI) => record.uploadTime
},
{
title: "操作",
key: "actions",
width: "280px",
width: "20%",
render: (_: unknown, record: DocumentUI) => (
<div className="operations-cell">
{record.auditStatus === 0 ? (
{(record.auditStatus === 0 || record.auditStatus == null) ? (
<Link
to={`/documents/${record.id}/review`}
className="mr-1 hover:underline"
@@ -525,7 +575,7 @@ export default function DocumentsIndex() {
<button
type="button"
className="text-error hover:underline hover:text-red-700"
onClick={() => handleDelete(record.id.toString(), record.name)}
onClick={() => handleDelete(record.id.toString(), record.name, record.fileStatus)}
>
<i className="ri-delete-bin-line"></i>
@@ -578,13 +628,13 @@ export default function DocumentsIndex() {
}
noActionDivider={true}
>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 w-full">
<SearchFilter
label="文档名称"
placeholder="请输入文档名称"
value={search}
onSearch={handleNameSearch}
instantSearch={true}
className="mr-2 w-50"
/>
<SearchFilter
@@ -593,7 +643,6 @@ export default function DocumentsIndex() {
value={documentNumber}
onSearch={handleDocumentNumberChange}
instantSearch={true}
className="mr-2 w-50"
/>
<FilterSelect
@@ -602,16 +651,22 @@ export default function DocumentsIndex() {
value={documentType}
options={documentTypeOptions}
onChange={handleDocumentTypeChange}
className="mr-2 w-30"
/>
<FilterSelect
label="文件状态"
name="fileStatus"
value={fileStatus}
options={fileStatusOptions}
onChange={handleFileStatusChange}
/>
<FilterSelect
label="审核状态"
name="status"
value={status}
options={documentStatusOptions}
name="auditStatus"
value={auditStatus}
options={auditStatusOptions}
onChange={handleStatusChange}
className="mr-2 w-50"
/>
<DateRangeFilter
@@ -620,9 +675,9 @@ export default function DocumentsIndex() {
endDate={dateTo}
onStartDateChange={(value) => handleDateChange('dateFrom', value)}
onEndDateChange={(value) => handleDateChange('dateTo', value)}
className="flex-1"
simple={true}
/>
</div>
</FilterPanel>
+4 -1
View File
@@ -71,7 +71,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
// 并行获取文档详情和文档类型列表
const [documentResponse, documentTypesResponse] = await Promise.all([
getDocument(id),
getDocumentTypes()
getDocumentTypes({ pageSize: 500 })
]);
if (documentResponse.error) {
@@ -198,6 +198,8 @@ export default function DocumentEdit() {
// 在新窗口打开文档预览
const openPreview = () => {
// 假设有一个预览URL的格式,比如 /preview?path=xxx
console.log('documentstest', document);
const previewUrl = `/preview?path=${encodeURIComponent(document.path)}&name=${encodeURIComponent(document.name)}`;
window.open(previewUrl, '_blank');
};
@@ -273,6 +275,7 @@ export default function DocumentEdit() {
name="type_id"
className="form-select"
defaultValue={document.type}
disabled={document.fileStatus !== 'Processed'}
required
>
{documentTypes.map((type: DocType) => (
+16 -25
View File
@@ -67,21 +67,7 @@ const PRIORITY_TO_CHINESE: Record<Priority, string> = {
[Priority.URGENT]: "紧急"
};
// 处理状态定义
export enum ProcessingStatus {
WAITING = "waiting",
PROCESSING = "processing",
SUCCESS = "success",
ERROR = "error"
}
// 处理步骤状态定义
export enum StepStatus {
CUTTING = "Cutting",
EXTRACTIONING = "extractioning",
REVIEWING = "reviewing",
COMPLETED = "completed"
}
// 模拟API支持的存储类型
const STORAGE_TYPES = [
@@ -105,7 +91,7 @@ export interface UploadedFile {
type: string;
fileType: FileType;
priority: Priority;
status: ProcessingStatus;
status: DocumentStatus;
uploadTime: string;
processingInfo?: {
progress: number;
@@ -353,7 +339,7 @@ export default function FilesUpload() {
// 获取所有未完成的文档ID
const incompleteIds = queueFiles
.filter(file => file.status !== DocumentStatus.COMPLETED && file.id)
.filter(file => file.status !== DocumentStatus.PROCESSED && file.id)
.map(file => file.id);
console.log('未完成的文档ID:', incompleteIds);
@@ -489,7 +475,7 @@ export default function FilesUpload() {
type: file.type,
fileType: fileType as FileType,
priority,
status: ProcessingStatus.PROCESSING,
status: DocumentStatus.WAITING,
uploadTime: getCurrentTime(),
processingInfo: {
progress: 0,
@@ -610,7 +596,7 @@ export default function FilesUpload() {
}
// 检查是否所有文件都已完成处理
const allCompleted = response.data.every(doc => doc.status === DocumentStatus.COMPLETED);
const allCompleted = response.data.every(doc => doc.status === DocumentStatus.PROCESSED);
// 更新步骤状态
if (allCompleted) {
@@ -679,7 +665,7 @@ export default function FilesUpload() {
updatedSteps[2].description = "正在抽取评查点...";
break;
case DocumentStatus.REVIEWING:
case DocumentStatus.EVALUATIONING:
updatedSteps[1].status = "done";
updatedSteps[1].description = "文档格式转换完成,内容已拆分";
updatedSteps[2].status = "done";
@@ -688,7 +674,7 @@ export default function FilesUpload() {
updatedSteps[3].description = "正在评查文档...";
break;
case DocumentStatus.COMPLETED:
case DocumentStatus.PROCESSED:
updatedSteps[1].status = "done";
updatedSteps[1].description = "文档格式转换完成,内容已拆分";
updatedSteps[2].status = "done";
@@ -835,19 +821,24 @@ export default function FilesUpload() {
case DocumentStatus.CUTTING:
statusClass = "status-processing";
statusIcon = "ri-loader-4-line";
statusText = "转换中";
statusText = "切分中";
break;
case DocumentStatus.EXTRACTIONING:
statusClass = "status-processing";
statusIcon = "ri-loader-4-line";
statusText = "抽取中";
break;
case DocumentStatus.REVIEWING:
case DocumentStatus.EVALUATIONING:
statusClass = "status-processing";
statusIcon = "ri-loader-4-line";
statusText = "审核中";
statusText = "评查中";
break;
case DocumentStatus.COMPLETED:
case DocumentStatus.FAILED:
statusClass = "status-error";
statusIcon = "ri-close-circle-line";
statusText = "抽取异常";
break;
case DocumentStatus.PROCESSED:
statusClass = "status-success";
statusIcon = "ri-checkbox-circle-line";
statusText = "已完成";
@@ -870,7 +861,7 @@ export default function FilesUpload() {
<Button
type="default"
size="small"
disabled={record.status !== DocumentStatus.COMPLETED}
disabled={record.status !== DocumentStatus.PROCESSED}
icon="ri-eye-line"
onClick={() => alert(`查看文件详情: ${record.name}`)}
>