Files
leaudit-platform-frontend/app/api/cross-checking/cross-files.ts
T
2026-05-07 19:26:37 +08:00

1209 lines
34 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { API_BASE_URL } from '../../config/api-config';
import { postgrestPut, postgrestGet } from '../postgrest-client';
import axios from 'axios';
export { uploadDocumentToTask } from './cross-files-upload';
// 交叉评查任务状态枚举
export enum CrossCheckingTaskStatus {
PENDING = 'pending',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed'
}
// 交叉评查任务类型枚举
export enum CrossCheckingTaskType {
CITY = 'CITY',
DISTRICT = 'DISTRICT'
}
// 案卷类型枚举
export enum CrossCheckingDocType {
PENALTY = 'penalty', // 行政处罚
PERMIT = 'permit' // 行政许可
}
// 文档类型接口(用于交叉评查案卷类型选项)
export interface DocumentType {
id: number;
name: string;
code: string;
evaluation_point_groups_ids?: number[];
}
// 交叉评查任务接口
export interface CrossCheckingTask {
id: number;
sequence: number;
taskName: string;
startDate: string;
taskType: CrossCheckingTaskType;
docType: string; // 改为直接使用返回的 doc_type 字符串
evaluationRegion: string[];
progress: number;
status: string; // 改为直接使用返回的 task_status 字符串
score: number;
operation: string;
documents: UserTaskDocument[]; // 改为 documents 数组,包含完整的文档信息
totalDocuments?: number; // 新增:任务包含的文档总数
}
// 用户任务文档接口类型定义
export interface UserTaskDocument {
document_id: number;
document_name: string;
document_type_id: number;
document_type_name: string;
}
// 新的用户任务信息接口(根据新的API格式)
export interface UserTaskInfo {
task_id: number;
task_name?: string;
task_status: string;
doc_type?: string;
task_created_at?: string;
evaluation_region?: string[];
task_type?: string;
progress?: number;
total_documents?: number; // 新增:任务包含的文档总数
}
// 用户任务API响应格式(新格式)
export interface UserTaskApiResponse {
total: number;
page: number;
page_size: number;
items: UserTaskInfo[];
}
// 任务文档接口类型定义(任务详情页兼容结构)
export interface TaskDocument {
document_id: number;
file_name: string;
status: string;
path: string;
file_code: string;
file_type_name: string;
file_type_id: number;
file_size: number;
upload_time: string;
created_at: string;
evaluations_status: number;
audit_status: number;
created_by_user_id: number;
issues: Array<{
severity: string;
message: string;
}>;
final_score: number;
score_summary: string ;
score_percent?: number | null;
pass_count: number;
warning_count: number;
fail_count: number;
manual_count: number;
}
// ==================== 新版接口类型定义(支持版本归纳)====================
/**
* 历史版本信息
* 每个历史版本都有独立的评查统计、消息列表、分数信息
*/
export interface CrossReviewHistoryVersion {
/** 历史版本的文档ID */
id: number;
/** 版本号(从1开始,数字越小越旧) */
version_number: number;
/** 创建时间(ISO 8601格式) */
created_at: string;
/** 文件大小(字节) */
file_size: number;
/** 文件存储路径 */
path: string;
/** 文档处理状态 */
status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed";
/** 文书号/文档编号(可为null) */
document_number: string | null;
/** 文档类型ID */
type_id: number;
/** 文档类型名称 */
type_name: string;
/** 上传时间(ISO 8601格式) */
upload_time: string;
/** 任务内评查完成状态:0=未评查, 1=已评查 */
audit_status: 0 | 1;
/** 总评查点数 */
total_evaluation_points: number;
/** 通过的评查点数量 */
pass_count: number;
/** 警告的评查点数量 */
warning_count: number;
/** 错误的评查点数量 */
error_count: number;
/** 需人工审核的评查点数量 */
manual_count: number;
/** 问题总数 */
issue_count: number;
/** 警告消息列表 */
warning_messages: string[];
/** 错误消息列表 */
error_messages: string[];
/** 问题消息列表(综合:警告+错误) */
issue_messages: string[];
/** 需人工确认的消息列表 */
manual_messages: string[];
/** 最终得分 */
final_score: number;
/** 满分 */
full_score: number;
/** 得分摘要(如 "85.5/100" */
score_summary: string;
/** 得分百分比(0-100 */
score_percent: number;
}
/**
* 文档信息(含版本和评查统计)- 新版接口
*/
export interface CrossReviewDocumentWithVersion {
// ========== 基本信息 ==========
/** 当前版本的文档ID */
id: number;
/** 文档名称 */
name: string;
/** 文件存储路径 */
path: string;
/** 当前版本号(最大值,从1开始) */
version_number: number;
/** 创建时间(ISO 8601格式) */
created_at: string;
/** 文档处理状态 */
status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed";
/** 文件大小(字节) */
file_size: number;
/** 文书号/文档编号(可为null) */
document_number: string | null;
/** 文档类型ID */
type_id: number;
/** 文档类型名称 */
type_name: string;
/** 上传时间(ISO 8601格式) */
upload_time: string;
// ========== 任务内评查状态 ==========
/** 任务内评查完成状态:0=未评查, 1=已评查 */
audit_status: 0 | 1;
// ========== 评查统计 ==========
/** 总评查点数 */
total_evaluation_points: number;
/** 通过的评查点数量 */
pass_count: number;
/** 警告的评查点数量 */
warning_count: number;
/** 错误的评查点数量 */
error_count: number;
/** 需人工审核的评查点数量 */
manual_count: number;
/** 问题总数 */
issue_count: number;
// ========== 评查消息列表 ==========
/** 警告消息列表 */
warning_messages: string[];
/** 错误消息列表 */
error_messages: string[];
/** 问题消息列表(综合) */
issue_messages: string[];
/** 需人工确认的消息列表 */
manual_messages: string[];
// ========== 交叉评查特有:分数信息 ==========
/** 最终得分 */
final_score: number;
/** 满分 */
full_score: number;
/** 得分摘要(如 "85.5/100" */
score_summary: string;
/** 得分百分比(0-100 */
score_percent: number;
// ========== 版本信息 ==========
/** 总版本数 */
total_versions: number;
/** 历史版本列表(按created_at降序,不包含当前版本) */
history_versions: CrossReviewHistoryVersion[];
// ========== 前端扩展字段 ==========
/** 是否已展开历史版本(前端状态) */
isExpanded?: boolean;
}
/**
* 交叉评查任务文档列表响应
*/
export interface CrossReviewDocumentListResponse {
/** 总文档数(按版本分组后的唯一文档数) */
total: number;
/** 当前页码 */
page: number;
/** 每页数量 */
page_size: number;
/** 总页数 */
total_pages: number;
/** 文档列表 */
documents: CrossReviewDocumentWithVersion[];
}
/**
* 获取任务文档列表请求参数(支持版本归纳)
*/
export interface GetTaskDocumentsWithVersionsParams {
/** 任务ID */
taskId: number;
/** 页码(从1开始) */
page?: number;
/** 每页数量(最大100 */
pageSize?: number;
/** 模糊搜索关键字(匹配文件名称或文档编号) */
keyword?: string;
/** JWT token */
jwtToken?: string;
}
// 任务文档API响应格式(新增)
export interface TaskDocumentApiResponse {
total: number;
page: number;
page_size: number;
items: TaskDocument[];
}
// API响应格式
export interface ApiResponse<T> {
success: boolean;
data?: T;
error?: string;
message?: string;
status?: number;
}
interface ResultEnvelope<T> {
code?: number;
msg?: string;
data?: T;
}
interface V3UserTaskItem {
taskId: number;
taskName: string;
taskType: string;
docTypeId?: number | null;
docTypeCode?: string | null;
status: string;
progress?: number;
totalDocuments?: number;
completedDocuments?: number;
createdAt?: string;
}
interface V3UserTaskPage {
total: number;
page: number;
pageSize: number;
items: V3UserTaskItem[];
}
interface V3TaskDocumentItem {
documentId: number;
name: string;
documentNumber?: string | null;
typeId?: number | null;
processingStatus?: string | null;
versionNo?: number;
isLatestVersion?: boolean;
auditStatus?: number;
createdAt?: string;
}
interface V3TaskDocumentPage {
taskId: number;
total: number;
page: number;
pageSize: number;
items: V3TaskDocumentItem[];
}
function unwrapResultEnvelope<T>(payload: unknown): T {
if (payload && typeof payload === 'object' && 'data' in (payload as ResultEnvelope<T>)) {
return ((payload as ResultEnvelope<T>).data ?? null) as T;
}
return payload as T;
}
function mapProcessingStatus(status?: string | null): CrossReviewDocumentWithVersion['status'] {
switch (status) {
case 'processed':
case 'Processed':
return 'Processed';
case 'failed':
case 'Failed':
return 'Failed';
case 'cutting':
case 'Cutting':
return 'Cutting';
case 'extracting':
case 'Extractioning':
return 'Extractioning';
case 'evaluating':
case 'Evaluationing':
return 'Evaluationing';
case 'waiting':
case 'Waiting':
default:
return 'Waiting';
}
}
function mapV3TaskToUserTaskInfo(item: V3UserTaskItem): UserTaskInfo {
return {
task_id: item.taskId,
task_name: item.taskName,
task_status: item.status,
doc_type: item.docTypeCode || undefined,
task_created_at: item.createdAt,
task_type: item.taskType,
progress: item.progress,
total_documents: item.totalDocuments
};
}
function mapV3DocumentToTaskDocument(item: V3TaskDocumentItem): TaskDocument {
return {
document_id: item.documentId,
file_name: item.name || '',
status: mapProcessingStatus(item.processingStatus),
path: '',
file_code: item.documentNumber || '',
file_type_name: item.typeId ? `类型${item.typeId}` : '',
file_type_id: item.typeId || 0,
file_size: 0,
upload_time: item.createdAt || '',
created_at: item.createdAt || '',
evaluations_status: 0,
audit_status: item.auditStatus || 0,
created_by_user_id: 0,
issues: [],
final_score: 0,
score_summary: '',
score_percent: null,
pass_count: 0,
warning_count: 0,
fail_count: 0,
manual_count: 0
};
}
function mapV3DocumentToVersionedDocument(item: V3TaskDocumentItem): CrossReviewDocumentWithVersion {
const typeName = item.typeId ? `类型${item.typeId}` : '未知类型';
return {
id: item.documentId,
name: item.name || '',
path: '',
version_number: item.versionNo || 1,
created_at: item.createdAt || '',
status: mapProcessingStatus(item.processingStatus),
file_size: 0,
document_number: item.documentNumber || null,
type_id: item.typeId || 0,
type_name: typeName,
upload_time: item.createdAt || '',
audit_status: (item.auditStatus || 0) as 0 | 1,
total_evaluation_points: 0,
pass_count: 0,
warning_count: 0,
error_count: 0,
manual_count: 0,
issue_count: 0,
warning_messages: [],
error_messages: [],
issue_messages: [],
manual_messages: [],
final_score: 0,
full_score: 100,
score_summary: '',
score_percent: 0,
total_versions: 1,
history_versions: []
};
}
// 任务列表查询参数
export interface TaskListParams {
page?: number;
pageSize?: number;
taskType?: string;
docType?: string;
status?: string;
keyword?: string;
dateFrom?: string;
dateTo?: string;
}
// 任务列表响应数据
export interface TaskListResponse {
tasks: CrossCheckingTask[];
totalCount: number;
currentPage: number;
pageSize: number;
totalPages: number;
}
/**
* 获取交叉评查任务列表
* @param params 查询参数
* @param userInfo 用户信息
* @param jwtToken JWT token
* @returns 任务列表响应
*/
export async function getCrossCheckingTasks(params: TaskListParams = {}, userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise<ApiResponse<TaskListResponse>> {
try {
const userTasksResponse = await getUserTaskDocuments(params, jwtToken);
// console.log('getUserTaskDocuments响应:', JSON.stringify(userTasksResponse,null,2));
if (!userTasksResponse.success || !userTasksResponse.data) {
console.error('获取用户任务失败:', userTasksResponse.error);
return {
success: false,
error: userTasksResponse.error || '获取用户任务失败'
};
}
// 将用户任务数据转换为CrossCheckingTask格式
const userTasks = userTasksResponse.data.items;
const convertedTasks: CrossCheckingTask[] = userTasks.map((userTask: UserTaskInfo, index: number) => {
// 从用户任务中提取任务信息,使用API返回的实际数据
const task: CrossCheckingTask = {
id: userTask.task_id,
sequence: index + 1,
taskName: userTask.task_name || `任务 ${userTask.task_id}`, // 使用API返回的任务名称
startDate: userTask.task_created_at ? new Date(userTask.task_created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
taskType: userTask.task_type, // 保持默认任务类型
docType: userTask.doc_type || '未知类型', // 使用API返回的文档类型
evaluationRegion: userTask.evaluation_region || [],
progress: userTask.progress || 0, // 使用API返回的进度
status: userTask.task_status || 'pending', // 使用API返回的任务状态
score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数
operation: userTask.task_status === 'completed' ? '查看结果' :
userTask.task_status === 'in_progress' ? '进行中' : '去评查',
documents: [], // 暂时为空数组,因为新API格式中任务列表不包含具体文档信息
totalDocuments: userTask.total_documents || 0 // 使用API返回的文档总数
};
return task;
});
const {
taskType,
docType,
status,
keyword,
dateFrom,
dateTo
} = params;
// 筛选数据
let filteredTasks = [...convertedTasks];
// 按任务类型筛选
if (taskType && taskType !== 'all') {
filteredTasks = filteredTasks.filter(task => task.taskType === taskType);
}
// 按案卷类型筛选
if (docType && docType !== 'all') {
filteredTasks = filteredTasks.filter(task => task.docType === docType);
}
// 按状态筛选
if (status && status !== 'all') {
filteredTasks = filteredTasks.filter(task => task.status === status);
}
// 按关键词搜索
if (keyword) {
const lowerKeyword = keyword.toLowerCase();
filteredTasks = filteredTasks.filter(task =>
task.taskName.toLowerCase().includes(lowerKeyword) ||
task.evaluationRegion.join(',').toLowerCase().includes(lowerKeyword)
);
}
// 按日期范围筛选
if (dateFrom || dateTo) {
filteredTasks = filteredTasks.filter(task => {
const taskDate = new Date(task.startDate);
if (dateFrom && new Date(dateFrom) > taskDate) return false;
if (dateTo && new Date(dateTo) < taskDate) return false;
return true;
});
}
return {
success: true,
data: {
tasks: filteredTasks,
totalCount: userTasksResponse.data.total,
currentPage: userTasksResponse.data.page,
pageSize: userTasksResponse.data.page_size,
totalPages: Math.ceil(userTasksResponse.data.total / userTasksResponse.data.page_size)
}
};
} catch (error) {
console.error('获取交叉评查任务列表失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '获取任务列表失败'
};
}
}
/**
* 删除交叉评查任务
* @param taskId 任务ID
* @returns 删除结果
*/
export async function deleteCrossCheckingTask(taskId: number): Promise<ApiResponse<boolean>> {
try {
// 模拟API延迟
await new Promise(resolve => setTimeout(resolve, 500));
// 这里应该调用实际的API来删除任务
// 目前暂时返回成功,因为没有实际的删除API
console.log(`尝试删除任务ID: ${taskId}`);
return {
success: true,
data: true,
message: '删除任务成功'
};
} catch (error) {
console.error('删除交叉评查任务失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '删除任务失败'
};
}
}
/**
* 获取任务详情及相关文档
* @param taskId 任务ID
* @param page 页码,默认为1
* @param pageSize 每页大小,默认为10
* @param jwtToken JWT token
* @returns 任务详情和文档列表
*/
export async function getCrossCheckingTaskDetail(
taskId: number,
page: number = 1,
pageSize: number = 10,
jwtToken?: string
): Promise<ApiResponse<{
task: CrossCheckingTask | null;
files: TaskDocument[];
total: number;
currentPage: number;
pageSize: number;
}>> {
try {
// console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize });
// 获取任务的文档列表
const taskDocumentsResponse = await getTaskDocuments(taskId, page, pageSize, jwtToken);
if (!taskDocumentsResponse.success || !taskDocumentsResponse.data) {
console.error('获取任务文档失败:', taskDocumentsResponse.error);
return {
success: false,
error: taskDocumentsResponse.error || '获取任务文档失败'
};
}
const documentsData = taskDocumentsResponse.data;
const result = {
success: true,
data: {
task: null, // 暂时不返回任务详情,因为新接口主要关注文档列表
files: documentsData.items,
total: documentsData.total,
currentPage: documentsData.page,
pageSize: documentsData.page_size
}
};
return result;
} catch (error) {
console.error('获取任务详情失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '获取任务详情失败'
};
}
}
/**
* 获取统计数据
* @param userInfo 用户信息
* @param jwtToken JWT token
* @returns 统计数据
*/
export async function getCrossCheckingStats(userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise<ApiResponse<{
totalTasks: number;
pendingTasks: number;
inProgressTasks: number;
completedTasks: number;
}>> {
try {
console.log('开始调用getCrossCheckingStats');
// 获取用户任务数据来计算统计(默认获取第一页数据进行统计)
const userTasksResponse = await getUserTaskDocuments({ page: 1, pageSize: 100 }, jwtToken);
if (!userTasksResponse.success || !userTasksResponse.data) {
console.error('获取用户任务失败:', userTasksResponse.error);
return {
success: false,
error: userTasksResponse.error || '获取用户任务失败'
};
}
const userTasks = userTasksResponse.data.items;
const totalTasks = userTasksResponse.data.total;
const pendingTasks = userTasks.filter(t => t.task_status === 'pending').length;
const inProgressTasks = userTasks.filter(t => t.task_status === 'in_progress').length;
const completedTasks = userTasks.filter(t => t.task_status === 'completed').length;
return {
success: true,
data: {
totalTasks,
pendingTasks,
inProgressTasks,
completedTasks
}
};
} catch (error) {
console.error('获取统计数据失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '获取统计数据失败'
};
}
}
// ==================== 更新:用户任务文档相关接口 ====================
/**
* 获取用户参与的所有任务列表(更新为新的API格式)
* @param page 页码
* @param pageSize 每页大小
* @param jwtToken JWT token
* @returns 用户任务列表
*/
export async function getUserTaskDocuments(params: TaskListParams = {}, jwtToken?: string): Promise<ApiResponse<UserTaskApiResponse>> {
try {
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/api/v3/cross-review/tasks/query`;
const page = params.page || 1;
const pageSize = params.pageSize || 10;
const requestBody: Record<string, unknown> = {
page,
pageSize
};
if (params.keyword?.trim()) {
requestBody.keyword = params.keyword.trim();
}
if (params.status && params.status !== 'all') {
requestBody.status = params.status;
}
if (params.taskType && params.taskType !== 'all') {
requestBody.taskType = params.taskType;
}
if (params.docType && params.docType !== 'all') {
requestBody.docTypeCode = params.docType;
}
const response = await axios.post<ResultEnvelope<V3UserTaskPage>>(url, requestBody, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
}
});
const pageData = unwrapResultEnvelope<V3UserTaskPage>(response.data);
const items = Array.isArray(pageData?.items) ? pageData.items.map(mapV3TaskToUserTaskInfo) : [];
return {
success: true,
data: {
total: pageData?.total || 0,
page: pageData?.page || page,
page_size: pageData?.pageSize || pageSize,
items
}
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`
};
}
return {
success: false,
error: error instanceof Error ? error.message : '获取用户任务列表失败'
};
}
}
/**
* 获取指定任务的文档列表(兼容任务详情使用)
* @param taskId 任务ID
* @param page 页码
* @param pageSize 每页大小
* @param jwtToken JWT token
* @returns 任务文档列表
*/
export async function getTaskDocuments(taskId: number, page: number = 1, pageSize: number = 10, jwtToken?: string): Promise<ApiResponse<TaskDocumentApiResponse>> {
try {
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/api/v3/cross-review/tasks/${taskId}/documents`;
const response = await axios.get<ResultEnvelope<V3TaskDocumentPage>>(url, {
params: {
page,
pageSize
},
headers: {
'Authorization': `Bearer ${jwtToken || ''}`
}
});
const pageData = unwrapResultEnvelope<V3TaskDocumentPage>(response.data);
const items = Array.isArray(pageData?.items) ? pageData.items.map(mapV3DocumentToTaskDocument) : [];
return {
success: true,
data: {
total: pageData?.total || 0,
page: pageData?.page || page,
page_size: pageData?.pageSize || pageSize,
items
}
};
} catch (error) {
if (axios.isAxiosError(error)) {
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`
};
}
return {
success: false,
error: error instanceof Error ? error.message : '获取任务文档列表失败'
};
}
}
/**
* 获取任务下文档列表(支持版本归纳)
*
* GET /api/v3/cross-review/tasks/{task_id}/documents
*
* 同一任务内同名且同类型的文档会被归纳为版本组,最新上传的为当前版本,其余为历史版本。
*
* @param params 请求参数
* @returns 文档列表响应(含版本信息)
*/
export async function getTaskDocumentsWithVersions(
params: GetTaskDocumentsWithVersionsParams
): Promise<ApiResponse<CrossReviewDocumentListResponse>> {
const { taskId, page = 1, pageSize = 10, keyword, jwtToken } = params;
try {
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/api/v3/cross-review/tasks/${taskId}/documents`;
const queryParams: {
page: number;
pageSize: number;
keyword?: string;
} = {
page,
pageSize
};
// 只有当 keyword 有值时才添加
if (keyword && keyword.trim()) {
queryParams.keyword = keyword.trim();
}
const response = await axios.get<ResultEnvelope<V3TaskDocumentPage>>(url, {
params: queryParams,
headers: {
'Authorization': `Bearer ${jwtToken || ''}`
}
});
const pageData = unwrapResultEnvelope<V3TaskDocumentPage>(response.data);
const documents = Array.isArray(pageData?.items) ? pageData.items.map(mapV3DocumentToVersionedDocument) : [];
return {
success: true,
data: {
total: pageData?.total || 0,
page: pageData?.page || page,
page_size: pageData?.pageSize || pageSize,
total_pages: Math.ceil((pageData?.total || 0) / (pageData?.pageSize || pageSize || 1)),
documents
}
};
} catch (error) {
if (axios.isAxiosError(error)) {
// 处理特定错误码
if (error.response?.status === 403) {
return {
success: false,
error: '无权访问任务:您不是该任务的参与者',
status: 403
};
}
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`,
status: error.response?.status
};
}
return {
success: false,
error: error instanceof Error ? error.message : '获取任务文档列表失败'
};
}
}
/**
* 更新文件的审核状态
* @param id 文件ID
* @param auditStatus 审核状态
* @returns 更新结果
*/
export async function updateDocumentAuditStatus(id: string, auditStatus: number, frontendJWT?: string): Promise<{
success?: boolean;
error?: string;
status?: number;
}> {
try {
if (!id) {
return { error: '文件ID不能为空', status: 400 };
}
const response = await postgrestPut<TaskDocument, Partial<TaskDocument>>(
'/api/postgrest/proxy/documents',
{ audit_status: auditStatus },
{
id: parseInt(id)
},
frontendJWT
);
if (response.error) {
return { error: response.error, status: response.status };
}
return { success: true };
} catch (error) {
console.error('更新文件审核状态失败:', error);
return {
error: error instanceof Error ? error.message : '更新文件审核状态失败',
status: 500
};
}
}
/**
* 获取可用于交叉评查的文档类型列表
* 条件:evaluation_point_groups_ids 不为空
* @param jwtToken JWT token
* @returns 文档类型列表
*/
export async function getCrossCheckingDocumentTypes(jwtToken?: string): Promise<ApiResponse<DocumentType[]>> {
try {
const response = await axios.get<{ data?: Array<{
id: number;
name: string;
code: string;
isEnabled?: boolean;
ruleSetIds?: number[];
}> }>(`${API_BASE_URL}/api/document-types`, {
headers: jwtToken ? { Authorization: `Bearer ${jwtToken}` } : undefined,
});
const rawItems = Array.isArray(response.data?.data) ? response.data.data : [];
const filteredData = rawItems
.filter((item) => item && item.isEnabled !== false)
.map((item) => ({
id: item.id,
name: item.name,
code: item.code,
}))
.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN'));
return {
success: true,
data: filteredData
};
} catch (error) {
console.error('[getCrossCheckingDocumentTypes] 获取交叉评查文档类型失败:', error);
return {
success: false,
error: error instanceof Error ? error.message : '获取文档类型失败'
};
}
}
// ==================== 追加附件 API ====================
/**
* 追加附件响应接口
*/
export interface AppendAttachmentsResponse {
success: boolean;
result?: {
original_document_id: number;
new_document_id: number;
new_version_number: number;
task_id: number;
message: string;
background_processing: boolean;
};
error?: string;
}
/**
* 追加附件参数接口
*/
export interface AppendAttachmentsParams {
/** 任务ID */
taskId: number;
/** 原文档ID */
documentId: number;
/** 附件文件列表 */
files: File[];
/** 备注说明(可选) */
remark?: string;
/** Word附件是否使用Markdown处理(可选,默认false */
useMarkdown?: boolean;
/** JWT Token */
jwtToken?: string;
}
/**
* 为交叉评查任务文档追加附件
*
* POST /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/append_attachments
*
* 追加附件后创建新文档(新版本),原文档保留。
* 新文档自动关联到当前任务,audit_status = 0(需重新评查)
*
* @param params 追加附件参数
* @returns 追加结果
*/
export async function appendTaskDocumentAttachments(
params: AppendAttachmentsParams
): Promise<ApiResponse<AppendAttachmentsResponse>> {
const { taskId, documentId, files, remark, useMarkdown = false, jwtToken } = params;
try {
if (!taskId || taskId <= 0) {
return { success: false, error: '任务ID无效' };
}
if (!documentId || documentId <= 0) {
return { success: false, error: '文档ID无效' };
}
if (!files || files.length === 0) {
return { success: false, error: '请选择附件文件' };
}
// 构建 FormData
const formData = new FormData();
files.forEach(file => {
formData.append('files', file);
});
if (remark) {
formData.append('remark', remark);
}
formData.append('use_markdown', useMarkdown.toString());
// 构建请求 URL
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
const url = `${base}/api/v2/cross_review/tasks/${taskId}/documents/${documentId}/append_attachments`;
const response = await axios.post<AppendAttachmentsResponse>(url, formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${jwtToken || ''}`
}
});
return {
success: true,
data: response.data
};
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 403) {
return {
success: false,
error: '无权追加附件:您不是任务创建者或负责人',
status: 403
};
}
if (error.response?.status === 400) {
return {
success: false,
error: error.response.data?.error || '请求参数错误',
status: 400
};
}
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`,
status: error.response?.status
};
}
return {
success: false,
error: error instanceof Error ? error.message : '追加附件失败'
};
}
}
// ==================== 上传模板 API ====================
/**
* 上传模板响应接口(文档级别)
*/
export interface UploadDocumentTemplateResponse {
success: boolean;
result?: {
document_id: number;
comparison_id: number;
template_name: string;
template_path: string;
status: string;
message: string;
};
error?: string;
}
/**
* 上传模板参数接口(文档级别)
*/
export interface UploadDocumentTemplateParams {
/** 文档ID */
documentId: number;
/** 模板文件 */
file: File;
/** 对比记录ID(可选,用于更新已有模板) */
comparisonId?: number;
/** JWT Token */
jwtToken?: string;
}
/**
* 为交叉评查任务中的文档上传模板(文档级别)
*
* 复用现有的 /upload_contract_template 接口,与 files-upload.ts 中的 uploadContractTemplate 保持一致
*
* @param params 上传模板参数
* @returns 上传结果
*/
export async function uploadCrossReviewDocumentTemplate(
params: UploadDocumentTemplateParams
): Promise<ApiResponse<UploadDocumentTemplateResponse>> {
const { documentId, file, comparisonId, jwtToken } = params;
try {
if (!documentId || documentId <= 0) {
return { success: false, error: '文档ID无效' };
}
if (!file) {
return { success: false, error: '请选择模板文件' };
}
// 构建 FormData,与 files-upload.ts 中的 uploadContractTemplate 保持一致
const formData = new FormData();
formData.append('file', file);
// upload_info 作为 JSON 字符串
const uploadInfo: { document_id: number; comparison_id?: number } = {
document_id: documentId
};
if (comparisonId) {
uploadInfo.comparison_id = comparisonId;
}
formData.append('upload_info', JSON.stringify(uploadInfo));
// 使用与 files-upload.ts 相同的上传接口
const { UPLOAD_URL } = await import('~/config/api-config');
const url = `${UPLOAD_URL}/upload_contract_template`;
const response = await axios.post<UploadDocumentTemplateResponse>(url, formData, {
headers: {
'Accept': 'application/json',
'Authorization': `Bearer ${jwtToken || ''}`
}
});
return {
success: true,
data: response.data
};
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 403) {
return {
success: false,
error: '无权上传模板',
status: 403
};
}
if (error.response?.status === 400) {
return {
success: false,
error: error.response.data?.error || '请求参数错误',
status: 400
};
}
return {
success: false,
error: `HTTP ${error.response?.status}: ${error.response?.statusText || error.message}`,
status: error.response?.status
};
}
return {
success: false,
error: error instanceof Error ? error.message : '上传模板失败'
};
}
}