Files
leaudit-platform-frontend/app/components/rules/new/ReviewSettings.tsx
T
2025-03-28 14:45:54 +08:00

1309 lines
55 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect, useContext, createContext } from 'react';
import { SimpleCodeEditor } from './SimpleCodeEditor';
interface RuleType {
id: string;
type: string;
config: Record<string, unknown>;
}
// 为配置项添加类型定义
interface ComparisonPair {
sourceField: string;
targetField: string;
compareMethod: string;
}
interface ReviewSettingsProps {
onChange?: (data: Record<string, unknown>) => void;
}
// 创建全局上下文以便在不同组件间共享数据
interface RuleContextType {
extractionFields: string[];
updateExtractionFields: (fields: string[]) => void;
}
// 创建全局Context对象
export const RuleContext = createContext<RuleContextType>({
extractionFields: [],
updateExtractionFields: () => {}
});
export function ReviewSettings({ onChange }: ReviewSettingsProps) {
const [rules, setRules] = useState<RuleType[]>([
{ id: '1', type: '', config: {} }
]);
const [combinationLogic, setCombinationLogic] = useState<string>('and');
const [customLogic, setCustomLogic] = useState<string>('');
const [showCustomLogic, setShowCustomLogic] = useState<boolean>(false);
// 添加评查后动作相关状态
const [actionType, setActionType] = useState<string>('none');
const [actionDescription, setActionDescription] = useState<string>('');
// 获取抽取字段的上下文
const { extractionFields } = useContext(RuleContext);
// 初始化评查通过/不通过/建议信息
const [passMessage, setPassMessage] = useState('文档检查通过,符合规范要求。');
const [failMessage, setFailMessage] = useState('文档存在以下问题,请修改后重新提交。');
const [suggestMessage, setSuggestMessage] = useState('');
// 错误严重程度
const [errorSeverity, setErrorSeverity] = useState('error');
// 保存最近一次可用的字段列表
const [availableFields, setAvailableFields] = useState<string[]>(extractionFields || []);
// 监听抽取设置中的字段变化
useEffect(() => {
// 当Context中的字段发生变化时,立即更新可用字段
if (extractionFields.length > 0) {
setAvailableFields(extractionFields);
// 同时更新已有规则中的可选字段状态
updateRulesWithNewFields(extractionFields);
return;
}
// 获取抽取字段函数 - 当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不可用时使用
const handleExtractionChange = (event: Event) => {
if (event instanceof CustomEvent && event.detail && Array.isArray(event.detail.fields)) {
setAvailableFields(event.detail.fields);
} else {
setAvailableFields(getExtractedFields());
}
};
// 添加事件监听器,监听抽取设置中的字段变化
document.addEventListener('extraction-fields-updated', handleExtractionChange);
// 组件卸载时移除事件监听
return () => {
document.removeEventListener('extraction-fields-updated', handleExtractionChange);
};
}, [extractionFields]);
// 当可用字段发生变化时,更新已有规则的配置
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() !== ''
);
return {
...rule,
config: {
...rule.config,
selectedFields: validSelectedFields
}
};
}
return rule;
});
setRules(updatedRules);
if (onChange) {
onChange({ rules: updatedRules });
}
};
const handleLogicChange = (logic: string) => {
setCombinationLogic(logic);
if (logic === 'custom') {
setShowCustomLogic(true);
} else {
setShowCustomLogic(false);
}
if (onChange) {
onChange({ combinationLogic: logic, showCustomLogic: logic === 'custom', customLogic });
}
};
const handleCustomLogicChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setCustomLogic(e.target.value);
if (onChange) {
onChange({ customLogic: e.target.value });
}
};
const handleActionTypeChange = (type: string) => {
setActionType(type);
if (onChange) {
onChange({ actionType: type });
}
};
const handleAddRule = () => {
const newId = `${rules.length + 1}`;
const newRule = { id: newId, type: '', config: {} };
setRules([...rules, newRule]);
if (onChange) {
onChange({ rules: [...rules, newRule] });
}
};
const handleRemoveRule = (id: string) => {
// 如果只有一个规则,不允许删除
if (rules.length <= 1) {
return;
}
const newRules = rules.filter(rule => rule.id !== id);
setRules(newRules);
// 重新编号规则
const reindexedRules = newRules.map((rule, index) => ({
...rule,
id: `${index + 1}`
}));
setRules(reindexedRules);
if (onChange) {
onChange({ rules: reindexedRules });
}
};
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<string, unknown>) => {
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[]
: [];
// 添加或删除字段
const newSelectedFields = selected
? [...selectedFields, fieldName]
: selectedFields.filter(f => f !== fieldName);
return {
...rule,
config: { ...rule.config, selectedFields: newSelectedFields }
};
}
return rule;
});
setRules(updatedRules);
if (onChange) {
onChange({ rules: updatedRules });
}
};
// 渲染字段标签,确保已选择的字段即使在新的字段列表中不存在也会显示
const renderFieldTags = (ruleId: string, ruleConfig: Record<string, unknown>) => {
const selectedFields = Array.isArray(ruleConfig.selectedFields)
? ruleConfig.selectedFields as string[]
: [];
// 合并已选择的字段和可用字段,以确保已选择但不再可用的字段仍然显示
const allFieldsToRender = [...new Set([...availableFields, ...selectedFields])];
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 (
<button
type="button"
key={buttonId}
id={buttonId}
className={fieldClass}
data-field={field}
onClick={() => handleFieldSelection(ruleId, field, !isSelected)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleFieldSelection(ruleId, field, !isSelected);
}
}}
aria-selected={isSelected}
role="option"
>
{field}
</button>
);
}).filter(Boolean); // 过滤掉null值
};
// 获取规则类型的Badge样式
const getRuleTypeBadgeClass = (type: string) => {
switch(type) {
case 'exists': return 'bg-green-500';
case 'consistency': return 'bg-blue-500';
case 'format': return 'bg-purple-500';
case 'logic': return 'bg-yellow-500';
case 'regex': return 'bg-red-500';
case 'ai': return 'bg-indigo-500';
case 'code': return 'bg-gray-700';
default: return 'bg-primary';
}
};
// 渲染规则配置区域
const renderRuleConfig = (rule: RuleType) => {
const { id, type, config } = rule;
if (!type) {
return (
<div className="rule-placeholder text-center py-6 text-secondary">
<i className="ri-settings-3-line text-4xl mb-2 block"></i>
<p></p>
</div>
);
}
switch(type) {
case 'exists':
return (
<div className="config-section">
<div className="mb-4">
<label className="form-label" htmlFor={`exists-fields-${id}`}> <span className="required-mark">*</span></label>
<div className="field-tags-container exists-fields-container" id={`exists-fields-container-${id}`}>
{renderFieldTags(id, config)}
</div>
<div className="form-tip mt-2"></div>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`existsLogic-all-${id}`}> <span className="required-mark">*</span></label>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
id={`existsLogic-all-${id}`}
name={`existsLogic_${id}`}
className="form-radio"
value="all"
defaultChecked
onChange={(e) => handleRuleConfigChange(id, { existsLogic: e.target.value })}
/>
<span></span>
</label>
<label className="form-radio-item">
<input
type="radio"
id={`existsLogic-any-${id}`}
name={`existsLogic_${id}`}
className="form-radio"
value="any"
onChange={(e) => handleRuleConfigChange(id, { existsLogic: e.target.value })}
/>
<span></span>
</label>
</div>
</div>
</div>
);
case 'consistency':
return (
<div className="config-section">
<div className="mb-4">
<label className="form-label" htmlFor={`consistency-fields-container-${id}`}> <span className="required-mark">*</span></label>
<div id={`consistency-fields-container-${id}`}>
{Array.isArray(config.pairs) && config.pairs.length > 0 ? (
config.pairs.map((pair, pairIndex) => (
<div key={`pair-${id}-${pairIndex}`} className="bg-gray-50 p-4 rounded-md mb-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div>
<label className="form-label text-sm" htmlFor={`source-field-${id}-${pairIndex}`}></label>
<select
id={`source-field-${id}-${pairIndex}`}
className="form-select"
value={pair.sourceField || ''}
onChange={(e) => {
const updatedPairs = [...(config.pairs as ComparisonPair[])];
updatedPairs[pairIndex] = {...pair, sourceField: e.target.value};
handleRuleConfigChange(id, { pairs: updatedPairs });
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`src-${pairIndex}-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`target-field-${id}-${pairIndex}`}></label>
<select
id={`target-field-${id}-${pairIndex}`}
className="form-select"
value={pair.targetField || ''}
onChange={(e) => {
const updatedPairs = [...(config.pairs as ComparisonPair[])];
updatedPairs[pairIndex] = {...pair, targetField: e.target.value};
handleRuleConfigChange(id, { pairs: updatedPairs });
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`tgt-${pairIndex}-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`compare-method-${id}-${pairIndex}`}> <span className="required-mark">*</span></label>
<select
id={`compare-method-${id}-${pairIndex}`}
className="form-select"
value={pair.compareMethod || ''}
onChange={(e) => {
const updatedPairs = [...(config.pairs as ComparisonPair[])];
updatedPairs[pairIndex] = {...pair, compareMethod: e.target.value};
handleRuleConfigChange(id, { pairs: updatedPairs });
}}
>
<option value=""></option>
<option value="exact"></option>
<option value="contains"></option>
<option value="semantic"></option>
</select>
</div>
</div>
<div className="flex justify-end">
<button
type="button"
className="text-red-500 hover:text-red-700"
onClick={() => {
const updatedPairs = [...(config.pairs as ComparisonPair[])];
updatedPairs.splice(pairIndex, 1);
handleRuleConfigChange(id, { pairs: updatedPairs });
}}
>
<i className="ri-delete-bin-line"></i>
</button>
</div>
</div>
))
) : (
<div className="bg-gray-50 p-4 rounded-md mb-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div>
<label className="form-label text-sm" htmlFor={`source-field-${id}-0`}></label>
<select
id={`source-field-${id}-0`}
className="form-select"
onChange={(e) => {
const firstPair = { sourceField: e.target.value, targetField: '', compareMethod: '' };
handleRuleConfigChange(id, { pairs: [firstPair] });
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`src-default-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`target-field-${id}-0`}></label>
<select
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 });
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`tgt-default-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`compare-method-${id}-0`}> <span className="required-mark">*</span></label>
<select
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 });
}}
>
<option value=""></option>
<option value="exact"></option>
<option value="contains"></option>
<option value="semantic"></option>
</select>
</div>
</div>
</div>
)}
</div>
<div>
<button
type="button"
className="ant-btn ant-btn-default"
onClick={() => {
// 添加新的比较对
const currentPairs = Array.isArray(config.pairs) ? config.pairs : [];
const newPair = { sourceField: '', targetField: '', compareMethod: '' };
handleRuleConfigChange(id, { pairs: [...currentPairs, newPair] });
}}
>
<i className="ri-add-line mr-1"></i>
</button>
</div>
</div>
<div className="mb-4">
<fieldset>
<legend className="form-label"> <span className="required-mark">*</span></legend>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
name={`logicRelation_${id}`}
className="form-radio"
value="and"
defaultChecked
onChange={(e) => handleRuleConfigChange(id, { logicRelation: e.target.value })}
/>
<span>AND</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name={`logicRelation_${id}`}
className="form-radio"
value="or"
onChange={(e) => handleRuleConfigChange(id, { logicRelation: e.target.value })}
/>
<span>OR</span>
</label>
</div>
</fieldset>
</div>
</div>
);
case 'logic':
return (
<div className="config-section">
<div className="mb-4">
<label className="form-label" htmlFor={`conditions-container-${id}`}> <span className="required-mark">*</span></label>
<div className="conditions-container" id={`conditions-container-${id}`}>
<div className="condition-row bg-gray-50 p-4 rounded-md mb-2">
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-2">
<div>
<label className="form-label text-sm" htmlFor={`field-${id}-0`}></label>
<select
id={`field-${id}-0`}
className="form-select"
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...config.conditions] : [];
currentConditions[0] = { ...currentConditions[0], field: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`field-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`operator-${id}-0`}></label>
<select
id={`operator-${id}-0`}
className="form-select"
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...config.conditions] : [];
currentConditions[0] = { ...currentConditions[0], operator: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
}}
>
<option value="eq"> (=)</option>
<option value="neq"> ()</option>
<option value="gt"> {`>`}</option>
<option value="gte"> {``}</option>
<option value="lt"> {`<`}</option>
<option value="lte"> {``}</option>
<option value="contains"></option>
<option value="not_contains"></option>
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`value-${id}-0`}></label>
<input
type="text"
id={`value-${id}-0`}
className="form-input"
placeholder="请输入比较值"
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...config.conditions] : [];
currentConditions[0] = { ...currentConditions[0], value: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
}}
/>
</div>
</div>
<div className="flex justify-end">
<button className="text-red-500 hover:text-red-700">
<i className="ri-delete-bin-line"></i>
</button>
</div>
</div>
</div>
<div className="mt-2">
<button
className="ant-btn ant-btn-default"
onClick={() => {
// 添加新的条件
const currentConditions = Array.isArray(config.conditions) ? [...config.conditions] : [];
const newCondition = { field: '', operator: 'eq', value: '' };
handleRuleConfigChange(id, { conditions: [...currentConditions, newCondition] });
}}
>
<i className="ri-add-line mr-1"></i>
</button>
</div>
</div>
<div className="mb-4">
<fieldset>
<legend className="form-label"> <span className="required-mark">*</span></legend>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
name={`logicRelation_${id}`}
className="form-radio"
value="and"
defaultChecked
onChange={(e) => handleRuleConfigChange(id, { logicRelation: e.target.value })}
/>
<span>AND</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name={`logicRelation_${id}`}
className="form-radio"
value="or"
onChange={(e) => handleRuleConfigChange(id, { logicRelation: e.target.value })}
/>
<span>OR</span>
</label>
</div>
</fieldset>
</div>
</div>
);
case 'regex':
return (
<div className="config-section">
<div className="mb-4">
<label className="form-label" htmlFor={`regex-field-${id}`}> <span className="required-mark">*</span></label>
<select
id={`regex-field-${id}`}
className="form-select"
onChange={(e) => handleRuleConfigChange(id, { checkField: e.target.value })}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`regex-field-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`regex-pattern-${id}`}> <span className="required-mark">*</span></label>
<textarea
id={`regex-pattern-${id}`}
className="form-textarea"
placeholder="请输入正则表达式"
onChange={(e) => handleRuleConfigChange(id, { regexPattern: e.target.value })}
></textarea>
</div>
<div className="mb-4">
<fieldset>
<legend className="form-label"> <span className="required-mark">*</span></legend>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
name={`regexMatchType_${id}`}
className="form-radio"
value="match"
defaultChecked
onChange={(e) => handleRuleConfigChange(id, { matchType: e.target.value })}
/>
<span></span>
</label>
<label className="form-radio-item">
<input
type="radio"
name={`regexMatchType_${id}`}
className="form-radio"
value="not_match"
onChange={(e) => handleRuleConfigChange(id, { matchType: e.target.value })}
/>
<span></span>
</label>
</div>
</fieldset>
</div>
</div>
);
case 'ai':
return (
<div className="config-section">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="form-label" htmlFor={`ai-model-${id}`}>
<span className="required-mark">*</span>
</label>
<select
id={`ai-model-${id}`}
className="form-select"
onChange={(e) => handleRuleConfigChange(id, { model: e.target.value })}
>
<option value="deepseek">DeepSeek</option>
<option value="qwen72b">Qwen72B-VL</option>
<option value="qwen14b">Qwen14B</option>
</select>
</div>
<div>
<label className="form-label" htmlFor={`ai-temp-${id}`}></label>
<input
type="number"
id={`ai-temp-${id}`}
className="form-input"
placeholder="0.1"
defaultValue="0.1"
min="0"
max="1"
step="0.1"
onChange={(e) => handleRuleConfigChange(id, { temperature: e.target.value })}
/>
</div>
<div className="col-span-1 md:col-span-2">
<label className="form-label" htmlFor={`ai-prompt-${id}`}> Prompt <span className="required-mark">*</span></label>
<textarea
id={`ai-prompt-${id}`}
className="form-textarea"
placeholder="请输入提示词,引导模型进行判断"
defaultValue={`请判断以下{字段}内容是否符合规范要求,仅回答"符合"或"不符合",并简要说明理由。
{字段内容}`}
onChange={(e) => handleRuleConfigChange(id, { prompt: e.target.value })}
></textarea>
</div>
<div className="col-span-1 md:col-span-2 flex flex-wrap gap-2 mt-2">
{availableFields.map((field, idx) => (
<button
key={`tag-${idx}`}
type="button"
className="ant-btn ant-btn-default tag-button"
onClick={() => {
// 将标签插入到文本区域
const textArea = document.getElementById(`ai-prompt-${id}`) as HTMLTextAreaElement;
if (textArea) {
const startPos = textArea.selectionStart;
const endPos = textArea.selectionEnd;
const textBefore = textArea.value.substring(0, startPos);
const textAfter = textArea.value.substring(endPos);
const tagText = `{${field}}`;
textArea.value = textBefore + tagText + textAfter;
textArea.focus();
textArea.selectionStart = startPos + tagText.length;
textArea.selectionEnd = startPos + tagText.length;
// 更新配置
handleRuleConfigChange(id, { prompt: textArea.value });
}
}}
>
{field}
</button>
))}
</div>
</div>
</div>
);
case 'code':
return (
<div className="config-section">
<div className="mb-4">
<fieldset>
<legend className="form-label"> <span className="required-mark">*</span></legend>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
name={`codeLanguage_${id}`}
className="form-radio"
value="javascript"
defaultChecked
onChange={(e) => handleRuleConfigChange(id, { language: e.target.value })}
/>
<span>JavaScript</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name={`codeLanguage_${id}`}
className="form-radio"
value="python"
onChange={(e) => handleRuleConfigChange(id, { language: e.target.value })}
/>
<span>Python</span>
</label>
</div>
</fieldset>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`code-editor-${id}`}> <span className="required-mark">*</span></label>
<SimpleCodeEditor
id={`code-editor-${id}`}
language={rule.config.language as 'javascript' | 'python' || 'javascript'}
onChange={(value) => handleRuleConfigChange(id, { code: value })}
/>
</div>
</div>
);
case 'format':
return (
<div className="config-section">
<div className="mb-4">
<label className="form-label" htmlFor={`format-field-${id}`}> <span className="required-mark">*</span></label>
<select
id={`format-field-${id}`}
className="form-select"
onChange={(e) => handleRuleConfigChange(id, { checkField: e.target.value })}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`format-field-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`format-type-${id}`}> <span className="required-mark">*</span></label>
<select
id={`format-type-${id}`}
className="form-select format-type"
onChange={(e) => handleRuleConfigChange(id, { formatType: e.target.value })}
>
<option value=""></option>
<option value="date"></option>
<option value="number"></option>
<option value="phone"></option>
<option value="email"></option>
<option value="bankcard"></option>
<option value="idcard"></option>
<option value="zipcode"></option>
<option value="uscc"></option>
</select>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`format-params-${id}`}></label>
<input
type="text"
id={`format-params-${id}`}
className="form-input"
placeholder="请输入参数设置"
onChange={(e) => handleRuleConfigChange(id, { formatParams: e.target.value })}
/>
</div>
</div>
);
default:
return (
<div className="p-4 bg-gray-50 rounded">
<p> {type} </p>
</div>
);
}
};
// 处理评查结果消息变更
const handleMessageChange = (type: string, value: string) => {
switch(type) {
case 'pass':
setPassMessage(value);
break;
case 'fail':
setFailMessage(value);
break;
case 'suggest':
setSuggestMessage(value);
break;
}
if (onChange) {
onChange({ [`${type}Message`]: value });
}
};
// 处理严重程度变更
const handleSeverityChange = (value: string) => {
setErrorSeverity(value);
if (onChange) {
onChange({ errorSeverity: value });
}
};
return (
<div className="ant-card">
<div className="ant-card-header">
<h3></h3>
</div>
<div className="ant-card-body">
<div className="mb-6">
<div className="form-section mb-6">
<div className="form-section-title mb-2">
<label className="form-label" htmlFor="logic-section"> <span className="required-mark">*</span></label>
</div>
<div id="logic-section">
<div className="form-radio-group mb-3">
<label className="form-radio-item">
<input
type="radio"
name="combination-logic"
className="form-radio"
checked={combinationLogic === 'and'}
onChange={() => handleLogicChange('and')}
/>
<span>AND</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name="combination-logic"
className="form-radio"
checked={combinationLogic === 'or'}
onChange={() => handleLogicChange('or')}
/>
<span>OR</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name="combination-logic"
className="form-radio"
checked={combinationLogic === 'custom'}
onChange={() => handleLogicChange('custom')}
/>
<span></span>
</label>
</div>
{showCustomLogic && (
<div className="mt-3 mb-4">
<label className="form-label" htmlFor="custom-logic-expression"> <span className="required-mark">*</span></label>
<textarea
id="custom-logic-expression"
className="form-textarea"
placeholder="请输入自定义组合逻辑,例如:(规则1 AND 规则2) OR 规则3"
value={customLogic}
onChange={handleCustomLogicChange}
></textarea>
<div className="form-tip">
使(ANDORNOT)
</div>
</div>
)}
</div>
</div>
<div className="form-section mb-6">
<div className="form-section-title mb-2">
<label className="form-label" htmlFor="rules-container"></label>
</div>
<div id="rules-container">
<div className="flex items-center justify-between mb-2">
<div> <span className="text-blue-600 font-medium">{rules.length}</span> </div>
<button
type="button"
className="ant-btn ant-btn-default"
onClick={handleAddRule}
>
<i className="ri-add-line mr-1"></i>
</button>
</div>
{rules.length === 0 ? (
<div className="border border-dashed border-gray-300 rounded-md p-6 text-center text-gray-500 bg-gray-50">
<div className="flex flex-col items-center justify-center">
<i className="ri-file-list-3-line text-4xl mb-2"></i>
<p></p>
<p className="text-xs mt-1">&ldquo;&rdquo;</p>
</div>
</div>
) : (
<div id="rule-items-container">
{rules.map((rule) => (
<div
key={rule.id}
className="rule-item border border-dashed border-gray-300 rounded-md p-6 mb-6 relative"
>
<div className="absolute top-3 right-3 flex gap-2">
<span className={`badge rounded-pill ${getRuleTypeBadgeClass(rule.type)} text-white px-2 py-1`}>
#{rule.id}
</span>
<button
className={`${rule.id === '1' && rules.length === 1 ? 'text-gray-400' : 'text-red-500 hover:text-red-700'}`}
onClick={() => handleRemoveRule(rule.id)}
disabled={rule.id === '1' && rules.length === 1}
>
<i className="ri-delete-bin-line"></i>
</button>
</div>
<div className="mb-4">
<label className="form-label" htmlFor={`rule-type-${rule.id}`}>
<span className="required-mark">*</span>
</label>
<select
id={`rule-type-${rule.id}`}
className="form-select rule-type-select"
value={rule.type}
onChange={(e) => handleRuleTypeChange(rule.id, e.target.value)}
>
<option value=""></option>
<option value="exists"></option>
<option value="consistency"></option>
<option value="format"></option>
<option value="logic"></option>
<option value="regex"></option>
<option value="ai"></option>
<option value="code"></option>
</select>
<div className="form-tip"></div>
</div>
<div className="rule-config-container">
{renderRuleConfig(rule)}
</div>
</div>
))}
</div>
)}
</div>
</div>
<div className="form-section mb-6">
<div className="form-section-title mb-2">
<label className="form-label" htmlFor="action-section"></label>
</div>
<div id="action-section">
<div className="form-radio-group mb-3">
<label className="form-radio-item">
<input
type="radio"
name="action-type"
className="form-radio"
checked={actionType === 'warning'}
onChange={() => handleActionTypeChange('warning')}
/>
<span></span>
</label>
<label className="form-radio-item">
<input
type="radio"
name="action-type"
className="form-radio"
checked={actionType === 'blocking'}
onChange={() => handleActionTypeChange('blocking')}
/>
<span></span>
</label>
<label className="form-radio-item">
<input
type="radio"
name="action-type"
className="form-radio"
checked={actionType === 'notification'}
onChange={() => handleActionTypeChange('notification')}
/>
<span></span>
</label>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-3">
<div className="col-span-1">
<label className="form-label" htmlFor="warning-message"></label>
<textarea
id="warning-message"
className="form-textarea"
placeholder="请输入触发规则时向用户显示的提示信息"
></textarea>
<div className="form-tip"></div>
</div>
{actionType === 'notification' && (
<div className="col-span-1">
<label className="form-label" htmlFor="notification-target"></label>
<select id="notification-target" className="form-select mb-2">
<option value="creator"></option>
<option value="supervisor"></option>
<option value="legal"></option>
<option value="custom"></option>
</select>
<div className="mt-2">
<label className="form-label" htmlFor="notification-method"></label>
<div className="flex items-center space-x-4">
<label className="inline-flex items-center">
<input type="checkbox" className="form-checkbox" id="notification-system" defaultChecked />
<span className="ml-2"></span>
</label>
<label className="inline-flex items-center">
<input type="checkbox" className="form-checkbox" id="notification-email" />
<span className="ml-2"></span>
</label>
<label className="inline-flex items-center">
<input type="checkbox" className="form-checkbox" id="notification-sms" />
<span className="ml-2"></span>
</label>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="divider"></div>
{/* 评查结果提示信息 */}
<div className="mb-4">
<label className="form-label">
</label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label className="form-label text-sm" htmlFor="pass-message"></label>
<textarea
id="pass-message"
className="form-textarea"
style={{ height: '80px', minHeight: '60px' }}
placeholder="请输入评查通过时的提示信息"
value={passMessage}
onChange={(e) => handleMessageChange('pass', e.target.value)}
></textarea>
</div>
<div>
<label className="form-label text-sm" htmlFor="fail-message"></label>
<textarea
id="fail-message"
className="form-textarea"
style={{ height: '80px', minHeight: '60px' }}
placeholder="请输入评查不通过时的提示信息"
value={failMessage}
onChange={(e) => handleMessageChange('fail', e.target.value)}
></textarea>
</div>
<div>
<label className="form-label text-sm" htmlFor="suggest-message"></label>
<textarea
id="suggest-message"
className="form-textarea"
style={{ height: '80px', minHeight: '60px' }}
placeholder="请输入对用户的建议信息"
value={suggestMessage}
onChange={(e) => handleMessageChange('suggest', e.target.value)}
></textarea>
</div>
</div>
</div>
{/* 不通过提示类别 */}
<div className="mb-4">
<label className="form-label" htmlFor="error-severity"></label>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4" id="error-severity">
<label className={`flex items-center cursor-pointer border-l-4 border-blue-500 bg-blue-50 p-3 hover:bg-blue-100 rounded-r-md severity-option ${errorSeverity === 'info' ? 'selected-severity' : ''}`}>
<input
type="radio"
name="errorSeverity"
className="severity-radio"
value="info"
checked={errorSeverity === 'info'}
onChange={() => handleSeverityChange('info')}
/>
<i className="ri-information-line text-blue-500 text-xl mr-3"></i>
<div>
<div className="font-medium"> (Info)</div>
<div className="text-sm text-gray-500"></div>
</div>
</label>
<label className={`flex items-center cursor-pointer border-l-4 border-yellow-500 bg-yellow-50 p-3 hover:bg-yellow-100 rounded-r-md severity-option ${errorSeverity === 'warning' ? 'selected-severity' : ''}`}>
<input
type="radio"
name="errorSeverity"
className="severity-radio"
value="warning"
checked={errorSeverity === 'warning'}
onChange={() => handleSeverityChange('warning')}
/>
<i className="ri-alert-line text-yellow-500 text-xl mr-3"></i>
<div>
<div className="font-medium"> (Warning)</div>
<div className="text-sm text-gray-500"></div>
</div>
</label>
<label className={`flex items-center cursor-pointer border-l-4 border-red-500 bg-red-50 p-3 hover:bg-red-100 rounded-r-md severity-option ${errorSeverity === 'error' ? 'selected-severity' : ''}`}>
<input
type="radio"
name="errorSeverity"
className="severity-radio"
value="error"
checked={errorSeverity === 'error'}
onChange={() => handleSeverityChange('error')}
/>
<i className="ri-error-warning-line text-red-500 text-xl mr-3"></i>
<div>
<div className="font-medium"> (Error)</div>
<div className="text-sm text-gray-500"></div>
</div>
</label>
</div>
<div className="form-tip"></div>
</div>
{/* 评查后动作 */}
<div className="mb-4">
<label className="form-label" htmlFor="action-type">
<span className="required-mark">*</span>
</label>
<select
className="form-select"
id="action-type"
value={actionType || 'none'}
onChange={(e) => {
setActionType(e.target.value);
if (onChange) {
onChange({ actionType: e.target.value });
}
}}
>
<option value="none"></option>
<option value="manual"></option>
<option value="replace"></option>
</select>
</div>
{/* 动作描述区域 */}
{actionType && actionType !== 'none' && (
<div className="mb-4" id="action-description-container">
<label className="form-label" htmlFor="action-description"></label>
<textarea
className="form-textarea"
id="action-description"
placeholder="请输入动作描述,说明评查通过或未通过时的处理方式"
value={actionDescription}
onChange={(e) => {
setActionDescription(e.target.value);
if (onChange) {
onChange({ actionDescription: e.target.value });
}
}}
></textarea>
</div>
)}
</div>
</div>
</div>
);
}