feat: 1. 完善起草合同页面的逻辑交互,对接minio的接口操作

This commit is contained in:
2025-12-05 20:17:37 +08:00
parent 3d1dbb3f97
commit 91b7518c99
21 changed files with 1249 additions and 1057 deletions
+9 -9
View File
@@ -82,7 +82,7 @@ export async function getContractCategories(jwt?: string) {
token: jwt
};
const response = await postgrestGet<ContractCategory[]>('contract_categories', params);
const response = await postgrestGet<ContractCategory[]>('/api/postgrest/proxy/contract_categories', params);
if (response.error) {
return { error: response.error, status: response.status || 500 };
@@ -107,7 +107,7 @@ export async function getContractCategories(jwt?: string) {
export async function getContractCategoriesWithCount(jwt?: string) {
try {
// 获取所有分类
const categoriesResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
const categoriesResponse = await postgrestGet<ContractCategory[]>('/api/postgrest/proxy/contract_categories', {
select: '*',
order: 'sort_order.asc,name.asc',
token: jwt
@@ -124,7 +124,7 @@ export async function getContractCategoriesWithCount(jwt?: string) {
categories.map(async (category) => {
try {
// 简化方案:获取该分类下的所有模板ID,然后计算数量
const countResponse = await postgrestGet<{ id: number }[]>('contract_templates', {
const countResponse = await postgrestGet<{ id: number }[]>('/api/postgrest/proxy/contract_templates', {
select: 'id',
filter: { 'category_id': `eq.${category.id}` },
token: jwt
@@ -214,7 +214,7 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
// 先查询匹配的分类ID
let matchingCategoryIds: number[] = [];
try {
const categoryResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
const categoryResponse = await postgrestGet<ContractCategory[]>('/api/postgrest/proxy/contract_categories', {
select: 'id',
filter: { 'name': `ilike.*${cleanKeyword}*` },
token
@@ -245,7 +245,7 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
// 如果有分类名称,需要先获取分类ID
if (category && !category_id) {
const categoryResponse = await postgrestGet<ContractCategory[]>('contract_categories', {
const categoryResponse = await postgrestGet<ContractCategory[]>('/api/postgrest/proxy/contract_categories', {
select: 'id',
filter: { 'name': `eq.${category}` },
token
@@ -264,7 +264,7 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
}
// 执行查询
const response = await postgrestGet<ContractTemplate[]>('contract_templates', params);
const response = await postgrestGet<ContractTemplate[]>('/api/postgrest/proxy/contract_templates', params);
if (response.error) {
return { error: response.error, status: response.status || 500 };
@@ -280,7 +280,7 @@ export async function getContractTemplates(searchParams: TemplateSearchParams =
token
};
const countResponse = await postgrestGet<{ id: number }[]>('contract_templates', countParams);
const countResponse = await postgrestGet<{ id: number }[]>('/api/postgrest/proxy/contract_templates', countParams);
let total = 0;
if (!countResponse.error && countResponse.data) {
const countData = extractApiData<{ id: number }[]>(countResponse.data) || [];
@@ -318,7 +318,7 @@ export async function getContractTemplate(id: string | number, jwt?: string) {
token: jwt
};
const response = await postgrestGet<ContractTemplate[]>('contract_templates', params);
const response = await postgrestGet<ContractTemplate[]>('/api/postgrest/proxy/contract_templates', params);
if (response.error) {
return { error: response.error, status: response.status || 500 };
@@ -355,7 +355,7 @@ export async function getFeaturedTemplates(limit: number = 6, jwt?: string) {
token: jwt
};
const response = await postgrestGet<ContractTemplate[]>('contract_templates', params);
const response = await postgrestGet<ContractTemplate[]>('/api/postgrest/proxy/contract_templates', params);
if (response.error) {
return { error: response.error, status: response.status || 500 };
-131
View File
@@ -80,134 +80,3 @@ export async function copyMinioFile(
}
}
/**
* 创建起草合同记录
* @param request 创建请求
* @param userId 用户ID
* @param draftFilePath 可选:草稿文件路径(如果不提供,使用模板路径)
* @returns 创建的记录
*
* 使用场景:
* 1. 不传 draftFilePath:直接使用模板文件路径,在原模板上编辑
* 2. 传 draftFilePath:使用复制后的文件路径(由文件复制接口提供)
*/
export async function createDraftContract(
request: CreateDraftRequest,
userId: number,
draftFilePath?: string,
jwt?: string
): Promise<DraftedContract> {
try {
// 1. 查询模板信息
const templateResponse = await postgrestGet('contract_templates', {
select: 'id,file_path',
filter: { id: `eq.${request.templateId}` },
token: jwt
});
if (!templateResponse.data || (Array.isArray(templateResponse.data) && templateResponse.data.length === 0)) {
throw new Error('模板不存在');
}
const template = Array.isArray(templateResponse.data) ? templateResponse.data[0] : templateResponse.data;
// 2. 确定使用的文件路径
// 如果没有提供草稿路径,直接使用模板路径(适合直接编辑模板的场景)
// 如果提供了草稿路径,使用复制后的文件路径
const finalFilePath = draftFilePath || template.file_path;
console.log('[Draft Service] 创建草稿:', {
templateId: request.templateId,
templatePath: template.file_path,
draftPath: draftFilePath,
finalPath: finalFilePath,
mode: draftFilePath ? '使用复制文件' : '直接使用模板文件'
});
// 3. 创建草稿记录
const insertResponse = await postgrestPost('drafted_contracts', {
body: {
template_id: request.templateId,
file_path: finalFilePath,
title: request.title,
placeholder_values: {},
status: 'draft',
created_by: userId
},
select: '*',
token: jwt
});
if (!insertResponse.data) {
throw new Error('创建草稿记录失败');
}
const draft = Array.isArray(insertResponse.data) ? insertResponse.data[0] : insertResponse.data;
return draft as DraftedContract;
} catch (error) {
console.error('[Draft Service] 创建草稿失败:', error);
throw error;
}
}
/**
* 删除草稿记录
* @param draftId 草稿ID
* @param userId 用户ID
* @param jwt JWT token
*/
export async function deleteDraft(
draftId: number,
userId: number,
jwt?: string
): Promise<void> {
try {
const response = await postgrestDelete('drafted_contracts', {
filter: {
id: `eq.${draftId}`,
created_by: `eq.${userId}` // 确保只能删除自己的草稿
},
token: jwt
});
console.log('[Draft Service] 草稿已删除:', draftId);
} catch (error) {
console.error('[Draft Service] 删除草稿失败:', error);
throw error;
}
}
/**
* 获取草稿详情
* @param draftId 草稿ID
* @param userId 用户ID
* @returns 草稿记录
*/
export async function getDraftById(
draftId: number,
userId: number,
jwt?: string
): Promise<DraftedContract | null> {
try {
const response = await postgrestGet('drafted_contracts', {
select: '*',
filter: {
id: `eq.${draftId}`,
created_by: `eq.${userId}`
},
token: jwt
});
if (!response.data || (Array.isArray(response.data) && response.data.length === 0)) {
return null;
}
const draft = Array.isArray(response.data) ? response.data[0] : response.data;
return draft as DraftedContract;
} catch (error) {
console.error('[Draft Service] 获取草稿失败:', error);
return null;
}
}
+2 -2
View File
@@ -90,7 +90,7 @@ async function safeGetJWT(jwtToken?: string): Promise<string> {
*/
export async function findIsProposer(taskId: string | number, userId: number | undefined, frontendJWT?: string): Promise<boolean> {
// 通过postgrest的get请求去cross_examination_tasks表中进行查找assignee_id是否等于userId
const response = await postgrestGet(`cross_examination_tasks`, {
const response = await postgrestGet(`/api/postgrest/proxy/cross_examination_tasks`, {
select: 'assigner_id',
filter: {
id: `eq.${taskId}`
@@ -399,7 +399,7 @@ export async function confirmReviewResults(
): Promise<{data?: unknown, error?: string, status?: number}> {
try {
// 通过postgrest的post请求去documents表中进行查找id等于documentId的数据,更新documents表的audit_status为1
const response = await postgrestPut(`documents`, {
const response = await postgrestPut(`/api/postgrest/proxy/documents`, {
audit_status: 1
}, {
id: documentId
+2 -2
View File
@@ -494,7 +494,7 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number,
}
const response = await postgrestPut<TaskDocument, Partial<TaskDocument>>(
'documents',
'/api/postgrest/proxy/documents',
{ audit_status: auditStatus },
{
id: parseInt(id)
@@ -526,7 +526,7 @@ export async function getCrossCheckingDocumentTypes(jwtToken?: string): Promise<
try {
// console.log('[getCrossCheckingDocumentTypes] 开始获取交叉评查文档类型');
const response = await postgrestGet<DocumentType>('document_types',{
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'
@@ -34,7 +34,7 @@ export async function verifyDocumentAccess(
try {
// 1. 检查文档是否属于该任务(通过 cross_task_document_mapping 表)
const documentMappingResponse = await postgrestGet('cross_task_document_mapping', {
const documentMappingResponse = await postgrestGet('/api/postgrest/proxy/cross_task_document_mapping', {
select: 'task_id,document_id',
filter: {
task_id: `eq.${taskId}`,
@@ -66,7 +66,7 @@ export async function verifyDocumentAccess(
}
// 2. 检查用户是否是该任务的参与者
const taskResponse = await postgrestGet('cross_examination_tasks', {
const taskResponse = await postgrestGet('/api/postgrest/proxy/cross_examination_tasks', {
select: 'assigner_id,assignee_ids',
filter: {
id: `eq.${taskId}`
+12 -11
View File
@@ -158,7 +158,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
limit: 1,
token: frontendJWT
};
const contractStructureComparisonResponse = await postgrestGet('contract_structure_comparison', contractStructureComparisonParams);
const contractStructureComparisonResponse = await postgrestGet('/api/postgrest/proxy/contract_structure_comparison', contractStructureComparisonParams);
// console.log('contract_structure_comparison', contractStructureComparisonResponse)
if (contractStructureComparisonResponse.error) {
@@ -201,7 +201,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
},
token: frontendJWT
};
const evaluationResultsResponse = await postgrestGet('evaluation_results', evaluationResultsParams);
const evaluationResultsResponse = await postgrestGet('/api/postgrest/proxy/evaluation_results', evaluationResultsParams);
// console.log('evaluationResultsResponse-------', evaluationResultsResponse,);
if (evaluationResultsResponse.error) {
@@ -230,7 +230,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
},
token: frontendJWT
};
const evaluationPointsResponse = await postgrestGet('evaluation_points', evaluationPointsParams);
const evaluationPointsResponse = await postgrestGet('/api/postgrest/proxy/evaluation_points', evaluationPointsParams);
if (evaluationPointsResponse.error) {
return { error: evaluationPointsResponse.error, status: evaluationPointsResponse.status };
@@ -257,7 +257,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
},
token: frontendJWT
};
const groupsResponse = await postgrestGet('evaluation_point_groups', groupsParams);
const groupsResponse = await postgrestGet('/api/postgrest/proxy/evaluation_point_groups', groupsParams);
if (groupsResponse.error) {
return { error: groupsResponse.error, status: groupsResponse.status };
@@ -281,7 +281,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
},
token: frontendJWT
};
const manualReviewPointsResponse = await postgrestGet('audit_status', manualReviewPointsParams);
const manualReviewPointsResponse = await postgrestGet('/api/postgrest/proxy/audit_status', manualReviewPointsParams);
if (manualReviewPointsResponse.error) {
return { error: manualReviewPointsResponse.error, status: manualReviewPointsResponse.status };
}
@@ -336,7 +336,7 @@ export async function getReviewPoints(fileId: string, request: Request) {
},
token: frontendJWT
};
const scoringProposalsResponse = await postgrestGet('cross_scoring_proposals', scoringProposalsParams);
const scoringProposalsResponse = await postgrestGet('/api/postgrest/proxy/cross_scoring_proposals', scoringProposalsParams);
if (scoringProposalsResponse.error) {
return { error: scoringProposalsResponse.error, status: scoringProposalsResponse.status };
@@ -776,12 +776,13 @@ export async function updateReviewResult(
}
// 首先获取当前评查结果数据
const currentResultResponse = await postgrestGet('evaluation_results', {
const currentResultResponse = await postgrestGet('/api/postgrest/proxy/evaluation_results', {
select: '*',
filter: { id: `eq.${resultId}` },
token: frontendJWT
});
console.log('/api/postgrest/proxy/evaluation_results',currentResultResponse.error)
if (currentResultResponse.error) {
return { error: currentResultResponse.error, status: currentResultResponse.status };
}
@@ -812,7 +813,7 @@ export async function updateReviewResult(
// 调用 API 更新评查点结果数据
const resultResponse = await postgrestPut<unknown, typeof updatedData>(
'evaluation_results',
'/api/postgrest/proxy/evaluation_results',
updatedData,
{ id: resultId },
frontendJWT
@@ -831,7 +832,7 @@ export async function updateReviewResult(
if (editAuditStatusId && editAuditStatusId !== '') {
// 更新现有审核状态记录
const auditStatusResponse = await postgrestPut(
'audit_status',
'/api/postgrest/proxy/audit_status',
{
edit_audit_status: editAuditStatusValue,
// 重新审核时不更新message
@@ -864,7 +865,7 @@ export async function updateReviewResult(
};
// 使用postgrestPost创建新记录
const postResponse = await postgrestPost('audit_status', newAuditStatus, frontendJWT);
const postResponse = await postgrestPost('/api/postgrest/proxy/audit_status', newAuditStatus, frontendJWT);
if (postResponse.error) {
return { error: postResponse.error, status: postResponse.status || 500 };
@@ -938,7 +939,7 @@ export async function confirmReviewResults(documentId: string, request: Request)
// 调用API更新文档审核状态
const response = await postgrestPut<{ id: string }, typeof updateDocumentParams>(
'documents',
'/api/postgrest/proxy/documents',
updateDocumentParams,
{
id: documentId,
-314
View File
@@ -88,122 +88,6 @@ export interface RuleGroupQueryParams {
token?: string;
}
/**
* 获取评查点分组列表(支持分页、筛选、排序)
* @deprecated 使用 getEvaluationPointGroups 代替(FastAPI v3
* @param params 查询参数
* @returns 评查点分组列表和总数
*/
export async function getRuleGroups_legacy(
params?: RuleGroupQueryParams
): Promise<{data: RuleGroup[]; totalCount?: number; error?: never} | {data?: never; error: string; status?: number}> {
try {
const {
page = 1,
pageSize = 50,
name,
code,
is_enabled,
pid = '0', // 默认获取一级分组
orderBy = 'created_at',
order = 'desc',
token
} = params || {};
// 构建筛选条件
const filter: Record<string, string> = {};
// 父级ID筛选 (pid=null或'0'表示一级分组)
if (pid === null || pid === '0') {
filter['pid'] = 'eq.0';
} else if (pid) {
filter['pid'] = `eq.${pid}`;
}
// 名称模糊搜索
if (name) {
filter['name'] = `ilike.*${name}*`;
}
// 编码模糊搜索
if (code) {
filter['code'] = `ilike.*${code}*`;
}
// 状态筛选
if (is_enabled !== undefined) {
filter['is_enabled'] = `eq.${is_enabled}`;
}
const postgrestParams: PostgrestParams = {
select: `
id,
pid,
name,
code,
description,
is_enabled,
created_at
`,
filter,
order: `${orderBy}.${order}`, // PostgREST order format: field.direction
limit: pageSize,
offset: (page - 1) * pageSize,
token
};
const response = await postgrestGet<{code: number; msg: string; data: Array<{
id: number;
pid: number;
name: string;
code?: string;
description?: string;
is_enabled: boolean;
created_at?: string;
}>}>('evaluation_point_groups', postgrestParams);
if (response.error) {
return { error: response.error, status: response.status };
}
// 处理响应数据
let groups: RuleGroup[] = [];
if (response.data && 'code' in response.data && response.data.data) {
groups = response.data.data.map(group => ({
id: group.id.toString(),
pid: group.pid.toString(),
name: group.name,
code: group.code,
description: group.description,
is_enabled: group.is_enabled,
createdAt: group.created_at ? formatDate(group.created_at) : undefined
}));
} else if (Array.isArray(response.data)) {
groups = response.data.map(group => ({
id: group.id.toString(),
pid: group.pid.toString(),
name: group.name,
code: group.code,
description: group.description,
is_enabled: group.is_enabled,
createdAt: group.created_at ? formatDate(group.created_at) : undefined
}));
}
// 注意:由于当前 PostgREST 客户端不支持 count 参数,totalCount 返回当前页的记录数
// 后续可优化为单独查询获取准确的总数
return {
data: groups,
totalCount: groups.length
};
} catch (error) {
console.error('获取评查点分组列表失败:', error);
return {
error: error instanceof Error ? error.message : '获取评查点分组列表失败',
status: 500
};
}
}
/**
* 获取指定分组的子分组(包含评查点数量统计)
@@ -241,204 +125,6 @@ export async function getChildGroups(parentId: string, token?: string): Promise<
// ==================== 批量操作接口 ====================
/**
* 批量更新分组状态(启用/禁用)
* @deprecated 使用 batchUpdateEvaluationPointGroupStatus 代替(FastAPI v3
* @param ids 分组ID列表
* @param is_enabled 目标状态
* @param token JWT token (可选)
* @returns 更新结果
*/
export async function batchUpdateRuleGroupStatus_legacy(
ids: string[],
is_enabled: boolean,
token?: string
): Promise<{
success: boolean;
updated_count: number;
failed_ids: string[];
errors?: Array<{ id: string; error: string }>;
}> {
try {
// ========== 1. 参数验证 ==========
if (!Array.isArray(ids) || ids.length === 0) {
return {
success: false,
updated_count: 0,
failed_ids: [],
errors: [{ id: 'validation', error: 'ID列表不能为空' }]
};
}
// 验证每个ID的有效性
const invalidIds = ids.filter(id => !id || id.trim() === '');
if (invalidIds.length > 0) {
return {
success: false,
updated_count: 0,
failed_ids: ids,
errors: [{ id: 'validation', error: '存在无效的分组ID' }]
};
}
// ========== 2. 逐个更新(确保每个分组都能被正确处理) ==========
const failedIds: string[] = [];
const errors: Array<{ id: string; error: string }> = [];
let updatedCount = 0;
for (const id of ids) {
try {
// 验证分组是否存在
const groupResponse = await getRuleGroup(id, token);
if (groupResponse.error || !groupResponse.data) {
failedIds.push(id);
errors.push({ id, error: '分组不存在或无法访问' });
continue;
}
// 执行更新
const updateResponse = await postgrestPut<ApiResponse<RuleGroup> | RuleGroup, Partial<ApiRuleGroup>>(
'evaluation_point_groups',
{ is_enabled },
{ id },
token
);
if (updateResponse.error) {
failedIds.push(id);
errors.push({ id, error: updateResponse.error });
} else {
updatedCount++;
}
} catch (error) {
failedIds.push(id);
errors.push({
id,
error: error instanceof Error ? error.message : '更新失败'
});
}
}
// ========== 3. 返回结果 ==========
return {
success: failedIds.length === 0,
updated_count: updatedCount,
failed_ids: failedIds,
errors: errors.length > 0 ? errors : undefined
};
} catch (error) {
console.error('批量更新分组状态失败:', error);
return {
success: false,
updated_count: 0,
failed_ids: ids,
errors: [{
id: 'batch',
error: error instanceof Error ? error.message : '批量更新失败'
}]
};
}
}
/**
* 批量删除分组(安全的阻止删除策略)
* @deprecated 使用 batchDeleteEvaluationPointGroups 代替(FastAPI v3
* @param ids 分组ID列表
* @param token JWT token (可选)
* @returns 删除结果
*/
export async function batchDeleteRuleGroups_legacy(
ids: string[],
token?: string
): Promise<{
success: boolean;
deleted_count: number;
failed_ids: string[];
errors?: Array<{ id: string; error: string; details?: { hasChildren?: boolean; hasPoints?: boolean } }>;
}> {
try {
// ========== 1. 参数验证 ==========
if (!Array.isArray(ids) || ids.length === 0) {
return {
success: false,
deleted_count: 0,
failed_ids: [],
errors: [{ id: 'validation', error: 'ID列表不能为空' }]
};
}
// 验证每个ID的有效性
const invalidIds = ids.filter(id => !id || id.trim() === '');
if (invalidIds.length > 0) {
return {
success: false,
deleted_count: 0,
failed_ids: ids,
errors: [{ id: 'validation', error: '存在无效的分组ID' }]
};
}
// ========== 2. 逐个删除(使用安全的阻止删除策略) ==========
const failedIds: string[] = [];
const errors: Array<{ id: string; error: string; details?: { hasChildren?: boolean; hasPoints?: boolean } }> = [];
let deletedCount = 0;
for (const id of ids) {
try {
const deleteResult = await deleteRuleGroup(id, token);
if (!deleteResult.success) {
failedIds.push(id);
errors.push({
id,
error: deleteResult.error || '删除失败',
details: deleteResult.details ? {
hasChildren: deleteResult.details.hasChildren,
hasPoints: deleteResult.details.hasPoints
} : undefined
});
} else {
deletedCount++;
}
} catch (error) {
failedIds.push(id);
errors.push({
id,
error: error instanceof Error ? error.message : '删除失败'
});
}
}
// ========== 3. 返回结果 ==========
return {
success: failedIds.length === 0,
deleted_count: deletedCount,
failed_ids: failedIds,
errors: errors.length > 0 ? errors : undefined
};
} catch (error) {
console.error('批量删除分组失败:', error);
return {
success: false,
deleted_count: 0,
failed_ids: ids,
errors: [{
id: 'batch',
error: error instanceof Error ? error.message : '批量删除失败'
}]
};
}
}
// ========================================
// FastAPI v3 接口函数(新版)
// ========================================
+1 -1
View File
@@ -136,7 +136,7 @@ export async function updateDocumentAuditStatus(id: string, auditStatus: number,
});
const response = await postgrestPut<Document, Partial<Document>>(
'documents',
'/api/postgrest/proxy/documents',
{ audit_status: auditStatus },
{
id: parseInt(id),
+15 -15
View File
@@ -364,7 +364,7 @@ export async function getRule(id: string, token?: string): Promise<{data: Rule;
};
// 获取评查点详情 - 使用正确的PostgREST格式
const response = await postgrestGet<{code: number; msg: string; data: ApiRule} | ApiRule[]>('evaluation_points', postgrestParams);
const response = await postgrestGet<{code: number; msg: string; data: ApiRule} | ApiRule[]>('/api/postgrest/proxy/evaluation_points', postgrestParams);
// 检查是否有错误响应
if (response.error) {
@@ -401,7 +401,7 @@ export async function getRule(id: string, token?: string): Promise<{data: Rule;
};
// 查询评查点分组
const groupResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('evaluation_point_groups', groupParams);
const groupResponse = await postgrestGet<{code: number; msg: string; data: {id: number; name: string}[]}>('/api/postgrest/proxy/evaluation_point_groups', groupParams);
if (groupResponse.data?.data && groupResponse.data.data.length > 0) {
// 将分组信息添加到评查点数据中
@@ -480,7 +480,7 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
}
// 检查分组是否存在
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('evaluation_point_groups', {
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('/api/postgrest/proxy/evaluation_point_groups', {
filter: { 'id': `eq.${ruleData.groupId}` },
select: 'id,name,pid',
token
@@ -524,7 +524,7 @@ export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'upda
};
// 使用postgrestPost创建评查点
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('evaluation_points', apiRuleData, token);
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('/api/postgrest/proxy/evaluation_points', apiRuleData, token);
// 检查是否有错误响应
if (response.error) {
@@ -599,7 +599,7 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
// 4. 验证分组ID有效性(如果提供)
if (ruleData.groupId !== undefined) {
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('evaluation_point_groups', {
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('/api/postgrest/proxy/evaluation_point_groups', {
filter: { 'id': `eq.${ruleData.groupId}` },
select: 'id,name,pid',
token
@@ -645,7 +645,7 @@ export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' |
}
// 使用postgrestPut更新评查点 - 使用正确的PostgREST格式
const response = await postgrestPut<{code: number; msg: string; data: ApiRule} | ApiRule[], typeof apiRuleData>('evaluation_points', apiRuleData, { id: parseInt(id) }, token);
const response = await postgrestPut<{code: number; msg: string; data: ApiRule} | ApiRule[], typeof apiRuleData>('/api/postgrest/proxy/evaluation_points', apiRuleData, { id: parseInt(id) }, token);
// 检查是否有错误响应
if (response.error) {
@@ -816,7 +816,7 @@ export async function getRuleTypes(documentTypeIds?: number[], token?: string):
name: string;
evaluation_point_groups_ids: number[];
}>
}>('document_types', documentTypesParams);
}>('/api/postgrest/proxy/document_types', documentTypesParams);
if (documentTypesResponse.error) {
return { error: documentTypesResponse.error, status: documentTypesResponse.status };
@@ -890,7 +890,7 @@ export async function getRuleTypes(documentTypeIds?: number[], token?: string):
description: string;
is_enabled: boolean;
}>
}>('evaluation_point_groups', groupsParams);
}>('/api/postgrest/proxy/evaluation_point_groups', groupsParams);
// 检查是否有错误响应
if (response.error) {
@@ -975,7 +975,7 @@ export async function getRuleGroupsByType(typeId: string, token?: string): Promi
description: string;
is_enabled: boolean;
}>;
}>('evaluation_point_groups', postgrestParams);
}>('/api/postgrest/proxy/evaluation_point_groups', postgrestParams);
// 检查是否有错误响应
if (response.error) {
@@ -1169,7 +1169,7 @@ export async function getFormattedEvaluationPoint(id: number): Promise<{
}
};
const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]} | ApiRule[]>('evaluation_points', postgrestParams);
const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]} | ApiRule[]>('/api/postgrest/proxy/evaluation_points', postgrestParams);
if (response.error) {
return {
@@ -1238,7 +1238,7 @@ export async function getEvaluationPointGroups(): Promise<{
updated_at?: string;
};
const response = await postgrestGet<{code: number; msg: string; data: EvaluationPointGroupType[]} | EvaluationPointGroupType[]>('evaluation_point_groups', postgrestParams);
const response = await postgrestGet<{code: number; msg: string; data: EvaluationPointGroupType[]} | EvaluationPointGroupType[]>('/api/postgrest/proxy/evaluation_point_groups', postgrestParams);
if (response.error) {
return {
@@ -1480,14 +1480,14 @@ export async function saveEvaluationPoint(evaluationPoint: EvaluationPointInput,
if (isEditMode) {
// 更新操作
response = await postgrestPut<{code: number; msg: string; data: ApiRule} | ApiRule, typeof cleanedData>(
`evaluation_points`,
`/api/postgrest/proxy/evaluation_points`,
cleanedData,
{id: cleanedData.id!}
);
} else {
// 创建操作
response = await postgrestPost<{code: number; msg: string; data: ApiRule} | ApiRule, typeof cleanedData>(
'evaluation_points',
'/api/postgrest/proxy/evaluation_points',
cleanedData
);
}
@@ -1566,7 +1566,7 @@ export async function getRuleStatistics(token?: string): Promise<{data: RuleStat
is_enabled: boolean;
risk: string;
evaluation_point_groups_id: number | null;
}>}>('evaluation_points', postgrestParams);
}>}>('/api/postgrest/proxy/evaluation_points', postgrestParams);
// 检查是否有错误响应
if (response.error) {
@@ -1628,7 +1628,7 @@ export async function getRuleStatistics(token?: string): Promise<{data: RuleStat
token
};
const groupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string}>}>('evaluation_point_groups', groupsParams);
const groupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string}>}>('/api/postgrest/proxy/evaluation_point_groups', groupsParams);
let groups: Array<{id: number; name: string}> = [];
if (groupsResponse.data && 'code' in groupsResponse.data && groupsResponse.data.data) {
+5 -103
View File
@@ -145,7 +145,7 @@ function getFileExtension(filename: string): string {
* @returns 评查结果
*/
async function getEvaluationResults(id: number, frontendJWT?: string) {
const response = await postgrestGet<[]>('evaluation_results', {
const response = await postgrestGet<[]>('/api/postgrest/proxy/evaluation_results', {
filter: {
'document_id': `eq.${id}`
},
@@ -254,7 +254,7 @@ export async function deleteDocument(id: string, userId: string, token?: string)
}
const response = await postgrestDelete(
'documents',
'/api/postgrest/proxy/documents',
{
filter: {
'id': `eq.${id}`,
@@ -298,7 +298,7 @@ export async function getDocument(id: string, userId: string, frontendJWT?: stri
}
const response = await postgrestGet<Document[]>(
'documents',
'/api/postgrest/proxy/documents',
{
filter: {
'id': `eq.${id}`,
@@ -349,7 +349,7 @@ export async function getDocumentWithNoUserId(id: string, frontendJWT?: string):
// console.log("get单个文档id", id)
const response = await postgrestGet<Document[]>(
'documents',
'/api/postgrest/proxy/documents',
{
filter: {
'id': `eq.${id}`,
@@ -430,7 +430,7 @@ export async function updateDocument(id: string, document: Partial<DocumentUI> &
// console.log('更新文档API数据:', apiDocument);
const response = await postgrestPut<Document, Partial<Document>>(
'documents',
'/api/postgrest/proxy/documents',
apiDocument,
{
id: parseInt(id),
@@ -653,101 +653,3 @@ export async function getDocumentsListFromAPI(searchParams: {
};
}
}
/**
* 获取文档历史版本列表
* @param documentName 文档名称
* @param userId 用户ID
* @param excludeId 排除的文档ID(当前最新版本的ID)
* @param token JWT token
* @returns 历史版本列表
*/
export async function getDocumentHistory(
documentName: string,
userId: string,
excludeId: number,
token?: string
): Promise<{
data?: DocumentVersionUI[];
error?: string;
status?: number;
}> {
try {
if (!documentName) {
return { error: '文档名称不能为空', status: 400 };
}
if (!userId) {
return { error: '用户身份验证失败', status: 401 };
}
// 调用 RPC 函数获取历史版本
const response = await postgrestPost<any[], unknown>(
'rpc/documents_get_document_history',
{
p_document_name: documentName,
p_user_id: parseInt(userId, 10),
p_exclude_id: excludeId
},
token
);
if (response.error || !response.data) {
return { error: response.error || '获取历史版本失败', status: response.status || 500 };
}
const historyDocs = response.data;
// 转换为 UI 格式,并计算问题数量差异
const documents: DocumentVersionUI[] = historyDocs.map((doc: any, index: number) => {
// 计算与下一个版本(更早的版本)的问题数量差异
let issuesDiff: number | undefined;
let issuesDiffType: 'increase' | 'decrease' | 'same' | undefined;
if (index < historyDocs.length - 1) {
const olderDoc = historyDocs[index + 1];
if (doc.false_count != null && olderDoc.false_count != null) {
const diff = doc.false_count - olderDoc.false_count;
issuesDiff = Math.abs(diff);
if (diff > 0) {
issuesDiffType = 'increase';
} else if (diff < 0) {
issuesDiffType = 'decrease';
} else {
issuesDiffType = 'same';
}
}
}
return {
id: doc.id,
name: doc.name,
documentNumber: doc.document_number,
type: doc.type_id.toString(),
typeName: doc.type_name || '未知类型',
size: doc.file_size,
auditStatus: doc.audit_status ?? 0,
fileStatus: doc.status || '',
issues: doc.false_count ?? null,
issuesDiff,
issuesDiffType,
uploadTime: formatDate(doc.created_at),
fileType: getFileExtension(doc.name),
path: doc.path,
isTest: doc.is_test_document,
updatedAt: formatDate(doc.updated_at),
pageCount: doc.ocr_result?.__meta?.page_count || 0,
ocrResult: doc.ocr_result,
versionNumber: historyDocs.length - index
};
});
return { data: documents };
} catch (error) {
console.error('获取文档历史版本失败:', error);
return {
error: error instanceof Error ? error.message : '获取文档历史版本失败',
status: 500
};
}
}
+4 -4
View File
@@ -497,7 +497,7 @@ export async function getTodayDocuments(
}
// console.log('发送请求参数:', params);
const response = await postgrestGet<Document[]>('documents', { ...params, token });
const response = await postgrestGet<Document[]>('/api/postgrest/proxy/documents', { ...params, token });
// console.log('API 响应:', response);
if (response.error) {
@@ -552,7 +552,7 @@ export async function getDocumentTypes(token?: string): Promise<{data: DocumentT
}
// 如果没有 documentTypeIds,返回所有文档类型(不添加过滤条件)
const response = await postgrestGet<DocumentType[]>('document_types', { ...params, token });
const response = await postgrestGet<DocumentType[]>('/api/postgrest/proxy/ocument_types', { ...params, token });
if (response.error) {
return { error: response.error, status: response.status };
@@ -600,7 +600,7 @@ export async function getDocumentsStatus(
'id': `in.(${documentIds.join(',')})`
}
};
documentsResponse = await postgrestGet<Document[]>('documents', { ...documentsParams, token });
documentsResponse = await postgrestGet<Document[]>('/api/postgrest/proxy/documents', { ...documentsParams, token });
}
// 查询合同附件状态
@@ -613,7 +613,7 @@ export async function getDocumentsStatus(
'id': `in.(${attachmentIds.join(',')})`
}
};
attachmentResponse = await postgrestGet<ContractStructureComparison[]>('contract_structure_comparison', { ...attachmentParams, token });
attachmentResponse = await postgrestGet<ContractStructureComparison[]>('/api/postgrest/proxy/contract_structure_comparison', { ...attachmentParams, token });
}
if (documentsResponse.error && attachmentResponse.error) {
+2 -2
View File
@@ -244,7 +244,7 @@ export async function getEntryModules(userRole: string | null | undefined, userA
filter: {}
};
const modulesResponse = await postgrestGet('entry_modules', { ...params, token });
const modulesResponse = await postgrestGet('/api/postgrest/proxy/entry_modules', { ...params, token });
if (modulesResponse.error) {
console.error('❌ [getEntryModules] 查询入口模块失败:', modulesResponse.error);
@@ -295,7 +295,7 @@ export async function getEntryModules(userRole: string | null | undefined, userA
}
};
const typesResponse = await postgrestGet('document_types', { ...typesParams, token });
const typesResponse = await postgrestGet('/api/postgrest/proxy/document_types', { ...typesParams, token });
if (typesResponse.error) {
console.error(`❌ [getEntryModules] 查询模块 ${module.id} 的文档类型失败:`, typesResponse.error);
+1 -244
View File
@@ -530,247 +530,4 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise<void
console.error("❌ [callIDaaSLogout] 调用IDaaS登出接口失败(非HTTP错误):", error);
throw error;
}
}
/**
* 保存用户信息到数据库
*
* 此函数实现以下逻辑:
* 1. 内部生成临时 JWTuser_id 为 'login',仅用于数据库操作)
* 2. 根据 userInfo.sub 查询 sso_users 表中是否已存在该用户
* 3. 如果存在,则更新用户信息(如果用户已有 area 值则不更新)
* 4. 如果不存在,则插入新的用户记录
* 5. 返回保存的用户数据和临时 JWT
*
* @param userInfo - 从 IDaaS 获取的用户信息
* @param userRole - 用户角色
* @param tokenExpiresIn - Token过期时间(秒)
* @param area - 用户所属地区,根据端口号确定
* @returns Promise<{success: boolean, data?: SsoUser, tempToken?: string, error?: string}>
*/
export async function saveUserInfo(
userInfo: UserInfo,
userRole: UserRole,
tokenExpiresIn: number,
area?: string
): Promise<{success: boolean, data?: SsoUser, tempToken?: string, error?: string}> {
try {
console.log("开始保存用户信息", userInfo);
// 验证必要字段
if (!userInfo.sub) {
return { success: false, error: "用户唯一标识 sub 不能为空" };
}
// 🔒 安全:在服务端生成临时 JWT,user_id 使用占位符 'login'
// 这样客户端无法看到真实的 user_id
const tempUserInfo: UserInfoForJWT = {
sub: userInfo.sub,
user_id: 'login', // 使用占位符,避免在客户端暴露真实ID
username: 'login',
nick_name: userInfo.nick_name || userInfo.nickname || userInfo.name || "未知用户",
email: userInfo.email,
phone_number: userInfo.phone_number,
ou_id: userInfo.ou_id || "default",
ou_name: userInfo.ou_name || "未知部门",
is_leader: userInfo.is_leader || false,
user_role: userRole
};
const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenExpiresIn);
// 1. 根据 sub 查询是否已存在该用户
const existingUserResult = await postgrestGet<SsoUser[]>("sso_users", {
filter: {
"sub": `eq.${userInfo.sub}`,
"deleted_at": "is.null" // 只查询未删除的记录
},
token: tempToken
});
if (existingUserResult.error) {
console.error("查询用户失败:", existingUserResult.error);
return { success: false, error: `查询用户失败: ${existingUserResult.error}` };
}
const existingUsers = existingUserResult.data || [];
const existingUser = existingUsers.length > 0 ? existingUsers[0] : null;
// 准备要保存的用户数据
// 注意:OAuth返回的字段是nickname,而不是nick_name
const userData: Partial<SsoUser> = {
sub: userInfo.sub,
username: userInfo.username || userInfo.name || userInfo.sub,
nick_name: userInfo.nick_name || userInfo.nickname || userInfo.name || "未知用户",
phone_number: userInfo.phone_number || undefined,
email: userInfo.email || undefined,
ou_id: userInfo.ou_id || "default",
ou_name: userInfo.ou_name || "未知部门",
status: userInfo.status !== undefined ? userInfo.status : 0,
is_leader: userInfo.is_leader || false,
};
if (existingUser) {
// 2. 用户已存在,执行更新操作
console.log("用户已存在,执行更新操作", existingUser.id);
// 只有在现有用户没有 area 或 area 为空时,才更新 area
if (area && !existingUser.area) {
userData.area = area;
console.log("用户原本无地区信息,更新地区为:", area);
} else if (existingUser.area) {
console.log("用户已有地区信息:", existingUser.area, "不更新");
}
const updateResult = await postgrestPut<SsoUser[], Partial<SsoUser>>(
"sso_users",
userData,
{ id: existingUser.id! },
tempToken
);
if (updateResult.error) {
console.error("更新用户失败:", updateResult.error);
return { success: false, error: `更新用户失败: ${updateResult.error}` };
}
console.log("用户信息更新成功");
return {
success: true,
data: Array.isArray(updateResult.data) ? updateResult.data[0] : updateResult.data as unknown as SsoUser,
tempToken // 返回临时 JWT
};
} else {
// 3. 用户不存在,执行插入操作,设置地区信息
console.log("用户不存在,执行插入操作");
// 新用户直接设置 area
if (area) {
userData.area = area;
console.log("新用户,设置地区为:", area);
}
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser, tempToken);
if (insertResult.error) {
console.error("插入用户失败:", insertResult.error);
return { success: false, error: `插入用户失败: ${insertResult.error}` };
}
console.log("用户信息插入成功");
// 4. 给这个用户默认添加一个角色,角色为common
const userData_with_id = Array.isArray(insertResult.data) ? insertResult.data[0] : insertResult.data as unknown as SsoUser;
if (userData_with_id?.id) {
await addDefaultRole(userData_with_id.id, 2, tempToken);
}
return {
success: true,
data: userData_with_id,
tempToken // 返回临时 JWT
};
}
} catch (error) {
console.error("保存用户信息时发生错误:", error);
return {
success: false,
error: `保存用户信息失败: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* 为用户添加默认角色
*
* @param userId - 用户ID
* @param roleId - 角色ID,默认为2common角色)
* @param token - JWT令牌,用于调用postgrest服务
* @returns 添加结果
*/
export async function addDefaultRole(userId: string, roleId: number = 2, token?: string) {
try {
console.log(`为用户 ${userId} 添加默认角色 ${roleId}`);
// 检查用户是否已经有此角色
const existingRoleResult = await postgrestGet<Array<{id: number, user_id: string, role_id: number}>>("user_role", {
filter: {
user_id: `eq.${userId}`,
role_id: `eq.${roleId}`
},
token
});
if (existingRoleResult.error) {
console.error("查询用户角色失败:", existingRoleResult.error);
return { success: false, error: `查询用户角色失败: ${existingRoleResult.error}` };
}
const existingRoles = existingRoleResult.data || [];
if (existingRoles.length > 0) {
console.log("用户已经拥有此角色,跳过添加");
return { success: true, data: existingRoles[0] };
}
// 添加角色
const addRoleResult = await postgrestPost<Array<{id: number, user_id: string, role_id: number}>, {user_id: string, role_id: number}>("user_role", {
user_id: userId,
role_id: roleId
}, token);
if (addRoleResult.error) {
console.error("添加用户角色失败:", addRoleResult.error);
return { success: false, error: `添加用户角色失败: ${addRoleResult.error}` };
}
console.log("用户角色添加成功");
return {
success: true,
data: Array.isArray(addRoleResult.data) ? addRoleResult.data[0] : addRoleResult.data
};
} catch (error) {
console.error("添加用户角色时发生错误:", error);
return {
success: false,
error: `添加用户角色失败: ${error instanceof Error ? error.message : String(error)}`
};
}
}
/**
* 通过用户sub获取用户信息
*
* @param sub - 用户的唯一标识
* @returns 用户信息
*/
export async function getUserBySub(sub: string) {
try {
// console.log(`查询用户: ${sub}`);
const userResult = await postgrestGet<SsoUser[]>("sso_users", {
filter: {
sub: `eq.${sub}`
}
});
if (userResult.error) {
console.error("查询用户失败:", userResult.error);
return { success: false, error: `查询用户失败: ${userResult.error}` };
}
const users = userResult.data || [];
const user = users.length > 0 ? users[0] : null;
if (!user) {
return { success: false, error: "用户不存在" };
}
return { success: true, data: user };
} catch (error) {
console.error("查询用户时发生错误:", error);
return {
success: false,
error: `查询用户失败: ${error instanceof Error ? error.message : String(error)}`
};
}
}
}
+159
View File
@@ -0,0 +1,159 @@
/**
* MinIO 存储管理 API 客户端
* 基于 /api/v2/storage 接口
*/
import { apiRequest } from '../axios-client';
export interface CopyFileRequest {
source_path: string;
destination_path: string;
source_bucket?: string | null;
destination_bucket?: string | null;
}
export interface CopyFileResponse {
success: boolean;
message: string;
source_path: string;
destination_path: string;
}
export interface MoveFileRequest {
source_path: string;
destination_path: string;
}
export interface MoveFileResponse {
success: boolean;
message: string;
source_path: string;
destination_path: string;
}
export interface DeleteFileRequest {
file_path: string;
}
export interface DeleteFileResponse {
success: boolean;
message: string;
file_path: string;
}
/**
* 复制文件
* @param request 复制文件请求参数
* @param token JWT token(可选)
* @returns 复制结果
*/
export async function copyFile(request: CopyFileRequest, token?: string) {
const response = await apiRequest<CopyFileResponse>(
'/api/v2/storage/files/copy',
{
method: 'POST',
data: request,
headers: {
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
throw new Error(response.error);
}
return response.data;
}
/**
* 移动/重命名文件
* @param request 移动文件请求参数
* @param token JWT token(可选)
* @returns 移动结果
*/
export async function moveFile(request: MoveFileRequest, token?: string) {
const response = await apiRequest<MoveFileResponse>(
'/api/v2/storage/files/move',
{
method: 'POST',
data: request,
headers: {
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
throw new Error(response.error);
}
return response.data;
}
/**
* 删除文件
* @param request 删除文件请求参数
* @param token JWT token(可选)
* @returns 删除结果
*/
export async function deleteFile(request: DeleteFileRequest, token?: string) {
const response = await apiRequest<DeleteFileResponse>(
'/api/v2/storage/files',
{
method: 'DELETE',
headers: {
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
},
{ file_path: request.file_path }
);
if (response.error) {
throw new Error(response.error);
}
return response.data;
}
/**
* 生成新的文件路径(用于复制模板文件)
* @param originalPath 原始文件路径
* @param area 地区代码
* @returns 新文件路径
*/
export function generateDraftFilePath(originalPath: string, area: string): string {
// 提取文件目录和文件名
const lastSlashIndex = originalPath.lastIndexOf('/');
const directory = lastSlashIndex >= 0 ? originalPath.substring(0, lastSlashIndex) : '';
const fileName = lastSlashIndex >= 0 ? originalPath.substring(lastSlashIndex + 1) : originalPath;
// 提取文件扩展名
const lastDotIndex = fileName.lastIndexOf('.');
const baseName = lastDotIndex >= 0 ? fileName.substring(0, lastDotIndex) : fileName;
const extension = lastDotIndex >= 0 ? fileName.substring(lastDotIndex) : '';
// 生成时间戳和 UUID
const timestamp = Date.now();
const uuid = generateUUID();
// 构建新文件名:原文件名_地区_时间戳_uuid.扩展名
const newFileName = `${baseName}_${area}_${timestamp}_${uuid}${extension}`;
// 构建完整路径
const newPath = directory ? `${directory}/${newFileName}` : newFileName;
return newPath;
}
/**
* 生成简单的 UUID(不依赖外部库)
* @returns UUID 字符串
*/
function generateUUID(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}