From cb52bf81790afc2a5797fb1f8aa8cf382d9f325f Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Sun, 13 Apr 2025 18:49:43 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E6=A1=A3=E5=88=97?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/client.ts | 2 +- app/api/files.ts | 133 -------------------------- app/api/files/documents.ts | 19 ++-- app/api/files/files-upload.ts | 128 +++++++++++++++++++++++++ app/api/filesApi.ts | 88 ----------------- app/api/rulesApi.ts | 75 --------------- app/routes/documents._index.tsx | 57 +++++++++-- app/routes/documents.edit.tsx | 126 +++++++++++++----------- app/routes/files.upload.tsx | 153 +++++++++++++++++++++++------- app/styles/pages/files_upload.css | 22 +++++ 10 files changed, 396 insertions(+), 407 deletions(-) delete mode 100644 app/api/files.ts delete mode 100644 app/api/filesApi.ts delete mode 100644 app/api/rulesApi.ts diff --git a/app/api/client.ts b/app/api/client.ts index 66263e5..c9b6575 100644 --- a/app/api/client.ts +++ b/app/api/client.ts @@ -16,7 +16,7 @@ export type QueryParams = Record; // 获取 API 基础 URL // const API_BASE_URL = '172.18.0.100:3000'; // const API_BASE_URL = '172.16.0.119:9000/admin'; -const API_BASE_URL = 'http://nas.7bm.co:3000'; +export const API_BASE_URL = 'http://nas.7bm.co:3000'; // 是否使用模拟数据(开发环境使用) const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题 diff --git a/app/api/files.ts b/app/api/files.ts deleted file mode 100644 index a699beb..0000000 --- a/app/api/files.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { apiRequest } from './client'; -import { FileType, Priority } from '~/types/enums'; - -/** - * 将文件转换为二进制数据 - */ -export async function uploadFileToBinary(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => { - if (reader.result instanceof ArrayBuffer) { - resolve(reader.result); - } else { - reject(new Error('文件读取失败')); - } - }; - reader.onerror = () => reject(new Error('文件读取失败')); - reader.readAsArrayBuffer(file); - }); -} - -/** - * 上传文件到服务器 - */ -export async function uploadFileToServer( - binaryData: ArrayBuffer, - fileName: string, - fileType: string, - documentType: FileType, - priority: Priority -): Promise<{ success: boolean; fileId?: string; message?: string; error?: string }> { - try { - const formData = new FormData(); - formData.append('file', new Blob([binaryData], { type: fileType }), fileName); - formData.append('documentType', documentType); - formData.append('priority', priority); - - const response = await apiRequest<{ success: boolean; fileId?: string; message?: string }>( - '/api/files/upload', - { - method: 'POST', - body: formData - } - ); - - if (response.error) { - return { success: false, error: response.error }; - } - - return { success: true, fileId: response.data?.fileId, message: response.data?.message }; - } catch (error) { - return { success: false, error: error instanceof Error ? error.message : '上传失败' }; - } -} - -/** - * 上传文件到文档审核系统 - */ -export async function uploadDocumentToServer( - binaryData: ArrayBuffer, - fileName: string, - fileType: string, - typeId: string | number, - priority: string, - documentNumber?: string | null, - remark?: string | null, - isTestDocument: boolean = false -): Promise<{ - success: boolean; - result?: { - id: number; - file_name: string; - file_size: number; - file_url: string; - type_id: number; - type_description: string; - document_number: string | null; - storage_type: string; - is_test_document: boolean; - remark: string | null; - background_processing: boolean; - evaluation_level: string; - }; - error: string | null; -}> { - try { - // 创建FormData对象 - const formData = new FormData(); - - // 将二进制数据转换为Blob并添加到FormData - const blob = new Blob([binaryData], { type: fileType }); - formData.append('file', blob, fileName); - - // 将信息添加到一个JSON对象中 - const uploadInfo = { - type_id: Number(typeId), - evaluation_level: priority, - document_number: documentNumber || null, - remark: remark || null, - is_test_document: isTestDocument - }; - - // 添加JSON字符串到FormData - formData.append('upload_info', JSON.stringify(uploadInfo)); - - // 发送请求 - const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', { - method: 'POST', - headers: { - 'X-File-Name': encodeURIComponent(fileName) - }, - body: formData - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error(`上传失败 (${response.status}): ${errorText}`); - return { - success: false, - error: `上传失败: ${response.status} ${response.statusText}` - }; - } - - const data = await response.json(); - return data; - } catch (error) { - console.error('上传错误:', error); - return { - success: false, - error: error instanceof Error ? error.message : '上传失败' - }; - } -} \ No newline at end of file diff --git a/app/api/files/documents.ts b/app/api/files/documents.ts index 74ccb9c..05c6017 100644 --- a/app/api/files/documents.ts +++ b/app/api/files/documents.ts @@ -67,6 +67,8 @@ export interface Document { is_test_document: boolean; evaluation_level: string; status: 'pass' | 'warning' | 'waiting' | 'processing' | 'fail'; + file_status: 'Waiting' | 'Cutting' | 'Extractioning' | 'Evaluationing' | 'Processed'; + audit_status: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中 ocr_result: unknown; extracted_results: unknown; summary: unknown; @@ -85,7 +87,8 @@ export interface DocumentUI { type: string; typeName: string; size: number; - status: string; + auditStatus: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中 + fileStatus: string; // Waiting, Cutting, Extractioning, Evaluationing, Processed issues: number | null; uploadTime: string; fileType: string; @@ -119,7 +122,8 @@ async function convertToUIDocument(doc: Document): Promise { type: doc.type_id.toString(), typeName: docType?.name || '未知类型', size: doc.file_size, - status: doc.status, + auditStatus: doc.audit_status, + fileStatus: doc.file_status || 'Processed', // 默认为已处理 issues: 0, // 固定为0 uploadTime: formatDate(doc.updated_at), fileType: getFileExtension(doc.name), @@ -332,13 +336,6 @@ export async function getFileDownloadUrl(filePath: string): Promise<{ return { error: '文件路径不能为空', status: 400 }; } - // 构建API请求参数 - const params: PostgrestParams = { - filter: { - 'path': `eq.${filePath}` - } - }; - // 这里应该调用获取文件下载链接的API // 假设后端有这样的端点:/api/files/generate-download-url?path=xxx // 实际项目中需要根据你的后端API调整 @@ -387,8 +384,8 @@ export async function updateDocument(id: string, document: Partial & apiDocument.type_id = parseInt(document.type); } - if (document.status !== undefined) { - apiDocument.status = document.status as 'pass' | 'warning' | 'waiting' | 'processing' | 'fail'; + if (document.auditStatus !== undefined) { + apiDocument.audit_status = document.auditStatus; } if (document.isTest !== undefined) { diff --git a/app/api/files/files-upload.ts b/app/api/files/files-upload.ts index c317975..f6ebb8c 100644 --- a/app/api/files/files-upload.ts +++ b/app/api/files/files-upload.ts @@ -1,5 +1,6 @@ import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client'; import dayjs from 'dayjs'; +// import { API_BASE_URL } from '../client'; /** * 格式化日期 @@ -39,6 +40,7 @@ function extractApiData(responseData: unknown): T | null { // 文档状态枚举 export enum DocumentStatus { + WAITING = "waiting", CUTTING = "Cutting", EXTRACTIONING = "extractioning", REVIEWING = "reviewing", @@ -80,6 +82,132 @@ export interface Document { remark?: string; } +// 文件上传响应接口 +export interface FileUploadResponse { + success: boolean; + result?: { + id: number; + file_name: string; + file_size: number; + file_url: string; + type_id: number; + type_description: string; + document_number: string | null; + storage_type: string; + is_test_document: boolean; + remark: string | null; + background_processing: boolean; + evaluation_level: string; + }; + error: string | null; +} + +/** + * 将文件转换为二进制数据 + */ +export async function uploadFileToBinary(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (reader.result instanceof ArrayBuffer) { + resolve(reader.result); + } else { + reject(new Error('文件读取失败')); + } + }; + reader.onerror = () => reject(new Error('文件读取失败')); + reader.readAsArrayBuffer(file); + }); +} + +/** + * 上传文件到文档审核系统 + * @param binaryData 文件的二进制数据 + * @param fileName 文件名 + * @param fileType 文件类型 + * @param typeId 文档类型ID + * @param priority 优先级 + * @param documentNumber 文档编号(可选) + * @param remark 备注信息(可选) + * @param isTestDocument 是否为测试文档 + * @returns 上传结果 + */ +export async function uploadDocumentToServer( + binaryData: ArrayBuffer, + fileName: string, + fileType: string, + typeId: string | number, + priority: string, + documentNumber?: string | null, + remark?: string | null, + isTestDocument: boolean = false +): Promise<{data: FileUploadResponse; error?: never} | {data?: never; error: string; status?: number}> { + try { + // 创建FormData对象 + const formData = new FormData(); + + // 将二进制数据转换为Blob并添加到FormData + const blob = new Blob([binaryData], { type: fileType }); + formData.append('file', blob, fileName); + + // 将信息添加到一个JSON对象中 + const uploadInfo = { + type_id: Number(typeId), + evaluation_level: priority, + document_number: documentNumber || null, + remark: remark || null, + is_test_document: isTestDocument + }; + + // 添加JSON字符串到FormData + formData.append('upload_info', JSON.stringify(uploadInfo)); + + console.log('上传信息:', { + fileName, + fileType, + typeId: Number(typeId), + priority, + documentNumber: documentNumber || null, + remark: remark || null, + isTestDocument + }); + + // 发送请求 + // const response = await fetch(`${API_BASE_URL}/admin/documents/upload`, { + const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', { + method: 'POST', + headers: { + 'X-File-Name': encodeURIComponent(fileName) + }, + body: formData + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`上传失败 (${response.status}): ${errorText}`); + return { + error: `上传失败: ${response.status} ${response.statusText}`, + status: response.status + }; + } + + const responseData = await response.json(); + const extractedData = extractApiData(responseData); + + if (!extractedData) { + return { error: '处理上传响应失败', status: 500 }; + } + + return { data: extractedData }; + } catch (error) { + console.error('上传错误:', error); + return { + error: error instanceof Error ? error.message : '上传失败', + status: 500 + }; + } +} + /** * 获取当天的文档列表 * @returns 文档列表 diff --git a/app/api/filesApi.ts b/app/api/filesApi.ts deleted file mode 100644 index b7f2340..0000000 --- a/app/api/filesApi.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { apiRequest, buildUrl, type PaginatedResponse } from './base'; -import type { File, DocumentType } from '~/models/file'; - -/** - * 文件API服务 - */ - -interface FileFilterParams { - documentTypeId?: string; - status?: string; - reviewStatus?: string; - keyword?: string; - startDate?: string; - endDate?: string; - page?: number; - pageSize?: number; -} - -// 获取文件列表 -export async function getFiles(params?: FileFilterParams): Promise> { - const url = buildUrl('/api/files', params); - return apiRequest>(url); -} - -// 获取单个文件 -export async function getFile(id: string): Promise { - const url = buildUrl(`/api/files/${id}`); - return apiRequest(url); -} - -// 上传文件 -export async function uploadFile(formData: FormData): Promise { - const url = buildUrl('/api/files/upload'); - - const response = await fetch(url, { - method: 'POST', - body: formData, - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || '文件上传失败'); - } - - return response.json().then(data => data.data); -} - -// 删除文件 -export async function deleteFile(id: string): Promise { - const url = buildUrl(`/api/files/${id}`); - return apiRequest(url, 'DELETE'); -} - -// 获取文档类型列表 -export async function getDocumentTypes(): Promise { - const url = buildUrl('/api/document-types'); - return apiRequest(url); -} - -// 获取单个文档类型 -export async function getDocumentType(id: string): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url); -} - -// 创建文档类型 -// 请使用 ~/api/document-types/document-types.ts 中的实现 -/* -export async function createDocumentType(documentType: Omit): Promise { - const url = buildUrl('/api/document-types'); - return apiRequest(url, 'POST', documentType); -} -*/ - -// 更新文档类型 -// 请使用 ~/api/document-types/document-types.ts 中的实现 -/* -export async function updateDocumentType(id: string, documentType: Partial>): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url, 'PUT', documentType); -} -*/ - -// 删除文档类型 -export async function deleteDocumentType(id: string): Promise { - const url = buildUrl(`/api/document-types/${id}`); - return apiRequest(url, 'DELETE'); -} \ No newline at end of file diff --git a/app/api/rulesApi.ts b/app/api/rulesApi.ts deleted file mode 100644 index 8b8d3ed..0000000 --- a/app/api/rulesApi.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { apiRequest, buildUrl, type PaginatedResponse } from './base'; -import type { Rule, RuleGroup } from '~/models/rule'; - -/** - * 评查规则API服务 - */ - -interface RuleFilterParams { - ruleType?: string; - groupId?: string; - isActive?: boolean; - keyword?: string; - page?: number; - pageSize?: number; -} - -// 获取评查点列表 -export async function getRules(params?: RuleFilterParams): Promise> { - const url = buildUrl('/api/rules', params); - return apiRequest>(url); -} - -// 获取单个评查点 -export async function getRule(id: string): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url); -} - -// 创建评查点 -export async function createRule(rule: Omit): Promise { - const url = buildUrl('/api/rules'); - return apiRequest(url, 'POST', rule); -} - -// 更新评查点 -export async function updateRule(id: string, rule: Partial>): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url, 'PUT', rule); -} - -// 删除评查点 -export async function deleteRule(id: string): Promise { - const url = buildUrl(`/api/rules/${id}`); - return apiRequest(url, 'DELETE'); -} - -// 获取评查点分组列表 -export async function getRuleGroups(): Promise { - const url = buildUrl('/api/rule-groups'); - return apiRequest(url); -} - -// 获取单个评查点分组 -export async function getRuleGroup(id: string): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url); -} - -// 创建评查点分组 -export async function createRuleGroup(group: Omit): Promise { - const url = buildUrl('/api/rule-groups'); - return apiRequest(url, 'POST', group); -} - -// 更新评查点分组 -export async function updateRuleGroup(id: string, group: Partial>): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url, 'PUT', group); -} - -// 删除评查点分组 -export async function deleteRuleGroup(id: string): Promise { - const url = buildUrl(`/api/rule-groups/${id}`); - return apiRequest(url, 'DELETE'); -} \ No newline at end of file diff --git a/app/routes/documents._index.tsx b/app/routes/documents._index.tsx index 342038e..19ff77f 100644 --- a/app/routes/documents._index.tsx +++ b/app/routes/documents._index.tsx @@ -5,12 +5,11 @@ 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 { getDocuments, deleteDocument, type DocumentUI } from "~/api/files/documents"; import { getDocumentTypes } from "~/api/document-types/document-types"; // 导入样式 @@ -122,6 +121,24 @@ const documentStatusOptions = [ { value: "fail", 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" }, +]; + +// 审核状态选项及样式 +const auditStatusMapping: Record = { + "-1": { label: "不通过", color: "red", icon: "ri-close-line" }, + "0": { label: "待审核", color: "blue", icon: "ri-time-line" }, + "1": { label: "通过", color: "green", icon: "ri-check-line" }, + "2": { label: "警告", color: "yellow", icon: "ri-alert-line" }, + "3": { label: "审核中", color: "purple", icon: "ri-search-line" }, +}; + // 格式化文件大小 const formatFileSize = (bytes: number) => { if (bytes === 0) return "0 Bytes"; @@ -417,12 +434,34 @@ export default function DocumentsIndex() { width: "100px", render: (_: unknown, record: DocumentUI) => formatFileSize(record.size) }, + { + title: "文件状态", + key: "fileStatus", + render: (_: unknown, record: DocumentUI) => { + const status = fileProcessingStatusOptions.find(s => s.value === record.fileStatus) || + fileProcessingStatusOptions[0]; + const isSpinning = record.fileStatus !== "Processed"; + return ( +
+ + {status.label} +
+ ); + } + }, { title: "审核状态", - key: "status", - render: (_: unknown, record: DocumentUI) => ( - - ) + key: "auditStatus", + render: (_: unknown, record: DocumentUI) => { + const statusKey = record.auditStatus.toString(); + const statusInfo = auditStatusMapping[statusKey] || auditStatusMapping["0"]; + return ( +
+ + {statusInfo.label} +
+ ); + } }, { title: "问题数量", @@ -443,7 +482,7 @@ export default function DocumentsIndex() { width: "280px", render: (_: unknown, record: DocumentUI) => (
- {record.status === "waiting" ? ( + {record.auditStatus === 0 ? ( 开始审核 - ) : record.status === "processing" ? ( + ) : record.auditStatus === 3 ? ( 上传文档 diff --git a/app/routes/documents.edit.tsx b/app/routes/documents.edit.tsx index f51a633..29b5db0 100644 --- a/app/routes/documents.edit.tsx +++ b/app/routes/documents.edit.tsx @@ -19,22 +19,31 @@ export const meta: MetaFunction = () => { ]; }; -// 文档状态定义 -enum DocumentStatus { - WAITING = "waiting", - PROCESSING = "processing", - PASS = "pass", - WARNING = "warning", - FAIL = "fail" +// 文档审核状态定义 +enum DocumentAuditStatus { + FAIL = -1, + WAITING = 0, + PASS = 1, + WARNING = 2, + PROCESSING = 3 } // 文档状态对应的中文标签 -const STATUS_LABELS: Record = { - [DocumentStatus.WAITING]: "待审核", - [DocumentStatus.PROCESSING]: "审核中", - [DocumentStatus.PASS]: "通过", - [DocumentStatus.WARNING]: "警告", - [DocumentStatus.FAIL]: "不通过" +const STATUS_LABELS: Record = { + [DocumentAuditStatus.FAIL]: "不通过", + [DocumentAuditStatus.WAITING]: "待审核", + [DocumentAuditStatus.PASS]: "通过", + [DocumentAuditStatus.WARNING]: "警告", + [DocumentAuditStatus.PROCESSING]: "审核中" +}; + +// 文档状态样式配置 +const STATUS_STYLES: Record = { + [-1]: { color: "red", icon: "ri-close-line" }, + [0]: { color: "blue", icon: "ri-time-line" }, + [1]: { color: "green", icon: "ri-check-line" }, + [2]: { color: "yellow", icon: "ri-alert-line" }, + [3]: { color: "purple", icon: "ri-search-line" } }; // 格式化文件大小 @@ -99,31 +108,31 @@ export async function action({ request }: ActionFunctionArgs) { // 从表单数据中提取字段 const type = formData.get("type_id") as string; const documentNumber = formData.get("document_number") as string; - const status = formData.get("status") as DocumentStatus; + const auditStatus = parseInt(formData.get("audit_status") as string); const isTest = formData.get("is_test_document") === "on"; const remark = formData.get("remark") as string; // 验证必填字段 - if (!type || !status) { + if (!type || auditStatus === undefined || isNaN(auditStatus)) { return Response.json( { error: "缺少必填字段", fieldErrors: { type_id: !type ? "文档类型不能为空" : null, - status: !status ? "状态不能为空" : null + audit_status: (auditStatus === undefined || isNaN(auditStatus)) ? "审核状态不能为空" : null } }, { status: 400 } ); } - console.log('提交更新:', { type, documentNumber, status, isTest, remark }); + console.log('提交更新:', { type, documentNumber, auditStatus, isTest, remark }); // 更新文档 const updateResponse = await updateDocument(id, { type, documentNumber, - status, + auditStatus, isTest, remark }); @@ -160,43 +169,38 @@ export default function DocumentEdit() { } // 状态 - const [localStatus, setLocalStatus] = useState(document.status as DocumentStatus); + const [localStatus, setLocalStatus] = useState(document.auditStatus); // 处理状态变更 const handleStatusChange = (e: React.ChangeEvent) => { - setLocalStatus(e.target.value as DocumentStatus); + setLocalStatus(parseInt(e.target.value)); }; // 获取文档类型名称 const getDocumentTypeName = (typeId: string): string => { - const docType = documentTypes.find((type) => (type as any).id.toString() === typeId); - return docType ? (docType as any).name : "未知类型"; + const docType = documentTypes.find((type: DocType) => type.id.toString() === typeId); + return docType ? docType.name : "未知类型"; }; // 渲染状态徽章 - const renderStatusBadge = (status: string) => { - const statusClasses: Record = { - "waiting": "status-badge status-pending", - "processing": "status-badge status-processing", - "pass": "status-badge status-pass", - "warning": "status-badge status-warning", - "fail": "status-badge status-fail" - }; - - const statusLabel: Record = { - "waiting": "待审核", - "processing": "审核中", - "pass": "通过", - "warning": "警告", - "fail": "不通过" - }; + const renderStatusBadge = (status: number) => { + const style = STATUS_STYLES[status] || STATUS_STYLES[0]; + const label = STATUS_LABELS[status as DocumentAuditStatus] || STATUS_LABELS[DocumentAuditStatus.WAITING]; return ( - - {statusLabel[status] || status} + + + {label} ); }; + + // 在新窗口打开文档预览 + const openPreview = () => { + // 假设有一个预览URL的格式,比如 /preview?path=xxx + const previewUrl = `/preview?path=${encodeURIComponent(document.path)}&name=${encodeURIComponent(document.name)}`; + window.open(previewUrl, '_blank'); + }; return (
@@ -250,7 +254,7 @@ export default function DocumentEdit() { {formatFileSize(document.size)}
- {renderStatusBadge(document.status)} + {renderStatusBadge(document.auditStatus)}
@@ -271,7 +275,7 @@ export default function DocumentEdit() { defaultValue={document.type} required > - {documentTypes.map(type => ( + {documentTypes.map((type: DocType) => ( ))} @@ -295,24 +299,24 @@ export default function DocumentEdit() {
- +
更改状态可能会影响此文档在列表中的显示和排序
- {actionData?.fieldErrors?.status && ( -
{actionData.fieldErrors.status}
+ {actionData?.fieldErrors?.audit_status && ( +
{actionData.fieldErrors.audit_status}
)}
@@ -356,7 +360,7 @@ export default function DocumentEdit() {
- + {document.name}
@@ -364,21 +368,31 @@ export default function DocumentEdit() { type="default" size="small" icon="ri-download-line" + className="mr-2" > 下载 +
- +

预览功能暂不可用

-

PDF文件需要外部查看器支持

+

点击"在新窗口打开"查看完整文档

@@ -388,7 +402,7 @@ export default function DocumentEdit() { {/* 修改历史 */} - +
{[ { diff --git a/app/routes/files.upload.tsx b/app/routes/files.upload.tsx index a3e5741..75620ad 100644 --- a/app/routes/files.upload.tsx +++ b/app/routes/files.upload.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from "react"; -import { MetaFunction, ActionFunctionArgs, json } from "@remix-run/node"; +import { MetaFunction, ActionFunctionArgs } from "@remix-run/node"; import { Form, useActionData, useLoaderData } from "@remix-run/react"; import { Card } from "~/components/ui/Card"; import { Button } from "~/components/ui/Button"; @@ -8,8 +8,17 @@ import { UploadArea, UploadAreaRef } from "~/components/ui/UploadArea"; import { FileProgress} from "~/components/ui/FileProgress"; import { ProcessingSteps, Step } from "~/components/ui/ProcessingSteps"; import uploadStyles from "~/styles/pages/files_upload.css?url"; -import { getTodayDocuments, getDocumentTypes, getDocumentsStatus, type Document, type DocumentType, DocumentStatus } from "~/api/files/files-upload"; -import { uploadFileToBinary, uploadDocumentToServer } from "~/api/files"; +import { + getTodayDocuments, + getDocumentTypes, + getDocumentsStatus, + uploadFileToBinary, + uploadDocumentToServer, + type Document, + type DocumentType, + type FileUploadResponse, + DocumentStatus +} from "~/api/files/files-upload"; export function links() { return [ @@ -74,6 +83,20 @@ export enum StepStatus { COMPLETED = "completed" } +// 模拟API支持的存储类型 +const STORAGE_TYPES = [ + { id: "minio", name: "MinIO对象存储" }, + { id: "local", name: "本地文件系统" }, + { id: "s3", name: "Amazon S3" } +]; + +// 文件上传完成后的操作选项 +const AFTER_UPLOAD_OPTIONS = [ + { id: "list", name: "返回文档列表" }, + { id: "stay", name: "留在当前页面" }, + { id: "audit", name: "立即开始审核" } +]; + // 上传的文件信息接口 export interface UploadedFile { id: number; @@ -90,26 +113,6 @@ export interface UploadedFile { }; } -// 文件上传响应接口 -interface FileUploadResponse { - success: boolean; - result?: { - id: number; - file_name: string; - file_size: number; - file_url: string; - type_id: number; - type_description: string; - document_number: string | null; - storage_type: string; - is_test_document: boolean; - remark: string | null; - background_processing: boolean; - evaluation_level: string; - }; - error: string | null; -} - // 模拟上传文件到服务器的API async function uploadFileToServer( binaryData: ArrayBuffer, @@ -122,7 +125,7 @@ async function uploadFileToServer( isTestDocument: boolean ): Promise { // 在实际应用中,这里会使用fetch或axios发送请求到后端API - console.log(`[模拟API] 上传文件: ${fileName}, 大小: ${binaryData.byteLength} 字节`); + console.log(`[API] 上传文件: ${fileName}, 大小: ${binaryData.byteLength} 字节`); try { // 使用封装的上传函数 @@ -137,9 +140,26 @@ async function uploadFileToServer( isTestDocument ); - return response; + if (response.error) { + console.error('[API] 上传错误:', response.error); + return { + success: false, + error: response.error + }; + } + + // 确保返回有效的FileUploadResponse对象 + if (response.data) { + return response.data; + } + + // 如果没有数据,则返回错误 + return { + success: false, + error: '上传失败,未获取到响应数据' + }; } catch (error) { - console.error('[模拟API] 上传错误:', error); + console.error('[API] 上传错误:', error); return { success: false, error: error instanceof Error ? error.message : '上传失败' @@ -183,7 +203,7 @@ export async function action({ request }: ActionFunctionArgs) { // 如果有错误,返回错误信息 if (Object.keys(errors).length > 0) { - return json({ errors }); + return Response.json({ errors }); } // 获取文件信息 @@ -195,7 +215,7 @@ export async function action({ request }: ActionFunctionArgs) { // 这里的代码仅用于模拟。在前端组件中,我们将实现实际的文件处理逻辑。 // 模拟文件上传成功响应 - return json({ + return Response.json({ success: true, message: "文件上传请求已接收", fileId: `file_${Date.now()}`, @@ -204,7 +224,7 @@ export async function action({ request }: ActionFunctionArgs) { }); } catch (error) { console.error("文件上传失败:", error); - return json( + return Response.json( { success: false, error: "文件上传失败,请重试" }, { status: 500 } ); @@ -254,6 +274,8 @@ export default function FilesUpload() { const { documents, documentTypes } = useLoaderData(); // 状态管理 + // 高级上传设置 + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [isTestDocument, setIsTestDocument] = useState(false); const [fileType, setFileType] = useState(""); const [priority, setPriority] = useState(Priority.NORMAL); @@ -262,7 +284,7 @@ export default function FilesUpload() { const [currentFiles, setCurrentFiles] = useState([]); const [uploadProgress, setUploadProgress] = useState(0); const [uploadSpeed, setUploadSpeed] = useState("0KB/s"); - const [uploadStage, setUploadStage] = useState<"idle" | "uploading" | "processing" | "completed">("idle"); + const [uploadStage, setUploadStage] = useState<"idle" | "uploading" | "processing" | "completed" | "hadden">("idle"); const [processingSteps, setProcessingSteps] = useState([ { title: "文件上传", description: "等待上传文件到服务器...", status: "waiting" }, { title: "文档转换拆分", description: "转换文档格式,拆分文档内容", status: "waiting" }, @@ -310,7 +332,8 @@ export default function FilesUpload() { useEffect(() => { console.log('设置上传队列状态检查定时器'); - // 设置定时器检查队列中文件的状态 + // 设置定时器检查队列中文件的状态,初始先加载一次查询 + checkQueueStatus(); statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000); // 只在组件卸载时清除 @@ -745,7 +768,6 @@ export default function FilesUpload() { }; - // 格式化文件大小显示 const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; @@ -805,6 +827,11 @@ export default function FilesUpload() { let statusText = ""; switch(record.status) { + case DocumentStatus.WAITING: + statusClass = "status-processing"; + statusIcon = "ri-loader-4-line"; + statusText = "等待中"; + break; case DocumentStatus.CUTTING: statusClass = "status-processing"; statusIcon = "ri-loader-4-line"; @@ -950,7 +977,9 @@ export default function FilesUpload() { tipText="支持单个或批量上传,文件格式:PDF、Word、Excel、图片" shouldPreventFileSelect={!fileType} /> -
+ + {/* 测试文档标记 */} +
+ + {/* 高级上传设置 */} + { showAdvancedOptions && ( +
+
setShowAdvancedOptions(!showAdvancedOptions)} + > + 高级上传设置 + +
+ +
+
+
+ + +
选择文档的存储位置
+
+ +
+ + +
上传完成后自动执行的操作
+
+
+
+
+ + )} )} @@ -982,7 +1066,8 @@ export default function FilesUpload() { )} {/* 文件信息显示 - 上传完成后显示 */} - {uploadStage !== "idle" && completedFiles.length > 0 && ( + {/* {uploadStage !== "idle" && completedFiles.length > 0 && ( */} + {uploadStage === "hadden" && completedFiles.length > 0 && (

文件信息

diff --git a/app/styles/pages/files_upload.css b/app/styles/pages/files_upload.css index 7cd382d..c9d2562 100644 --- a/app/styles/pages/files_upload.css +++ b/app/styles/pages/files_upload.css @@ -58,6 +58,28 @@ @apply transform translate-x-5; } +/* 高级上传设置 */ +/* 高级选项区域 */ +.file-upload-page .advanced-options { + @apply mt-4; +} + +.file-upload-page .advanced-options-toggle { + @apply text-[var(--primary-color)] cursor-pointer inline-flex items-center text-sm; +} + +.file-upload-page .advanced-options-toggle i { + @apply ml-1 transition-transform duration-200; +} + +.file-upload-page .advanced-options-toggle.open i { + @apply rotate-180; +} + +.file-upload-page .advanced-options-content { + @apply mt-3 p-3 bg-gray-50 border border-gray-200 rounded-md hidden; +} + .file-upload-page .form-label { @apply block text-sm font-medium text-gray-700 mb-2; }