1309 lines
55 KiB
TypeScript
1309 lines
55 KiB
TypeScript
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">
|
||
使用规则编号和逻辑运算符(AND、OR、NOT)组合
|
||
</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">点击“添加规则”按钮开始创建评查规则</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>
|
||
);
|
||
}
|