diff --git a/app/components/rules/new/ExtractionSettings.tsx b/app/components/rules/new/ExtractionSettings.tsx index 8f6cef6..a6fc131 100644 --- a/app/components/rules/new/ExtractionSettings.tsx +++ b/app/components/rules/new/ExtractionSettings.tsx @@ -50,6 +50,23 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { llm: '' }); + // 获取所有可用字段(合并大模型、多模态和正则抽取的字段) + const getAllFields = (): string[] => { + const llm_ocr_fields = fields.llm_ocr || []; + // 从多模态字段中提取基本字段名(去除类型后缀) + const llm_fields = (fields.llm || []).map(field => { + const [fieldName] = field.split('_'); + return fieldName; + }); + // 获取正则字段名 + const regex_fields = regexFields + .map(field => field.fieldName) + .filter(name => name.trim() !== ''); + + // 合并并去重 + return [...new Set([...llm_ocr_fields, ...llm_fields, ...regex_fields])]; + }; + // 在组件初始化时,如果Context中已有字段数据,则使用Context数据初始化 useEffect(() => { if (ruleContext && ruleContext.extractionFields.length > 0) { @@ -61,14 +78,47 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { } }, []); + // 更新所有抽取字段到Context + const updateAllFields = () => { + const allFields = getAllFields(); + + // 更新全局Context中的字段 + if (ruleContext) { + ruleContext.updateExtractionFields(allFields); + } + + // 触发自定义事件,通知字段已更新(兼容非Context的实现) + const event = new CustomEvent('extraction-fields-updated', { + detail: { + fields: allFields, + tab: currentTab, + fieldsData: { + llm_ocr: fields.llm_ocr || [], + llm: fields.llm || [], + regex: regexFields.map(f => f.fieldName).filter(name => name.trim() !== '') + } + } + }); + document.dispatchEvent(event); + + if (onChange) { + onChange({ + extractionMethod: currentTab, + fields, + regexFields, + allFields // 添加合并后的所有字段 + }); + } + }; + + // 在所有字段集合变化时自动更新 + useEffect(() => { + updateAllFields(); + }, [fields, regexFields]); + const handleTabChange = (tab: string) => { setCurrentTab(tab); - // 当切换抽取方法时,更新全局Context中的字段 - if (ruleContext && fields[tab]) { - ruleContext.updateExtractionFields(fields[tab]); - } - if (onChange) { onChange({ extractionMethod: tab }); } @@ -114,26 +164,8 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { setSelectedFieldType('default'); } - // 更新全局Context中的字段 - if (ruleContext) { - ruleContext.updateExtractionFields(newFields); - } - - if (onChange) { - onChange({ - extractionMethod: currentTab, - fields: { - ...fields, - [type]: newFields - } - }); - } - - // 触发自定义事件,通知字段已更新(兼容非Context的实现) - const event = new CustomEvent('extraction-fields-updated', { - detail: { fields: newFields } - }); - document.dispatchEvent(event); + // 立即触发字段更新事件,通知评查设置组件 + setTimeout(() => updateAllFields(), 0); } }; @@ -153,26 +185,8 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { [type]: newFields }); - // 更新全局Context中的字段 - if (ruleContext) { - ruleContext.updateExtractionFields(newFields); - } - - if (onChange) { - onChange({ - extractionMethod: currentTab, - fields: { - ...fields, - [type]: newFields - } - }); - } - - // 触发自定义事件,通知字段已更新(兼容非Context的实现) - const event = new CustomEvent('extraction-fields-updated', { - detail: { fields: newFields } - }); - document.dispatchEvent(event); + // 立即触发字段更新事件,通知评查设置组件 + setTimeout(() => updateAllFields(), 0); }; // 添加正则表达式字段行 @@ -180,14 +194,15 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { const newId = `${regexFields.length + 1}`; setRegexFields([...regexFields, { id: newId, fieldName: '', regex: '' }]); + // 如果是新增了regex字段,也要更新字段列表通知评查设置组件 + setTimeout(() => updateAllFields(), 0); + if (onChange) { onChange({ extractionMethod: currentTab, regexFields: [...regexFields, { id: newId, fieldName: '', regex: '' }] }); } - - // 添加字段时不触发事件,因为此时字段名称尚未填写 }; // 删除正则表达式字段行 @@ -216,13 +231,9 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { setRegexFields(newRegexFields); - // 如果是字段名更新且当前抽取方法是正则抽取,则更新Context - if (key === 'fieldName' && currentTab === 'ocr_regex' && ruleContext) { - const fieldNames = newRegexFields - .map(field => field.fieldName) - .filter(name => name.trim() !== ''); - - ruleContext.updateExtractionFields(fieldNames); + // 如果更新的是字段名,则触发字段更新事件 + if (key === 'fieldName') { + setTimeout(() => updateAllFields(), 0); } if (onChange) { @@ -231,17 +242,6 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) { regexFields: newRegexFields }); } - - // 如果更新的是字段名,触发自定义事件通知字段已更新 - if (key === 'fieldName' && value.trim()) { - const allFieldNames = [...fields[currentTab], ...newRegexFields.map(f => f.fieldName).filter(f => f)]; - - // 触发自定义事件,通知字段已更新(兼容非Context的实现) - const event = new CustomEvent('extraction-fields-updated', { - detail: { fields: allFieldNames } - }); - document.dispatchEvent(event); - } }; // 应用正则模板 diff --git a/app/components/rules/new/ReviewSettings.tsx b/app/components/rules/new/ReviewSettings.tsx index 399c705..e02c7f9 100644 --- a/app/components/rules/new/ReviewSettings.tsx +++ b/app/components/rules/new/ReviewSettings.tsx @@ -57,60 +57,51 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { // 监听抽取设置中的字段变化 useEffect(() => { - // 当Context中的字段发生变化时,立即更新可用字段 + // 当Context中的字段发生变化时,更新可用字段但保留已有配置 if (extractionFields.length > 0) { - setAvailableFields(extractionFields); + // 检查是否有字段被删除 + const deletedFields = availableFields.filter(field => !extractionFields.includes(field)); - // 同时更新已有规则中的可选字段状态 - updateRulesWithNewFields(extractionFields); - return; + // 处理新增的字段 + const newFields = extractionFields.filter((field: string) => !availableFields.includes(field)); + + if (newFields.length > 0 || deletedFields.length > 0) { + // 设置最新的可用字段列表 + setAvailableFields(extractionFields); + + // 处理规则中已删除的字段 + if (deletedFields.length > 0) { + handleDeletedFields(deletedFields); + } + + // 使用最新的字段列表更新规则配置 + updateRulesWithNewFields(extractionFields); + } } - // 获取抽取字段函数 - 当Context不可用时的备选方案 - const getExtractedFields = (): string[] => { - const extractedFields: string[] = []; - - // 查找当前活跃的抽取设置容器 - const activeExtractConfig = document.querySelector('.extraction-config:not(.hidden)'); - if (!activeExtractConfig) { - // 如果没有找到活跃的配置,返回空数组 - return []; - } - - // 查找字段容器 - const fieldsContainer = activeExtractConfig.querySelector('.chips-container'); - if (fieldsContainer) { - // 获取所有字段芯片 - const chips = fieldsContainer.querySelectorAll('.chip'); - chips.forEach(chip => { - const fieldName = chip.textContent?.replace('×', '').trim(); - if (fieldName) { - extractedFields.push(fieldName); - } - }); - } - - // 查找正则字段 - const regexFields = document.querySelectorAll('.regex-field-row'); - regexFields.forEach(row => { - const fieldNameInput = row.querySelector('input[placeholder="字段名称"]'); - if (fieldNameInput instanceof HTMLInputElement && fieldNameInput.value.trim()) { - extractedFields.push(fieldNameInput.value.trim()); - } - }); - - return extractedFields; - }; - - // 初始化获取字段 - 仅在Context不可用时使用 - setAvailableFields(getExtractedFields()); - - // 监听抽取设置的变化 - 仅在Context不可用时使用 + // 监听抽取设置的变化 - 用于捕获非Context更新的情况 const handleExtractionChange = (event: Event) => { if (event instanceof CustomEvent && event.detail && Array.isArray(event.detail.fields)) { - setAvailableFields(event.detail.fields); - } else { - setAvailableFields(getExtractedFields()); + const incomingFields = event.detail.fields; + + // 检查是否有字段被删除 + const deletedFields = availableFields.filter(field => !incomingFields.includes(field)); + + // 识别新增的字段 + const newFields = incomingFields.filter((field: string) => !availableFields.includes(field)); + + if (newFields.length > 0 || deletedFields.length > 0) { + // 设置最新的可用字段列表 + setAvailableFields(incomingFields); + + // 处理规则中已删除的字段 + if (deletedFields.length > 0) { + handleDeletedFields(deletedFields); + } + + // 使用最新的字段列表更新规则配置 + updateRulesWithNewFields(incomingFields); + } } }; @@ -121,39 +112,110 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { return () => { document.removeEventListener('extraction-fields-updated', handleExtractionChange); }; - }, [extractionFields]); + }, [extractionFields, availableFields]); - // 当可用字段发生变化时,更新已有规则的配置 - const updateRulesWithNewFields = (newFields: string[]) => { - // 更新已有规则中的字段选择,保留已选择的字段 - const updatedRules = rules.map(rule => { - if (rule.type === 'exists' || rule.type === 'consistency' || rule.type === 'logic' || rule.type === 'regex') { - // 检查和更新字段相关的配置 - const currentSelectedFields = Array.isArray(rule.config.selectedFields) - ? rule.config.selectedFields as string[] - : []; - - // 使用已选中的字段和新字段的交集作为更新后的选中字段 - const validSelectedFields = currentSelectedFields.filter(field => - newFields.includes(field) || field.trim() !== '' - ); + // 处理已删除字段的函数 + const handleDeletedFields = (deletedFields: string[]) => { + setRules(prevRules => { + return prevRules.map(rule => { + const updatedConfig = { ...rule.config }; + + switch (rule.type) { + case 'exists': + case 'logic': + case 'regex': + // 从已选字段中移除被删除的字段 + if (Array.isArray(updatedConfig.selectedFields)) { + updatedConfig.selectedFields = (updatedConfig.selectedFields as string[]).filter( + field => !deletedFields.includes(field) + ); + } + break; + + case 'consistency': + // 从配对字段中移除被删除的字段 + if (Array.isArray(updatedConfig.pairs)) { + updatedConfig.pairs = (updatedConfig.pairs as ComparisonPair[]).filter( + pair => !deletedFields.includes(pair.sourceField) && !deletedFields.includes(pair.targetField) + ); + } + break; + + case 'format': + // 如果判断字段被删除,则清空字段 + if (updatedConfig.checkField && deletedFields.includes(updatedConfig.checkField as string)) { + updatedConfig.checkField = ''; + } + break; + + default: + break; + } + + // 更新可用字段列表,移除被删除的字段 + if (Array.isArray(updatedConfig.availableFields)) { + updatedConfig.availableFields = (updatedConfig.availableFields as string[]).filter( + field => !deletedFields.includes(field) + ); + } return { ...rule, - config: { - ...rule.config, - selectedFields: validSelectedFields - } + config: updatedConfig }; - } - return rule; + }); + }); + }; + + // 更新规则配置中的可用字段但保留已选择的字段和规则配置 + const updateRulesWithNewFields = (newFields: string[]) => { + // 更新每个规则的可用字段列表,但保留现有配置 + setRules(prevRules => { + return prevRules.map(rule => { + const updatedConfig = { ...rule.config }; + + // 对所有规则类型都更新availableFields字段 + updatedConfig.availableFields = newFields; + + // 根据规则类型更新其他相关字段 + if (rule.type) { + switch (rule.type) { + case 'field_validation': + // 保留已有的字段选择,只添加新字段 + if (!updatedConfig.fields) { + updatedConfig.fields = []; + } + break; + + case 'field_comparison': + // 保留已配置的比较项 + if (!updatedConfig.pairs) { + updatedConfig.pairs = []; + } + break; + + case 'field_regex': + // 保留正则表达式配置 + break; + + case 'custom_code': + break; + + default: + // 对于所有类型规则,确保selectedFields字段存在 + if (!updatedConfig.selectedFields) { + updatedConfig.selectedFields = []; + } + break; + } + } + + return { + ...rule, + config: updatedConfig + }; + }); }); - - setRules(updatedRules); - - if (onChange) { - onChange({ rules: updatedRules }); - } }; const handleLogicChange = (logic: string) => { @@ -218,46 +280,73 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { }; const handleRuleTypeChange = (id: string, type: string) => { - const updatedRules = rules.map(rule => - rule.id === id ? { ...rule, type, config: {} } : rule - ); - - setRules(updatedRules); - - if (onChange) { - onChange({ rules: updatedRules }); - } - }; - - const handleRuleConfigChange = (id: string, config: Record) => { - const updatedRules = rules.map(rule => - rule.id === id ? { ...rule, config: { ...rule.config, ...config } } : rule - ); - - setRules(updatedRules); - - if (onChange) { - onChange({ rules: updatedRules }); - } - }; - - // 处理字段选择 - const handleFieldSelection = (id: string, fieldName: string, selected: boolean) => { + // 更新规则类型 const updatedRules = rules.map(rule => { if (rule.id === id) { - // 获取当前已选字段 - const selectedFields = Array.isArray(rule.config.selectedFields) - ? rule.config.selectedFields as string[] - : []; + // 为新类型初始化配置 + let initialConfig: Record = {}; - // 添加或删除字段 - const newSelectedFields = selected - ? [...selectedFields, fieldName] - : selectedFields.filter(f => f !== fieldName); + // 根据类型设置初始配置 + switch(type) { + case 'exists': + case 'logic': + case 'regex': + initialConfig = { + selectedFields: [], + availableFields: availableFields // 使用当前最新的可用字段列表 + }; + break; + case 'consistency': + initialConfig = { + pairs: [], + availableFields: availableFields // 使用当前最新的可用字段列表 + }; + break; + case 'format': + initialConfig = { + formatType: '', + formatParams: '', + availableFields: availableFields // 使用当前最新的可用字段列表 + }; + break; + default: + // 确保所有规则类型都包含availableFields + initialConfig = { + availableFields: availableFields + }; + break; + } return { ...rule, - config: { ...rule.config, selectedFields: newSelectedFields } + type, + config: initialConfig + }; + } + return rule; + }); + + setRules(updatedRules); + + if (onChange) { + onChange({ rules: updatedRules }); + } + }; + + const handleRuleConfigChange = (id: string, configChanges: Record) => { + const updatedRules = rules.map(rule => { + if (rule.id === id) { + // 合并现有配置和变更 + const updatedConfig = { ...rule.config, ...configChanges }; + + // 确保availableFields字段存在 + if (!updatedConfig.availableFields) { + updatedConfig.availableFields = availableFields; + } + + return { + ...rule, + config: updatedConfig }; } return rule; @@ -271,49 +360,50 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { }; // 渲染字段标签,确保已选择的字段即使在新的字段列表中不存在也会显示 - const renderFieldTags = (ruleId: string, ruleConfig: Record) => { - const selectedFields = Array.isArray(ruleConfig.selectedFields) - ? ruleConfig.selectedFields as string[] - : []; + const renderFieldTags = (ruleId: string, config: Record) => { + // 获取规则的当前已选字段 + const selectedFields = Array.isArray(config.selectedFields) ? config.selectedFields as string[] : []; - // 合并已选择的字段和可用字段,以确保已选择但不再可用的字段仍然显示 - const allFieldsToRender = [...new Set([...availableFields, ...selectedFields])]; + // 优先使用配置中存储的可用字段,如果没有则使用当前可用字段 + const fieldsToRender = Array.isArray(config.availableFields) ? + config.availableFields as string[] : + availableFields; - return allFieldsToRender.map((field, index) => { - const isSelected = selectedFields.includes(field); - const isAvailable = availableFields.includes(field); - const buttonId = `field-tag-${ruleId}-${index}`; - - // 如果字段不再可用但已被选中,用特殊样式显示 - const fieldClass = !isAvailable && isSelected ? 'field-tag selected unavailable' : - isSelected ? 'field-tag selected' : 'field-tag'; - - // 只显示可用的字段或已选择的字段 - if (!isAvailable && !isSelected) { - return null; - } - - return ( - - ); - }).filter(Boolean); // 过滤掉null值 + return ( +
+ {fieldsToRender.map((field, index) => ( +
{ + // 切换选中状态 + const newSelectedFields = selectedFields.includes(field) + ? selectedFields.filter(f => f !== field) + : [...selectedFields, field]; + + handleRuleConfigChange(ruleId, { + selectedFields: newSelectedFields + }); + }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + const newSelectedFields = selectedFields.includes(field) + ? selectedFields.filter(f => f !== field) + : [...selectedFields, field]; + + handleRuleConfigChange(ruleId, { + selectedFields: newSelectedFields + }); + } + }} + role="button" + tabIndex={0} + > + {field} +
+ ))} +
+ ); }; // 获取规则类型的Badge样式 @@ -334,6 +424,17 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { const renderRuleConfig = (rule: RuleType) => { const { id, type, config } = rule; + // 如果规则中的availableFields不是最新的,则更新它 + if (type && config && (!config.availableFields || + (Array.isArray(config.availableFields) && + !availableFields.every((field) => (config.availableFields as string[]).includes(field))))) { + // 延迟更新以避免在渲染过程中修改状态 + setTimeout(() => { + const updatedConfig = { ...config, availableFields: availableFields }; + handleRuleConfigChange(id, updatedConfig); + }, 0); + } + if (!type) { return (
@@ -1167,9 +1268,7 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { {/* 评查结果提示信息 */}
- +

评查结果提示信息

@@ -1209,57 +1308,114 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) { {/* 不通过提示类别 */}
- -
-
{/* 评查后动作 */} diff --git a/app/routes/rule.new.tsx b/app/routes/rule.new.tsx index 63fd8c7..482b356 100644 --- a/app/routes/rule.new.tsx +++ b/app/routes/rule.new.tsx @@ -44,14 +44,25 @@ export default function RuleNew() { }; const handleExtractionChange = (data: Record) => { - // 当抽取设置更新时,获取最新的字段列表 + // 使用合并后的所有字段列表 + if (data.allFields && Array.isArray(data.allFields)) { + updateExtractionFields(data.allFields); + return; + } + + // 旧版本兼容逻辑 if (data.fields) { - const fieldData = data.fields as Record; - const currentMethod = data.extractionMethod as string; - - // 提取当前抽取方法的字段 - if (fieldData[currentMethod]) { - updateExtractionFields(fieldData[currentMethod]); + // 尝试获取合并的字段列表 + if (Array.isArray(data.fields)) { + updateExtractionFields(data.fields); + } else { + const fieldData = data.fields as Record; + const currentMethod = data.extractionMethod as string; + + // 提取当前抽取方法的字段 + if (fieldData[currentMethod]) { + updateExtractionFields(fieldData[currentMethod]); + } } } else if (data.regexFields) { // 处理正则字段情况