基础组件完善

This commit is contained in:
2025-04-09 01:34:14 +08:00
parent e421bcd44b
commit ebdf97aebf
11 changed files with 2673 additions and 1692 deletions
+130 -200
View File
@@ -1,182 +1,57 @@
import React, { useState, useEffect } from 'react';
import type { EvaluationPoint } from '~/models/evaluation_points';
import type { EvaluationPointGroup } from '~/models/evaluation_point_groups';
interface BasicInfoProps {
onChange?: (data: Record<string, unknown>) => void;
initialData?: FormDataType;
evaluationPointGroups?: Array<{id: number, pid: number, code: string, name: string, is_enabled: boolean}>;
initialData?: EvaluationPoint;
evaluationPointGroups?: EvaluationPointGroup[];
riskOptions?: Array<{value: string, label: string}>;
}
// 定义表单数据类型
interface FormDataType {
name: string;
code: string;
risk: string;
is_enabled: boolean;
description: string;
references_laws: {
name: string;
articles: string[];
content: string;
};
evaluation_point_groups_id: number | null;
evaluation_point_groups_pid: number | null;
type: string;
id?: number;
}
export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }: BasicInfoProps) {
const [formData, setFormData] = useState<FormDataType>({
name: '',
code: '',
risk: 'medium',
is_enabled: true,
description: '',
references_laws: {
name: '',
articles: [],
content: ''
},
evaluation_point_groups_id: null,
evaluation_point_groups_pid: null,
type: ''
// 评查点基本信息组件
export function BasicInfo({ onChange, initialData, evaluationPointGroups = [], riskOptions = [] }: BasicInfoProps) {
const [formData, setFormData] = useState<EvaluationPoint>({
risk: 'medium', // 风险等级 默认中风险
is_enabled: true, // 是否启用 默认启用
...(initialData || {}) // 合并初始数据
});
// 找到当前评查点类型对应的code
const getCheckpointTypeCode = () => {
if (!formData.evaluation_point_groups_pid) return "";
const typeGroup = evaluationPointGroups.find(
group => group.id === formData.evaluation_point_groups_pid && group.pid === 0
);
return typeGroup?.code || "";
};
// 评查点描述与法律依据 展开状态
const [isDescExpanded, setIsDescExpanded] = useState(false);
const [lawArticlesInput, setLawArticlesInput] = useState('');
const [filteredRuleGroups, setFilteredRuleGroups] = useState<Array<{id: number, pid: number, code: string, name: string, is_enabled: boolean}>>([]);
// 条款号临时输入字符串(不会触发自动分割)
const [lawArticlesText, setLawArticlesText] = useState('');
// 当initialData变化时更新表单数据
useEffect(() => {
if (initialData) {
const newFormData = {
name: initialData.name || '',
code: initialData.code || '',
risk: initialData.risk || 'medium',
is_enabled: initialData.is_enabled !== undefined ? initialData.is_enabled : true,
description: initialData.description || '',
references_laws: initialData.references_laws || {
name: '',
articles: [],
content: ''
},
evaluation_point_groups_id: initialData.evaluation_point_groups_id || null,
evaluation_point_groups_pid: initialData.evaluation_point_groups_pid || null,
type: initialData.type || ''
};
setFormData(newFormData);
// 更新法律条款输入框
if (initialData.references_laws && Array.isArray(initialData.references_laws.articles)) {
setLawArticlesInput(initialData.references_laws.articles.join(','));
}
// 如果有描述或法律依据,默认展开详细信息
if (initialData.description ||
(initialData.references_laws &&
(initialData.references_laws.name ||
initialData.references_laws.content ||
(initialData.references_laws.articles && initialData.references_laws.articles.length > 0)))) {
setIsDescExpanded(true);
}
}
}, [initialData]);
// 当评查点类型或评查点组数据变化时,过滤规则组列表
useEffect(() => {
if (evaluationPointGroups && evaluationPointGroups.length > 0) {
console.log("评查点组数据更新,当前类型:", formData.type);
console.log("评查点组数据:", evaluationPointGroups);
if (formData.type) {
// 获取所选评查点类型的组ID
const typeGroup = evaluationPointGroups.find(group =>
group.pid === 0 &&
group.code === formData.type
);
console.log("找到的类型组:", typeGroup);
if (typeGroup) {
// 更新评查点类型组ID
if (formData.evaluation_point_groups_pid !== typeGroup.id) {
const newData = {
...formData,
evaluation_point_groups_pid: typeGroup.id
};
setFormData(newData);
if (onChange) onChange(newData);
}
// 过滤出属于该类型的规则组(pid等于类型组ID的项)
const groups = evaluationPointGroups.filter(group =>
group.pid === typeGroup.id &&
group.is_enabled
);
console.log("过滤后的规则组:", groups);
setFilteredRuleGroups(groups);
// 如果当前选择的规则组不在过滤结果中,重置选择
if (formData.evaluation_point_groups_id &&
!groups.some(group => group.id === formData.evaluation_point_groups_id)) {
console.log("当前选择的规则组不在过滤结果中,重置选择");
const newData = {
...formData,
evaluation_point_groups_id: null
};
setFormData(newData);
if (onChange) onChange(newData);
}
} else {
console.log("未找到对应的类型组");
setFilteredRuleGroups([]);
// 重置评查点类型组ID
if (formData.evaluation_point_groups_pid !== null) {
const newData = {
...formData,
evaluation_point_groups_pid: null
};
setFormData(newData);
if (onChange) onChange(newData);
}
}
} else {
console.log("未选择评查点类型");
setFilteredRuleGroups([]);
// 重置评查点类型组ID
if (formData.evaluation_point_groups_pid !== null) {
const newData = {
...formData,
evaluation_point_groups_pid: null
};
setFormData(newData);
if (onChange) onChange(newData);
}
}
}
}, [formData.type, evaluationPointGroups, formData.evaluation_point_groups_id, formData.evaluation_point_groups_pid, onChange]);
// 根据选择的评查点类型筛选可用的规则组
const filteredRuleGroups = evaluationPointGroups.filter(group =>
formData.evaluation_point_groups_pid &&
group.pid === formData.evaluation_point_groups_pid &&
group.is_enabled
);
// 获取评查点类型选项(pid=0的数据)
const getCheckpointTypeOptions = () => {
if (!evaluationPointGroups || evaluationPointGroups.length === 0) {
console.log("无评查点组数据,使用默认类型选项");
return (
<>
<option value=""></option>
<option value="essential"></option>
<option value="content"></option>
<option value="format"></option>
<option value="legal"></option>
<option value="business"></option>
</>
);
}
const typeGroups = evaluationPointGroups.filter(group => group.pid === 0 && group.is_enabled);
console.log("可用的评查点类型:", typeGroups);
return (
<>
<option value=""></option>
@@ -189,14 +64,15 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
);
};
// 评查点描述与法律依据 展开状态
const handleToggleDescription = () => {
setIsDescExpanded(!isDescExpanded);
};
// 处理表单输入变化
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { id, value } = e.target;
const newData = { ...formData };
// 映射id到表单字段名
switch(id) {
case 'rule-name':
@@ -214,32 +90,32 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
case 'rule-description':
newData.description = value;
break;
case 'law-name':
newData.references_laws.name = value;
case 'law-name':
newData.references_laws = {
...formData.references_laws,
name: value
};
break;
case 'law-content':
newData.references_laws.content = value;
break;
case 'law-articles':
setLawArticlesInput(value);
case 'law-content':
newData.references_laws = {
...formData.references_laws,
content: value
};
break;
case 'evaluation-point-group':
newData.evaluation_point_groups_id = value ? parseInt(value) : null;
break;
case 'checkpoint-type':
newData.type = value;
// 重置规则组选择
newData.evaluation_point_groups_id = null;
// 设置评查点类型组ID
// 处理评查点类型选择
if (value) {
const typeGroup = evaluationPointGroups.find(group =>
group.pid === 0 &&
group.code === value
);
newData.evaluation_point_groups_pid = typeGroup ? typeGroup.id : null;
// 找到选中的类型组
const selectedType = evaluationPointGroups.find(group => group.code === value && group.pid === 0);
if (selectedType) {
newData.evaluation_point_groups_pid = selectedType.id;
}
} else {
newData.evaluation_point_groups_pid = null;
newData.evaluation_point_groups_id = null; // 清空规则组选择
}
break;
}
@@ -251,18 +127,32 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
}
};
const handleLawArticlesChange = (value: string) => {
setLawArticlesInput(value);
const articles = value.split(',')
// 处理条款号输入框变化
const handleLawArticlesChange = (e: React.ChangeEvent<HTMLInputElement>) => {
// 设置临时字符串状态,这样不会触发任何处理
setLawArticlesText(e.target.value);
};
// 处理条款号输入框失去焦点
const handleLawArticlesBlur = () => {
if (!lawArticlesText) return;
// 将输入的文本转换为数组
const articles = lawArticlesText
.split(',')
.map(article => article.trim())
.filter(article => article !== '');
// 创建一个新的引用法律对象,保留现有字段
const referencesLaws = {
...(formData.references_laws || {}),
articles: articles.length > 0 ? articles : []
};
// 更新表单数据
const newData = {
...formData,
references_laws: {
...formData.references_laws,
articles
}
references_laws: referencesLaws
};
setFormData(newData);
@@ -272,6 +162,33 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
}
};
// 初始化条款号文本字段
useEffect(() => {
if (formData.references_laws?.articles && formData.references_laws.articles.length > 0) {
setLawArticlesText(formData.references_laws.articles.join(','));
}
}, [formData.references_laws?.articles]);
// 检查是否需要自动展开描述区域
useEffect(() => {
// 如果描述或法律依据相关字段有值,则自动展开
if (
formData.description ||
formData.references_laws?.name ||
(formData.references_laws?.articles && formData.references_laws.articles.length > 0) ||
formData.references_laws?.content
) {
setIsDescExpanded(true);
}
}, [formData]);
useEffect(() => {
// 可以在这里通知父组件
if (onChange && filteredRuleGroups.length === 1) {
onChange({ evaluation_point_groups_id: filteredRuleGroups[0].id });
}
}, [filteredRuleGroups, onChange]);
return (
<div className="ant-card">
<div className="ant-card-header">
@@ -317,9 +234,19 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
value={formData.risk}
onChange={handleInputChange}
>
<option value="high"></option>
<option value="medium"></option>
<option value="low"></option>
{riskOptions.length > 0 ? (
riskOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
<>
<option value="high"></option>
<option value="medium"></option>
<option value="low"></option>
</>
)}
</select>
<div className="form-tip"></div>
</div>
@@ -330,7 +257,7 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
<select
id="checkpoint-type"
className="form-select"
value={formData.type}
value={getCheckpointTypeCode()}
onChange={handleInputChange}
>
{getCheckpointTypeOptions()}
@@ -343,13 +270,13 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
</label>
<select
id="evaluation-point-group"
className={`form-select ${!formData.type || filteredRuleGroups.length === 0 ? 'bg-gray-100 cursor-not-allowed' : ''}`}
className={`form-select ${!formData.evaluation_point_groups_pid || filteredRuleGroups.length === 0 ? 'bg-gray-100 cursor-not-allowed' : ''}`}
value={formData.evaluation_point_groups_id?.toString() || ""}
onChange={handleInputChange}
disabled={!formData.type || filteredRuleGroups.length === 0}
disabled={!formData.evaluation_point_groups_pid || filteredRuleGroups.length === 0}
>
<option value="">
{!formData.type ? "请先选择评查点类型" :
{!formData.evaluation_point_groups_pid ? "请先选择评查点类型" :
filteredRuleGroups.length === 0 ? "该类型下暂无可用规则组" :
"请选择规则组"}
</option>
@@ -360,7 +287,7 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
))}
</select>
<div className="form-tip">
{!formData.type ? "请先选择评查点类型" :
{!formData.evaluation_point_groups_pid ? "请先选择评查点类型" :
filteredRuleGroups.length === 0 ? "该类型下暂无可用规则组" :
"选择评查点所属的规则组"}
</div>
@@ -420,20 +347,23 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
className="form-input"
placeholder="例如:《中华人民共和国民法典》"
id="law-name"
value={formData.references_laws.name}
value={formData.references_laws?.name || ''}
onChange={handleInputChange}
/>
</div>
<div className="mb-3">
<label className="text-sm text-gray-600 mb-1 block" htmlFor="law-articles"> <span className="text-xs text-gray-400">()</span></label>
<label className="text-sm text-gray-600 mb-1 block" htmlFor="law-articles">
<span className="text-xs text-gray-400">()</span>
</label>
<input
type="text"
className="form-input"
placeholder="例如:第五百八十五条,第五百八十六条"
id="law-articles"
value={lawArticlesInput}
onChange={(e) => handleLawArticlesChange(e.target.value)}
value={lawArticlesText}
onChange={handleLawArticlesChange}
onBlur={handleLawArticlesBlur}
/>
<div className="form-tip"></div>
</div>
@@ -445,7 +375,7 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
style={{ minHeight: '60px' }}
placeholder="例如:当事人应当按照约定全面履行自己的义务。"
id="law-content"
value={formData.references_laws.content}
value={formData.references_laws?.content || ''}
onChange={handleInputChange}
></textarea>
</div>
@@ -459,10 +389,10 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
<div className="text-sm font-medium mb-2"></div>
<div className="law-reference">
<div className="law-reference-title" id="preview-law-name">
{formData.references_laws.name || '《中华人民共和国民法典》'}
{formData.references_laws?.name || '《中华人民共和国民法典》'}
</div>
<div className="law-reference-articles" id="preview-law-articles">
{formData.references_laws.articles.length > 0 ?
{formData.references_laws?.articles && formData.references_laws.articles.length > 0 ?
formData.references_laws.articles.map((article, index) => (
<span key={index} className="law-article">{article}</span>
)) : (
@@ -473,7 +403,7 @@ export function BasicInfo({ onChange, initialData, evaluationPointGroups = [] }:
)}
</div>
<div className="law-reference-content" id="preview-law-content">
{formData.references_laws.content || '当事人应当按照约定全面履行自己的义务。'}
{formData.references_laws?.content || '当事人应当按照约定全面履行自己的义务。'}
</div>
</div>
</div>
+69 -61
View File
@@ -1,6 +1,7 @@
import { useState, KeyboardEvent, FormEvent, useContext, useEffect, useCallback, useRef } from 'react';
import { RuleContext } from '~/contexts/RuleContext';
import { processFieldName } from '~/utils';
import type { PromptType, VLMFieldType } from '~/models/evaluation_points';
// 定义通知函数的类型
type NotifyFn = (data: Record<string, unknown>) => void;
@@ -41,7 +42,7 @@ interface RegexField {
interface VlmField {
name: string;
type: string;
type: VLMFieldType | string;
}
interface PromptTemplate {
@@ -72,9 +73,16 @@ interface ExtractionSettingsProps {
fields?: RegexField[];
};
};
promptTypeOptions?: Array<{value: string, label: string}>;
vlmFieldTypeOptions?: Array<{value: string, label: string}>;
}
export function ExtractionSettings({ onChange, initialData }: ExtractionSettingsProps) {
export function ExtractionSettings({
onChange,
initialData,
promptTypeOptions = [],
vlmFieldTypeOptions = []
}: ExtractionSettingsProps) {
const ruleContext = useContext(RuleContext);
const lastUpdateTimeRef = useRef(0); // 添加一个ref来记录上次更新时间
const lastEventFieldsRef = useRef<string[]>([]);
@@ -884,6 +892,62 @@ export function ExtractionSettings({ onChange, initialData }: ExtractionSettings
setSelectedFieldType(e.currentTarget.value);
};
// 在渲染选择模态字段类型的下拉列表时使用vlmFieldTypeOptions
const renderVlmFieldTypeSelect = (field: string, index: number) => {
return (
<select
className="form-select"
value={selectedFieldType}
onChange={handleFieldTypeChange}
>
{vlmFieldTypeOptions.length > 0 ? (
vlmFieldTypeOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
// 默认选项,如果没有提供字段类型选项
<>
<option value="default"></option>
<option value="currency"></option>
<option value="print"></option>
<option value="seal"></option>
<option value="cross-seal"></option>
<option value="english"></option>
<option value="number"></option>
<option value="handwriting"></option>
</>
)}
</select>
);
};
// 在渲染提示词类型的选择器时使用promptTypeOptions
const renderPromptTypeSelect = (type: string, promptType: 'llm' | 'vlm') => {
return (
<select
className="form-select"
value={type}
onChange={(e) => handlePromptTypeChange(e, promptType)}
>
{promptTypeOptions.length > 0 ? (
promptTypeOptions.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))
) : (
// 默认选项,如果没有提供提示词类型选项
<>
<option value="system">使</option>
<option value="custom">使</option>
</>
)}
</select>
);
};
return (
<div className="ant-card">
<div className="ant-card-header">
@@ -970,28 +1034,7 @@ export function ExtractionSettings({ onChange, initialData }: ExtractionSettings
</label>
<div className="flex items-center mb-2" id="llm-prompt-settings">
<label className="inline-flex items-center mr-6">
<input
type="radio"
name="llm-prompt-type"
value="system"
checked={promptType.llm === 'system'}
onChange={(e) => handlePromptTypeChange(e, 'llm')}
className="form-radio"
/>
<span className="ml-2">使</span>
</label>
<label className="inline-flex items-center">
<input
type="radio"
name="llm-prompt-type"
value="custom"
checked={promptType.llm === 'custom'}
onChange={(e) => handlePromptTypeChange(e, 'llm')}
className="form-radio"
/>
<span className="ml-2">使</span>
</label>
{renderPromptTypeSelect(promptType.llm, 'llm')}
</div>
<div
@@ -1084,21 +1127,7 @@ export function ExtractionSettings({ onChange, initialData }: ExtractionSettings
onChange={(e) => handleFieldInputChange(e, 'vlm')}
onKeyDown={(e) => handleKeyDown(e, 'vlm')}
/>
<select
className="form-select mr-2"
id="field-type-vlm"
value={selectedFieldType}
onChange={handleFieldTypeChange}
>
<option value="default"></option>
<option value="seal"></option>
<option value="cross-seal"></option>
<option value="handwriting"></option>
<option value="print"></option>
<option value="english"></option>
<option value="number"></option>
<option value="currency"></option>
</select>
{renderVlmFieldTypeSelect(inputValue.vlm, 0)}
<button
className="ant-btn ant-btn-default"
id="add-field-btn-vlm"
@@ -1143,28 +1172,7 @@ export function ExtractionSettings({ onChange, initialData }: ExtractionSettings
</label>
<div className="flex items-center mb-2" id="multimodal-prompt-settings">
<label className="inline-flex items-center mr-6">
<input
type="radio"
name="multimodal-prompt-type"
value="system"
checked={promptType.vlm === 'system'}
onChange={(e) => handlePromptTypeChange(e, 'vlm')}
className="form-radio"
/>
<span className="ml-2">使</span>
</label>
<label className="inline-flex items-center">
<input
type="radio"
name="multimodal-prompt-type"
value="custom"
checked={promptType.vlm === 'custom'}
onChange={(e) => handlePromptTypeChange(e, 'vlm')}
className="form-radio"
/>
<span className="ml-2">使</span>
</label>
{renderPromptTypeSelect(promptType.vlm, 'vlm')}
</div>
<div
className="bg-gray-50 p-2 rounded text-xs text-gray-600 mb-2"
+30 -1
View File
@@ -2,6 +2,13 @@ import React, { useState, useEffect, useContext, useCallback, useRef } from 'rea
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;
@@ -38,9 +45,31 @@ interface ReviewSettingsProps {
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 }: ReviewSettingsProps) {
export function ReviewSettings({
onChange,
initialData,
ruleTypeOptions = [],
logicTypeOptions = [],
logicOperatorOptions = [],
compareMethodOptions = [],
formatTypeOptions = [],
comparisonOperatorOptions = [],
matchTypeOptions = [],
suggestionMessageTypeOptions = [],
postActionOptions = []
}: ReviewSettingsProps) {
const [rules, setRules] = useState<RuleType[]>([
{ id: '1', type: '', config: {} }
]);