修改文档列表
This commit is contained in:
+1
-1
@@ -16,7 +16,7 @@ export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|||||||
// 获取 API 基础 URL
|
// 获取 API 基础 URL
|
||||||
// const API_BASE_URL = '172.18.0.100:3000';
|
// const API_BASE_URL = '172.18.0.100:3000';
|
||||||
// const API_BASE_URL = '172.16.0.119:9000/admin';
|
// 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连接问题
|
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
|
||||||
|
|||||||
@@ -1,133 +0,0 @@
|
|||||||
import { apiRequest } from './client';
|
|
||||||
import { FileType, Priority } from '~/types/enums';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将文件转换为二进制数据
|
|
||||||
*/
|
|
||||||
export async function uploadFileToBinary(file: File): Promise<ArrayBuffer> {
|
|
||||||
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 : '上传失败'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -67,6 +67,8 @@ export interface Document {
|
|||||||
is_test_document: boolean;
|
is_test_document: boolean;
|
||||||
evaluation_level: string;
|
evaluation_level: string;
|
||||||
status: 'pass' | 'warning' | 'waiting' | 'processing' | 'fail';
|
status: 'pass' | 'warning' | 'waiting' | 'processing' | 'fail';
|
||||||
|
file_status: 'Waiting' | 'Cutting' | 'Extractioning' | 'Evaluationing' | 'Processed';
|
||||||
|
audit_status: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中
|
||||||
ocr_result: unknown;
|
ocr_result: unknown;
|
||||||
extracted_results: unknown;
|
extracted_results: unknown;
|
||||||
summary: unknown;
|
summary: unknown;
|
||||||
@@ -85,7 +87,8 @@ export interface DocumentUI {
|
|||||||
type: string;
|
type: string;
|
||||||
typeName: string;
|
typeName: string;
|
||||||
size: number;
|
size: number;
|
||||||
status: string;
|
auditStatus: number; // -1: 不通过, 0: 待审核, 1: 通过, 2: 警告, 3: 审核中
|
||||||
|
fileStatus: string; // Waiting, Cutting, Extractioning, Evaluationing, Processed
|
||||||
issues: number | null;
|
issues: number | null;
|
||||||
uploadTime: string;
|
uploadTime: string;
|
||||||
fileType: string;
|
fileType: string;
|
||||||
@@ -119,7 +122,8 @@ async function convertToUIDocument(doc: Document): Promise<DocumentUI> {
|
|||||||
type: doc.type_id.toString(),
|
type: doc.type_id.toString(),
|
||||||
typeName: docType?.name || '未知类型',
|
typeName: docType?.name || '未知类型',
|
||||||
size: doc.file_size,
|
size: doc.file_size,
|
||||||
status: doc.status,
|
auditStatus: doc.audit_status,
|
||||||
|
fileStatus: doc.file_status || 'Processed', // 默认为已处理
|
||||||
issues: 0, // 固定为0
|
issues: 0, // 固定为0
|
||||||
uploadTime: formatDate(doc.updated_at),
|
uploadTime: formatDate(doc.updated_at),
|
||||||
fileType: getFileExtension(doc.name),
|
fileType: getFileExtension(doc.name),
|
||||||
@@ -332,13 +336,6 @@ export async function getFileDownloadUrl(filePath: string): Promise<{
|
|||||||
return { error: '文件路径不能为空', status: 400 };
|
return { error: '文件路径不能为空', status: 400 };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建API请求参数
|
|
||||||
const params: PostgrestParams = {
|
|
||||||
filter: {
|
|
||||||
'path': `eq.${filePath}`
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 这里应该调用获取文件下载链接的API
|
// 这里应该调用获取文件下载链接的API
|
||||||
// 假设后端有这样的端点:/api/files/generate-download-url?path=xxx
|
// 假设后端有这样的端点:/api/files/generate-download-url?path=xxx
|
||||||
// 实际项目中需要根据你的后端API调整
|
// 实际项目中需要根据你的后端API调整
|
||||||
@@ -387,8 +384,8 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
|
|||||||
apiDocument.type_id = parseInt(document.type);
|
apiDocument.type_id = parseInt(document.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.status !== undefined) {
|
if (document.auditStatus !== undefined) {
|
||||||
apiDocument.status = document.status as 'pass' | 'warning' | 'waiting' | 'processing' | 'fail';
|
apiDocument.audit_status = document.auditStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.isTest !== undefined) {
|
if (document.isTest !== undefined) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client';
|
import { postgrestGet, postgrestPut, postgrestPost, type PostgrestParams } from '../postgrest-client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
// import { API_BASE_URL } from '../client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化日期
|
* 格式化日期
|
||||||
@@ -39,6 +40,7 @@ function extractApiData<T>(responseData: unknown): T | null {
|
|||||||
|
|
||||||
// 文档状态枚举
|
// 文档状态枚举
|
||||||
export enum DocumentStatus {
|
export enum DocumentStatus {
|
||||||
|
WAITING = "waiting",
|
||||||
CUTTING = "Cutting",
|
CUTTING = "Cutting",
|
||||||
EXTRACTIONING = "extractioning",
|
EXTRACTIONING = "extractioning",
|
||||||
REVIEWING = "reviewing",
|
REVIEWING = "reviewing",
|
||||||
@@ -80,6 +82,132 @@ export interface Document {
|
|||||||
remark?: string;
|
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<ArrayBuffer> {
|
||||||
|
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<FileUploadResponse>(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 文档列表
|
* @returns 文档列表
|
||||||
|
|||||||
@@ -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<PaginatedResponse<File>> {
|
|
||||||
const url = buildUrl('/api/files', params);
|
|
||||||
return apiRequest<PaginatedResponse<File>>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单个文件
|
|
||||||
export async function getFile(id: string): Promise<File> {
|
|
||||||
const url = buildUrl(`/api/files/${id}`);
|
|
||||||
return apiRequest<File>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 上传文件
|
|
||||||
export async function uploadFile(formData: FormData): Promise<File> {
|
|
||||||
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<void> {
|
|
||||||
const url = buildUrl(`/api/files/${id}`);
|
|
||||||
return apiRequest<void>(url, 'DELETE');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取文档类型列表
|
|
||||||
export async function getDocumentTypes(): Promise<DocumentType[]> {
|
|
||||||
const url = buildUrl('/api/document-types');
|
|
||||||
return apiRequest<DocumentType[]>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单个文档类型
|
|
||||||
export async function getDocumentType(id: string): Promise<DocumentType> {
|
|
||||||
const url = buildUrl(`/api/document-types/${id}`);
|
|
||||||
return apiRequest<DocumentType>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建文档类型
|
|
||||||
// 请使用 ~/api/document-types/document-types.ts 中的实现
|
|
||||||
/*
|
|
||||||
export async function createDocumentType(documentType: Omit<DocumentType, 'id' | 'createdAt' | 'updatedAt'>): Promise<DocumentType> {
|
|
||||||
const url = buildUrl('/api/document-types');
|
|
||||||
return apiRequest<DocumentType>(url, 'POST', documentType);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 更新文档类型
|
|
||||||
// 请使用 ~/api/document-types/document-types.ts 中的实现
|
|
||||||
/*
|
|
||||||
export async function updateDocumentType(id: string, documentType: Partial<Omit<DocumentType, 'id' | 'createdAt' | 'updatedAt'>>): Promise<DocumentType> {
|
|
||||||
const url = buildUrl(`/api/document-types/${id}`);
|
|
||||||
return apiRequest<DocumentType>(url, 'PUT', documentType);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 删除文档类型
|
|
||||||
export async function deleteDocumentType(id: string): Promise<void> {
|
|
||||||
const url = buildUrl(`/api/document-types/${id}`);
|
|
||||||
return apiRequest<void>(url, 'DELETE');
|
|
||||||
}
|
|
||||||
@@ -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<PaginatedResponse<Rule>> {
|
|
||||||
const url = buildUrl('/api/rules', params);
|
|
||||||
return apiRequest<PaginatedResponse<Rule>>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单个评查点
|
|
||||||
export async function getRule(id: string): Promise<Rule> {
|
|
||||||
const url = buildUrl(`/api/rules/${id}`);
|
|
||||||
return apiRequest<Rule>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建评查点
|
|
||||||
export async function createRule(rule: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>): Promise<Rule> {
|
|
||||||
const url = buildUrl('/api/rules');
|
|
||||||
return apiRequest<Rule>(url, 'POST', rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新评查点
|
|
||||||
export async function updateRule(id: string, rule: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>): Promise<Rule> {
|
|
||||||
const url = buildUrl(`/api/rules/${id}`);
|
|
||||||
return apiRequest<Rule>(url, 'PUT', rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除评查点
|
|
||||||
export async function deleteRule(id: string): Promise<void> {
|
|
||||||
const url = buildUrl(`/api/rules/${id}`);
|
|
||||||
return apiRequest<void>(url, 'DELETE');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取评查点分组列表
|
|
||||||
export async function getRuleGroups(): Promise<RuleGroup[]> {
|
|
||||||
const url = buildUrl('/api/rule-groups');
|
|
||||||
return apiRequest<RuleGroup[]>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取单个评查点分组
|
|
||||||
export async function getRuleGroup(id: string): Promise<RuleGroup> {
|
|
||||||
const url = buildUrl(`/api/rule-groups/${id}`);
|
|
||||||
return apiRequest<RuleGroup>(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建评查点分组
|
|
||||||
export async function createRuleGroup(group: Omit<RuleGroup, 'id' | 'createdAt' | 'updatedAt'>): Promise<RuleGroup> {
|
|
||||||
const url = buildUrl('/api/rule-groups');
|
|
||||||
return apiRequest<RuleGroup>(url, 'POST', group);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新评查点分组
|
|
||||||
export async function updateRuleGroup(id: string, group: Partial<Omit<RuleGroup, 'id' | 'createdAt' | 'updatedAt'>>): Promise<RuleGroup> {
|
|
||||||
const url = buildUrl(`/api/rule-groups/${id}`);
|
|
||||||
return apiRequest<RuleGroup>(url, 'PUT', group);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除评查点分组
|
|
||||||
export async function deleteRuleGroup(id: string): Promise<void> {
|
|
||||||
const url = buildUrl(`/api/rule-groups/${id}`);
|
|
||||||
return apiRequest<void>(url, 'DELETE');
|
|
||||||
}
|
|
||||||
@@ -5,12 +5,11 @@ import { Card } from "~/components/ui/Card";
|
|||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
import { Table } from "~/components/ui/Table";
|
import { Table } from "~/components/ui/Table";
|
||||||
import { Pagination } from "~/components/ui/Pagination";
|
import { Pagination } from "~/components/ui/Pagination";
|
||||||
import { StatusBadge } from "~/components/ui/StatusBadge";
|
|
||||||
import { FileTypeTag } from "~/components/ui/FileTypeTag";
|
import { FileTypeTag } from "~/components/ui/FileTypeTag";
|
||||||
import { FileTag } from "~/components/ui/FileTag";
|
import { FileTag } from "~/components/ui/FileTag";
|
||||||
import { FilterPanel, FilterSelect, SearchFilter, DateRangeFilter } from "~/components/ui/FilterPanel";
|
import { FilterPanel, FilterSelect, SearchFilter, DateRangeFilter } from "~/components/ui/FilterPanel";
|
||||||
import documentsIndexStyles from "~/styles/pages/documents_index.css?url";
|
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";
|
import { getDocumentTypes } from "~/api/document-types/document-types";
|
||||||
|
|
||||||
// 导入样式
|
// 导入样式
|
||||||
@@ -122,6 +121,24 @@ const documentStatusOptions = [
|
|||||||
{ value: "fail", label: "不通过" },
|
{ 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<string, { label: string; color: string; icon: string }> = {
|
||||||
|
"-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) => {
|
const formatFileSize = (bytes: number) => {
|
||||||
if (bytes === 0) return "0 Bytes";
|
if (bytes === 0) return "0 Bytes";
|
||||||
@@ -417,12 +434,34 @@ export default function DocumentsIndex() {
|
|||||||
width: "100px",
|
width: "100px",
|
||||||
render: (_: unknown, record: DocumentUI) => formatFileSize(record.size)
|
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 (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<i className={`${status.icon} ${isSpinning ? "animate-spin" : ""} mr-1`}></i>
|
||||||
|
<span>{status.label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "审核状态",
|
title: "审核状态",
|
||||||
key: "status",
|
key: "auditStatus",
|
||||||
render: (_: unknown, record: DocumentUI) => (
|
render: (_: unknown, record: DocumentUI) => {
|
||||||
<StatusBadge status={record.status} showIcon={false} />
|
const statusKey = record.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>
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "问题数量",
|
title: "问题数量",
|
||||||
@@ -443,7 +482,7 @@ export default function DocumentsIndex() {
|
|||||||
width: "280px",
|
width: "280px",
|
||||||
render: (_: unknown, record: DocumentUI) => (
|
render: (_: unknown, record: DocumentUI) => (
|
||||||
<div className="operations-cell">
|
<div className="operations-cell">
|
||||||
{record.status === "waiting" ? (
|
{record.auditStatus === 0 ? (
|
||||||
<Link
|
<Link
|
||||||
to={`/documents/${record.id}/review`}
|
to={`/documents/${record.id}/review`}
|
||||||
className="mr-1 hover:underline"
|
className="mr-1 hover:underline"
|
||||||
@@ -451,7 +490,7 @@ export default function DocumentsIndex() {
|
|||||||
<i className="ri-play-circle-line"></i>
|
<i className="ri-play-circle-line"></i>
|
||||||
开始审核
|
开始审核
|
||||||
</Link>
|
</Link>
|
||||||
) : record.status === "processing" ? (
|
) : record.auditStatus === 3 ? (
|
||||||
<Link
|
<Link
|
||||||
to={`/documents/${record.id}/progress`}
|
to={`/documents/${record.id}/progress`}
|
||||||
className="mr-1 hover:underline"
|
className="mr-1 hover:underline"
|
||||||
@@ -505,7 +544,7 @@ export default function DocumentsIndex() {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="ri-upload-line"
|
icon="ri-upload-line"
|
||||||
to="/documents/upload"
|
to="/files/upload"
|
||||||
className="hover:text-white"
|
className="hover:text-white"
|
||||||
>
|
>
|
||||||
上传文档
|
上传文档
|
||||||
|
|||||||
@@ -19,22 +19,31 @@ export const meta: MetaFunction = () => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// 文档状态定义
|
// 文档审核状态定义
|
||||||
enum DocumentStatus {
|
enum DocumentAuditStatus {
|
||||||
WAITING = "waiting",
|
FAIL = -1,
|
||||||
PROCESSING = "processing",
|
WAITING = 0,
|
||||||
PASS = "pass",
|
PASS = 1,
|
||||||
WARNING = "warning",
|
WARNING = 2,
|
||||||
FAIL = "fail"
|
PROCESSING = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// 文档状态对应的中文标签
|
// 文档状态对应的中文标签
|
||||||
const STATUS_LABELS: Record<DocumentStatus, string> = {
|
const STATUS_LABELS: Record<DocumentAuditStatus, string> = {
|
||||||
[DocumentStatus.WAITING]: "待审核",
|
[DocumentAuditStatus.FAIL]: "不通过",
|
||||||
[DocumentStatus.PROCESSING]: "审核中",
|
[DocumentAuditStatus.WAITING]: "待审核",
|
||||||
[DocumentStatus.PASS]: "通过",
|
[DocumentAuditStatus.PASS]: "通过",
|
||||||
[DocumentStatus.WARNING]: "警告",
|
[DocumentAuditStatus.WARNING]: "警告",
|
||||||
[DocumentStatus.FAIL]: "不通过"
|
[DocumentAuditStatus.PROCESSING]: "审核中"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文档状态样式配置
|
||||||
|
const STATUS_STYLES: Record<number, { color: string; icon: string }> = {
|
||||||
|
[-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 type = formData.get("type_id") as string;
|
||||||
const documentNumber = formData.get("document_number") 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 isTest = formData.get("is_test_document") === "on";
|
||||||
const remark = formData.get("remark") as string;
|
const remark = formData.get("remark") as string;
|
||||||
|
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (!type || !status) {
|
if (!type || auditStatus === undefined || isNaN(auditStatus)) {
|
||||||
return Response.json(
|
return Response.json(
|
||||||
{
|
{
|
||||||
error: "缺少必填字段",
|
error: "缺少必填字段",
|
||||||
fieldErrors: {
|
fieldErrors: {
|
||||||
type_id: !type ? "文档类型不能为空" : null,
|
type_id: !type ? "文档类型不能为空" : null,
|
||||||
status: !status ? "状态不能为空" : null
|
audit_status: (auditStatus === undefined || isNaN(auditStatus)) ? "审核状态不能为空" : null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('提交更新:', { type, documentNumber, status, isTest, remark });
|
console.log('提交更新:', { type, documentNumber, auditStatus, isTest, remark });
|
||||||
|
|
||||||
// 更新文档
|
// 更新文档
|
||||||
const updateResponse = await updateDocument(id, {
|
const updateResponse = await updateDocument(id, {
|
||||||
type,
|
type,
|
||||||
documentNumber,
|
documentNumber,
|
||||||
status,
|
auditStatus,
|
||||||
isTest,
|
isTest,
|
||||||
remark
|
remark
|
||||||
});
|
});
|
||||||
@@ -160,43 +169,38 @@ export default function DocumentEdit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const [localStatus, setLocalStatus] = useState<DocumentStatus>(document.status as DocumentStatus);
|
const [localStatus, setLocalStatus] = useState<number>(document.auditStatus);
|
||||||
|
|
||||||
// 处理状态变更
|
// 处理状态变更
|
||||||
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleStatusChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
setLocalStatus(e.target.value as DocumentStatus);
|
setLocalStatus(parseInt(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 获取文档类型名称
|
// 获取文档类型名称
|
||||||
const getDocumentTypeName = (typeId: string): string => {
|
const getDocumentTypeName = (typeId: string): string => {
|
||||||
const docType = documentTypes.find((type) => (type as any).id.toString() === typeId);
|
const docType = documentTypes.find((type: DocType) => type.id.toString() === typeId);
|
||||||
return docType ? (docType as any).name : "未知类型";
|
return docType ? docType.name : "未知类型";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染状态徽章
|
// 渲染状态徽章
|
||||||
const renderStatusBadge = (status: string) => {
|
const renderStatusBadge = (status: number) => {
|
||||||
const statusClasses: Record<string, string> = {
|
const style = STATUS_STYLES[status] || STATUS_STYLES[0];
|
||||||
"waiting": "status-badge status-pending",
|
const label = STATUS_LABELS[status as DocumentAuditStatus] || STATUS_LABELS[DocumentAuditStatus.WAITING];
|
||||||
"processing": "status-badge status-processing",
|
|
||||||
"pass": "status-badge status-pass",
|
|
||||||
"warning": "status-badge status-warning",
|
|
||||||
"fail": "status-badge status-fail"
|
|
||||||
};
|
|
||||||
|
|
||||||
const statusLabel: Record<string, string> = {
|
|
||||||
"waiting": "待审核",
|
|
||||||
"processing": "审核中",
|
|
||||||
"pass": "通过",
|
|
||||||
"warning": "警告",
|
|
||||||
"fail": "不通过"
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={statusClasses[status] || "status-badge"}>
|
<span className={`status-badge bg-${style.color}-100 text-${style.color}-800`}>
|
||||||
{statusLabel[status] || status}
|
<i className={`${style.icon} mr-1`}></i>
|
||||||
|
{label}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 在新窗口打开文档预览
|
||||||
|
const openPreview = () => {
|
||||||
|
// 假设有一个预览URL的格式,比如 /preview?path=xxx
|
||||||
|
const previewUrl = `/preview?path=${encodeURIComponent(document.path)}&name=${encodeURIComponent(document.name)}`;
|
||||||
|
window.open(previewUrl, '_blank');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="document-edit-page">
|
<div className="document-edit-page">
|
||||||
@@ -250,7 +254,7 @@ export default function DocumentEdit() {
|
|||||||
<span>{formatFileSize(document.size)}</span>
|
<span>{formatFileSize(document.size)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="meta-item">
|
<div className="meta-item">
|
||||||
{renderStatusBadge(document.status)}
|
{renderStatusBadge(document.auditStatus)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -271,7 +275,7 @@ export default function DocumentEdit() {
|
|||||||
defaultValue={document.type}
|
defaultValue={document.type}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
{documentTypes.map(type => (
|
{documentTypes.map((type: DocType) => (
|
||||||
<option key={type.id} value={type.id}>{type.name}</option>
|
<option key={type.id} value={type.id}>{type.name}</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
@@ -295,24 +299,24 @@ export default function DocumentEdit() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="status" className="form-label">状态 <span className="text-red-500">*</span></label>
|
<label htmlFor="audit-status" className="form-label">审核状态 <span className="text-red-500">*</span></label>
|
||||||
<select
|
<select
|
||||||
id="status"
|
id="audit-status"
|
||||||
name="status"
|
name="audit_status"
|
||||||
className="form-select"
|
className="form-select"
|
||||||
value={localStatus}
|
value={localStatus}
|
||||||
onChange={handleStatusChange}
|
onChange={handleStatusChange}
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
<option value={DocumentStatus.WAITING}>{STATUS_LABELS[DocumentStatus.WAITING]}</option>
|
<option value={DocumentAuditStatus.WAITING}>{STATUS_LABELS[DocumentAuditStatus.WAITING]}</option>
|
||||||
<option value={DocumentStatus.PROCESSING}>{STATUS_LABELS[DocumentStatus.PROCESSING]}</option>
|
<option value={DocumentAuditStatus.PROCESSING}>{STATUS_LABELS[DocumentAuditStatus.PROCESSING]}</option>
|
||||||
<option value={DocumentStatus.PASS}>{STATUS_LABELS[DocumentStatus.PASS]}</option>
|
<option value={DocumentAuditStatus.PASS}>{STATUS_LABELS[DocumentAuditStatus.PASS]}</option>
|
||||||
<option value={DocumentStatus.WARNING}>{STATUS_LABELS[DocumentStatus.WARNING]}</option>
|
<option value={DocumentAuditStatus.WARNING}>{STATUS_LABELS[DocumentAuditStatus.WARNING]}</option>
|
||||||
<option value={DocumentStatus.FAIL}>{STATUS_LABELS[DocumentStatus.FAIL]}</option>
|
<option value={DocumentAuditStatus.FAIL}>{STATUS_LABELS[DocumentAuditStatus.FAIL]}</option>
|
||||||
</select>
|
</select>
|
||||||
<div className="text-sm text-secondary mt-1">更改状态可能会影响此文档在列表中的显示和排序</div>
|
<div className="text-sm text-secondary mt-1">更改状态可能会影响此文档在列表中的显示和排序</div>
|
||||||
{actionData?.fieldErrors?.status && (
|
{actionData?.fieldErrors?.audit_status && (
|
||||||
<div className="text-red-500 text-sm mt-1">{actionData.fieldErrors.status}</div>
|
<div className="text-red-500 text-sm mt-1">{actionData.fieldErrors.audit_status}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -356,7 +360,7 @@ export default function DocumentEdit() {
|
|||||||
<div className="document-preview">
|
<div className="document-preview">
|
||||||
<div className="preview-toolbar">
|
<div className="preview-toolbar">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<i className="ri-file-pdf-line text-red-500 mr-1"></i>
|
<i className={`ri-file-${document.fileType}-line text-${document.fileType === 'pdf' ? 'red' : 'blue'}-500 mr-1`}></i>
|
||||||
<span>{document.name}</span>
|
<span>{document.name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -364,21 +368,31 @@ export default function DocumentEdit() {
|
|||||||
type="default"
|
type="default"
|
||||||
size="small"
|
size="small"
|
||||||
icon="ri-download-line"
|
icon="ri-download-line"
|
||||||
|
className="mr-2"
|
||||||
>
|
>
|
||||||
下载
|
下载
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon="ri-external-link-line"
|
||||||
|
onClick={openPreview}
|
||||||
|
>
|
||||||
|
在新窗口打开
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="preview-content">
|
<div className="preview-content">
|
||||||
<div className="preview-placeholder">
|
<div className="preview-placeholder">
|
||||||
<i className="ri-file-pdf-line"></i>
|
<i className={`ri-file-${document.fileType}-line`}></i>
|
||||||
<p>预览功能暂不可用</p>
|
<p>预览功能暂不可用</p>
|
||||||
<p className="text-xs mt-2">PDF文件需要外部查看器支持</p>
|
<p className="text-xs mt-2">点击"在新窗口打开"查看完整文档</p>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
icon="ri-external-link-line"
|
icon="ri-external-link-line"
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
|
onClick={openPreview}
|
||||||
>
|
>
|
||||||
在新窗口打开
|
在新窗口打开
|
||||||
</Button>
|
</Button>
|
||||||
@@ -388,7 +402,7 @@ export default function DocumentEdit() {
|
|||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* 修改历史 */}
|
{/* 修改历史 */}
|
||||||
<Card title="修改历史">
|
<Card title="修改历史" className="hidden">
|
||||||
<div className="history-timeline">
|
<div className="history-timeline">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
|
|||||||
+119
-34
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
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 { Form, useActionData, useLoaderData } from "@remix-run/react";
|
||||||
import { Card } from "~/components/ui/Card";
|
import { Card } from "~/components/ui/Card";
|
||||||
import { Button } from "~/components/ui/Button";
|
import { Button } from "~/components/ui/Button";
|
||||||
@@ -8,8 +8,17 @@ import { UploadArea, UploadAreaRef } from "~/components/ui/UploadArea";
|
|||||||
import { FileProgress} from "~/components/ui/FileProgress";
|
import { FileProgress} from "~/components/ui/FileProgress";
|
||||||
import { ProcessingSteps, Step } from "~/components/ui/ProcessingSteps";
|
import { ProcessingSteps, Step } from "~/components/ui/ProcessingSteps";
|
||||||
import uploadStyles from "~/styles/pages/files_upload.css?url";
|
import uploadStyles from "~/styles/pages/files_upload.css?url";
|
||||||
import { getTodayDocuments, getDocumentTypes, getDocumentsStatus, type Document, type DocumentType, DocumentStatus } from "~/api/files/files-upload";
|
import {
|
||||||
import { uploadFileToBinary, uploadDocumentToServer } from "~/api/files";
|
getTodayDocuments,
|
||||||
|
getDocumentTypes,
|
||||||
|
getDocumentsStatus,
|
||||||
|
uploadFileToBinary,
|
||||||
|
uploadDocumentToServer,
|
||||||
|
type Document,
|
||||||
|
type DocumentType,
|
||||||
|
type FileUploadResponse,
|
||||||
|
DocumentStatus
|
||||||
|
} from "~/api/files/files-upload";
|
||||||
|
|
||||||
export function links() {
|
export function links() {
|
||||||
return [
|
return [
|
||||||
@@ -74,6 +83,20 @@ export enum StepStatus {
|
|||||||
COMPLETED = "completed"
|
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 {
|
export interface UploadedFile {
|
||||||
id: number;
|
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
|
// 模拟上传文件到服务器的API
|
||||||
async function uploadFileToServer(
|
async function uploadFileToServer(
|
||||||
binaryData: ArrayBuffer,
|
binaryData: ArrayBuffer,
|
||||||
@@ -122,7 +125,7 @@ async function uploadFileToServer(
|
|||||||
isTestDocument: boolean
|
isTestDocument: boolean
|
||||||
): Promise<FileUploadResponse> {
|
): Promise<FileUploadResponse> {
|
||||||
// 在实际应用中,这里会使用fetch或axios发送请求到后端API
|
// 在实际应用中,这里会使用fetch或axios发送请求到后端API
|
||||||
console.log(`[模拟API] 上传文件: ${fileName}, 大小: ${binaryData.byteLength} 字节`);
|
console.log(`[API] 上传文件: ${fileName}, 大小: ${binaryData.byteLength} 字节`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用封装的上传函数
|
// 使用封装的上传函数
|
||||||
@@ -137,9 +140,26 @@ async function uploadFileToServer(
|
|||||||
isTestDocument
|
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) {
|
} catch (error) {
|
||||||
console.error('[模拟API] 上传错误:', error);
|
console.error('[API] 上传错误:', error);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : '上传失败'
|
error: error instanceof Error ? error.message : '上传失败'
|
||||||
@@ -183,7 +203,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
|
|
||||||
// 如果有错误,返回错误信息
|
// 如果有错误,返回错误信息
|
||||||
if (Object.keys(errors).length > 0) {
|
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,
|
success: true,
|
||||||
message: "文件上传请求已接收",
|
message: "文件上传请求已接收",
|
||||||
fileId: `file_${Date.now()}`,
|
fileId: `file_${Date.now()}`,
|
||||||
@@ -204,7 +224,7 @@ export async function action({ request }: ActionFunctionArgs) {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("文件上传失败:", error);
|
console.error("文件上传失败:", error);
|
||||||
return json(
|
return Response.json(
|
||||||
{ success: false, error: "文件上传失败,请重试" },
|
{ success: false, error: "文件上传失败,请重试" },
|
||||||
{ status: 500 }
|
{ status: 500 }
|
||||||
);
|
);
|
||||||
@@ -254,6 +274,8 @@ export default function FilesUpload() {
|
|||||||
const { documents, documentTypes } = useLoaderData<LoaderData>();
|
const { documents, documentTypes } = useLoaderData<LoaderData>();
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
|
// 高级上传设置
|
||||||
|
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||||
const [isTestDocument, setIsTestDocument] = useState(false);
|
const [isTestDocument, setIsTestDocument] = useState(false);
|
||||||
const [fileType, setFileType] = useState<FileType | "">("");
|
const [fileType, setFileType] = useState<FileType | "">("");
|
||||||
const [priority, setPriority] = useState<Priority>(Priority.NORMAL);
|
const [priority, setPriority] = useState<Priority>(Priority.NORMAL);
|
||||||
@@ -262,7 +284,7 @@ export default function FilesUpload() {
|
|||||||
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
|
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
const [uploadSpeed, setUploadSpeed] = useState("0KB/s");
|
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<Step[]>([
|
const [processingSteps, setProcessingSteps] = useState<Step[]>([
|
||||||
{ title: "文件上传", description: "等待上传文件到服务器...", status: "waiting" },
|
{ title: "文件上传", description: "等待上传文件到服务器...", status: "waiting" },
|
||||||
{ title: "文档转换拆分", description: "转换文档格式,拆分文档内容", status: "waiting" },
|
{ title: "文档转换拆分", description: "转换文档格式,拆分文档内容", status: "waiting" },
|
||||||
@@ -310,7 +332,8 @@ export default function FilesUpload() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('设置上传队列状态检查定时器');
|
console.log('设置上传队列状态检查定时器');
|
||||||
|
|
||||||
// 设置定时器检查队列中文件的状态
|
// 设置定时器检查队列中文件的状态,初始先加载一次查询
|
||||||
|
checkQueueStatus();
|
||||||
statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000);
|
statusCheckIntervalRef.current = setInterval(checkQueueStatus, 10000);
|
||||||
|
|
||||||
// 只在组件卸载时清除
|
// 只在组件卸载时清除
|
||||||
@@ -745,7 +768,6 @@ export default function FilesUpload() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 格式化文件大小显示
|
// 格式化文件大小显示
|
||||||
const formatFileSize = (bytes: number) => {
|
const formatFileSize = (bytes: number) => {
|
||||||
if (bytes === 0) return '0 Bytes';
|
if (bytes === 0) return '0 Bytes';
|
||||||
@@ -805,6 +827,11 @@ export default function FilesUpload() {
|
|||||||
let statusText = "";
|
let statusText = "";
|
||||||
|
|
||||||
switch(record.status) {
|
switch(record.status) {
|
||||||
|
case DocumentStatus.WAITING:
|
||||||
|
statusClass = "status-processing";
|
||||||
|
statusIcon = "ri-loader-4-line";
|
||||||
|
statusText = "等待中";
|
||||||
|
break;
|
||||||
case DocumentStatus.CUTTING:
|
case DocumentStatus.CUTTING:
|
||||||
statusClass = "status-processing";
|
statusClass = "status-processing";
|
||||||
statusIcon = "ri-loader-4-line";
|
statusIcon = "ri-loader-4-line";
|
||||||
@@ -950,7 +977,9 @@ export default function FilesUpload() {
|
|||||||
tipText="支持单个或批量上传,文件格式:PDF、Word、Excel、图片"
|
tipText="支持单个或批量上传,文件格式:PDF、Word、Excel、图片"
|
||||||
shouldPreventFileSelect={!fileType}
|
shouldPreventFileSelect={!fileType}
|
||||||
/>
|
/>
|
||||||
<div className="switch-container">
|
|
||||||
|
{/* 测试文档标记 */}
|
||||||
|
<div className="switch-container mb-4">
|
||||||
<label className="switch" aria-label="标记为测试文档">
|
<label className="switch" aria-label="标记为测试文档">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -961,6 +990,61 @@ export default function FilesUpload() {
|
|||||||
</label>
|
</label>
|
||||||
<span>标记为测试文档(不计入正式统计)</span>
|
<span>标记为测试文档(不计入正式统计)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 高级上传设置 */}
|
||||||
|
{ showAdvancedOptions && (
|
||||||
|
<div className="advanced-options">
|
||||||
|
<div
|
||||||
|
className={`advanced-options-toggle ${showAdvancedOptions ? 'open' : ''}`}
|
||||||
|
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||||
|
>
|
||||||
|
<span>高级上传设置</span>
|
||||||
|
<i className="ri-arrow-down-s-line"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="advanced-options-content"
|
||||||
|
style={{ display: showAdvancedOptions ? 'block' : 'none' }}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="storageType">存储方式</label>
|
||||||
|
<select
|
||||||
|
id="storageType"
|
||||||
|
name="storageType"
|
||||||
|
className="form-select w-full"
|
||||||
|
defaultValue="minio"
|
||||||
|
>
|
||||||
|
{STORAGE_TYPES.map(type => (
|
||||||
|
<option key={type.id} value={type.id}>
|
||||||
|
{type.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className="form-tip">选择文档的存储位置</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="form-group">
|
||||||
|
<label className="form-label" htmlFor="afterUpload">上传后操作</label>
|
||||||
|
<select
|
||||||
|
id="afterUpload"
|
||||||
|
name="afterUpload"
|
||||||
|
className="form-select w-full"
|
||||||
|
defaultValue="list"
|
||||||
|
>
|
||||||
|
{AFTER_UPLOAD_OPTIONS.map(option => (
|
||||||
|
<option key={option.id} value={option.id}>
|
||||||
|
{option.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<div className="form-tip">上传完成后自动执行的操作</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -982,7 +1066,8 @@ export default function FilesUpload() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 文件信息显示 - 上传完成后显示 */}
|
{/* 文件信息显示 - 上传完成后显示 */}
|
||||||
{uploadStage !== "idle" && completedFiles.length > 0 && (
|
{/* {uploadStage !== "idle" && completedFiles.length > 0 && ( */}
|
||||||
|
{uploadStage === "hadden" && completedFiles.length > 0 && (
|
||||||
<div className="file-info-grid">
|
<div className="file-info-grid">
|
||||||
<div>
|
<div>
|
||||||
<h4 className="font-medium mb-3">文件信息</h4>
|
<h4 className="font-medium mb-3">文件信息</h4>
|
||||||
|
|||||||
@@ -58,6 +58,28 @@
|
|||||||
@apply transform translate-x-5;
|
@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 {
|
.file-upload-page .form-label {
|
||||||
@apply block text-sm font-medium text-gray-700 mb-2;
|
@apply block text-sm font-medium text-gray-700 mb-2;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user