From f5a5887651e04afebf283955ae9db20ef702257c Mon Sep 17 00:00:00 2001 From: Wenyan Date: Tue, 25 Nov 2025 14:55:42 +0800 Subject: [PATCH] =?UTF-8?q?feat(evaluation):=20=E5=AE=8C=E6=88=90=E8=AF=84?= =?UTF-8?q?=E6=9F=A5=E7=82=B9=E5=AE=8C=E6=95=B4CRUD=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要变更 ### API层 (app/api/evaluation_points/rules.ts) - 新增 `EvaluationPointData` 接口,支持完整评查点数据结构 - 新增 `createEvaluationPoint` 函数,用于创建评查点 - 新增 `updateEvaluationPoint` 函数,用于更新评查点 - 新增 `getEvaluationPoint` 函数,用于获取完整评查点数据 - 重命名原 `getEvaluationPoint` 为 `getFormattedEvaluationPoint`,避免命名冲突 - 修复 `postgrestPut` 调用的类型参数问题 ### 前端页面 (app/routes/rules.new.tsx) - 更新 `fetchEvaluationPoint` 函数,使用新的 `getEvaluationPoint` API - 更新 `handleSave` 函数,使用 `createEvaluationPoint` 和 `updateEvaluationPoint` API - 添加 `postgrestGet` 导入,支持评查点组数据获取 - 优化错误处理逻辑,统一使用新API响应格式 - 修复类型转换问题,正确处理 `EvaluationPointData` 和 `EvaluationPoint` 类型 ## 技术改进 - 替代直接调用 `postgrestPost`/`postgrestPut`,使用封装的API函数 - 统一错误处理和响应格式 - 保留 `extractApiData` 辅助函数用于评查点组数据处理 - 所有变更通过 TypeScript 类型检查 ## 相关文档 参考 docs/evaluation/evaluation_points.md 中的 FastAPI 接口定义 --- app/api/evaluation_points/rules.ts | 209 +++++++++++++++++++++++++++-- app/routes/rules.new.tsx | 125 ++++++++--------- 2 files changed, 260 insertions(+), 74 deletions(-) diff --git a/app/api/evaluation_points/rules.ts b/app/api/evaluation_points/rules.ts index 7a68f0c..312b97d 100644 --- a/app/api/evaluation_points/rules.ts +++ b/app/api/evaluation_points/rules.ts @@ -1306,14 +1306,19 @@ export function convertApiRuleToFormData(apiRule: ApiRule): FormattedEvaluationP * @param id 评查点ID * @returns 评查点数据 */ -export async function getEvaluationPoint(id: number): Promise<{ +/** + * 获取格式化的评查点数据(用于列表视图) + * @param id 评查点ID + * @returns 格式化的评查点数据 + */ +export async function getFormattedEvaluationPoint(id: number): Promise<{ data?: FormattedEvaluationPoint; error?: string; status?: number; }> { try { // console.log(`获取评查点数据,ID: ${id}`); - + // 使用 postgrestGet 替代直接调用 fetch const postgrestParams: PostgrestParams = { select: `*`, @@ -1321,27 +1326,27 @@ export async function getEvaluationPoint(id: number): Promise<{ 'id': `eq.${id}` } }; - + const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]} | ApiRule[]>('evaluation_points', postgrestParams); - + if (response.error) { - return { - error: response.error, - status: response.status + return { + error: response.error, + status: response.status }; } - + // 使用 extractApiData 统一处理响应数据 const extractedData = extractApiData(response.data); - + if (extractedData && Array.isArray(extractedData) && extractedData.length > 0) { // 转换数据为前端格式 const formattedData = convertApiRuleToFormData(extractedData[0]); return { data: formattedData }; } else { - return { - error: '获取数据失败: 返回数据为空', - status: 404 + return { + error: '获取数据失败: 返回数据为空', + status: 404 }; } } catch (error) { @@ -1927,4 +1932,184 @@ export async function batchDeleteRules( failed_ids: failedIds, errors: errors.length > 0 ? errors : undefined }; +} + +/** + * 完整评查点数据结构(对应前端 EvaluationPoint 类型) + */ +export interface EvaluationPointData { + id?: number; + name: string; + code: string; + risk: string; + is_enabled: boolean; + description?: string; + evaluation_point_groups_id: number | null; + evaluation_point_groups_pid: number | null; + references_laws: { + name: string; + content: string; + articles: string[]; + }; + extraction_config: { + llm: { + fields: string[]; + prompt_setting: { + type: string; + template: string; + }; + }; + vlm: { + fields: Array; + prompt_setting: { + type: string; + template: string; + }; + }; + regex: { + fields: Array<{ field: string; pattern: string }>; + }; + }; + evaluation_config: { + logicType: string; + customLogic: string; + rules: Array<{ + id: string; + type: string; + config: Record; + }>; + }; + pass_message: string; + fail_message: string; + suggestion_message?: string; + suggestion_message_type: string; + post_action?: string; + action_config?: string; + score: number; + area?: string; + created_at?: string; + updated_at?: string; +} + +/** + * 创建完整评查点(调用后端 FastAPI 接口) + * @param evaluationPointData 完整的评查点数据 + * @param token JWT token (可选) + * @returns 创建的评查点数据 + */ +export async function createEvaluationPoint( + evaluationPointData: Omit, + token?: string +): 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); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + if (response.data && Array.isArray(response.data) && response.data.length > 0) { + return { data: response.data[0] }; + } else if (response.data && !Array.isArray(response.data)) { + return { data: response.data }; + } + + return { error: '创建评查点失败:返回数据格式不正确', status: 500 }; + } catch (error) { + console.error('创建完整评查点出错:', error); + return { + error: error instanceof Error ? error.message : '创建评查点失败', + status: 500 + }; + } +} + +/** + * 更新完整评查点(调用后端 FastAPI 接口) + * @param id 评查点ID + * @param evaluationPointData 完整的评查点数据(部分更新) + * @param token JWT token (可选) + * @returns 更新后的评查点数据 + */ +export async function updateEvaluationPoint( + id: string, + evaluationPointData: Partial>, + token?: string +): 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 + ); + + if (response.error) { + return { error: response.error, status: response.status }; + } + + if (response.data && Array.isArray(response.data) && response.data.length > 0) { + return { data: response.data[0] }; + } else if (response.data && !Array.isArray(response.data)) { + return { data: response.data }; + } + + return { error: '更新评查点失败:返回数据格式不正确', status: 500 }; + } catch (error) { + console.error('更新完整评查点出错:', error); + return { + error: error instanceof Error ? error.message : '更新评查点失败', + status: 500 + }; + } +} + +/** + * 获取完整评查点详情(用于编辑页面) + * @param id 评查点ID + * @param token JWT token (可选) + * @returns 完整的评查点数据 + */ +export async function getEvaluationPoint( + id: string, + 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); + + 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) { + return { error: '评查点不存在', status: 404 }; + } + + return { data: evaluationPoint }; + } catch (error) { + console.error('获取完整评查点出错:', error); + return { + error: error instanceof Error ? error.message : '获取评查点失败', + status: 500 + }; + } } \ No newline at end of file diff --git a/app/routes/rules.new.tsx b/app/routes/rules.new.tsx index dfa9e5d..811dacd 100644 --- a/app/routes/rules.new.tsx +++ b/app/routes/rules.new.tsx @@ -48,11 +48,17 @@ import { EVALUATION_OPTIONS } from "~/models/evaluation_points"; import type { EvaluationPointGroup } from "~/models/evaluation_point_groups"; // 导入RuleContext上下文 import { RuleContext } from "~/contexts/RuleContext"; -import { postgrestGet, postgrestPost, postgrestPut } from "~/api/postgrest-client"; import { toastService } from '~/components/ui/Toast'; import type { UserRole } from '~/root'; import { getPromptTemplateOptions } from '~/api/prompts/prompts'; +import { + createEvaluationPoint, + updateEvaluationPoint, + getEvaluationPoint, + type EvaluationPointData +} from '~/api/evaluation_points/rules'; import { getRulesList } from '~/api/evaluation_points/rules'; +import { postgrestGet } from '~/api/postgrest-client'; export const meta: MetaFunction = () => { return [ @@ -277,69 +283,62 @@ export default function RuleNew() { try { setIsLoading(true); // console.log(`获取评查点数据,ID: ${id}, 复制模式: ${isCopy}`); - // 使用 postgrestGet 替代直接调用 fetch - const postgrestParams = { - filter: { - 'id': `eq.${id}` - }, - token: frontendJWT - }; - const response = await postgrestGet('evaluation_points', postgrestParams); + // 使用新的 getEvaluationPoint API 获取数据 + const response = await getEvaluationPoint(String(id), frontendJWT); + + if (response.error) { + // API返回错误 + toastService.error(`获取评查点数据失败: ${response.error}`); + resetFormData(); + navigate('/rules'); + return; + } if (response.data) { - // 使用extractApiData从响应中提取数据 - const evaluationPoints = extractApiData(response.data); + try { + // 使用JSON序列化和反序列化来进行深拷贝,避免浏览器差异 + const originalData = response.data; + const jsonString = JSON.stringify(originalData); + const data = JSON.parse(jsonString) as EvaluationPointData; - if (evaluationPoints && Array.isArray(evaluationPoints) && evaluationPoints.length > 0) { - try { - // 使用JSON序列化和反序列化来进行深拷贝,避免浏览器差异 - const originalData = evaluationPoints[0]; - const jsonString = JSON.stringify(originalData); - const data = JSON.parse(jsonString); - - // 🔄 复制模式:删除不应该复制的字段 - if (isCopy) { - delete data.id; - delete data.created_at; - delete data.updated_at; - delete data.usage_count; - - // console.log('📋 复制模式:已清除不应复制的字段(id, created_at, updated_at, usage_count)'); + // 🔄 复制模式:删除不应该复制的字段 + if (isCopy) { + delete data.id; + delete data.created_at; + delete data.updated_at; + // usage_count 不在 EvaluationPointData 接口中,但可能存在于响应数据中 + if ('usage_count' in data) { + delete (data as Record).usage_count; } - // 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符 - // 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi' - if (data.code) { - const lastDoubleHyphenIndex = data.code.lastIndexOf('--'); - if (lastDoubleHyphenIndex !== -1) { - data.code = data.code.substring(0, lastDoubleHyphenIndex); - // console.log('🔑 已清洗评查点编码:', data.code); - } - } - - // 设置表单数据 - setFormData(data); - - // 初始化extractionFields - const extractedFields = extractFieldsFromFormData(data); - setExtractionFields(extractedFields); - - // 设置实例键 - setInstanceKey(isCopy ? `copy_${id}_${Date.now()}` : `edit_${id}_${Date.now()}`); - } catch (jsonError) { - console.error('JSON处理错误:', jsonError); - toastService.error(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`); - resetFormData(); - navigate('/rules'); + // console.log('📋 复制模式:已清除不应复制的字段(id, created_at, updated_at, usage_count)'); } - } else { - console.error('获取数据失败: 返回数据为空'); - toastService.error('获取数据失败: 返回数据为空'); + + // 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符 + // 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi' + if (data.code) { + const lastDoubleHyphenIndex = data.code.lastIndexOf('--'); + if (lastDoubleHyphenIndex !== -1) { + data.code = data.code.substring(0, lastDoubleHyphenIndex); + // console.log('🔑 已清洗评查点编码:', data.code); + } + } + + // 设置表单数据(EvaluationPointData 兼容 EvaluationPoint) + setFormData(data as EvaluationPoint); + + // 初始化extractionFields + const extractedFields = extractFieldsFromFormData(data as EvaluationPoint); + setExtractionFields(extractedFields); + + // 设置实例键 + setInstanceKey(isCopy ? `copy_${id}_${Date.now()}` : `edit_${id}_${Date.now()}`); + } catch (jsonError) { + console.error('JSON处理错误:', jsonError); + toastService.error(`数据处理错误: ${jsonError instanceof Error ? jsonError.message : '未知错误'}`); resetFormData(); navigate('/rules'); } - } else { - throw new Error(`响应状态: ${response.error}`); } } catch (error) { console.error('获取评查点数据失败:', error); @@ -802,27 +801,29 @@ export default function RuleNew() { let response; if (isEditMode) { - response = await postgrestPut('evaluation_points', finalData, {id: formData.id!}, frontendJWT); + // 使用新的 updateEvaluationPoint API + response = await updateEvaluationPoint(String(formData.id!), finalData, frontendJWT); // console.log("最终提交的数据", finalData) } else { - response = await postgrestPost('evaluation_points', finalData, frontendJWT); + // 使用新的 createEvaluationPoint API + response = await createEvaluationPoint(finalData as Omit, frontendJWT); } if (response.error) { if (response.error.includes('evaluation_points_code_key')) { toastService.error('在基本信息中:评查点编码已存在,请修改后保存。'); - } else { + } else { toastService.error(`系统繁忙: ${response.error}`); } setIsLoading(false); - } else if (response.data && Array.isArray(response.data) && response.data.length > 0) { + } else if (response.data) { // 获取新创建或更新的评查点ID - const savedPointId = response.data[0]?.id; - + const savedPointId = response.data.id; + if (savedPointId) { // 显示成功消息 toastService.success(`评查点${isEditMode ? '更新' : '创建'}成功!`); - + // 保存成功后跳转到编辑页面并重新加载数据 navigate(`/rules/new?id=${savedPointId}`, { replace: true }); // 重新获取评查点数据