diff --git a/.gitignore b/.gitignore
index 80ec311..d836877 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@ node_modules
/.cache
/build
.env
+
+.idea
+
diff --git a/app/components/rules/new/ExtractionSettings.tsx b/app/components/rules/new/ExtractionSettings.tsx
index 26c3d64..aee74af 100644
--- a/app/components/rules/new/ExtractionSettings.tsx
+++ b/app/components/rules/new/ExtractionSettings.tsx
@@ -50,23 +50,6 @@ 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) {
@@ -78,6 +61,79 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
}
}, []);
+ // 当组件首次加载时更新字段
+ useEffect(() => {
+ updateAllFields();
+ }, []);
+
+ // 获取所有可用字段(合并大模型、多模态和正则抽取的字段)
+ const getAllFields = (): string[] => {
+ // 从大模型OCR抽取中获取字段
+ 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() !== '');
+
+ // 合并并去重
+ const allFields = [...new Set([...llm_ocr_fields, ...llm_fields, ...regex_fields])];
+ console.log("所有可用字段:", allFields);
+ return allFields;
+ };
+
+ // 检查字段名是否存在(精确匹配)
+ const isFieldNameExists = (fieldName: string, excludeId?: string): boolean => {
+ // 获取所有字段名称(不转换为小写)
+ const existingFields = getAllFields();
+
+ // 检查精确匹配(区分大小写)
+ for (const existingField of existingFields) {
+ // 严格相等比较,确保完全匹配而不是部分匹配
+ if (existingField === fieldName) {
+ console.log(`字段名 '${fieldName}' 在现有字段中存在(严格匹配)`);
+ return true;
+ }
+ }
+
+ // 检查正则字段组中的其他字段(精确匹配)
+ // 排除当前正在编辑的字段ID
+ const otherRegexFields = regexFields
+ .filter(f => !excludeId || f.id !== excludeId)
+ .map(f => f.fieldName);
+
+ for (const regexField of otherRegexFields) {
+ // 严格相等比较
+ if (regexField === fieldName) {
+ console.log(`字段名 '${fieldName}' 在正则字段中存在(严格匹配)`);
+ return true;
+ }
+ }
+
+ // 不区分大小写的检查(保留这部分功能,但仍然是精确匹配)
+ const fieldNameLower = fieldName.toLowerCase();
+ const existingFieldsLower = existingFields.map(f => f.toLowerCase());
+ const otherRegexFieldsLower = otherRegexFields.map(f => f.toLowerCase());
+
+ if (existingFieldsLower.includes(fieldNameLower)) {
+ console.log(`字段名 '${fieldName}' 在现有字段中存在(不区分大小写)`);
+ return true;
+ }
+
+ if (otherRegexFieldsLower.includes(fieldNameLower)) {
+ console.log(`字段名 '${fieldName}' 在正则字段中存在(不区分大小写)`);
+ return true;
+ }
+
+ return false;
+ };
+
// 更新所有抽取字段到Context
const updateAllFields = () => {
const allFields = getAllFields();
@@ -111,10 +167,13 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
}
};
- // 在所有字段集合变化时自动更新
+ // 使用useEffect监听字段变化并更新Context
useEffect(() => {
+ // 立即更新字段列表
updateAllFields();
- }, [fields, regexFields]);
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [fields.llm_ocr, fields.llm, regexFields]);
const handleTabChange = (tab: string) => {
setCurrentTab(tab);
@@ -141,19 +200,40 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
// OCR+LLM模式下,支持多个字段同时添加(用逗号、顿号或空格分隔)
if (type === 'llm_ocr') {
- newFields = [
- ...fields[type],
- ...inputValue[type].split(/[\s、,]+/).map(f => f.trim()).filter(f => f !== '')
- ];
+ const fieldsToAdd = inputValue[type].split(/[\s、,]+/)
+ .map(f => f.trim())
+ .filter(f => f !== '');
+
+ console.log(`添加OCR字段:`, fieldsToAdd);
+
+ // 仅添加不存在的字段
+ const uniqueFields = fieldsToAdd.filter(field => !isFieldNameExists(field));
+
+ if (uniqueFields.length === 0) {
+ // 如果没有唯一字段可添加,显示提示并返回
+ alert("所有字段名已存在,请确保字段名称唯一");
+ return;
+ }
+
+ newFields = [...fields[type], ...uniqueFields];
} else {
- // 多模态抽取模式下,一次只添加一个字段(带类型)
- newFields = [...fields[type], `${inputValue[type].trim()}_${selectedFieldType}`];
+ // 多模态抽取模式下,处理字段名称唯一性
+ const fieldName = inputValue[type].trim();
+ console.log(`添加多模态字段:${fieldName}`);
+
+ // 检查字段名是否已存在
+ if (isFieldNameExists(fieldName)) {
+ alert(`字段名 "${fieldName}" 已存在,请确保字段名称唯一`);
+ return;
+ }
+
+ newFields = [...fields[type], `${fieldName}_${selectedFieldType}`];
}
- setFields({
- ...fields,
+ setFields(prevFields => ({
+ ...prevFields,
[type]: newFields
- });
+ }));
setInputValue({
...inputValue,
@@ -163,9 +243,6 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
if (type === 'llm') {
setSelectedFieldType('default');
}
-
- // 立即触发字段更新事件,通知评查设置组件
- setTimeout(() => updateAllFields(), 0);
}
};
@@ -180,13 +257,18 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
const newFields = [...fields[type]];
newFields.splice(index, 1);
- setFields({
- ...fields,
- [type]: newFields
+ // 使用新的方式更新,确保状态立即更新并触发后续操作
+ setFields(prevFields => {
+ const updatedFields = {
+ ...prevFields,
+ [type]: newFields
+ };
+
+ // 状态更新后立即触发字段更新事件
+ Promise.resolve().then(() => updateAllFields());
+
+ return updatedFields;
});
-
- // 立即触发字段更新事件,通知评查设置组件
- setTimeout(() => updateAllFields(), 0);
};
// 添加正则表达式字段行
@@ -223,19 +305,20 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
}
};
- // 更新正则表达式字段值
+ // 更新正则表达式字段
const updateRegexField = (id: string, key: 'fieldName' | 'regex', value: string) => {
- const newRegexFields = regexFields.map(field =>
- field.id === id ? { ...field, [key]: value } : field
- );
+ // 更新字段值
+ const newRegexFields = regexFields.map(field => {
+ if (field.id === id) {
+ return { ...field, [key]: value };
+ }
+ return field;
+ });
+ // 仅更新状态,不触发其他事件
setRegexFields(newRegexFields);
- // 如果更新的是字段名,则触发字段更新事件
- if (key === 'fieldName') {
- setTimeout(() => updateAllFields(), 0);
- }
-
+ // 更新onChange回调
if (onChange) {
onChange({
extractionMethod: currentTab,
@@ -244,6 +327,74 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
}
};
+ // 处理正则字段失去焦点事件,检查唯一性并更新字段列表
+ const handleRegexFieldBlur = (id: string, key: 'fieldName' | 'regex') => {
+ // 只有在修改字段名时需要检查唯一性并更新字段列表
+ if (key === 'fieldName') {
+ const currentField = regexFields.find(field => field.id === id);
+ if (currentField && currentField.fieldName.trim() !== '') {
+ const fieldName = currentField.fieldName.trim();
+ console.log(`检查正则字段 '${fieldName}' 的唯一性,ID: ${id}`);
+
+ // 检查当前正则字段组中是否有重名(排除自身)
+ const duplicateInRegex = regexFields
+ .filter(f => f.id !== id)
+ .find(f => f.fieldName === fieldName);
+
+ if (duplicateInRegex) {
+ console.log(`字段名 '${fieldName}' 在正则字段中存在重复,ID: ${duplicateInRegex.id}`);
+ alert(`字段名 "${fieldName}" 已存在,请确保字段名称唯一`);
+
+ // 重置为空字段名
+ const resetFields = regexFields.map(field => {
+ if (field.id === id) {
+ return { ...field, fieldName: '' };
+ }
+ return field;
+ });
+
+ setRegexFields(resetFields);
+ return;
+ }
+
+ // 检查其他抽取方法中的字段(不区分大小写)
+ const otherExtractFields = [
+ ...fields.llm_ocr.map(f => f.toLowerCase()),
+ ...fields.llm.map(f => {
+ const [name] = f.split('_');
+ return name.toLowerCase();
+ })
+ ];
+
+ const fieldNameLower = fieldName.toLowerCase();
+ const duplicateInOtherMethods = otherExtractFields.includes(fieldNameLower);
+
+ if (duplicateInOtherMethods) {
+ console.log(`字段名 '${fieldName}' 在其他抽取方法中存在(不区分大小写)`);
+ alert(`字段名 "${fieldName}" 已存在,请确保字段名称唯一`);
+
+ // 重置为空字段名
+ const resetFields = regexFields.map(field => {
+ if (field.id === id) {
+ return { ...field, fieldName: '' };
+ }
+ return field;
+ });
+
+ setRegexFields(resetFields);
+ return;
+ }
+
+ // 字段名有效,更新字段列表
+ console.log(`字段名 '${fieldName}' 检查通过,更新字段列表`);
+ updateAllFields();
+ }
+ } else {
+ // 对于regex字段,只需更新字段列表
+ updateAllFields();
+ }
+ };
+
// 应用正则模板
const applyRegexTemplate = (regex: string) => {
// 找到当前正在编辑的行,或者最后一行
@@ -909,6 +1060,7 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
placeholder="如:合同编号"
value={field.fieldName}
onChange={(e) => updateRegexField(field.id, 'fieldName', e.target.value)}
+ onBlur={() => handleRegexFieldBlur(field.id, 'fieldName')}
/>
@@ -920,6 +1072,7 @@ export function ExtractionSettings({ onChange }: ExtractionSettingsProps) {
placeholder="如:\\d{4}[-/年](0?[1-9]|1[0-2])[-/月](0?[1-9]|[12][0-9]|3[01])[日]?"
value={field.regex}
onChange={(e) => updateRegexField(field.id, 'regex', e.target.value)}
+ onBlur={() => handleRegexFieldBlur(field.id, 'regex')}
/>
diff --git a/app/components/rules/new/ReviewSettings.tsx b/app/components/rules/new/ReviewSettings.tsx
index 2023881..91b2e9e 100644
--- a/app/components/rules/new/ReviewSettings.tsx
+++ b/app/components/rules/new/ReviewSettings.tsx
@@ -14,6 +14,13 @@ interface ComparisonPair {
compareMethod: string;
}
+// 添加逻辑条件接口
+interface Condition {
+ field: string;
+ operator: string;
+ value: string;
+}
+
interface ReviewSettingsProps {
onChange?: (data: Record
) => void;
}
@@ -59,12 +66,16 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
useEffect(() => {
// 当Context中的字段发生变化时,更新可用字段但保留已有配置
if (extractionFields.length > 0) {
+ console.log('extractionFields updated in ReviewSettings:', extractionFields);
// 检查是否有字段被删除
const deletedFields = availableFields.filter(field => !extractionFields.includes(field));
// 处理新增的字段
const newFields = extractionFields.filter((field: string) => !availableFields.includes(field));
+ console.log('New fields:', newFields);
+ console.log('Deleted fields:', deletedFields);
+
if (newFields.length > 0 || deletedFields.length > 0) {
// 设置最新的可用字段列表
setAvailableFields(extractionFields);
@@ -83,6 +94,7 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
const handleExtractionChange = (event: Event) => {
if (event instanceof CustomEvent && event.detail && Array.isArray(event.detail.fields)) {
const incomingFields = event.detail.fields;
+ console.log('Received extraction fields update:', incomingFields);
// 检查是否有字段被删除
const deletedFields = availableFields.filter(field => !incomingFields.includes(field));
@@ -90,6 +102,9 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
// 识别新增的字段
const newFields = incomingFields.filter((field: string) => !availableFields.includes(field));
+ console.log('Deleted fields:', deletedFields);
+ console.log('New fields:', newFields);
+
if (newFields.length > 0 || deletedFields.length > 0) {
// 设置最新的可用字段列表
setAvailableFields(incomingFields);
@@ -112,7 +127,36 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
return () => {
document.removeEventListener('extraction-fields-updated', handleExtractionChange);
};
- }, [extractionFields, availableFields]);
+ }, [extractionFields]);
+
+ // 检查并更新字段(仍然保留此函数供需要时手动触发)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const checkAndUpdateFields = () => {
+ if (extractionFields.length > 0) {
+ // 检查是否有字段被删除
+ const deletedFields = availableFields.filter(field => !extractionFields.includes(field));
+
+ // 处理新增的字段
+ const newFields = extractionFields.filter((field: string) => !availableFields.includes(field));
+
+ if (newFields.length > 0 || deletedFields.length > 0) {
+ console.log('Updating fields in checkAndUpdateFields - deleted:', deletedFields, 'new:', newFields);
+ // 设置最新的可用字段列表
+ setAvailableFields(extractionFields);
+
+ // 处理规则中已删除的字段
+ if (deletedFields.length > 0) {
+ handleDeletedFields(deletedFields);
+ }
+
+ // 使用最新的字段列表更新规则配置
+ updateRulesWithNewFields(extractionFields);
+
+ return true; // 表示字段已更新
+ }
+ }
+ return false; // 表示字段未更新
+ };
// 初始化评查配置
useEffect(() => {
@@ -453,9 +497,11 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
// 如果规则中的availableFields不是最新的,则更新它
if (type && config && (!config.availableFields ||
(Array.isArray(config.availableFields) &&
- !availableFields.every((field) => (config.availableFields as string[]).includes(field))))) {
+ !availableFields.every((field) => (config.availableFields as string[]).includes(field)) ||
+ !(config.availableFields as string[]).every((field) => availableFields.includes(field))))) {
// 延迟更新以避免在渲染过程中修改状态
setTimeout(() => {
+ console.log('Updating rule config with new available fields:', availableFields);
const updatedConfig = { ...config, availableFields: availableFields };
handleRuleConfigChange(id, updatedConfig);
}, 0);
@@ -601,6 +647,7 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
id={`source-field-${id}-0`}
className="form-select"
onChange={(e) => {
+ // 直接初始化一个完整的比较对数组
const firstPair = { sourceField: e.target.value, targetField: '', compareMethod: '' };
handleRuleConfigChange(id, { pairs: [firstPair] });
}}
@@ -617,9 +664,12 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
id={`target-field-${id}-0`}
className="form-select"
onChange={(e) => {
- const pairs = Array.isArray(config.pairs) ? [...config.pairs] : [{ sourceField: '', compareMethod: '' }];
- pairs[0] = { ...pairs[0], targetField: e.target.value };
- handleRuleConfigChange(id, { pairs });
+ // 获取sourceField的值
+ const sourceField = document.getElementById(`source-field-${id}-0`) ?
+ (document.getElementById(`source-field-${id}-0`) as HTMLSelectElement).value : '';
+
+ const firstPair = { sourceField, targetField: e.target.value, compareMethod: '' };
+ handleRuleConfigChange(id, { pairs: [firstPair] });
}}
>
@@ -634,9 +684,14 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
id={`compare-method-${id}-0`}
className="form-select"
onChange={(e) => {
- const pairs = Array.isArray(config.pairs) ? [...config.pairs] : [{ sourceField: '', targetField: '' }];
- pairs[0] = { ...pairs[0], compareMethod: e.target.value };
- handleRuleConfigChange(id, { pairs });
+ // 获取sourceField和targetField的值
+ const sourceField = document.getElementById(`source-field-${id}-0`) ?
+ (document.getElementById(`source-field-${id}-0`) as HTMLSelectElement).value : '';
+ const targetField = document.getElementById(`target-field-${id}-0`) ?
+ (document.getElementById(`target-field-${id}-0`) as HTMLSelectElement).value : '';
+
+ const firstPair = { sourceField, targetField, compareMethod: e.target.value };
+ handleRuleConfigChange(id, { pairs: [firstPair] });
}}
>
@@ -655,9 +710,31 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
className="ant-btn ant-btn-default"
onClick={() => {
// 添加新的比较对
- const currentPairs = Array.isArray(config.pairs) ? config.pairs : [];
+ // 直接获取当前的pairs数组,或初始化为空数组
+ const pairs = Array.isArray(config.pairs) ? [...(config.pairs as ComparisonPair[])] : [];
+
+ // 创建新的空白比较对
const newPair = { sourceField: '', targetField: '', compareMethod: '' };
- handleRuleConfigChange(id, { pairs: [...currentPairs, newPair] });
+
+ // 如果数组为空,确保先初始化第一个条目
+ if (pairs.length === 0) {
+ // 如果界面上已有值,则添加两行:一行是当前值,一行是新的空行
+ const sourceField = document.getElementById(`source-field-${id}-0`) ?
+ (document.getElementById(`source-field-${id}-0`) as HTMLSelectElement).value : '';
+ const targetField = document.getElementById(`target-field-${id}-0`) ?
+ (document.getElementById(`target-field-${id}-0`) as HTMLSelectElement).value : '';
+ const compareMethod = document.getElementById(`compare-method-${id}-0`) ?
+ (document.getElementById(`compare-method-${id}-0`) as HTMLSelectElement).value : '';
+
+ // 将第一行设置为当前值(如果有)
+ pairs.push({ sourceField, targetField, compareMethod });
+ }
+
+ // 无论如何,都添加一个新的空白行
+ pairs.push(newPair);
+
+ // 更新配置
+ handleRuleConfigChange(id, { pairs });
}}
>
添加比较对
@@ -701,76 +778,194 @@ export function ReviewSettings({ onChange }: ReviewSettingsProps) {
-
-
-
-
-
+ )}