feat: 1. 完善交叉评查上传创建任务,改成动态加载文档类型。
2. 重新对齐交叉评查的接口。
This commit is contained in:
@@ -102,10 +102,10 @@ export async function findIsProposer(taskId: string | number, userId: number | u
|
||||
return false;
|
||||
}
|
||||
const data = extractApiData<{assigner_id: number}[]>(response.data);
|
||||
// console.log('data', data);
|
||||
// console.log('data', data, userId);
|
||||
|
||||
if (data && data.length > 0) {
|
||||
return data[0].assigner_id === userId;
|
||||
return data[0].assigner_id.toString() === userId?.toString();
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -124,6 +124,7 @@ export async function submitCrossCheckingOpinion(
|
||||
): Promise<ApiResponse<SubmitOpinionResponse>> {
|
||||
try {
|
||||
// 获取JWT token
|
||||
console.log('jwtToken', jwtToken)
|
||||
const token = await safeGetJWT(jwtToken);
|
||||
|
||||
const requestData = {
|
||||
@@ -151,9 +152,21 @@ export async function submitCrossCheckingOpinion(
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('提交交叉评查意见失败:', error);
|
||||
|
||||
// 正确处理 axios 错误响应
|
||||
let errorMessage = '提交意见失败';
|
||||
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
// 从 axios 错误响应中提取 msg 字段
|
||||
errorMessage = error.response.data.msg || errorMessage;
|
||||
} else if (error instanceof Error) {
|
||||
// 处理普通 Error 对象
|
||||
errorMessage = error.message || errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '提交意见失败',
|
||||
status: 500
|
||||
error: errorMessage,
|
||||
status: axios.isAxiosError(error) ? error.response?.status || 500 : 500
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -245,9 +258,21 @@ export async function getCrossCheckingOpinions(
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取交叉评查意见失败:', error);
|
||||
|
||||
// 正确处理 axios 错误响应
|
||||
let errorMessage = '获取意见列表失败';
|
||||
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
// 从 axios 错误响应中提取 msg 字段
|
||||
errorMessage = error.response.data.msg || errorMessage;
|
||||
} else if (error instanceof Error) {
|
||||
// 处理普通 Error 对象
|
||||
errorMessage = error.message || errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '获取意见列表失败',
|
||||
status: 500
|
||||
error: errorMessage,
|
||||
status: axios.isAxiosError(error) ? error.response?.status || 500 : 500
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -343,9 +368,21 @@ export async function performOpinionAction(
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('执行意见操作失败:', error);
|
||||
|
||||
// 正确处理 axios 错误响应
|
||||
let errorMessage = '操作失败';
|
||||
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
// 从 axios 错误响应中提取 msg 字段
|
||||
errorMessage = error.response.data.msg || errorMessage;
|
||||
} else if (error instanceof Error) {
|
||||
// 处理普通 Error 对象
|
||||
errorMessage = error.message || errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '操作失败',
|
||||
status: 500
|
||||
error: errorMessage,
|
||||
status: axios.isAxiosError(error) ? error.response?.status || 500 : 500
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -427,9 +464,21 @@ export async function checkProposalVotes(
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('检查失败:', error);
|
||||
|
||||
// 正确处理 axios 错误响应
|
||||
let errorMessage = '检查失败';
|
||||
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
// 从 axios 错误响应中提取 msg 字段
|
||||
errorMessage = error.response.data.msg || errorMessage;
|
||||
} else if (error instanceof Error) {
|
||||
// 处理普通 Error 对象
|
||||
errorMessage = error.message || errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '检查失败',
|
||||
status: 500
|
||||
error: errorMessage,
|
||||
status: axios.isAxiosError(error) ? error.response?.status || 500 : 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { UPLOAD_URL } from '../../config/api-config';
|
||||
import { UPLOAD_URL, API_BASE_URL } from '../../config/api-config';
|
||||
import axios from 'axios';
|
||||
|
||||
/**
|
||||
@@ -208,6 +208,7 @@ export async function uploadCrossCheckingDocument(
|
||||
* @param assignUserIds 需要分配的用户ID数组
|
||||
* @param taskName 任务名称
|
||||
* @param docType 文档类型(如 XZCF、XZXK)
|
||||
* @param taskType 任务类型(如 市局间交叉评查、区局间交叉评查)
|
||||
* @param token JWT Token
|
||||
*/
|
||||
export async function batchUploadAndAssignCrossCheckingFiles(
|
||||
@@ -220,6 +221,7 @@ export async function batchUploadAndAssignCrossCheckingFiles(
|
||||
assignUserIds: number[],
|
||||
taskName: string,
|
||||
docType: string,
|
||||
taskType: string = '市局间交叉评查',
|
||||
token: string | null = null
|
||||
): Promise<{
|
||||
successes: Array<{file: CrossCheckingUploadedFile; result: Record<string, unknown>}>;
|
||||
@@ -229,6 +231,10 @@ export async function batchUploadAndAssignCrossCheckingFiles(
|
||||
const failures: Array<{file: CrossCheckingUploadedFile; error: string}> = [];
|
||||
const uploadEndpoint = '/cross_review/documents/upload_and_assign';
|
||||
const uploadUrl = UPLOAD_URL + uploadEndpoint;
|
||||
|
||||
// console.log('[批量上传] 任务类型:', taskType, '文档类型:', docType);
|
||||
|
||||
|
||||
for (const fileInfo of files) {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
@@ -240,15 +246,22 @@ export async function batchUploadAndAssignCrossCheckingFiles(
|
||||
remark: remark || null,
|
||||
is_test_document: isTestDocument,
|
||||
task_name: taskName,
|
||||
doc_type: typeof docType === 'string' ? docType.toUpperCase() : docType
|
||||
doc_type: typeof docType === 'string' ? docType.toUpperCase() : docType,
|
||||
task_type: taskType
|
||||
};
|
||||
// console.log('fileInfo', fileInfo)
|
||||
|
||||
formData.append('upload_info', JSON.stringify(uploadInfo));
|
||||
formData.append('assign_user_ids', JSON.stringify(assignUserIds));
|
||||
|
||||
const headers: Record<string, string> = {};
|
||||
|
||||
if (token) headers['Authorization'] = `Bearer ${token}`;
|
||||
|
||||
const response = await axios.post(uploadUrl, formData, {
|
||||
headers
|
||||
});
|
||||
|
||||
const result = response.data;
|
||||
if (result && result.success) {
|
||||
successes.push({ file: fileInfo, result });
|
||||
@@ -262,6 +275,71 @@ export async function batchUploadAndAssignCrossCheckingFiles(
|
||||
return { successes, failures };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交叉评查任务
|
||||
* @param taskData 任务数据
|
||||
* @param token JWT Token
|
||||
* @returns 创建结果
|
||||
*/
|
||||
export async function createCrossReviewTask(taskData: {
|
||||
documentIds: number[];
|
||||
userIds: number[];
|
||||
assignerId: number;
|
||||
taskName: string;
|
||||
docType: string;
|
||||
taskType?: string;
|
||||
}, token: string | null = null): Promise<{
|
||||
success: boolean;
|
||||
data?: unknown;
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const requestBody = {
|
||||
document_ids: taskData.documentIds,
|
||||
user_ids: taskData.userIds,
|
||||
assigner_id: taskData.assignerId,
|
||||
task_name: taskData.taskName,
|
||||
doc_type: taskData.docType,
|
||||
task_type: taskData.taskType || '市局间交叉评查'
|
||||
};
|
||||
|
||||
// console.log('[创建任务] 请求数据:', requestBody);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await axios.post(
|
||||
`${API_BASE_URL}/admin/cross_review/tasks/assign`,
|
||||
requestBody,
|
||||
{ headers }
|
||||
);
|
||||
|
||||
console.log('[创建任务] 成功:', response.data);
|
||||
return {
|
||||
success: true,
|
||||
data: response.data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[创建任务] 失败:', error);
|
||||
|
||||
let errorMessage = '创建任务失败';
|
||||
if (axios.isAxiosError(error) && error.response?.data) {
|
||||
errorMessage = error.response.data.msg || errorMessage;
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message || errorMessage;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一文件ID
|
||||
* @returns 唯一ID字符串
|
||||
|
||||
@@ -11,8 +11,8 @@ export enum CrossCheckingTaskStatus {
|
||||
|
||||
// 交叉评查任务类型枚举
|
||||
export enum CrossCheckingTaskType {
|
||||
CITY = 'city',
|
||||
COUNTY = 'county'
|
||||
CITY = 'CITY',
|
||||
DISTRICT = 'DISTRICT'
|
||||
}
|
||||
|
||||
// 案卷类型枚举
|
||||
@@ -37,7 +37,7 @@ export interface CrossCheckingTask {
|
||||
startDate: string;
|
||||
taskType: CrossCheckingTaskType;
|
||||
docType: string; // 改为直接使用返回的 doc_type 字符串
|
||||
evaluationRegion: string;
|
||||
evaluationRegion: string[];
|
||||
progress: number;
|
||||
status: string; // 改为直接使用返回的 task_status 字符串
|
||||
score: number;
|
||||
@@ -61,6 +61,8 @@ export interface UserTaskInfo {
|
||||
task_status: string;
|
||||
doc_type?: string;
|
||||
task_created_at?: string;
|
||||
evaluation_region?: string[];
|
||||
task_type?: string;
|
||||
progress?: number;
|
||||
total_documents?: number; // 新增:任务包含的文档总数
|
||||
}
|
||||
@@ -150,7 +152,7 @@ export interface TaskListResponse {
|
||||
*/
|
||||
export async function getCrossCheckingTasks(params: TaskListParams = {}, userInfo?: { user_id?: number; [key: string]: unknown }, jwtToken?: string): Promise<ApiResponse<TaskListResponse>> {
|
||||
try {
|
||||
console.log('开始调用getCrossCheckingTasks,参数:', params);
|
||||
// console.log('开始调用getCrossCheckingTasks,参数:', params);
|
||||
|
||||
// 调用用户任务API,获取当前用户参与的任务
|
||||
const userTasksResponse = await getUserTaskDocuments(params.page || 1, params.pageSize || 10, jwtToken);
|
||||
@@ -174,9 +176,9 @@ export async function getCrossCheckingTasks(params: TaskListParams = {}, userInf
|
||||
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: CrossCheckingTaskType.CITY, // 保持默认任务类型
|
||||
taskType: userTask.task_type, // 保持默认任务类型
|
||||
docType: userTask.doc_type || '未知类型', // 使用API返回的文档类型
|
||||
evaluationRegion: '待定', // 保持默认评查地区
|
||||
evaluationRegion: userTask.evaluation_region || [], // 保持默认评查地区
|
||||
progress: userTask.progress || 0, // 使用API返回的进度
|
||||
status: userTask.task_status || 'pending', // 使用API返回的任务状态
|
||||
score: userTask.task_status === 'completed' ? 85 : 0, // 默认分数
|
||||
@@ -302,7 +304,7 @@ export async function getCrossCheckingTaskDetail(
|
||||
pageSize: number;
|
||||
}>> {
|
||||
try {
|
||||
console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize });
|
||||
// console.log('开始调用getCrossCheckingTaskDetail,参数:', { taskId, page, pageSize });
|
||||
|
||||
// 获取任务的文档列表
|
||||
const taskDocumentsResponse = await getTaskDocuments(taskId, page, pageSize, jwtToken);
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* 验证用户是否有权访问指定文档
|
||||
*
|
||||
* 权限验证逻辑:
|
||||
* 1. 检查文档是否属于指定的任务
|
||||
* 2. 检查用户是否是该任务的参与者(评审员或发起人)
|
||||
* 3. 防止用户通过修改 URL 参数访问未授权的文档
|
||||
*/
|
||||
|
||||
import { postgrestGet } from "../postgrest-client";
|
||||
|
||||
interface DocumentAccessCheckParams {
|
||||
documentId: string | number;
|
||||
taskId: string | number;
|
||||
userId: number;
|
||||
jwtToken?: string;
|
||||
}
|
||||
|
||||
interface DocumentAccessCheckResult {
|
||||
hasAccess: boolean;
|
||||
reason?: string;
|
||||
userRole?: 'assigner' | 'assignee' | 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文档访问权限
|
||||
* @param params 验证参数
|
||||
* @returns 验证结果
|
||||
*/
|
||||
export async function verifyDocumentAccess(
|
||||
params: DocumentAccessCheckParams
|
||||
): Promise<DocumentAccessCheckResult> {
|
||||
const { documentId, taskId, userId, jwtToken } = params;
|
||||
|
||||
try {
|
||||
// 1. 检查文档是否属于该任务(通过 cross_task_document_mapping 表)
|
||||
const documentMappingResponse = await postgrestGet('cross_task_document_mapping', {
|
||||
select: 'task_id,document_id',
|
||||
filter: {
|
||||
task_id: `eq.${taskId}`,
|
||||
document_id: `eq.${documentId}`
|
||||
},
|
||||
token: jwtToken
|
||||
});
|
||||
|
||||
if (documentMappingResponse.error) {
|
||||
console.error('❌ [verifyDocumentAccess] 查询文档-任务映射失败:', documentMappingResponse.error);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: '查询文档-任务映射失败'
|
||||
};
|
||||
}
|
||||
|
||||
// 提取数据
|
||||
const mappingData = Array.isArray(documentMappingResponse.data)
|
||||
? documentMappingResponse.data
|
||||
: [];
|
||||
|
||||
// 文档不属于该任务
|
||||
if (mappingData.length === 0) {
|
||||
console.warn(`⚠️ [verifyDocumentAccess] 文档 ${documentId} 不属于任务 ${taskId}`);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: '文档不属于该任务'
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 检查用户是否是该任务的参与者
|
||||
const taskResponse = await postgrestGet('cross_examination_tasks', {
|
||||
select: 'assigner_id,assignee_ids',
|
||||
filter: {
|
||||
id: `eq.${taskId}`
|
||||
},
|
||||
token: jwtToken
|
||||
});
|
||||
|
||||
if (taskResponse.error) {
|
||||
console.error('❌ [verifyDocumentAccess] 查询任务信息失败:', taskResponse.error);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: '查询任务信息失败'
|
||||
};
|
||||
}
|
||||
|
||||
const taskData = Array.isArray(taskResponse.data) ? taskResponse.data[0] : null;
|
||||
|
||||
if (!taskData) {
|
||||
console.warn(`⚠️ [verifyDocumentAccess] 任务 ${taskId} 不存在`);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: '任务不存在'
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 验证用户身份
|
||||
const isAssigner = taskData.assigner_id === userId;
|
||||
const assigneeIds = Array.isArray(taskData.assignee_ids) ? taskData.assignee_ids : [];
|
||||
const isAssignee = assigneeIds.includes(userId);
|
||||
|
||||
if (isAssigner) {
|
||||
console.log(`✅ [verifyDocumentAccess] 用户 ${userId} 是任务 ${taskId} 的发起人,允许访问文档 ${documentId}`);
|
||||
return {
|
||||
hasAccess: true,
|
||||
userRole: 'assigner'
|
||||
};
|
||||
}
|
||||
|
||||
if (isAssignee) {
|
||||
console.log(`✅ [verifyDocumentAccess] 用户 ${userId} 是任务 ${taskId} 的评审员,允许访问文档 ${documentId}`);
|
||||
return {
|
||||
hasAccess: true,
|
||||
userRole: 'assignee'
|
||||
};
|
||||
}
|
||||
|
||||
// 用户既不是发起人也不是评审员
|
||||
console.warn(`⚠️ [verifyDocumentAccess] 用户 ${userId} 无权访问任务 ${taskId} 的文档 ${documentId}`);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: '您没有权限访问该文档',
|
||||
userRole: 'none'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ [verifyDocumentAccess] 验证失败:', error);
|
||||
return {
|
||||
hasAccess: false,
|
||||
reason: error instanceof Error ? error.message : '权限验证失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速验证文档访问权限(仅返回布尔值)
|
||||
* @param params 验证参数
|
||||
* @returns 是否有权限访问
|
||||
*/
|
||||
export async function canAccessDocument(
|
||||
params: DocumentAccessCheckParams
|
||||
): Promise<boolean> {
|
||||
const result = await verifyDocumentAccess(params);
|
||||
return result.hasAccess;
|
||||
}
|
||||
@@ -43,6 +43,7 @@ export interface DocumentTypeCreateDTO {
|
||||
name: string;
|
||||
description?: string;
|
||||
group_ids: number[]; // 改为 number[] 数组
|
||||
code: string | null;
|
||||
entry_module_id?: number | null;
|
||||
llm_extraction_template_id?: number | null;
|
||||
vlm_extraction_template_id?: number | null;
|
||||
@@ -236,6 +237,7 @@ export async function createDocumentType(
|
||||
name: documentType.name.trim(),
|
||||
description: documentType.description || '',
|
||||
entry_module_id: documentType.entry_module_id || null,
|
||||
code: documentType.code || null,
|
||||
group_ids: documentType.group_ids,
|
||||
llm_extraction_template_id: documentType.llm_extraction_template_id || null,
|
||||
vlm_extraction_template_id: documentType.vlm_extraction_template_id || null,
|
||||
@@ -305,6 +307,7 @@ export async function updateDocumentType(
|
||||
name: documentType.name.trim(),
|
||||
description: documentType.description || '',
|
||||
entry_module_id: documentType.entry_module_id || null,
|
||||
code: documentType.code || null,
|
||||
group_ids: documentType.group_ids,
|
||||
llm_extraction_template_id: documentType.llm_extraction_template_id || null,
|
||||
vlm_extraction_template_id: documentType.vlm_extraction_template_id || null,
|
||||
|
||||
@@ -1023,7 +1023,7 @@ export async function getReviewPoints_fromApi(fileId: string, request: Request)
|
||||
// 成功响应
|
||||
if (response.data) {
|
||||
// console.log('✅ [getReviewPoints_fromApi] API调用成功,返回数据结构:', JSON.stringify({
|
||||
// // 评查点数量: response.data.data?.length || 0,
|
||||
// 评查点数量: response.data.data?.length || 0,
|
||||
// // 统计信息: response.data.stats,
|
||||
// // 评查信息: response.data.reviewInfo,
|
||||
// 有文档数据: response.data.document,
|
||||
|
||||
Reference in New Issue
Block a user