Files
leaudit-platform-frontend/app/components/rules/new/ReviewSettings.tsx
T
2025-04-09 01:34:14 +08:00

2056 lines
84 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, useCallback, useRef } from 'react';
import { SimpleCodeEditor } from './SimpleCodeEditor';
import { RuleContext } from '~/contexts/RuleContext';
import { processFieldNames, areArraysDifferent, getArrayDifference, debounce } from '~/utils';
import type {
Rule as ModelRule,
RuleType as ModelRuleType,
LogicType,
SuggestionMessageType,
PostActionType
} from '~/models/evaluation_points';
interface RuleType {
id: string;
type: string;
config: Record<string, unknown>;
}
// 为配置项添加类型定义
interface ComparisonPair {
sourceField: string;
targetField: string;
compareMethod: string;
}
// 添加逻辑条件接口
interface Condition {
field: string;
operator: string;
value: string;
}
interface ReviewSettingsProps {
onChange?: (data: Record<string, unknown>) => void;
initialData?: {
rules?: RuleType[];
combinationLogic?: string;
customLogic?: string;
pass_message?: string;
fail_message?: string;
suggestion_message?: string;
suggestion_message_type?: string;
post_action?: string;
action_config?: string;
score?: number;
scoreDisplay?: string;
};
// 添加选项数据参数
ruleTypeOptions?: Array<{ value: string; label: string }>;
logicTypeOptions?: Array<{ value: string; label: string }>;
logicOperatorOptions?: Array<{ value: string; label: string }>;
compareMethodOptions?: Array<{ value: string; label: string }>;
formatTypeOptions?: Array<{ value: string; label: string }>;
comparisonOperatorOptions?: Array<{ value: string; label: string }>;
matchTypeOptions?: Array<{ value: string; label: string }>;
suggestionMessageTypeOptions?: Array<{ value: string; label: string }>;
postActionOptions?: Array<{ value: string; label: string }>;
}
export function ReviewSettings({
onChange,
initialData,
ruleTypeOptions = [],
logicTypeOptions = [],
logicOperatorOptions = [],
compareMethodOptions = [],
formatTypeOptions = [],
comparisonOperatorOptions = [],
matchTypeOptions = [],
suggestionMessageTypeOptions = [],
postActionOptions = []
}: 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 [post_action, setPostAction] = useState<string>('none');
const [action_config, setActionConfig] = useState<string>('');
// 添加分数状态
const [score, setScore] = useState<number>(0);
const [scoreDisplay, setScoreDisplay] = useState<string>('');
// 获取抽取字段的上下文
const { extractionFields } = useContext(RuleContext);
// 初始化评查通过/不通过/建议信息
const [pass_message, setPassMessage] = useState('文档检查通过,符合规范要求。');
const [fail_message, setFailMessage] = useState('文档存在以下问题,请修改后重新提交。');
const [suggestion_message, setSuggestMessage] = useState('');
// 提示类型
const [suggestion_message_type, setSuggestionMessageType] = useState('warning');
// 保存最近一次可用的字段列表
const [availableFields, setAvailableFields] = useState<string[]>(
// 初始化时就处理字段,去掉类型后缀
processFieldNames(extractionFields || [])
);
// 使用useRef跟踪是否已经初始化过
const initializedRef = useRef(false);
// 保存初始数据的引用,用于检测是否有实际变更
const initialDataRef = useRef<any>(null);
// 加载初始数据
useEffect(() => {
// 如果已经初始化过,则跳过此次处理
if (initializedRef.current) {
console.log("ReviewSettings已初始化,跳过后续初始化处理");
return;
}
// 记录初始化处理
console.log("ReviewSettings开始初始化,数据:", initialData);
// 保存初始数据引用,用于后续比较
initialDataRef.current = JSON.parse(JSON.stringify(initialData));
// 设置已初始化标记
initializedRef.current = true;
// 只有在有initialData时才进行初始化设置
if (initialData) {
// 处理初始规则数据
if (initialData.rules && Array.isArray(initialData.rules) && initialData.rules.length > 0) {
console.log("设置初始规则数据:", initialData.rules);
const validRules = initialData.rules.map(rule => {
// 确保每个规则都有id
if (!rule.id) {
rule.id = `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// 确保配置对象存在
if (!rule.config) {
rule.config = {};
}
// 添加可用字段
if (availableFields.length > 0) {
rule.config.availableFields = availableFields;
}
return rule;
});
// 如果没有规则或规则为空,添加一个默认规则
if (validRules.length === 0) {
validRules.push({ id: '1', type: '', config: { availableFields } });
}
setRules(validRules);
} else {
// 如果rules为空或不是数组,添加一个默认规则
setRules([{ id: '1', type: '', config: { availableFields } }]);
}
// 设置组合逻辑
if (initialData.combinationLogic) {
setCombinationLogic(initialData.combinationLogic);
if (initialData.combinationLogic === 'custom') {
setShowCustomLogic(true);
}
}
// 设置自定义逻辑
if (initialData.customLogic) {
setCustomLogic(initialData.customLogic);
}
// 设置通过/不通过消息
if (initialData.pass_message) {
setPassMessage(initialData.pass_message);
}
if (initialData.fail_message) {
setFailMessage(initialData.fail_message);
}
// 设置建议消息
if (initialData.suggestion_message) {
setSuggestMessage(initialData.suggestion_message);
}
if (initialData.suggestion_message_type) {
setSuggestionMessageType(initialData.suggestion_message_type);
}
// 设置后处理动作
if (initialData.post_action) {
setPostAction(initialData.post_action);
}
if (initialData.action_config) {
setActionConfig(initialData.action_config);
}
// 设置分数
if (initialData.score !== undefined) {
setScore(initialData.score);
}
// 设置分数显示值
if (initialData.scoreDisplay) {
setScoreDisplay(initialData.scoreDisplay);
} else if (initialData.score !== undefined && initialData.score > 0) {
setScoreDisplay(String(initialData.score));
}
// 数据加载完成后,生成一次完整的评查配置
setTimeout(() => {
generateEvaluationConfig();
}, 0);
}
// 移除availableFields依赖,避免死循环
}, [initialData]);
// 监听extractionFields的变化
useEffect(() => {
if (extractionFields && extractionFields.length > 0) {
// 使用工具函数处理字段
const uniqueFields = processFieldNames(extractionFields);
// 只在字段列表实际发生变化时更新
if (areArraysDifferent(uniqueFields, availableFields)) {
// 检查删除和新增的字段
const { added, removed } = getArrayDifference(uniqueFields, availableFields);
// 处理删除的字段
if (removed.length > 0) {
handleDeletedFields(removed);
}
// 更新可用字段
setAvailableFields(uniqueFields);
// 使用最新的字段更新规则
updateRulesWithNewFields(uniqueFields);
}
}
}, [extractionFields, availableFields]);
// 检查并更新字段(仍然保留此函数供需要时手动触发)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const checkAndUpdateFields = () => {
if (extractionFields.length > 0) {
// 处理字段,去掉类型后缀
const processedFields = extractionFields.map(field => {
if (field.includes('_')) {
return field.split('_')[0]; // 只保留类型前面的字段名
}
return field;
});
// 去重
const uniqueFields = [...new Set(processedFields)];
// 检查是否有字段被删除
const deletedFields = availableFields.filter(field => !uniqueFields.includes(field));
// 处理新增的字段
const newFields = uniqueFields.filter((field: string) => !availableFields.includes(field));
if (newFields.length > 0 || deletedFields.length > 0) {
console.log('Updating fields in checkAndUpdateFields - deleted:', deletedFields, 'new:', newFields);
// 设置最新的可用字段列表
setAvailableFields(uniqueFields);
// 处理规则中已删除的字段
if (deletedFields.length > 0) {
handleDeletedFields(deletedFields);
}
// 使用最新的字段列表更新规则配置
updateRulesWithNewFields(uniqueFields);
return true; // 表示字段已更新
}
}
return false; // 表示字段未更新
};
// 初始化评查配置
useEffect(() => {
// 生成并更新评查配置
generateEvaluationConfig();
}, []);
// 处理已删除字段的函数
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: updatedConfig
};
});
});
};
// 更新规则配置中的可用字段但保留已选择的字段和规则配置
const updateRulesWithNewFields = (newFields: string[]) => {
// 更新每个规则的可用字段列表,但保留现有配置
setRules(prevRules => {
return prevRules.map(rule => {
const updatedConfig = { ...rule.config };
// 对所有规则类型都更新availableFields字段
// 处理字段,只保留字段名,去掉类型后缀
const processedFields = newFields.map(field => {
if (field.includes('_')) {
return field.split('_')[0]; // 只保留类型前面的字段名
}
return field;
});
// 去重
const uniqueFields = [...new Set(processedFields)];
updatedConfig.availableFields = uniqueFields;
// 根据规则类型更新其他相关字段
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
};
});
});
};
const handleLogicChange = (logic: string) => {
setCombinationLogic(logic);
setShowCustomLogic(logic === 'custom');
if (onChange) {
// 确保将完整的数据传递给父组件
const updateData = {
combinationLogic: logic,
// 如果切换到自定义逻辑,同时传递自定义逻辑内容
customLogic: logic === 'custom' ? customLogic : ''
};
onChange(updateData);
// 生成完整的评查配置
setTimeout(() => generateEvaluationConfig(), 0);
}
};
const handleCustomLogicChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setCustomLogic(value);
if (onChange) {
onChange({ customLogic: value });
}
};
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 newRules = rules.map(rule => {
if (rule.id === id) {
// 为新类型初始化配置
let initialConfig: Record<string, unknown> = {};
// 如果类型没变,保留原配置
if (type === rule.type) {
initialConfig = { ...rule.config };
} else {
// 根据类型设置初始配置
switch(type) {
case 'exists':
initialConfig = {
fields: [], // 使用fields替代selectedFields
logic: 'and', // 使用logic替代existsLogic
availableFields: availableFields
};
break;
case 'consistency':
initialConfig = {
pairs: [],
logic: 'and', // 使用logic替代logicRelation
availableFields: availableFields
};
break;
case 'format':
initialConfig = {
field: '', // 用于内部运算
checkField: '', // 用于UI展示
formatType: '',
formatParams: '',
availableFields: availableFields
};
break;
case 'logic':
initialConfig = {
conditions: [],
logic: 'and', // 使用logic替代logicRelation
availableFields: availableFields
};
break;
case 'regex':
initialConfig = {
field: '', // 用于内部运算
checkField: '', // 用于UI展示
pattern: '',
regexPattern: '', // 用于UI展示
matchType: 'match', // 默认为必须匹配
availableFields: availableFields
};
break;
case 'ai':
initialConfig = {
model: 'qwen14b',
temperature: 0.1,
prompt: '',
availableFields: availableFields
};
break;
case 'code':
initialConfig = {
language: 'javascript',
code: '',
availableFields: availableFields
};
break;
default:
initialConfig = {
availableFields: availableFields
};
}
}
return {
...rule,
type,
config: initialConfig
};
}
return rule;
});
setRules(newRules);
if (onChange) {
onChange({ rules: newRules });
}
// 更新评查配置
generateEvaluationConfig();
};
// 处理规则配置变更
const handleRuleConfigChange = (id: string, configChanges: Record<string, unknown>) => {
const newRules = rules.map(rule => {
if (rule.id === id) {
// 处理特殊的字段映射,确保不同名称的字段保持同步
const processedChanges = { ...configChanges };
// 对于格式判断,确保checkField和field字段同步
if (rule.type === 'format' && 'checkField' in configChanges) {
processedChanges.field = configChanges.checkField;
}
// 对于正则判断,确保字段名和模式保持同步
if (rule.type === 'regex') {
if ('checkField' in configChanges) {
processedChanges.field = configChanges.checkField;
}
if ('regexPattern' in configChanges) {
processedChanges.pattern = configChanges.regexPattern;
}
}
return { ...rule, config: { ...rule.config, ...processedChanges } };
}
return rule;
});
setRules(newRules);
if (onChange) {
// 立即触发父组件的onChange回调,确保数据能保存到父组件
onChange({ rules: newRules });
}
};
// 渲染字段标签,确保已选择的字段即使在新的字段列表中不存在也会显示
const renderFieldTags = (ruleId: string, config: Record<string, unknown>) => {
// 获取规则的当前已选字段
const selectedFields = Array.isArray(config.selectedFields) ? config.selectedFields as string[] : [];
// 优先使用配置中存储的可用字段,如果没有则使用当前可用字段
const fieldsToRender = Array.isArray(config.availableFields) ?
config.availableFields as string[] :
availableFields;
return (
<div className="field-tags">
{fieldsToRender.map((field, index) => {
// 使用includes方法检查选中状态
const isSelected = selectedFields.includes(field);
return (
<div
key={`field-${ruleId}-${index}`}
className={`field-tag ${isSelected ? 'selected' : ''}`}
onClick={() => {
// 切换选中状态
const newSelectedFields = isSelected
? selectedFields.filter(f => f !== field)
: [...selectedFields, field];
// 更新规则配置
handleRuleConfigChange(ruleId, {
fields: newSelectedFields
});
// 直接触发配置更新
generateEvaluationConfig();
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
const newSelectedFields = isSelected
? selectedFields.filter(f => f !== field)
: [...selectedFields, field];
handleRuleConfigChange(ruleId, {
fields: newSelectedFields
});
// 直接触发配置更新
generateEvaluationConfig();
}
}}
role="button"
tabIndex={0}
>
{field}
</div>
);
})}
</div>
);
};
// 获取规则类型的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;
// 如果规则中的availableFields不是最新的,则更新它
if (type && config && (!config.availableFields ||
(Array.isArray(config.availableFields) &&
!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);
}
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={`logic-and-${id}`}> <span className="required-mark">*</span></label>
<div className="form-radio-group">
<label className="form-radio-item">
<input
type="radio"
id={`logic-and-${id}`}
name={`logic_${id}`}
className="form-radio"
value="and"
checked={!config.logic || config.logic === 'and'}
onChange={(e) => {
console.log(`[调试] 选择判断逻辑 and,规则ID: ${id}, 当前值: ${config.logic}`);
handleRuleConfigChange(id, { logic: e.target.value });
// 直接触发配置更新
generateEvaluationConfig();
}}
/>
<span></span>
</label>
<label className="form-radio-item">
<input
type="radio"
id={`logic-or-${id}`}
name={`logic_${id}`}
className="form-radio"
value="or"
checked={config.logic === 'or'}
onChange={(e) => {
console.log(`[调试] 选择判断逻辑 or,规则ID: ${id}, 当前值: ${config.logic}`);
handleRuleConfigChange(id, { logic: e.target.value });
// 直接触发配置更新
generateEvaluationConfig();
}}
/>
<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 });
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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 });
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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 });
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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 });
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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"
value={(config.initialSourceField as string) || ''}
onChange={(e) => {
// 创建新的比较对数组并存储当前选择的源字段
const sourceField = e.target.value;
handleRuleConfigChange(id, {
initialSourceField: sourceField,
pairs: [{ sourceField, targetField: '', compareMethod: '' }]
});
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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"
value={(config.initialTargetField as string) || ''}
onChange={(e) => {
// 获取当前选择的源字段和目标字段
const sourceField = (config.initialSourceField as string) || '';
const targetField = e.target.value;
handleRuleConfigChange(id, {
initialTargetField: targetField,
pairs: [{ sourceField, targetField, compareMethod: '' }]
});
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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"
value={(config.initialCompareMethod as string) || ''}
onChange={(e) => {
// 获取当前选择的源字段、目标字段和比较方式
const sourceField = (config.initialSourceField as string) || '';
const targetField = (config.initialTargetField as string) || '';
const compareMethod = e.target.value;
handleRuleConfigChange(id, {
initialCompareMethod: compareMethod,
pairs: [{ sourceField, targetField, compareMethod }]
});
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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={() => {
// 添加新的比较对
// 直接获取当前的pairs数组,或初始化为空数组
const pairs = Array.isArray(config.pairs) ? [...(config.pairs as ComparisonPair[])] : [];
// 创建新的空白比较对
const newPair = { sourceField: '', targetField: '', compareMethod: '' };
// 如果数组为空,确保先初始化第一个条目
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 });
}}
>
<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"
checked={!config.logicRelation || config.logicRelation === 'and'}
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"
checked={config.logicRelation === '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}`}>
{Array.isArray(config.conditions) && config.conditions.length > 0 ? (
config.conditions.map((condition, conditionIndex) => (
<div key={`condition-${id}-${conditionIndex}`} 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}-${conditionIndex}`}></label>
<select
id={`field-${id}-${conditionIndex}`}
className="form-select"
value={condition.field || ''}
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...(config.conditions as Condition[])] : [];
currentConditions[conditionIndex] = { ...currentConditions[conditionIndex], field: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
// 触发配置更新
generateEvaluationConfig();
}}
>
<option value=""></option>
{availableFields.map((field, idx) => (
<option key={`field-${conditionIndex}-${idx}`} value={field}>{field}</option>
))}
</select>
</div>
<div>
<label className="form-label text-sm" htmlFor={`operator-${id}-${conditionIndex}`}></label>
<select
id={`operator-${id}-${conditionIndex}`}
className="form-select"
value={condition.operator || 'eq'}
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...(config.conditions as Condition[])] : [];
currentConditions[conditionIndex] = { ...currentConditions[conditionIndex], operator: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
// 触发配置更新
generateEvaluationConfig();
}}
>
<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}-${conditionIndex}`}></label>
<input
type="text"
id={`value-${id}-${conditionIndex}`}
className="form-input"
value={condition.value || ''}
placeholder="请输入比较值"
onChange={(e) => {
const currentConditions = Array.isArray(config.conditions) ? [...(config.conditions as Condition[])] : [];
currentConditions[conditionIndex] = { ...currentConditions[conditionIndex], value: e.target.value };
handleRuleConfigChange(id, { conditions: currentConditions });
// 触发配置更新
generateEvaluationConfig();
}}
/>
</div>
</div>
<div className="flex justify-end">
<button
type="button"
className="text-red-500 hover:text-red-700"
onClick={() => {
const currentConditions = Array.isArray(config.conditions) ? [...(config.conditions as Condition[])] : [];
currentConditions.splice(conditionIndex, 1);
handleRuleConfigChange(id, { conditions: currentConditions });
// 触发配置更新
generateEvaluationConfig();
}}
>
<i className="ri-delete-bin-line"></i>
</button>
</div>
</div>
))
) : (
<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"
value={(config.initialField as string) || ''}
onChange={(e) => {
// 直接初始化一个完整的条件对象
const field = e.target.value;
handleRuleConfigChange(id, {
initialField: field,
conditions: [{ field, operator: 'eq', value: '' }]
});
// 触发配置更新
generateEvaluationConfig();
}}
>
<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"
value={(config.initialOperator as string) || 'eq'}
onChange={(e) => {
// 获取field和operator的值
const field = (config.initialField as string) || '';
const operator = e.target.value;
handleRuleConfigChange(id, {
initialOperator: operator,
conditions: [{ field, operator, value: '' }]
});
// 触发配置更新
generateEvaluationConfig();
}}
>
<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="请输入比较值"
value={(config.initialValue as string) || ''}
onChange={(e) => {
// 获取field和operator的值
const field = (config.initialField as string) || '';
const operator = (config.initialOperator as string) || 'eq';
const value = e.target.value;
handleRuleConfigChange(id, {
initialValue: value,
conditions: [{ field, operator, value }]
});
// 触发配置更新
generateEvaluationConfig();
}}
/>
</div>
</div>
</div>
)}
</div>
<div className="mt-2">
<button
type="button"
className="ant-btn ant-btn-default"
onClick={() => {
// 添加新的条件
// 直接获取当前的conditions数组,或初始化为空数组
const conditions = Array.isArray(config.conditions) ? [...(config.conditions as Condition[])] : [];
// 创建新的空白条件
const newCondition = { field: '', operator: 'eq', value: '' };
// 无论如何,都添加一个新的空白行
conditions.push(newCondition);
// 更新配置
handleRuleConfigChange(id, { conditions });
// 触发配置更新
generateEvaluationConfig();
}}
>
<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"
checked={!config.logicRelation || config.logicRelation === 'and'}
onChange={(e) => {
handleRuleConfigChange(id, { logicRelation: e.target.value });
// 触发配置更新
generateEvaluationConfig();
}}
/>
<span>AND</span>
</label>
<label className="form-radio-item">
<input
type="radio"
name={`logicRelation_${id}`}
className="form-radio"
value="or"
checked={config.logicRelation === 'or'}
onChange={(e) => {
handleRuleConfigChange(id, { logicRelation: e.target.value });
// 触发配置更新
generateEvaluationConfig();
}}
/>
<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"
value={config.checkField as string || ''}
onChange={(e) => {
handleRuleConfigChange(id, {
checkField: e.target.value,
field: e.target.value // 同步更新内部字段
});
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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="请输入正则表达式"
value={config.regexPattern as string || ''}
onChange={(e) => {
handleRuleConfigChange(id, {
regexPattern: e.target.value,
pattern: e.target.value // 同步更新内部字段
});
// 直接触发配置更新
generateEvaluationConfig();
}}
></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"
checked={!config.matchType || config.matchType === 'match'}
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"
checked={config.matchType === '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"
value={config.checkField as string || ''}
onChange={(e) => {
handleRuleConfigChange(id, {
checkField: e.target.value,
field: e.target.value // 同步更新内部字段
});
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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"
value={config.formatType as string || ''}
onChange={(e) => {
handleRuleConfigChange(id, { formatType: e.target.value });
// 直接触发配置更新
generateEvaluationConfig();
}}
>
<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="请输入参数设置"
value={config.formatParams as string || ''}
onChange={(e) => {
handleRuleConfigChange(id, {
formatParams: e.target.value,
parameters: e.target.value // 同步更新内部参数字段
});
// 直接触发配置更新
generateEvaluationConfig();
}}
/>
</div>
</div>
);
default:
return (
<div className="p-4 bg-gray-50 rounded">
<p> {type} </p>
</div>
);
}
};
// 生成完整的评查配置数据并在提交保存时使用
const generateEvaluationConfig = useCallback(() => {
const config = {
logicType: combinationLogic,
customLogic: combinationLogic === 'custom' ? customLogic : '',
rules: rules.map(rule => {
// 创建一个深拷贝以避免修改原始对象
const processedConfig = JSON.parse(JSON.stringify(rule.config || {}));
// 根据规则类型处理特定的字段映射
switch(rule.type) {
case 'exists':
// 将UI字段名映射为API字段名
if (processedConfig.selectedFields) {
processedConfig.fields = processedConfig.selectedFields;
delete processedConfig.selectedFields;
}
if (processedConfig.existsLogic) {
console.log(`[调试] exists规则 ${rule.id} 转换: existsLogic=${processedConfig.existsLogic} 映射到 logic`);
processedConfig.logic = processedConfig.existsLogic;
delete processedConfig.existsLogic;
}
break;
case 'consistency':
case 'logic':
// 将UI字段名映射为API字段名
if (processedConfig.logicRelation) {
processedConfig.logic = processedConfig.logicRelation;
delete processedConfig.logicRelation;
}
// 删除用于UI的临时字段
delete processedConfig.initialSourceField;
delete processedConfig.initialTargetField;
delete processedConfig.initialCompareMethod;
delete processedConfig.initialField;
delete processedConfig.initialOperator;
delete processedConfig.initialValue;
break;
case 'format':
// 确保field字段正确设置
if (processedConfig.checkField) {
processedConfig.field = processedConfig.checkField;
delete processedConfig.checkField;
}
if (processedConfig.formatParams) {
processedConfig.parameters = processedConfig.formatParams;
delete processedConfig.formatParams;
}
break;
case 'regex':
// 确保field和pattern字段正确设置
if (processedConfig.checkField) {
processedConfig.field = processedConfig.checkField;
delete processedConfig.checkField;
}
if (processedConfig.regexPattern) {
processedConfig.pattern = processedConfig.regexPattern;
delete processedConfig.regexPattern;
}
break;
}
// 移除辅助用的UI字段
delete processedConfig.availableFields;
return {
id: rule.id,
type: rule.type,
config: processedConfig
};
}).filter(rule => rule.type && rule.type.trim() !== '')
};
// 使用setTimeout避免连锁更新
setTimeout(() => {
if (onChange) {
onChange({
rules: config.rules,
combinationLogic: config.logicType,
customLogic: config.customLogic,
pass_message: pass_message,
fail_message: fail_message,
suggestion_message: suggestion_message,
suggestion_message_type: suggestion_message_type,
post_action: post_action,
action_config: action_config,
score: score,
scoreDisplay: scoreDisplay
});
}
}, 0);
return config;
}, [rules, combinationLogic, customLogic, pass_message, fail_message, suggestion_message, suggestion_message_type, post_action, action_config, score, scoreDisplay, onChange]);
// 组件初次渲染后,主动发送一次完整配置数据
useEffect(() => {
// 如果有初始数据,在组件挂载后主动发送一次完整规则配置
if (initialDataRef.current && onChange) {
console.log("组件挂载后发送初始完整配置");
setTimeout(() => generateEvaluationConfig(), 100);
}
}, [generateEvaluationConfig, onChange]);
// 处理评查结果消息变更
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 });
// 触发完整配置生成以确保数据保存
generateEvaluationConfig();
}
};
// 处理严重程度变更
const handleSeverityChange = (value: string) => {
setSuggestionMessageType(value);
if (onChange) {
onChange({ suggestion_message_type: value });
// 触发完整配置生成以确保数据保存
generateEvaluationConfig();
}
};
// 处理分数变更
const handleScoreChange = (value: string) => {
// 保存用户输入的显示值
setScoreDisplay(value);
// 只在值不为空时更新实际分数
if (value.trim() !== '') {
const numValue = parseFloat(value);
if (!isNaN(numValue)) {
const validScore = Math.min(Math.max(numValue, 0), 100);
setScore(validScore);
} else {
setScore(0);
}
} else {
setScore(0);
}
if (onChange) {
if (value.trim() === '') {
// 空值处理
onChange({ score: 0, scoreDisplay: '' });
} else {
onChange({ score: score, scoreDisplay: value });
}
}
};
return (
<div className="ant-card">
<style>
{`
.field-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.field-tag {
background-color: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 4px 8px;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s;
}
.field-tag:hover {
background-color: #e5e7eb;
}
.field-tag.selected {
background-color: #1890ff;
border-color: #096dd9;
color: white;
}
`}
</style>
<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}
onBlur={() => {
// 确保在失去焦点时也触发更新
if (onChange) {
onChange({
combinationLogic: 'custom',
customLogic: customLogic
});
}
}}
></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>
</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="flex justify-center">
<button
type="button"
className="ant-btn ant-btn-default"
onClick={handleAddRule}
>
<i className="ri-add-line mr-1"></i>
</button>
</div>
<div className="divider"></div>
{/* 评查结果提示信息 */}
<div className="mb-4">
<h4 className="form-label"></h4>
<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={pass_message}
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={fail_message}
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={suggestion_message}
onChange={(e) => handleMessageChange('suggest', e.target.value)}
></textarea>
</div>
</div>
</div>
{/* 建议信息类别 */}
<div className="mb-4">
<h4 className="form-label"></h4>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 mt-2">
<div
className={`flex items-center cursor-pointer border-l-4 border-blue-500 ${suggestion_message_type === 'info'
? 'bg-blue-100 shadow-md ring-2 ring-blue-200'
: 'bg-blue-50 hover:bg-blue-100'} p-3 rounded-r-md transition-all duration-200`}
onClick={() => handleSeverityChange('info')}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleSeverityChange('info');
}
}}
role="radio"
aria-checked={suggestion_message_type === 'info'}
tabIndex={0}
>
<input
type="radio"
name="suggestion_message_type"
id="severity-info"
className="severity-radio hidden"
value="info"
checked={suggestion_message_type === 'info'}
onChange={() => handleSeverityChange('info')}
/>
<i className={`ri-information-line text-blue-500 text-xl mr-3 ${suggestion_message_type === 'info' ? 'animate-pulse' : ''}`}></i>
<div>
<div className="font-medium"> (Info)</div>
<div className="text-sm text-gray-500"></div>
</div>
{suggestion_message_type === 'info' && (
<div className="ml-auto">
<i className="ri-check-line text-blue-500 text-lg"></i>
</div>
)}
</div>
<div
className={`flex items-center cursor-pointer border-l-4 border-yellow-500 ${suggestion_message_type === 'warning'
? 'bg-yellow-100 shadow-md ring-2 ring-yellow-200'
: 'bg-yellow-50 hover:bg-yellow-100'} p-3 rounded-r-md transition-all duration-200`}
onClick={() => handleSeverityChange('warning')}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleSeverityChange('warning');
}
}}
role="radio"
aria-checked={suggestion_message_type === 'warning'}
tabIndex={0}
>
<input
type="radio"
name="suggestion_message_type"
id="severity-warning"
className="severity-radio hidden"
value="warning"
checked={suggestion_message_type === 'warning'}
onChange={() => handleSeverityChange('warning')}
/>
<i className={`ri-alert-line text-yellow-500 text-xl mr-3 ${suggestion_message_type === 'warning' ? 'animate-pulse' : ''}`}></i>
<div>
<div className="font-medium"> (Warning)</div>
<div className="text-sm text-gray-500"></div>
</div>
{suggestion_message_type === 'warning' && (
<div className="ml-auto">
<i className="ri-check-line text-yellow-500 text-lg"></i>
</div>
)}
</div>
<div
className={`flex items-center cursor-pointer border-l-4 border-red-500 ${suggestion_message_type === 'error'
? 'bg-red-100 shadow-md ring-2 ring-red-200'
: 'bg-red-50 hover:bg-red-100'} p-3 rounded-r-md transition-all duration-200`}
onClick={() => handleSeverityChange('error')}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
handleSeverityChange('error');
}
}}
role="radio"
aria-checked={suggestion_message_type === 'error'}
tabIndex={0}
>
<input
type="radio"
name="suggestion_message_type"
id="severity-error"
className="severity-radio hidden"
value="error"
checked={suggestion_message_type === 'error'}
onChange={() => handleSeverityChange('error')}
/>
<i className={`ri-error-warning-line text-red-500 text-xl mr-3 ${suggestion_message_type === 'error' ? 'animate-pulse' : ''}`}></i>
<div>
<div className="font-medium"> (Error)</div>
<div className="text-sm text-gray-500"></div>
</div>
{suggestion_message_type === 'error' && (
<div className="ml-auto">
<i className="ri-check-line text-red-500 text-lg"></i>
</div>
)}
</div>
</div>
<div className="form-tip mt-2"></div>
</div>
{/* 评查后动作 */}
<div className="mb-4">
<label className="form-label" htmlFor="post-action">
<span className="required-mark">*</span>
</label>
<select
className="form-select"
id="post-action"
value={post_action || 'none'}
onChange={(e) => {
setPostAction(e.target.value);
if (onChange) {
onChange({ post_action: e.target.value });
}
}}
>
<option value="none"></option>
<option value="manual"></option>
<option value="replace"></option>
</select>
</div>
{/* 分数设置 */}
<div className="mb-4">
<label className="form-label" htmlFor="score">
</label>
<div className="flex items-center">
<input
type="text"
className="form-input"
id="score"
placeholder="请输入分数 (0-100)"
value={scoreDisplay}
onChange={(e) => handleScoreChange(e.target.value)}
/>
<span className="ml-2 text-gray-600"></span>
</div>
<div className="form-tip">0-100</div>
</div>
{/* 动作描述区域 */}
{post_action && post_action !== 'none' && (
<div className="mb-4" id="action-config-container">
<label className="form-label" htmlFor="action-config"></label>
<textarea
className="form-textarea"
id="action-config"
placeholder="请输入动作描述,说明评查通过或未通过时的处理方式"
value={action_config}
onChange={(e) => {
setActionConfig(e.target.value);
if (onChange) {
onChange({ action_config: e.target.value });
}
}}
></textarea>
</div>
)}
</div>
</div>
</div>
);
}