/** * 评查点管理页面 - 创建或编辑评查点规则 * * 功能概述: * - 支持创建新的评查点规则或编辑现有规则 * - 评查点包含基本信息、抽取设置和评查设置三大部分 * - 支持使用三种抽取方式: 大模型(LLM)抽取、多模态(VLM)抽取和正则表达式抽取 * - 支持配置各种类型的评查规则,如存在性检查、内容一致性检查等 * - 支持保存评查规则和保存草稿功能 * * 组件结构: * - PageHeader: 页面标题和保存按钮 * - BasicInfo: 评查点的基本信息设置,如名称、编码、风险级别等 * - ExtractionSettings: 抽取设置,配置从文档中抽取哪些字段及抽取方式 * - ReviewSettings: 评查设置,基于抽取字段配置评查规则、评查结果消息等 * - ActionButtons: 页面底部的操作按钮,包括保存、保存草稿和返回 * * 数据流转: * 1. 页面加载时检查URL参数,确定是新建还是编辑模式 * 2. 编辑模式下从API获取评查点数据并填充表单 * 3. ExtractionSettings通过RuleContext将抽取的字段传递给ReviewSettings * 4. 各组件的onChange回调收集表单变更并更新formData状态 * 5. 保存时将formData转换为API需要的格式并提交 * * @author 中国烟草AI合同及卷宗审核系统开发团队 */ import { type MetaFunction } from "@remix-run/node"; import { useState, useEffect, useCallback } from "react"; import { BasicInfo } from "~/components/rules/new/BasicInfo"; import { ExtractionSettings } from "~/components/rules/new/ExtractionSettings"; import { ReviewSettings } from "~/components/rules/new/ReviewSettings"; import { ActionButtons } from "~/components/rules/new/ActionButtons"; import { PageHeader } from "~/components/rules/new/PageHeader"; import rulesStyles from "~/styles/rules.css?url"; import { useNavigate, useLocation } from "@remix-run/react"; // 导入评查点模型定义和常量 import type { EvaluationPoint } from "~/models/evaluation_points"; import { EVALUATION_OPTIONS } from "~/models/evaluation_points"; import type { EvaluationPointGroup } from "~/models/evaluation_point_groups"; // 导入RuleContext上下文 import { RuleContext } from "~/contexts/RuleContext"; // 导入API函数 import { getEvaluationPoint, getEvaluationPointGroups, saveEvaluationPoint } from "~/api/evaluation_points/rules"; export const meta: MetaFunction = () => { return [ { title: "评查点管理 - 中国烟草AI合同及卷宗审核系统" }, { name: "description", content: "创建或修改评查点,设置规则参数" } ]; }; export const handle = { breadcrumb: "评查点管理" }; export function links() { return [{ rel: "stylesheet", href: rulesStyles }]; } export default function RuleNew() { const navigate = useNavigate(); const location = useLocation(); const [isEditMode, setIsEditMode] = useState(false); const [isLoading, setIsLoading] = useState(false); const [instanceKey, setInstanceKey] = useState('new'); const [formData, setFormData] = useState({}); const [evaluationPointGroups, setEvaluationPointGroups] = useState([]); // 添加用于共享的字段数据状态 const [extractionFields, setExtractionFields] = useState([]); /** * 从表单数据中提取所有字段 * 用于编辑模式下初始化字段数据 */ const extractFieldsFromFormData = useCallback((data: EvaluationPoint) => { if (!data || !data.extraction_config) return []; const fields: string[] = []; const config = data.extraction_config; // 提取LLM字段 if (config.llm && Array.isArray(config.llm.fields)) { fields.push(...config.llm.fields); } // 提取VLM字段 if (config.vlm && Array.isArray(config.vlm.fields)) { const vlmFields = config.vlm.fields.map((f: string | { name: string, type: string }) => typeof f === 'string' ? f : f.name ); fields.push(...vlmFields); } // 提取正则字段 if (config.regex && Array.isArray(config.regex.fields)) { const regexFields = config.regex.fields.map((f: { field: string, pattern: string }) => f.field).filter(Boolean); fields.push(...regexFields); } return fields; }, []); /** * 重置表单数据到默认状态 */ const resetFormData = useCallback(() => { console.log("重置表单数据到默认状态"); setFormData({ name: '', code: '', risk: 'low', is_enabled: true, description: '', references_laws: { name: '', articles: [], content: '' }, evaluation_point_groups_pid: null, evaluation_point_groups_id: null, extraction_config: { llm: { fields: [], prompt_setting: { type: 'system', template: '' } }, vlm: { fields: [], prompt_setting: { type: 'system', template: '' } }, regex: { fields: [] } }, evaluation_config: { logicType: 'and', customLogic: '', rules: [] }, pass_message: '文档检查通过,符合规范要求。', fail_message: '文档存在以下问题,请修改后重新提交。', suggestion_message: '', suggestion_message_type: 'warning', post_action: 'none', action_config: '', score: 0 }); setExtractionFields([]); // 生成新的实例键,强制所有子组件重新渲染 setInstanceKey(`new_${Date.now()}`); }, []); /** * 获取评查点数据 * 编辑模式下从API获取指定ID的评查点数据 * @param id 评查点ID */ const fetchEvaluationPoint = useCallback(async (id: number) => { try { setIsLoading(true); console.log(`获取评查点数据,ID: ${id}`); const response = await getEvaluationPoint(id); if (response.error) { console.error('获取评查点数据失败:', response.error); alert(`获取评查点数据失败: ${response.error}`); resetFormData(); navigate('/rules'); return; } if (response.data) { setFormData(response.data as EvaluationPoint); // 初始化extractionFields const extractedFields = extractFieldsFromFormData(response.data as EvaluationPoint); setExtractionFields(extractedFields); // 设置编辑模式的实例键 setInstanceKey(`edit_${id}_${Date.now()}`); } else { console.error('获取数据失败: 返回数据为空'); alert('获取数据失败: 返回数据为空'); resetFormData(); navigate('/rules'); } } catch (error) { console.error('获取评查点数据失败:', error); alert(`获取评查点数据失败: ${error instanceof Error ? error.message : '未知错误'}`); // 获取数据失败时返回上一页 resetFormData(); navigate('/rules'); } finally { setIsLoading(false); } }, [navigate, extractFieldsFromFormData, resetFormData]); /** * 获取评查点组数据 * 从API获取所有评查点组,用于基本信息表单中选择 */ const fetchEvaluationPointGroups = useCallback(async () => { try { console.log("获取评查点组数据"); const response = await getEvaluationPointGroups(); if (response.error) { console.error('获取评查点组数据失败:', response.error); alert(`获取评查点组数据失败: ${response.error}\n将使用默认数据`); return; } if (response.data) { setEvaluationPointGroups(response.data); } else { console.error('获取评查点组数据失败: 返回数据为空'); alert('获取评查点组数据失败: 返回数据为空\n将使用默认数据'); } } catch (error) { console.error('获取评查点组数据失败:', error); // 显示错误提示但不影响应用继续使用 alert(`获取评查点组数据失败: ${error instanceof Error ? error.message : '未知错误'}\n将使用默认数据`); } }, []); const handleSave = () => { console.log("保存评查点", formData); // 验证必填字段 if (!formData.name?.trim()) { alert("评查点名称不能为空"); return; } if (!formData.code?.trim()) { alert("评查点编码不能为空"); return; } // 显示保存中状态 setIsLoading(true); // 调用API保存数据 saveEvaluationPoint(formData, isEditMode) .then(response => { if (response.error) { console.error("保存评查点失败:", response.error); alert(`保存评查点失败: ${response.error}`); return; } if (response.data) { // 获取新创建或更新的评查点ID const savedPointId = response.data[0]?.id; if (savedPointId) { // 显示成功消息 alert(`评查点${isEditMode ? '更新' : '创建'}成功!`); // 保存成功后跳转到编辑页面,加载刚保存的数据 navigate(`/rules/new?id=${savedPointId}`); } else { // 无法获取ID的情况 alert(`评查点${isEditMode ? '更新' : '创建'}成功,但无法获取ID。正在返回列表页面。`); navigate('/rules'); } } else { alert(`保存成功,但返回数据为空。正在返回列表页面。`); navigate('/rules'); } }) .catch(error => { console.error("保存评查点出错:", error); alert(`保存评查点出错: ${error instanceof Error ? error.message : '未知错误'}`); }) .finally(() => { setIsLoading(false); }); } const handleSaveDraft = () => { console.log("保存草稿"); } const handleBasicInfoChange = (data: Record) => { setFormData(prev => ({ ...prev, ...data })); } const handleExtractionSettingsChange = (data: Record) => { setFormData(prev => ({ ...prev, ...data })); // 提取并处理字段数据 const processFields = () => { // 使用类型断言处理字段访问 const extractionConfig = data.extraction_config as { llm?: { fields?: string[] }; vlm?: { fields?: Array }; regex?: { fields?: Array<{ field: string, pattern: string }> }; } | undefined; if (!extractionConfig) return; const llmFields = extractionConfig.llm?.fields || []; const vlmFields = extractionConfig.vlm?.fields || []; const regexFields = (extractionConfig.regex?.fields || []).map((f: { field: string }) => f.field); // 合并所有字段 const allFields = [ ...llmFields, ...vlmFields.map((f: string | { name: string }) => typeof f === 'string' ? f : f.name), ...regexFields ].filter(Boolean); setExtractionFields(allFields); }; // 处理字段数据 if (data.extraction_config) { processFields(); } }; const handleReviewSettingsChange = (data: Record) => { // console.log("评查设置变更:", data); // 检查数据中是否包含evaluation_config对象 if (data.evaluation_config) { // 确保formData.evaluation_config存在并具有必要的默认属性 const currentConfig = formData.evaluation_config || { logicType: 'and', customLogic: '', rules: [] }; // 合并评查配置数据 const mergedConfig = { ...currentConfig, ...(data.evaluation_config as object) }; // 更新表单数据 setFormData(prev => ({ ...prev, evaluation_config: mergedConfig })); } else { // 处理其他普通字段 setFormData(prev => ({ ...prev, ...data })); } } /** * 添加事件监听,处理抽取字段更新 */ useEffect(() => { // 定义事件处理函数 const handleExtractionFieldsUpdated = (event: CustomEvent<{fields: string[]}>) => { const { fields } = event.detail; if (Array.isArray(fields) && fields.length > 0) { // 更新字段数据 setExtractionFields(fields); } }; // 添加事件监听 window.addEventListener('extraction-fields-updated', handleExtractionFieldsUpdated as EventListener); // 清理函数 return () => { window.removeEventListener('extraction-fields-updated', handleExtractionFieldsUpdated as EventListener); }; }, []); /** * 页面加载时初始化处理 * 1. 检查URL参数,判断是新建还是编辑模式 * 2. 编辑模式下获取评查点数据 * 3. 获取评查点组数据(用于表单选择项) */ useEffect(() => { const searchParams = new URLSearchParams(location.search); const id = searchParams.get('id'); // 设置编辑模式 const newIsEditMode = !!id; setIsEditMode(newIsEditMode); if (id) { // 编辑模式:获取数据 fetchEvaluationPoint(parseInt(id)); } else { // 新建模式:重置表单数据 resetFormData(); } // 获取评查点组数据 fetchEvaluationPointGroups(); }, [location.search, fetchEvaluationPoint, fetchEvaluationPointGroups, resetFormData]); // 渲染页面内容 return (
{/* 页面标题和右上角保存按钮 */} {/* 加载状态显示 */} {isLoading ? (
加载中...
) : ( {/* 使用key属性强制在模式切换时重新渲染所有组件 */}
{/* 评查点基本信息设置 */}
{/* 抽取设置 - 配置从文档中提取的字段 */}
{/* 评查设置 - 配置评查规则、消息等 */}
{/* 底部操作按钮区域 */}
)}
); }