diff --git a/app/api/evaluation_points/rules.ts b/app/api/evaluation_points/rules.ts index a137b45..67a2d1d 100644 --- a/app/api/evaluation_points/rules.ts +++ b/app/api/evaluation_points/rules.ts @@ -1,4 +1,5 @@ import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client'; +import { apiRequest } from '../axios-client'; import { formatDate } from '../../utils'; /** @@ -57,6 +58,7 @@ export interface ApiRule { name: string; area?: string; // 地区 evaluation_point_groups_id: number | null; + evaluation_point_groups_pid?: number | null; // 一级分组ID (评查点类型) risk: string; description: string; is_enabled: boolean; @@ -73,8 +75,9 @@ export interface ApiRule { id: number; name: string; } | null; - references_laws: Record; - extraction_config: { + // 以下字段仅在详情接口中返回,列表接口可能不包含 + references_laws?: Record; + extraction_config?: { type: string; fields: string[]; prompt_setting?: { @@ -82,7 +85,7 @@ export interface ApiRule { template: string; }; }; - evaluation_config: { + evaluation_config?: { rules: Array<{ id: string; type: string; @@ -90,12 +93,12 @@ export interface ApiRule { }>; logicType: string; }; - pass_message: string; - fail_message: string; - suggestion_message: string; - suggestion_message_type: string; - post_action: string; - action_config: string; + pass_message?: string; + fail_message?: string; + suggestion_message?: string; + suggestion_message_type?: string; + post_action?: string; + action_config?: string; created_at: string; updated_at: string; } @@ -182,7 +185,6 @@ function mapApiRuleToFrontendModel(apiRule: ApiRule): Rule { */ export async function getRulesList(params: RulesQueryParams): Promise<{data: RulesListResponse; error?: never} | {data?: never; error: string; status?: number}> { try { - // 解构并设置默认值 const { page = 1, pageSize = 10, @@ -192,196 +194,111 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul isActive, keyword, area, - orderBy = 'created_at', - orderDirection = 'desc', userRole, token } = params; // 🔑 如果没有传递 userRole,尝试从 localStorage 中获取 - let user_role = '' - if (!userRole && typeof window !== 'undefined' && window.localStorage) { + let user_role = userRole || ''; + if (!user_role && typeof window !== 'undefined' && window.localStorage) { try { const userInfoStr = localStorage.getItem('user_info'); if (userInfoStr) { const userInfo = JSON.parse(userInfoStr); - user_role = userInfo.user_role || userInfo.userRole; - // console.log('📋 [getRulesList] 从 localStorage 获取用户角色:', userRole); + user_role = userInfo.user_role || userInfo.userRole || ''; } } catch (error) { console.error('❌ [getRulesList] 解析 localStorage 用户信息失败:', error); } } - - // 构建PostgrestParams参数 - const postgrestParams: PostgrestParams = { - // 🆕 使用 PostgREST 双连接查询(直接连接父子分组) - // child_group: 通过 evaluation_point_groups_id 获取子分组(所属规则组) - // parent_group: 通过 evaluation_point_groups_pid 直接获取父分组(评查点类型) - // ⚠️ 重要:使用 .replace() 移除换行符,PostgREST 的 select 参数不支持多行字符串 - select: ` - id, - code, - name, - area, - evaluation_point_groups_id, - evaluation_point_groups_pid, - risk, - description, - is_enabled, - created_at, - updated_at, - child_group:evaluation_point_groups!fk_evaluation_points_group(id,name), - parent_group:evaluation_point_groups!fk_evaluation_points_parent_group(id,name) - `.replace(/\s+/g, ' ').trim(), - // 设置分页 - limit: pageSize, - offset: (page - 1) * pageSize, - // 设置排序 - order: `${orderBy}.${orderDirection}`, + // 🆕 调用后端 FastAPI 接口: GET /api/v3/evaluation-points + // 构建查询参数 + const queryParams = new URLSearchParams(); + queryParams.append('page', page.toString()); + queryParams.append('page_size', pageSize.toString()); - // 构建过滤条件 - filter: {}, + // 添加一级分组ID(评查点类型) + if (ruleType) { + queryParams.append('evaluation_point_groups_pid', ruleType); + } - // 添加额外头部,用于获取总记录数 - headers: { - 'Prefer': 'count=exact' - }, - token - }; - - // 添加精确匹配过滤:规则组ID + // 添加二级分组ID(规则组) if (groupId) { - postgrestParams.filter!['evaluation_point_groups_id'] = `eq.${groupId}`; + queryParams.append('evaluation_point_groups_id', groupId); } // 添加风险等级筛选 if (risk) { - postgrestParams.filter!['risk'] = `eq.${risk}`; + queryParams.append('risk', risk); } + // 添加启用状态筛选 if (isActive !== undefined) { - postgrestParams.filter!['is_enabled'] = `eq.${isActive}`; + queryParams.append('is_enabled', isActive.toString()); } // 🔑 添加地区过滤 - if (user_role == 'provincial_admin') { - postgrestParams.filter!['area'] = `eq.省级`; - }else{ - postgrestParams.filter!['area'] = `eq.${area}`; + if (user_role === 'provincial_admin') { + queryParams.append('area', '省级'); + } else if (area) { + queryParams.append('area', area); } - // 如果指定了评查点类型ID,需要先查询该类型下的所有规则组ID - if (ruleType) { - try { - // 🔑 检查是否为多个类型(逗号分隔) - const isMultipleTypes = ruleType.includes(','); - - // 先获取该类型(或多个类型)下的所有规则组 - const groupsParams: PostgrestParams = { - select: 'id', - filter: { - // 如果是多个类型,使用 in.(type1,type2),否则使用 eq.type - 'pid': isMultipleTypes ? `in.(${ruleType})` : `eq.${ruleType}` - }, - token - }; - - const groupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number}>}>('evaluation_point_groups', groupsParams); - - if (groupsResponse.error) { - console.error('获取规则组列表失败:', groupsResponse.error); - } else { - let groupIds: number[] = []; - - // 处理不同API响应格式 - if (groupsResponse.data && 'code' in groupsResponse.data && groupsResponse.data.data) { - if (Array.isArray(groupsResponse.data.data) && groupsResponse.data.data.length > 0) { - groupIds = groupsResponse.data.data.map(group => group.id); - } - } else if (Array.isArray(groupsResponse.data) && groupsResponse.data.length > 0) { - groupIds = groupsResponse.data.map(group => group.id); - } - - // 使用in过滤条件,如果找到规则组 - if (groupIds.length > 0) { - postgrestParams.filter!['evaluation_point_groups_id'] = `in.(${groupIds.join(',')})`; - } - if (groupId) { - postgrestParams.filter!['evaluation_point_groups_id'] = `eq.${groupId}`; - } - } - } catch (error) { - console.error('获取规则组ID出错:', error); - // 错误不中断流程,继续使用其他筛选条件 - } - } - - // 添加模糊搜索 + // 添加关键词搜索(后端会同时搜索 name 和 code) if (keyword) { - // 使用PostgREST的or条件查询 - // 同时搜索name和code字段 - postgrestParams.or = [ - { name: `ilike.*${keyword}*` }, - { code: `ilike.*${keyword}*` } - ]; + queryParams.append('name', keyword); + queryParams.append('code', keyword); } - - // 使用postgrestGet发送请求 - const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]}>('evaluation_points', postgrestParams); - - // 检查是否有错误响应 + + // 调用 FastAPI 接口 + const response = await apiRequest<{ + data: EvaluationPointData[]; + total: number; + page: number; + page_size: number; + }>( + `/api/v3/evaluation-points?${queryParams.toString()}`, + { + method: 'GET', + headers: { + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + } + ); + if (response.error) { return { error: response.error, status: response.status }; } - - // 处理不同的API响应格式 - let apiRules: ApiRule[] = []; - let totalCount = 0; - - // 9000端口格式 {code: number, msg: string, data: ApiRule[]} - if (response.data && 'code' in response.data && response.data.data) { - if (Array.isArray(response.data.data)) { - apiRules = response.data.data; - } else { - return { error: '接口返回数据格式不正确', status: 500 }; - } - } - // 3000端口格式 ApiRule[] - else if (Array.isArray(response.data)) { - apiRules = response.data; - } - // 不支持的格式 - else { + + if (!response.data || !Array.isArray(response.data.data)) { return { error: '接口返回数据格式不正确', status: 500 }; } - - // 尝试从响应中获取总数 - let rangeHeader = ''; - - // 安全地检查头信息是否存在 - if (response && 'headers' in response && response.headers && typeof response.headers === 'object') { - rangeHeader = (response.headers as Record)['content-range'] || ''; - } - - if (rangeHeader) { - // 例如 Content-Range: 0-9/42 表示总共有 42 条记录 - const total = rangeHeader.split('/')[1]; - if (total !== '*') { // '*' 表示未知总数 - totalCount = parseInt(total, 10); - } - } else { - // 如果没有响应头,则使用当前返回的数据长度作为默认值 - totalCount = apiRules.length; - } - - // 🆕 使用嵌套查询后,不再需要手动查询分组信息 - // PostgREST 的嵌套 select 已经自动关联了分组数据 - console.log('📋 [getRulesList] 使用 PostgREST 嵌套查询获取评查点数据'); - // 将API返回的数据映射到前端模型 - const mappedRules = apiRules.map(apiRule => { + console.log('✅ [getRulesList] 成功获取评查点列表,共', response.data.total, '条'); + + // 🆕 将 FastAPI 返回的 EvaluationPointData 映射到前端的 Rule 模型 + // 注意:FastAPI 已经包含了分组信息,不需要再额外查询 + const mappedRules: Rule[] = response.data.data.map((point: EvaluationPointData) => { + // 将 EvaluationPointData 转换为 ApiRule 格式(临时兼容旧的映射函数) + const apiRule: ApiRule = { + id: point.id!, + code: point.code, + name: point.name, + area: point.area, + evaluation_point_groups_id: point.evaluation_point_groups_id, + evaluation_point_groups_pid: point.evaluation_point_groups_pid, + risk: point.risk, + description: point.description || '', + is_enabled: point.is_enabled, + created_at: point.created_at!, + updated_at: point.updated_at!, + // FastAPI 返回的数据已包含分组信息(如果后端实现了关联查询) + // 如果没有,这里可以设置为 null,前端会显示 ID + child_group: null, + parent_group: null + }; + const rule = mapApiRuleToFrontendModel(apiRule); // 格式化日期字段 @@ -390,17 +307,16 @@ export async function getRulesList(params: RulesQueryParams): Promise<{data: Rul return rule; }); - - // 返回结果 + return { data: { rules: mappedRules, - totalCount + totalCount: response.data.total } }; } catch (error) { - console.error('获取评查点列表出错:', error); - return { + console.error('❌ 获取评查点列表出错:', error); + return { error: error instanceof Error ? error.message : '获取评查点列表失败', status: 500 }; @@ -769,145 +685,33 @@ export async function updateRule(id: string, ruleData: Partial { +export async function deleteRule(id: string, token?: string): Promise<{data: {success: boolean; message: string}; error?: never} | {data?: never; error: string; status?: number}> { try { - // 1. 验证评查点是否存在 - const existingRuleResponse = await getRule(id, token); - if (existingRuleResponse.error || !existingRuleResponse.data) { - return { error: '评查点不存在', status: 404 }; - } - - // 2. 检查是否有关联的评查结果 - // 注意:这里假设存在 evaluation_results 表,如果不存在可以跳过此检查 - try { - const resultsParams: PostgrestParams = { - select: 'id', - filter: { - 'evaluation_point_id': `eq.${id}` - }, - limit: 1, // 只需要知道是否存在,不需要全部 - token - }; - - const resultsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number}>}>('evaluation_results', resultsParams); - - let hasResults = false; - if (resultsResponse.data && 'code' in resultsResponse.data && resultsResponse.data.data) { - hasResults = Array.isArray(resultsResponse.data.data) && resultsResponse.data.data.length > 0; - } else if (Array.isArray(resultsResponse.data)) { - hasResults = resultsResponse.data.length > 0; + // 调用后端 FastAPI 接口: DELETE /api/v3/evaluation-points/{id} + // 后端会处理所有验证逻辑(检查是否存在、是否有关联数据等) + const response = await apiRequest<{success: boolean; message: string}>( + `/api/v3/evaluation-points/${id}`, + { + method: 'DELETE', + headers: { + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } } + ); - if (hasResults) { - return { - error: '该评查点已被使用,无法删除。如需停用,请使用禁用功能。', - status: 400 - }; - } - } catch (error) { - // 如果 evaluation_results 表不存在,忽略此错误并继续删除 - console.warn('检查评查结果关联时出错(表可能不存在):', error); - } - - // 3. 使用 PostgREST 语法,通过查询参数指定要删除的行 - const postgrestParams: PostgrestParams = { - filter: { - 'id': `eq.${id}` - }, - headers: { - 'Prefer': 'return=representation' // 请求返回被删除的记录 - }, - token - }; - - // 使用postgrestDelete删除评查点 - const response = await postgrestDelete('evaluation_points', postgrestParams); - - // console.log('删除请求响应:', JSON.stringify(response, null, 2)); - - // 检查是否有错误响应 if (response.error) { - console.error('删除评查点API返回错误:', response.error); return { error: response.error, status: response.status }; } - - // 确保响应数据存在 - if (!response.data) { - console.error('API响应缺少数据字段'); - return { error: 'API返回数据为空', status: 500 }; - } - - // 创建一个模拟的成功删除结果 - const createMockSuccessRule = (): Rule => { - return { - id: id, - code: '', - name: '', - ruleType: '', - groupId: '', - groupName: '', - priority: 'medium', - description: '', - isActive: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString() - }; - }; - - // 处理9000端口响应格式 - if (typeof response.data === 'object' && response.data !== null && 'code' in response.data) { - const apiResponse = response.data as {code: number; msg: string; data?: ApiRule | ApiRule[]}; - - // 检查响应的code - 如果code为0则表示操作成功 - if (apiResponse.code === 0) { - // 如果data不存在或是空数组,返回模拟数据 - if (!apiResponse.data || (Array.isArray(apiResponse.data) && apiResponse.data.length === 0)) { - return { data: createMockSuccessRule() }; - } - - // 处理存在的data - let apiRule: ApiRule; - if (Array.isArray(apiResponse.data)) { - apiRule = apiResponse.data[0]; - } else { - apiRule = apiResponse.data; - } - - // 将API返回的数据映射到前端模型 - const rule = mapApiRuleToFrontendModel(apiRule); - return { data: rule }; - } - - // 如果code不为0,则返回错误信息 - return { - error: apiResponse.msg || '删除失败,服务器返回错误', - status: 500 - }; - } - // 处理3000端口响应格式 (直接返回数据) - else { - // 处理数组响应 - if (Array.isArray(response.data)) { - if (response.data.length === 0) { - // 空数组表示成功但没有返回数据 - return { data: createMockSuccessRule() }; - } else { - // 返回数组中的第一个元素 - const apiRule = response.data[0] as ApiRule; - const rule = mapApiRuleToFrontendModel(apiRule); - return { data: rule }; - } - } - // 处理单一对象响应 - else { - const apiRule = response.data as ApiRule; - const rule = mapApiRuleToFrontendModel(apiRule); - return { data: rule }; - } + + if (response.data && response.data.success) { + console.log('✅ deleteRule 成功:', response.data.message); + return { data: response.data }; } + + return { error: '删除评查点失败', status: 500 }; } catch (error) { - console.error('删除评查点出错:', error); - return { + console.error('❌ 删除评查点出错:', error); + return { error: error instanceof Error ? error.message : '删除评查点失败', status: 500 }; @@ -1306,7 +1110,7 @@ export function convertApiRuleToFormData(apiRule: ApiRule): FormattedEvaluationP risk: apiRule.risk, is_enabled: apiRule.is_enabled, description: apiRule.description, - references_laws: apiRule.references_laws, + references_laws: apiRule.references_laws || null, evaluation_point_groups_pid: apiRule.evaluation_point_groups?.first_name ? null : null, evaluation_point_groups_id: apiRule.evaluation_point_groups_id, extraction_config: extractFields(), @@ -2029,39 +1833,30 @@ export async function createEvaluationPoint( ): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> { try { // 调用后端 FastAPI 接口: POST /api/v3/evaluation-points - const response = await postgrestPost('evaluation_points', evaluationPointData, token); + const response = await apiRequest( + '/api/v3/evaluation-points', + { + method: 'POST', + body: JSON.stringify(evaluationPointData), + headers: { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + } + ); if (response.error) { return { error: response.error, status: response.status }; } - // 调试日志:查看实际返回的数据格式 - console.log('createEvaluationPoint 响应数据:', { - data: response.data, - isArray: Array.isArray(response.data), - type: typeof response.data, - length: Array.isArray(response.data) ? response.data.length : 'N/A' - }); - if (response.data) { - // 如果是数组且有数据,返回第一个元素 - if (Array.isArray(response.data) && response.data.length > 0) { - return { data: response.data[0] }; - } - // 如果是数组但为空,PostgREST 可能没有配置正确的 Prefer 头部 - else if (Array.isArray(response.data) && response.data.length === 0) { - console.error('PostgREST 返回空数组,无法获取新创建的评查点数据'); - return { error: '创建成功但无法获取创建的数据,请刷新页面', status: 500 }; - } - // 如果不是数组,直接返回 - else if (!Array.isArray(response.data)) { - return { data: response.data }; - } + console.log('✅ createEvaluationPoint 成功:', response.data); + return { data: response.data }; } return { error: '创建评查点失败:返回数据格式不正确', status: 500 }; } catch (error) { - console.error('创建完整评查点出错:', error); + console.error('❌ 创建评查点出错:', error); return { error: error instanceof Error ? error.message : '创建评查点失败', status: 500 @@ -2083,49 +1878,30 @@ export async function updateEvaluationPoint( ): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> { try { // 调用后端 FastAPI 接口: PUT /api/v3/evaluation-points/{id} - const response = await postgrestPut>>( - 'evaluation_points', - evaluationPointData, - { id: parseInt(id) }, - token + const response = await apiRequest( + `/api/v3/evaluation-points/${id}`, + { + method: 'PUT', + body: JSON.stringify(evaluationPointData), + headers: { + 'Content-Type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + } ); if (response.error) { return { error: response.error, status: response.status }; } - // 调试日志:查看实际返回的数据格式 - console.log('updateEvaluationPoint 响应数据:', { - data: response.data, - isArray: Array.isArray(response.data), - type: typeof response.data, - length: Array.isArray(response.data) ? response.data.length : 'N/A' - }); - if (response.data) { - // 如果是数组且有数据,返回第一个元素 - if (Array.isArray(response.data) && response.data.length > 0) { - return { data: response.data[0] }; - } - // 如果是数组但为空,说明可能是 PostgREST 配置问题,但更新应该成功了 - else if (Array.isArray(response.data) && response.data.length === 0) { - console.warn('PostgREST 返回空数组,可能需要检查 Prefer 头部设置'); - // 尝试重新获取数据 - const refetchResponse = await getEvaluationPoint(id, token); - if (refetchResponse.data) { - return { data: refetchResponse.data }; - } - return { error: '更新成功但无法获取更新后的数据', status: 500 }; - } - // 如果不是数组,直接返回 - else if (!Array.isArray(response.data)) { - return { data: response.data }; - } + console.log('✅ updateEvaluationPoint 成功:', response.data); + return { data: response.data }; } return { error: '更新评查点失败:返回数据格式不正确', status: 500 }; } catch (error) { - console.error('更新完整评查点出错:', error); + console.error('❌ 更新评查点出错:', error); return { error: error instanceof Error ? error.message : '更新评查点失败', status: 500 @@ -2144,36 +1920,29 @@ export async function getEvaluationPoint( token?: string ): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> { try { - const postgrestParams: PostgrestParams = { - filter: { 'id': `eq.${id}` }, - select: '*', - token - }; - - const response = await postgrestGet('evaluation_points', postgrestParams); + // 调用后端 FastAPI 接口: GET /api/v3/evaluation-points/{id} + const response = await apiRequest( + `/api/v3/evaluation-points/${id}`, + { + method: 'GET', + headers: { + ...(token ? { 'Authorization': `Bearer ${token}` } : {}) + } + } + ); if (response.error) { return { error: response.error, status: response.status }; } - // 处理响应数据 - let evaluationPoint: EvaluationPointData | null = null; - - if (response.data) { - if (Array.isArray(response.data)) { - evaluationPoint = response.data.length > 0 ? response.data[0] : null; - } else { - evaluationPoint = response.data; - } - } - - if (!evaluationPoint) { + if (!response.data) { return { error: '评查点不存在', status: 404 }; } - return { data: evaluationPoint }; + console.log('✅ getEvaluationPoint 成功:', response.data); + return { data: response.data }; } catch (error) { - console.error('获取完整评查点出错:', error); + console.error('❌ 获取评查点出错:', error); return { error: error instanceof Error ? error.message : '获取评查点失败', status: 500