9951f16e50
2.交叉评查的任务中上传文件。 3.添加dify库名解决保存配置失败的问题。
1033 lines
29 KiB
TypeScript
1033 lines
29 KiB
TypeScript
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;
|
||
}
|
||
|
||
// 任务列表查询参数
|
||
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 {
|
||
// console.log('开始调用getCrossCheckingTasks,参数:', params);
|
||
|
||
// 调用用户任务API,获取当前用户参与的任务
|
||
const userTasksResponse = await getUserTaskDocuments(params.page || 1, params.pageSize || 10, 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.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(1, 100, jwtToken); // 获取前100个任务用于统计
|
||
|
||
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(page: number = 1, pageSize: number = 10, jwtToken?: string): Promise<ApiResponse<UserTaskApiResponse>> {
|
||
try {
|
||
// 拼接绝对路径,去除多余斜杠
|
||
const base = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
|
||
const url = `${base}/admin/v2/cross_review/tasks/user_tasks`;
|
||
|
||
const response = await axios.post(url, {
|
||
page: page,
|
||
page_size: pageSize
|
||
}, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${jwtToken || ''}`
|
||
}
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
data: response.data
|
||
};
|
||
} 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}/admin/v2/cross_review/tasks/${taskId}/documents`;
|
||
// console.log('最终请求URL:', url);
|
||
|
||
const response = await axios.post(url, {
|
||
page: page,
|
||
page_size: pageSize
|
||
}, {
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${jwtToken || ''}`
|
||
}
|
||
});
|
||
|
||
return {
|
||
success: true,
|
||
data: response.data
|
||
};
|
||
} 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 : '获取任务文档列表失败'
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取任务下文档列表(支持版本归纳)- 新版接口
|
||
*
|
||
* POST /api/v2/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/v2/cross_review/tasks/${taskId}/documents`;
|
||
|
||
// 构建请求体
|
||
const queryParams: {
|
||
page: number;
|
||
page_size: number;
|
||
keyword?: string;
|
||
} = {
|
||
page,
|
||
page_size: pageSize
|
||
};
|
||
|
||
// 只有当 keyword 有值时才添加
|
||
if (keyword && keyword.trim()) {
|
||
queryParams.keyword = keyword.trim();
|
||
}
|
||
|
||
const response = await axios.get<CrossReviewDocumentListResponse>(url, {
|
||
params: queryParams,
|
||
headers: {
|
||
'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
|
||
};
|
||
}
|
||
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 {
|
||
// console.log('[getCrossCheckingDocumentTypes] 开始获取交叉评查文档类型');
|
||
|
||
const response = await postgrestGet<DocumentType>('/api/postgrest/proxy/document_types',{
|
||
select: 'id,name,code,evaluation_point_groups_ids',
|
||
filter: {
|
||
evaluation_point_groups_ids: 'not.is.null'
|
||
},
|
||
token: jwtToken
|
||
});
|
||
|
||
if (response.error) {
|
||
console.error('[getCrossCheckingDocumentTypes] 获取失败:', response.error);
|
||
return {
|
||
success: false,
|
||
error: response.error
|
||
};
|
||
}
|
||
|
||
// 进一步过滤,确保 evaluation_point_groups_ids 是非空数组
|
||
const dataArray = Array.isArray(response.data) ? response.data : [];
|
||
const filteredData = dataArray.filter(
|
||
(item: DocumentType) => item.evaluation_point_groups_ids &&
|
||
Array.isArray(item.evaluation_point_groups_ids) &&
|
||
item.evaluation_point_groups_ids.length > 0
|
||
);
|
||
|
||
// console.log('[getCrossCheckingDocumentTypes] 获取成功,共', filteredData.length, '个文档类型');
|
||
|
||
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 : '上传模板失败'
|
||
};
|
||
}
|
||
} |