合并评查点新增代码
This commit is contained in:
@@ -1,6 +1,27 @@
|
||||
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
/**
|
||||
* 从不同格式的 API 响应中提取数据
|
||||
* @param responseData API 响应数据
|
||||
* @returns 提取后的数据或 null
|
||||
*/
|
||||
function extractApiData<T>(responseData: unknown): T | null {
|
||||
if (!responseData) return null;
|
||||
|
||||
// 格式1: { code: number, msg: string, data: T }
|
||||
if (typeof responseData === 'object' && responseData !== null &&
|
||||
'code' in responseData &&
|
||||
'data' in responseData &&
|
||||
(responseData as { data: unknown }).data) {
|
||||
return (responseData as { data: T }).data;
|
||||
}
|
||||
|
||||
// 格式2: 直接是数据对象
|
||||
return responseData as T;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 评查点列表查询参数
|
||||
*/
|
||||
@@ -927,4 +948,509 @@ export async function getRuleGroupsByType(typeId: string): Promise<{data: RuleGr
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 定义提取配置类型
|
||||
interface ExtractionConfigType {
|
||||
llm: {
|
||||
fields: string[];
|
||||
prompt_setting: {
|
||||
type: string;
|
||||
template: string;
|
||||
};
|
||||
};
|
||||
vlm: {
|
||||
fields: Array<string | { name: string; type: string }>;
|
||||
prompt_setting: {
|
||||
type: string;
|
||||
template: string;
|
||||
};
|
||||
};
|
||||
regex: {
|
||||
fields: Array<{ field: string; pattern: string }>;
|
||||
};
|
||||
}
|
||||
|
||||
// 定义转换后的评查点数据类型
|
||||
interface FormattedEvaluationPoint {
|
||||
id: number; // 修改为只接受 number 类型
|
||||
name: string;
|
||||
code: string;
|
||||
risk: string;
|
||||
is_enabled: boolean;
|
||||
description: string;
|
||||
references_laws: Record<string, unknown> | null;
|
||||
evaluation_point_groups_pid: number | null;
|
||||
evaluation_point_groups_id: number | null;
|
||||
extraction_config: ExtractionConfigType;
|
||||
evaluation_config: {
|
||||
logicType: string;
|
||||
customLogic: string;
|
||||
rules: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
config: Record<string, unknown>;
|
||||
}>;
|
||||
};
|
||||
pass_message: string;
|
||||
fail_message: string;
|
||||
suggestion_message: string;
|
||||
suggestion_message_type: string;
|
||||
post_action: string;
|
||||
action_config: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 API 返回的数据格式转换为前端需要的格式
|
||||
* @param apiRule API 返回的评查点数据
|
||||
* @returns 转换后的前端格式数据
|
||||
*/
|
||||
export function convertApiRuleToFormData(apiRule: ApiRule): FormattedEvaluationPoint {
|
||||
// 提取旧格式的字段数据
|
||||
const extractFields = (): ExtractionConfigType => {
|
||||
const fields: ExtractionConfigType = {
|
||||
llm: { fields: [], prompt_setting: { type: 'system', template: '' } },
|
||||
vlm: { fields: [], prompt_setting: { type: 'system', template: '' } },
|
||||
regex: { fields: [] }
|
||||
};
|
||||
|
||||
if (!apiRule.extraction_config) return fields;
|
||||
|
||||
// 处理不同的字段类型
|
||||
if (apiRule.extraction_config.type === 'OCR+LLM' || apiRule.extraction_config.type === 'LLM') {
|
||||
fields.llm.fields = apiRule.extraction_config.fields || [];
|
||||
if (apiRule.extraction_config.prompt_setting) {
|
||||
fields.llm.prompt_setting = {
|
||||
type: apiRule.extraction_config.prompt_setting.type || 'system',
|
||||
template: apiRule.extraction_config.prompt_setting.template || ''
|
||||
};
|
||||
}
|
||||
} else if (apiRule.extraction_config.type === 'VLM') {
|
||||
fields.vlm.fields = apiRule.extraction_config.fields || [];
|
||||
if (apiRule.extraction_config.prompt_setting) {
|
||||
fields.vlm.prompt_setting = {
|
||||
type: apiRule.extraction_config.prompt_setting.type || 'system',
|
||||
template: apiRule.extraction_config.prompt_setting.template || ''
|
||||
};
|
||||
}
|
||||
} else if (apiRule.extraction_config.type === 'REGEX') {
|
||||
// 将字符串字段转换为 regex 格式对象
|
||||
fields.regex.fields = apiRule.extraction_config.fields.map(field => ({
|
||||
field,
|
||||
pattern: ''
|
||||
}));
|
||||
}
|
||||
|
||||
return fields;
|
||||
};
|
||||
|
||||
// 转换后的数据
|
||||
const formattedData: FormattedEvaluationPoint = {
|
||||
id: typeof apiRule.id === 'string' ? parseInt(apiRule.id, 10) : apiRule.id, // 确保 id 是数字
|
||||
name: apiRule.name,
|
||||
code: apiRule.code,
|
||||
risk: apiRule.risk,
|
||||
is_enabled: apiRule.is_enabled,
|
||||
description: apiRule.description,
|
||||
references_laws: apiRule.references_laws,
|
||||
evaluation_point_groups_pid: apiRule.evaluation_point_groups?.first_name ? null : null,
|
||||
evaluation_point_groups_id: apiRule.evaluation_point_groups_id,
|
||||
extraction_config: extractFields(),
|
||||
evaluation_config: {
|
||||
logicType: apiRule.evaluation_config?.logicType || 'and',
|
||||
customLogic: '',
|
||||
rules: apiRule.evaluation_config?.rules || []
|
||||
},
|
||||
pass_message: apiRule.pass_message || '',
|
||||
fail_message: apiRule.fail_message || '',
|
||||
suggestion_message: apiRule.suggestion_message || '',
|
||||
suggestion_message_type: apiRule.suggestion_message_type || 'warning',
|
||||
post_action: apiRule.post_action || 'none',
|
||||
action_config: apiRule.action_config || '',
|
||||
score: 0
|
||||
};
|
||||
|
||||
return formattedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单个评查点数据
|
||||
* @param id 评查点ID
|
||||
* @returns 评查点数据
|
||||
*/
|
||||
export async function getEvaluationPoint(id: number): Promise<{
|
||||
data?: FormattedEvaluationPoint;
|
||||
error?: string;
|
||||
status?: number;
|
||||
}> {
|
||||
try {
|
||||
console.log(`获取评查点数据,ID: ${id}`);
|
||||
|
||||
// 使用 postgrestGet 替代直接调用 fetch
|
||||
const postgrestParams: PostgrestParams = {
|
||||
select: `*`,
|
||||
filter: {
|
||||
'id': `eq.${id}`
|
||||
}
|
||||
};
|
||||
|
||||
const response = await postgrestGet<{code: number; msg: string; data: ApiRule[]} | ApiRule[]>('evaluation_points', postgrestParams);
|
||||
|
||||
if (response.error) {
|
||||
return {
|
||||
error: response.error,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
// 使用 extractApiData 统一处理响应数据
|
||||
const extractedData = extractApiData<ApiRule[]>(response.data);
|
||||
|
||||
if (extractedData && Array.isArray(extractedData) && extractedData.length > 0) {
|
||||
// 转换数据为前端格式
|
||||
const formattedData = convertApiRuleToFormData(extractedData[0]);
|
||||
return { data: formattedData };
|
||||
} else {
|
||||
return {
|
||||
error: '获取数据失败: 返回数据为空',
|
||||
status: 404
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查点数据失败:', error);
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '获取评查点数据失败',
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评查点组数据
|
||||
* @returns 评查点组列表
|
||||
*/
|
||||
export async function getEvaluationPointGroups(): Promise<{
|
||||
data?: Array<{
|
||||
id: number;
|
||||
pid: number;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
is_enabled: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}>;
|
||||
error?: string;
|
||||
status?: number;
|
||||
}> {
|
||||
try {
|
||||
console.log("获取评查点组数据");
|
||||
|
||||
// 使用 postgrestGet 替代直接调用 fetch
|
||||
const postgrestParams: PostgrestParams = {
|
||||
select: `*`
|
||||
};
|
||||
|
||||
// 定义评查点组类型
|
||||
type EvaluationPointGroupType = {
|
||||
id: number;
|
||||
pid: number;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
is_enabled: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
const response = await postgrestGet<{code: number; msg: string; data: EvaluationPointGroupType[]} | EvaluationPointGroupType[]>('evaluation_point_groups', postgrestParams);
|
||||
|
||||
if (response.error) {
|
||||
return {
|
||||
error: response.error,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
// 使用 extractApiData 统一处理响应数据
|
||||
const extractedData = extractApiData<EvaluationPointGroupType[]>(response.data);
|
||||
|
||||
if (extractedData) {
|
||||
// 确保每个组都有 created_at 和 updated_at 字段
|
||||
const currentTime = new Date().toISOString();
|
||||
const formattedGroups = extractedData.map(group => ({
|
||||
...group,
|
||||
created_at: group.created_at || currentTime,
|
||||
updated_at: group.updated_at || currentTime
|
||||
}));
|
||||
|
||||
return { data: formattedGroups };
|
||||
} else {
|
||||
return {
|
||||
error: '获取评查点组数据失败: 返回数据为空',
|
||||
status: 404
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查点组数据失败:', error);
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '获取评查点组数据失败',
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 用于评查点输入的接口
|
||||
interface EvaluationPointInput {
|
||||
id?: number | string;
|
||||
name?: string;
|
||||
code?: string;
|
||||
risk?: string;
|
||||
is_enabled?: boolean;
|
||||
description?: string;
|
||||
references_laws?: Record<string, unknown> | null;
|
||||
evaluation_point_groups_pid?: number | null;
|
||||
evaluation_point_groups_id?: number | null;
|
||||
extraction_config?: {
|
||||
llm?: {
|
||||
fields?: string[];
|
||||
prompt_setting?: {
|
||||
type?: string;
|
||||
template?: string;
|
||||
};
|
||||
};
|
||||
vlm?: {
|
||||
fields?: Array<string | { name: string, type: string }>;
|
||||
prompt_setting?: {
|
||||
type?: string;
|
||||
template?: string;
|
||||
};
|
||||
};
|
||||
regex?: {
|
||||
fields?: Array<{ field: string, pattern: string }>;
|
||||
};
|
||||
};
|
||||
evaluation_config?: {
|
||||
logicType?: string;
|
||||
customLogic?: string;
|
||||
rules?: Array<{
|
||||
id: string;
|
||||
type: string;
|
||||
config: Record<string, unknown>;
|
||||
}>;
|
||||
};
|
||||
pass_message?: string;
|
||||
fail_message?: string;
|
||||
suggestion_message?: string;
|
||||
suggestion_message_type?: string;
|
||||
post_action?: string;
|
||||
action_config?: string;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存评查点数据
|
||||
* @param evaluationPoint 评查点数据
|
||||
* @param isEditMode 是否为编辑模式
|
||||
* @returns 保存结果
|
||||
*/
|
||||
export async function saveEvaluationPoint(evaluationPoint: EvaluationPointInput, isEditMode: boolean): Promise<{
|
||||
data?: FormattedEvaluationPoint[];
|
||||
error?: string;
|
||||
status?: number;
|
||||
}> {
|
||||
try {
|
||||
console.log(`${isEditMode ? '更新' : '创建'}评查点数据`);
|
||||
|
||||
// 创建一个符合数据库模式的数据副本
|
||||
const cleanedData = {
|
||||
id: evaluationPoint.id,
|
||||
name: evaluationPoint.name?.trim(),
|
||||
code: evaluationPoint.code?.trim(),
|
||||
risk: evaluationPoint.risk || 'low',
|
||||
is_enabled: evaluationPoint.is_enabled !== undefined ? evaluationPoint.is_enabled : true,
|
||||
description: evaluationPoint.description || '',
|
||||
references_laws: evaluationPoint.references_laws || null,
|
||||
evaluation_point_groups_pid: evaluationPoint.evaluation_point_groups_pid || null,
|
||||
evaluation_point_groups_id: evaluationPoint.evaluation_point_groups_id || null,
|
||||
extraction_config: {
|
||||
llm: {
|
||||
fields: Array.isArray(evaluationPoint.extraction_config?.llm?.fields) ?
|
||||
[...evaluationPoint.extraction_config.llm.fields] : [],
|
||||
prompt_setting: {
|
||||
type: evaluationPoint.extraction_config?.llm?.prompt_setting?.type || 'system',
|
||||
template: evaluationPoint.extraction_config?.llm?.prompt_setting?.template || ''
|
||||
}
|
||||
},
|
||||
vlm: {
|
||||
fields: Array.isArray(evaluationPoint.extraction_config?.vlm?.fields) ?
|
||||
[...evaluationPoint.extraction_config.vlm.fields] : [],
|
||||
prompt_setting: {
|
||||
type: evaluationPoint.extraction_config?.vlm?.prompt_setting?.type || 'system',
|
||||
template: evaluationPoint.extraction_config?.vlm?.prompt_setting?.template || ''
|
||||
}
|
||||
},
|
||||
regex: {
|
||||
fields: Array.isArray(evaluationPoint.extraction_config?.regex?.fields) ?
|
||||
[...evaluationPoint.extraction_config.regex.fields] : []
|
||||
}
|
||||
},
|
||||
evaluation_config: {
|
||||
logicType: evaluationPoint.evaluation_config?.logicType || 'and',
|
||||
customLogic: evaluationPoint.evaluation_config?.customLogic || '',
|
||||
rules: Array.isArray(evaluationPoint.evaluation_config?.rules) ?
|
||||
evaluationPoint.evaluation_config.rules.map((rule) => ({
|
||||
id: rule.id || '1',
|
||||
type: rule.type || '',
|
||||
config: rule.config || {}
|
||||
})) : []
|
||||
},
|
||||
pass_message: evaluationPoint.pass_message || '文档检查通过,符合规范要求。',
|
||||
fail_message: evaluationPoint.fail_message || '文档存在以下问题,请修改后重新提交。',
|
||||
suggestion_message: evaluationPoint.suggestion_message || '',
|
||||
suggestion_message_type: evaluationPoint.suggestion_message_type || 'warning',
|
||||
post_action: evaluationPoint.post_action || 'none',
|
||||
action_config: evaluationPoint.action_config || '',
|
||||
score: evaluationPoint.score !== undefined ? Number(evaluationPoint.score) : 0
|
||||
};
|
||||
|
||||
// 如果是新建模式,则删除id字段
|
||||
if (!isEditMode) {
|
||||
delete cleanedData.id;
|
||||
}
|
||||
|
||||
// 确保配置对象中的规则配置被正确处理
|
||||
if (cleanedData.evaluation_config && Array.isArray(cleanedData.evaluation_config.rules)) {
|
||||
cleanedData.evaluation_config.rules = cleanedData.evaluation_config.rules
|
||||
.filter(rule => rule && rule.type) // 确保规则有类型
|
||||
.map(rule => {
|
||||
// 根据规则类型确保config中有必要的字段
|
||||
const config = { ...rule.config };
|
||||
|
||||
// 移除辅助字段(同rules.new.tsx中的逻辑)
|
||||
switch (rule.type) {
|
||||
case 'exists':
|
||||
if (!Array.isArray(config.fields)) config.fields = [];
|
||||
if (!config.logic) config.logic = 'and';
|
||||
delete config.availableFields;
|
||||
delete config.selectedFields;
|
||||
delete config.existsLogic;
|
||||
break;
|
||||
|
||||
case 'consistency':
|
||||
if (!Array.isArray(config.pairs)) config.pairs = [];
|
||||
if (!config.logic) config.logic = 'and';
|
||||
delete config.availableFields;
|
||||
delete config.logicRelation;
|
||||
delete config.initialSourceField;
|
||||
delete config.initialTargetField;
|
||||
delete config.initialCompareMethod;
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
if (!config.field) config.field = '';
|
||||
if (!config.formatType) config.formatType = 'date';
|
||||
if (!config.parameters) config.parameters = '';
|
||||
delete config.availableFields;
|
||||
delete config.checkField;
|
||||
delete config.formatParams;
|
||||
break;
|
||||
|
||||
case 'logic':
|
||||
if (!Array.isArray(config.conditions)) config.conditions = [];
|
||||
if (!config.logic) config.logic = 'and';
|
||||
delete config.availableFields;
|
||||
delete config.logicRelation;
|
||||
delete config.initialField;
|
||||
delete config.initialOperator;
|
||||
delete config.initialValue;
|
||||
break;
|
||||
|
||||
case 'regex':
|
||||
if (!config.field) config.field = '';
|
||||
if (!config.pattern) config.pattern = '';
|
||||
if (!config.matchType) config.matchType = 'match';
|
||||
delete config.availableFields;
|
||||
delete config.checkField;
|
||||
delete config.regexPattern;
|
||||
break;
|
||||
|
||||
case 'ai':
|
||||
if (!config.model) config.model = 'qwen14b';
|
||||
if (typeof config.temperature !== 'number') config.temperature = 0.1;
|
||||
if (!config.prompt) config.prompt = '';
|
||||
delete config.availableFields;
|
||||
break;
|
||||
|
||||
case 'code':
|
||||
if (!config.language) config.language = 'javascript';
|
||||
if (!config.code) config.code = '';
|
||||
delete config.availableFields;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
id: rule.id,
|
||||
type: rule.type,
|
||||
config
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
console.log("准备发送到API的数据大小:", JSON.stringify(cleanedData).length, "字节");
|
||||
|
||||
// 使用 postgrest-client 替代直接 fetch 调用
|
||||
let response;
|
||||
|
||||
if (isEditMode) {
|
||||
// 更新操作
|
||||
response = await postgrestPut<{code: number; msg: string; data: ApiRule} | ApiRule, typeof cleanedData>(
|
||||
`evaluation_points`,
|
||||
cleanedData,
|
||||
{id: cleanedData.id!}
|
||||
);
|
||||
} else {
|
||||
// 创建操作
|
||||
response = await postgrestPost<{code: number; msg: string; data: ApiRule} | ApiRule, typeof cleanedData>(
|
||||
'evaluation_points',
|
||||
cleanedData
|
||||
);
|
||||
}
|
||||
|
||||
// 处理错误响应
|
||||
if (response.error) {
|
||||
return {
|
||||
error: response.error,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
// 使用 extractApiData 统一处理响应数据
|
||||
const extractedData = extractApiData<ApiRule | ApiRule[]>(response.data);
|
||||
|
||||
if (extractedData) {
|
||||
// 转换数据格式后返回
|
||||
if (Array.isArray(extractedData)) {
|
||||
return {
|
||||
data: extractedData.map(rule => convertApiRuleToFormData(rule))
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: [convertApiRuleToFormData(extractedData)]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
error: `${isEditMode ? '更新' : '创建'}评查点数据失败: 返回数据为空`,
|
||||
status: 404
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("保存评查点失败:", error);
|
||||
return {
|
||||
error: error instanceof Error ? error.message : '保存评查点失败',
|
||||
status: 500
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -51,4 +51,83 @@ export async function uploadFileToServer(
|
||||
} catch (error) {
|
||||
return { success: false, error: error instanceof Error ? error.message : '上传失败' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件到文档审核系统
|
||||
*/
|
||||
export async function uploadDocumentToServer(
|
||||
binaryData: ArrayBuffer,
|
||||
fileName: string,
|
||||
fileType: string,
|
||||
typeId: string | number,
|
||||
priority: string,
|
||||
documentNumber?: string | null,
|
||||
remark?: string | null,
|
||||
isTestDocument: boolean = false
|
||||
): Promise<{
|
||||
success: boolean;
|
||||
result?: {
|
||||
id: number;
|
||||
file_name: string;
|
||||
file_size: number;
|
||||
file_url: string;
|
||||
type_id: number;
|
||||
type_description: string;
|
||||
document_number: string | null;
|
||||
storage_type: string;
|
||||
is_test_document: boolean;
|
||||
remark: string | null;
|
||||
background_processing: boolean;
|
||||
evaluation_level: string;
|
||||
};
|
||||
error: string | null;
|
||||
}> {
|
||||
try {
|
||||
// 创建FormData对象
|
||||
const formData = new FormData();
|
||||
|
||||
// 将二进制数据转换为Blob并添加到FormData
|
||||
const blob = new Blob([binaryData], { type: fileType });
|
||||
formData.append('file', blob, fileName);
|
||||
|
||||
// 将信息添加到一个JSON对象中
|
||||
const uploadInfo = {
|
||||
type_id: Number(typeId),
|
||||
evaluation_level: priority,
|
||||
document_number: documentNumber || null,
|
||||
remark: remark || null,
|
||||
is_test_document: isTestDocument
|
||||
};
|
||||
|
||||
// 添加JSON字符串到FormData
|
||||
formData.append('upload_info', JSON.stringify(uploadInfo));
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch('http://172.16.0.55:8000/admin/documents/upload', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-File-Name': encodeURIComponent(fileName)
|
||||
},
|
||||
body: formData
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`上传失败 (${response.status}): ${errorText}`);
|
||||
return {
|
||||
success: false,
|
||||
error: `上传失败: ${response.status} ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('上传错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : '上传失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -503,7 +503,7 @@ function preprocessData(data: Record<string, unknown>): Record<string, unknown>
|
||||
export async function postgrestPut<T, D extends object>(
|
||||
endpoint: string,
|
||||
data: D,
|
||||
filters?: Record<string, string>
|
||||
filters?: Record<string | number, string | number>
|
||||
): Promise<{data: T; error?: never} | {data?: never; error: string; status?: number}> {
|
||||
try {
|
||||
// 确保端点没有前导斜杠
|
||||
|
||||
@@ -84,7 +84,7 @@ export function Sidebar({ onToggle, collapsed }: SidebarProps) {
|
||||
{
|
||||
id: 'rule-new',
|
||||
title: '新增评查点',
|
||||
path: '/rules/new',
|
||||
path: '/rules-new',
|
||||
icon: 'ri-add-circle-line'
|
||||
},
|
||||
{
|
||||
|
||||
@@ -238,7 +238,7 @@ export function ReviewSettings({
|
||||
useEffect(() => {
|
||||
// 如果已经初始化过,则跳过此次处理
|
||||
if (initializedRef.current) {
|
||||
console.log("ReviewSettings已初始化,跳过后续初始化处理");
|
||||
// console.log("ReviewSettings已初始化,跳过后续初始化处理");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1717,7 +1717,7 @@ export function ReviewSettings({
|
||||
useEffect(() => {
|
||||
// 如果有初始数据,在组件挂载后主动发送一次完整规则配置
|
||||
if (initialDataRef.current && onChange) {
|
||||
console.log("组件挂载后发送初始完整配置");
|
||||
// console.log("组件挂载后发送初始完整配置");
|
||||
setTimeout(() => generateEvaluationConfig(), 100);
|
||||
}
|
||||
}, [generateEvaluationConfig, onChange]);
|
||||
|
||||
+71
-83
@@ -9,6 +9,7 @@ import { FileProgress} from "~/components/ui/FileProgress";
|
||||
import { ProcessingSteps, Step } from "~/components/ui/ProcessingSteps";
|
||||
import uploadStyles from "~/styles/pages/files_upload.css?url";
|
||||
import { getTodayDocuments, getDocumentTypes, getDocumentsStatus, type Document, type DocumentType, DocumentStatus } from "~/api/files/files-upload";
|
||||
import { uploadFileToBinary, uploadDocumentToServer } from "~/api/files";
|
||||
|
||||
export function links() {
|
||||
return [
|
||||
@@ -109,99 +110,34 @@ interface FileUploadResponse {
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
// 将文件转换为二进制数据
|
||||
async function uploadFileToBinary(file: File): Promise<ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
if (reader.result instanceof ArrayBuffer) {
|
||||
resolve(reader.result);
|
||||
} else {
|
||||
reject(new Error('无法将文件转换为二进制格式'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('读取文件失败'));
|
||||
};
|
||||
|
||||
// 读取文件为 ArrayBuffer (二进制格式)
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
|
||||
// 模拟上传文件到服务器的API
|
||||
async function uploadFileToServer(
|
||||
binaryData: ArrayBuffer,
|
||||
fileName: string,
|
||||
fileType: string,
|
||||
documentType: FileType,
|
||||
priority: Priority
|
||||
priority: Priority,
|
||||
documentNumber: string | null,
|
||||
remark: string | null,
|
||||
isTestDocument: boolean
|
||||
): Promise<FileUploadResponse> {
|
||||
// 在实际应用中,这里会使用fetch或axios发送请求到后端API
|
||||
console.log(`[模拟API] 上传文件: ${fileName}, 大小: ${binaryData.byteLength} 字节`);
|
||||
|
||||
// 模拟网络延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
try {
|
||||
// 创建FormData对象,将文件和其他信息一起提交
|
||||
const formData = new FormData();
|
||||
// 使用封装的上传函数
|
||||
const response = await uploadDocumentToServer(
|
||||
binaryData,
|
||||
fileName,
|
||||
fileType,
|
||||
documentType,
|
||||
PRIORITY_TO_CHINESE[priority],
|
||||
documentNumber,
|
||||
remark,
|
||||
isTestDocument
|
||||
);
|
||||
|
||||
// 将二进制数据转换为Blob并添加到FormData
|
||||
const blob = new Blob([binaryData], { type: fileType });
|
||||
formData.append('file', blob, fileName);
|
||||
|
||||
// 将 type_id 和 priority 添加到一个JSON对象中
|
||||
const uploadInfo = {
|
||||
type_id: Number(documentType), // 确保 type_id 是数字值
|
||||
evaluation_level: PRIORITY_TO_CHINESE[priority] // 转换为中文优先级
|
||||
};
|
||||
|
||||
// 添加 JSON 字符串到 FormData
|
||||
formData.append('upload_info', JSON.stringify(uploadInfo));
|
||||
|
||||
// 创建HTTP请求的参数
|
||||
const requestParams = {
|
||||
method: 'POST',
|
||||
url: 'http://172.16.0.55:8000/admin/documents/upload',
|
||||
headers: {
|
||||
// FormData会自动设置Content-Type为multipart/form-data
|
||||
'X-File-Name': encodeURIComponent(fileName)
|
||||
},
|
||||
body: formData
|
||||
};
|
||||
|
||||
// 打印 FormData 内容的正确方式
|
||||
console.log('[模拟API] 请求参数:', {
|
||||
url: requestParams.url,
|
||||
headers: requestParams.headers,
|
||||
uploadInfo, // 直接打印原始对象更有帮助
|
||||
formDataEntries: Array.from(formData.entries()).map(([key, value]) => {
|
||||
if (!(value instanceof Blob)) {
|
||||
return { key, value };
|
||||
}
|
||||
}),
|
||||
fileName
|
||||
});
|
||||
|
||||
// 实际API调用 - 在生产环境中实现
|
||||
const response = await fetch(requestParams.url, {
|
||||
method: requestParams.method,
|
||||
headers: requestParams.headers,
|
||||
body: requestParams.body
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// 获取更多错误信息
|
||||
const errorText = await response.text();
|
||||
console.error(`上传失败 (${response.status}): ${errorText}`);
|
||||
throw new Error(`上传失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[模拟API] 上传错误:', error);
|
||||
return {
|
||||
@@ -318,8 +254,11 @@ export default function FilesUpload() {
|
||||
const { documents, documentTypes } = useLoaderData<LoaderData>();
|
||||
|
||||
// 状态管理
|
||||
const [isTestDocument, setIsTestDocument] = useState(false);
|
||||
const [fileType, setFileType] = useState<FileType | "">("");
|
||||
const [priority, setPriority] = useState<Priority>(Priority.NORMAL);
|
||||
const [documentNumber, setDocumentNumber] = useState<string>("");
|
||||
const [remark, setRemark] = useState<string>("");
|
||||
const [currentFiles, setCurrentFiles] = useState<File[]>([]);
|
||||
const [uploadProgress, setUploadProgress] = useState(0);
|
||||
const [uploadSpeed, setUploadSpeed] = useState("0KB/s");
|
||||
@@ -506,7 +445,10 @@ export default function FilesUpload() {
|
||||
file.name,
|
||||
file.type,
|
||||
fileType as FileType,
|
||||
priority
|
||||
priority,
|
||||
documentNumber || null,
|
||||
remark || null,
|
||||
isTestDocument
|
||||
);
|
||||
|
||||
if (!response.success || !response.result) {
|
||||
@@ -801,6 +743,8 @@ export default function FilesUpload() {
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 格式化文件大小显示
|
||||
const formatFileSize = (bytes: number) => {
|
||||
@@ -920,7 +864,7 @@ export default function FilesUpload() {
|
||||
<Form method="post" encType="multipart/form-data" ref={formRef}>
|
||||
{/* 文件类型选择 */}
|
||||
<Card title={<h3>选择文件类型</h3>} className="mb-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="form-group">
|
||||
<label htmlFor="file-type-select" className="form-label">文件类型 <span className="text-red-500">*</span></label>
|
||||
<select
|
||||
@@ -959,6 +903,37 @@ export default function FilesUpload() {
|
||||
</select>
|
||||
<div className="form-tip">优先级影响文档在队列中的处理顺序</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="docNumber" className="form-label">文档编号</label>
|
||||
<input
|
||||
type="text"
|
||||
id="docNumber"
|
||||
name="docNumber"
|
||||
className="form-input w-full"
|
||||
placeholder="请输入合同编号、许可证号等"
|
||||
value={documentNumber}
|
||||
onChange={(e) => setDocumentNumber(e.target.value)}
|
||||
disabled={uploadStage !== "idle"}
|
||||
/>
|
||||
<div className="form-tip">如无编号可留空,系统将自动识别</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 mt-4">
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="docRemark">
|
||||
备注信息
|
||||
</label>
|
||||
<textarea
|
||||
id="docRemark"
|
||||
name="docRemark"
|
||||
className="form-textarea w-full"
|
||||
placeholder="可输入文档的相关描述或备注信息"
|
||||
rows={2}
|
||||
value={remark}
|
||||
onChange={(e) => setRemark(e.target.value)}
|
||||
disabled={uploadStage !== "idle"}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -975,6 +950,17 @@ export default function FilesUpload() {
|
||||
tipText="支持单个或批量上传,文件格式:PDF、Word、Excel、图片"
|
||||
shouldPreventFileSelect={!fileType}
|
||||
/>
|
||||
<div className="switch-container">
|
||||
<label className="switch" aria-label="标记为测试文档">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={isTestDocument}
|
||||
onChange={e => setIsTestDocument(e.target.checked)}
|
||||
/>
|
||||
<span className="slider"></span>
|
||||
</label>
|
||||
<span>标记为测试文档(不计入正式统计)</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1082,6 +1068,8 @@ export default function FilesUpload() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
</Card>
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import { type MetaFunction, LinksFunction } from "@remix-run/node";
|
||||
import { useState } from "react";
|
||||
import { BasicInfo } from "~/components/rules/new/BasicInfo";
|
||||
import { ExtractionSettings } from "~/components/rules/new/ExtractionSettings";
|
||||
import { ReviewSettings, RuleContext } from "~/components/rules/new/ReviewSettings";
|
||||
import { ActionButtons } from "~/components/rules/new/ActionButtons";
|
||||
import { PageHeader } from "~/components/rules/new/PageHeader";
|
||||
import rulesStyles from "~/styles/rules.css";
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
{ title: "新增评查点 - 中国烟草AI合同及卷宗审核系统" },
|
||||
{
|
||||
name: "description",
|
||||
content: "创建新的评查点,设置规则参数"
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: rulesStyles }
|
||||
];
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "新增评查点"
|
||||
};
|
||||
|
||||
export default function RuleNew() {
|
||||
// 用于保存抽取字段的状态,在抽取设置和评查设置组件之间共享
|
||||
const [extractionFields, setExtractionFields] = useState<string[]>([]);
|
||||
|
||||
const updateExtractionFields = (fields: string[]) => {
|
||||
setExtractionFields(fields);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
// 实现保存逻辑
|
||||
console.log('保存评查点');
|
||||
};
|
||||
|
||||
const handleSaveDraft = () => {
|
||||
// 实现保存草稿逻辑
|
||||
console.log('保存为草稿');
|
||||
};
|
||||
|
||||
const handleExtractionChange = (data: Record<string, unknown>) => {
|
||||
// 使用合并后的所有字段列表
|
||||
if (data.allFields && Array.isArray(data.allFields)) {
|
||||
updateExtractionFields(data.allFields);
|
||||
return;
|
||||
}
|
||||
|
||||
// 旧版本兼容逻辑
|
||||
if (data.fields) {
|
||||
// 尝试获取合并的字段列表
|
||||
if (Array.isArray(data.fields)) {
|
||||
updateExtractionFields(data.fields);
|
||||
} else {
|
||||
const fieldData = data.fields as Record<string, string[]>;
|
||||
const currentMethod = data.extractionMethod as string;
|
||||
|
||||
// 提取当前抽取方法的字段
|
||||
if (fieldData[currentMethod]) {
|
||||
updateExtractionFields(fieldData[currentMethod]);
|
||||
}
|
||||
}
|
||||
} else if (data.regexFields) {
|
||||
// 处理正则字段情况
|
||||
const regexFields = data.regexFields as { id: string; fieldName: string; regex: string }[];
|
||||
const fieldNames = regexFields
|
||||
.map(field => field.fieldName)
|
||||
.filter(name => name.trim() !== '');
|
||||
|
||||
updateExtractionFields(fieldNames);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<RuleContext.Provider value={{ extractionFields, updateExtractionFields }}>
|
||||
<div className="px-4 py-6 bg-white border-b border-gray-200 shadow-sm">
|
||||
<PageHeader
|
||||
title="新增评查点"
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="container py-6">
|
||||
<div className="px-4">
|
||||
<BasicInfo />
|
||||
|
||||
<ExtractionSettings onChange={handleExtractionChange} />
|
||||
|
||||
<ReviewSettings />
|
||||
|
||||
<ActionButtons
|
||||
onSave={handleSave}
|
||||
onSaveDraft={handleSaveDraft}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</RuleContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -36,18 +36,14 @@ import rulesStyles from "~/styles/rules.css?url";
|
||||
import { useNavigate, useLocation } from "@remix-run/react";
|
||||
// 导入评查点模型定义和常量
|
||||
import type {
|
||||
EvaluationPoint,
|
||||
LogicOperator,
|
||||
CompareMethod,
|
||||
FormatType,
|
||||
ComparisonOperator,
|
||||
MatchType,
|
||||
ProgrammingLanguage
|
||||
EvaluationPoint
|
||||
} from "~/models/evaluation_points";
|
||||
import { EVALUATION_OPTIONS } from "~/models/evaluation_points";
|
||||
import type { EvaluationPointGroup } from "~/models/evaluation_point_groups";
|
||||
// 导入RuleContext上下文
|
||||
import { RuleContext } from "~/contexts/RuleContext";
|
||||
// 导入API函数
|
||||
import { getEvaluationPoint, getEvaluationPointGroups, saveEvaluationPoint } from "~/api/evaluation_points/rules";
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
@@ -59,76 +55,14 @@ export const meta: MetaFunction = () => {
|
||||
];
|
||||
};
|
||||
|
||||
export function links() {
|
||||
return [{ rel: "stylesheet", href: rulesStyles }];
|
||||
}
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: "评查点管理"
|
||||
};
|
||||
|
||||
// 添加规则配置接口
|
||||
interface BaseRuleConfig {
|
||||
availableFields?: string[];
|
||||
export function links() {
|
||||
return [{ rel: "stylesheet", href: rulesStyles }];
|
||||
}
|
||||
|
||||
interface ExistsRuleConfig extends BaseRuleConfig {
|
||||
fields: string[];
|
||||
logic: LogicOperator;
|
||||
selectedFields?: string[];
|
||||
existsLogic?: string;
|
||||
}
|
||||
|
||||
interface ConsistencyRuleConfig extends BaseRuleConfig {
|
||||
pairs: Array<{sourceField: string; targetField: string; compareMethod: CompareMethod}>;
|
||||
logic: LogicOperator;
|
||||
logicRelation?: string;
|
||||
initialSourceField?: string;
|
||||
initialTargetField?: string;
|
||||
initialCompareMethod?: string;
|
||||
}
|
||||
|
||||
interface FormatRuleConfig extends BaseRuleConfig {
|
||||
field: string;
|
||||
formatType: FormatType;
|
||||
parameters: string;
|
||||
checkField?: string;
|
||||
formatParams?: string;
|
||||
}
|
||||
|
||||
interface LogicRuleConfig extends BaseRuleConfig {
|
||||
conditions: {
|
||||
field: string;
|
||||
operator: ComparisonOperator;
|
||||
value: string;
|
||||
}[];
|
||||
logic: LogicOperator;
|
||||
logicRelation?: string;
|
||||
initialField?: string;
|
||||
initialOperator?: string;
|
||||
initialValue?: string;
|
||||
}
|
||||
|
||||
interface RegexRuleConfig extends BaseRuleConfig {
|
||||
field: string;
|
||||
pattern: string;
|
||||
matchType: MatchType;
|
||||
checkField?: string;
|
||||
regexPattern?: string;
|
||||
}
|
||||
|
||||
interface AIRuleConfig extends BaseRuleConfig {
|
||||
model: string;
|
||||
temperature: number;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
interface CodeRuleConfig extends BaseRuleConfig {
|
||||
language: ProgrammingLanguage;
|
||||
code: string;
|
||||
}
|
||||
|
||||
type RuleConfig = ExistsRuleConfig | ConsistencyRuleConfig | FormatRuleConfig | LogicRuleConfig | RegexRuleConfig | AIRuleConfig | CodeRuleConfig;
|
||||
|
||||
export default function RuleNew() {
|
||||
const navigate = useNavigate();
|
||||
@@ -235,33 +169,31 @@ export default function RuleNew() {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
console.log(`获取评查点数据,ID: ${id}`);
|
||||
const response = await fetch(`http://172.16.0.119:9000/admin/evaluation_points?id=eq.${id}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.data && data.data[0]) {
|
||||
setFormData(data.data[0]);
|
||||
|
||||
// 初始化extractionFields
|
||||
const extractedFields = extractFieldsFromFormData(data.data[0]);
|
||||
setExtractionFields(extractedFields);
|
||||
|
||||
// 设置编辑模式的实例键
|
||||
setInstanceKey(`edit_${id}_${Date.now()}`);
|
||||
} else {
|
||||
console.error('获取数据失败: 返回数据为空');
|
||||
alert('获取数据失败: 返回数据为空');
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
}
|
||||
|
||||
const response = await getEvaluationPoint(id);
|
||||
|
||||
if (response.error) {
|
||||
console.error('获取评查点数据失败:', response.error);
|
||||
alert(`获取评查点数据失败: ${response.error}`);
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
setFormData(response.data as EvaluationPoint);
|
||||
|
||||
// 初始化extractionFields
|
||||
const extractedFields = extractFieldsFromFormData(response.data as EvaluationPoint);
|
||||
setExtractionFields(extractedFields);
|
||||
|
||||
// 设置编辑模式的实例键
|
||||
setInstanceKey(`edit_${id}_${Date.now()}`);
|
||||
} else {
|
||||
throw new Error(`响应状态: ${response.status}`);
|
||||
console.error('获取数据失败: 返回数据为空');
|
||||
alert('获取数据失败: 返回数据为空');
|
||||
resetFormData();
|
||||
navigate('/rules');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查点数据失败:', error);
|
||||
@@ -281,17 +213,20 @@ export default function RuleNew() {
|
||||
const fetchEvaluationPointGroups = useCallback(async () => {
|
||||
try {
|
||||
console.log("获取评查点组数据");
|
||||
const response = await fetch("http://172.16.0.119:9000/admin/evaluation_point_groups", {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
console.log(response);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setEvaluationPointGroups(data.data);
|
||||
|
||||
const response = await getEvaluationPointGroups();
|
||||
|
||||
if (response.error) {
|
||||
console.error('获取评查点组数据失败:', response.error);
|
||||
alert(`获取评查点组数据失败: ${response.error}\n将使用默认数据`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
setEvaluationPointGroups(response.data);
|
||||
} else {
|
||||
console.error('获取评查点组数据失败: 返回数据为空');
|
||||
alert('获取评查点组数据失败: 返回数据为空\n将使用默认数据');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取评查点组数据失败:', error);
|
||||
@@ -317,198 +252,18 @@ export default function RuleNew() {
|
||||
// 显示保存中状态
|
||||
setIsLoading(true);
|
||||
|
||||
// 根据模式决定是创建还是更新
|
||||
const apiMethod = isEditMode ? 'PATCH' : 'POST';
|
||||
const apiUrl = isEditMode
|
||||
? `http://172.16.0.119:9000/admin/evaluation_points?id=eq.${formData.id}`
|
||||
: 'http://172.16.0.119:9000/admin/evaluation_points';
|
||||
|
||||
try {
|
||||
// 创建一个符合数据库模式的数据副本
|
||||
const cleanedData = {
|
||||
id: formData.id,
|
||||
name: formData.name?.trim(),
|
||||
code: formData.code?.trim(),
|
||||
risk: formData.risk || 'low',
|
||||
is_enabled: formData.is_enabled !== undefined ? formData.is_enabled : true,
|
||||
description: formData.description || '',
|
||||
references_laws: formData.references_laws || null,
|
||||
evaluation_point_groups_pid: formData.evaluation_point_groups_pid || null,
|
||||
evaluation_point_groups_id: formData.evaluation_point_groups_id || null,
|
||||
extraction_config: {
|
||||
llm: {
|
||||
fields: Array.isArray(formData.extraction_config?.llm?.fields) ?
|
||||
[...formData.extraction_config.llm.fields] : [],
|
||||
prompt_setting: {
|
||||
type: formData.extraction_config?.llm?.prompt_setting?.type || 'system',
|
||||
template: formData.extraction_config?.llm?.prompt_setting?.template || ''
|
||||
}
|
||||
},
|
||||
vlm: {
|
||||
fields: Array.isArray(formData.extraction_config?.vlm?.fields) ?
|
||||
[...formData.extraction_config.vlm.fields] : [],
|
||||
prompt_setting: {
|
||||
type: formData.extraction_config?.vlm?.prompt_setting?.type || 'system',
|
||||
template: formData.extraction_config?.vlm?.prompt_setting?.template || ''
|
||||
}
|
||||
},
|
||||
regex: {
|
||||
fields: Array.isArray(formData.extraction_config?.regex?.fields) ?
|
||||
[...formData.extraction_config.regex.fields] : []
|
||||
}
|
||||
},
|
||||
evaluation_config: {
|
||||
logicType: formData.evaluation_config?.logicType || 'and',
|
||||
customLogic: formData.evaluation_config?.customLogic || '',
|
||||
rules: Array.isArray(formData.evaluation_config?.rules) ?
|
||||
formData.evaluation_config.rules.map(rule => ({
|
||||
id: rule.id || '1',
|
||||
type: rule.type || '',
|
||||
config: rule.config || {}
|
||||
})) : []
|
||||
},
|
||||
pass_message: formData.pass_message || '文档检查通过,符合规范要求。',
|
||||
fail_message: formData.fail_message || '文档存在以下问题,请修改后重新提交。',
|
||||
suggestion_message: formData.suggestion_message || '',
|
||||
suggestion_message_type: formData.suggestion_message_type || 'warning',
|
||||
post_action: formData.post_action || 'none',
|
||||
action_config: formData.action_config || '',
|
||||
score: formData.score !== undefined ? Number(formData.score) : 0
|
||||
};
|
||||
|
||||
// 确保rules中的每个配置对象都被正确处理
|
||||
if (cleanedData.evaluation_config && Array.isArray(cleanedData.evaluation_config.rules)) {
|
||||
cleanedData.evaluation_config.rules = cleanedData.evaluation_config.rules
|
||||
.filter(rule => rule && rule.type) // 确保规则有类型
|
||||
.map(rule => {
|
||||
// 根据规则类型确保config中有必要的字段
|
||||
const config = { ...rule.config } as RuleConfig;
|
||||
|
||||
switch (rule.type) {
|
||||
case 'exists':
|
||||
if (!Array.isArray((config as ExistsRuleConfig).fields)) (config as ExistsRuleConfig).fields = [];
|
||||
if (!(config as ExistsRuleConfig).logic) (config as ExistsRuleConfig).logic = 'and';
|
||||
// 删除不必要的字段
|
||||
delete (config as ExistsRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as ExistsRuleConfig).selectedFields;
|
||||
delete (config as ExistsRuleConfig).existsLogic;
|
||||
break;
|
||||
|
||||
case 'consistency':
|
||||
if (!Array.isArray((config as ConsistencyRuleConfig).pairs)) (config as ConsistencyRuleConfig).pairs = [];
|
||||
if (!(config as ConsistencyRuleConfig).logic) (config as ConsistencyRuleConfig).logic = 'and';
|
||||
delete (config as ConsistencyRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as ConsistencyRuleConfig).logicRelation;
|
||||
delete (config as ConsistencyRuleConfig).initialSourceField;
|
||||
delete (config as ConsistencyRuleConfig).initialTargetField;
|
||||
delete (config as ConsistencyRuleConfig).initialCompareMethod;
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
if (!(config as FormatRuleConfig).field) (config as FormatRuleConfig).field = '';
|
||||
if (!(config as FormatRuleConfig).formatType) (config as FormatRuleConfig).formatType = 'date';
|
||||
if (!(config as FormatRuleConfig).parameters) (config as FormatRuleConfig).parameters = '';
|
||||
delete (config as FormatRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as FormatRuleConfig).checkField;
|
||||
delete (config as FormatRuleConfig).formatParams;
|
||||
break;
|
||||
|
||||
case 'logic':
|
||||
if (!Array.isArray((config as LogicRuleConfig).conditions)) (config as LogicRuleConfig).conditions = [];
|
||||
if (!(config as LogicRuleConfig).logic) (config as LogicRuleConfig).logic = 'and';
|
||||
delete (config as LogicRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as LogicRuleConfig).logicRelation;
|
||||
delete (config as LogicRuleConfig).initialField;
|
||||
delete (config as LogicRuleConfig).initialOperator;
|
||||
delete (config as LogicRuleConfig).initialValue;
|
||||
break;
|
||||
|
||||
case 'regex':
|
||||
if (!(config as RegexRuleConfig).field) (config as RegexRuleConfig).field = '';
|
||||
if (!(config as RegexRuleConfig).pattern) (config as RegexRuleConfig).pattern = '';
|
||||
if (!(config as RegexRuleConfig).matchType) (config as RegexRuleConfig).matchType = 'match';
|
||||
delete (config as RegexRuleConfig & {availableFields?: string}).availableFields;
|
||||
delete (config as RegexRuleConfig).checkField;
|
||||
delete (config as RegexRuleConfig).regexPattern;
|
||||
break;
|
||||
|
||||
case 'ai':
|
||||
if (!(config as AIRuleConfig).model) (config as AIRuleConfig).model = 'qwen14b';
|
||||
if (typeof (config as AIRuleConfig).temperature !== 'number') (config as AIRuleConfig).temperature = 0.1;
|
||||
if (!(config as AIRuleConfig).prompt) (config as AIRuleConfig).prompt = '';
|
||||
delete (config as AIRuleConfig & {availableFields?: string}).availableFields;
|
||||
break;
|
||||
|
||||
case 'code':
|
||||
if (!(config as CodeRuleConfig).language) (config as CodeRuleConfig).language = 'javascript';
|
||||
if (!(config as CodeRuleConfig).code) (config as CodeRuleConfig).code = '';
|
||||
delete (config as CodeRuleConfig & {availableFields?: string}).availableFields;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
id: rule.id,
|
||||
type: rule.type,
|
||||
config
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 如果是新建模式,则删除id字段
|
||||
if (!isEditMode) {
|
||||
delete cleanedData.id;
|
||||
}
|
||||
|
||||
// 确保extraction_config和evaluation_config是有效的JSON对象
|
||||
// 通过先序列化再解析来验证
|
||||
try {
|
||||
JSON.parse(JSON.stringify(cleanedData.extraction_config));
|
||||
} catch (e) {
|
||||
throw new Error("extraction_config 格式无效");
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(JSON.stringify(cleanedData.evaluation_config));
|
||||
} catch (e) {
|
||||
throw new Error("evaluation_config 格式无效");
|
||||
}
|
||||
|
||||
// 检查JSON字符串长度,如果太长可能会被截断
|
||||
const jsonData = JSON.stringify(cleanedData);
|
||||
const maxLength = 65536; // 通常PostgreSQL的jsonb列可以存储的最大长度
|
||||
|
||||
if (jsonData.length > maxLength) {
|
||||
throw new Error(`数据大小超过限制 (${jsonData.length} > ${maxLength})`);
|
||||
}
|
||||
|
||||
console.log("准备提交到API的数据:", cleanedData);
|
||||
console.log("JSON数据长度:", jsonData.length);
|
||||
|
||||
// 发送数据到API
|
||||
fetch(apiUrl, {
|
||||
method: apiMethod,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(cleanedData)
|
||||
})
|
||||
.then(async response => {
|
||||
// 尝试解析响应内容
|
||||
let responseData;
|
||||
try {
|
||||
responseData = await response.json();
|
||||
} catch (e) {
|
||||
responseData = { code: 500, msg: "响应解析失败" };
|
||||
// 调用API保存数据
|
||||
saveEvaluationPoint(formData, isEditMode)
|
||||
.then(response => {
|
||||
if (response.error) {
|
||||
console.error("保存评查点失败:", response.error);
|
||||
alert(`保存评查点失败: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据响应码处理不同情况
|
||||
if (responseData.code === 0) {
|
||||
// 成功情况
|
||||
console.log("保存成功:", responseData);
|
||||
|
||||
if (response.data) {
|
||||
// 获取新创建或更新的评查点ID
|
||||
const savedPointId = responseData.data[0]?.id;
|
||||
const savedPointId = response.data[0]?.id;
|
||||
|
||||
if (savedPointId) {
|
||||
// 显示成功消息
|
||||
@@ -522,46 +277,17 @@ export default function RuleNew() {
|
||||
navigate('/rules');
|
||||
}
|
||||
} else {
|
||||
// 处理各种错误情况
|
||||
console.error("API错误:", responseData);
|
||||
|
||||
// 根据错误码显示不同的错误消息
|
||||
switch (responseData.code) {
|
||||
case 400:
|
||||
alert(`参数错误: ${responseData.msg}`);
|
||||
break;
|
||||
case 401:
|
||||
alert(`未授权: ${responseData.msg}`);
|
||||
break;
|
||||
case 403:
|
||||
alert(`禁止访问: ${responseData.msg}`);
|
||||
break;
|
||||
case 404:
|
||||
alert(`未找到资源: ${responseData.msg}`);
|
||||
break;
|
||||
case 409:
|
||||
alert(`资源冲突: ${responseData.msg}`);
|
||||
break;
|
||||
case 500:
|
||||
alert(`服务器错误: ${responseData.msg}`);
|
||||
break;
|
||||
default:
|
||||
alert(`保存失败: ${responseData.msg || '未知错误'}`);
|
||||
}
|
||||
alert(`保存成功,但返回数据为空。正在返回列表页面。`);
|
||||
navigate('/rules');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("网络或解析错误:", error);
|
||||
alert(`网络或解析错误: ${error.message || '未知错误'}`);
|
||||
console.error("保存评查点出错:", error);
|
||||
alert(`保存评查点出错: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("数据处理错误:", error);
|
||||
alert(`数据处理错误: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDraft = () => {
|
||||
@@ -606,7 +332,7 @@ export default function RuleNew() {
|
||||
};
|
||||
|
||||
const handleReviewSettingsChange = (data: Record<string, unknown>) => {
|
||||
console.log("评查设置变更:", data);
|
||||
// console.log("评查设置变更:", data);
|
||||
|
||||
// 检查数据中是否包含evaluation_config对象
|
||||
if (data.evaluation_config) {
|
||||
@@ -1,418 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { redirect, type MetaFunction, type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node';
|
||||
import { useLoaderData, useActionData, Form, useSubmit, useNavigate } from '@remix-run/react';
|
||||
import { Button } from '~/components/ui/Button';
|
||||
import { Card } from '~/components/ui/Card';
|
||||
import { Breadcrumb } from '~/components/layout/Breadcrumb';
|
||||
import type { Rule, RuleType, RulePriority } from '~/models/rule';
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
return [
|
||||
{ title: "中国烟草AI合同及卷宗审核系统 - 评查规则详情" },
|
||||
{ name: "description", content: "评查规则详情编辑页面" }
|
||||
];
|
||||
};
|
||||
|
||||
export const handle = {
|
||||
breadcrumb: '编辑评查点'
|
||||
};
|
||||
|
||||
interface LoaderData {
|
||||
rule: Rule;
|
||||
ruleTypes: { label: string; value: RuleType }[];
|
||||
rulePriorities: { label: string; value: RulePriority }[];
|
||||
groupOptions: { label: string; value: string }[];
|
||||
}
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
const { ruleId } = params;
|
||||
|
||||
// 判断是否为新建规则
|
||||
const isNewRule = ruleId === 'new';
|
||||
|
||||
// 模拟数据,实际项目中应从API获取
|
||||
const rule: Rule = isNewRule ? {
|
||||
id: '',
|
||||
name: '',
|
||||
description: '',
|
||||
content: '',
|
||||
type: 'text',
|
||||
priority: 'medium',
|
||||
groupId: '',
|
||||
groupName: '',
|
||||
isActive: true,
|
||||
createdAt: '',
|
||||
updatedAt: ''
|
||||
} : {
|
||||
id: ruleId,
|
||||
name: '许可证编号格式检查',
|
||||
description: '检查烟草专卖零售许可证编号是否符合"烟零许(年份)序号号"的标准格式',
|
||||
content: '许可证编号应当符合"烟零许(年份)序号号"的标准格式,如"烟零许(2023)12345号"',
|
||||
type: 'regex',
|
||||
priority: 'high',
|
||||
groupId: '1',
|
||||
groupName: '专卖许可证规则组',
|
||||
isActive: true,
|
||||
createdAt: '2023-10-15 09:30',
|
||||
updatedAt: '2023-12-10 14:20'
|
||||
};
|
||||
|
||||
// 规则类型选项
|
||||
const ruleTypes = [
|
||||
{ label: '文本匹配', value: 'text' },
|
||||
{ label: '正则表达式', value: 'regex' },
|
||||
{ label: '数值范围', value: 'range' },
|
||||
{ label: '日期检查', value: 'date' },
|
||||
{ label: 'AI智能检查', value: 'ai' }
|
||||
];
|
||||
|
||||
// 规则优先级选项
|
||||
const rulePriorities = [
|
||||
{ label: '低', value: 'low' },
|
||||
{ label: '中', value: 'medium' },
|
||||
{ label: '高', value: 'high' },
|
||||
{ label: '关键', value: 'critical' }
|
||||
];
|
||||
|
||||
// 规则组选项
|
||||
const groupOptions = [
|
||||
{ label: '专卖许可证规则组', value: '1' },
|
||||
{ label: '合同协议规则组', value: '2' },
|
||||
{ label: '财务票据规则组', value: '3' },
|
||||
{ label: '采购订单规则组', value: '4' },
|
||||
{ label: '销售报表规则组', value: '5' }
|
||||
];
|
||||
|
||||
return Response.json({
|
||||
rule,
|
||||
ruleTypes,
|
||||
rulePriorities,
|
||||
groupOptions
|
||||
});
|
||||
}
|
||||
|
||||
interface ActionData {
|
||||
success?: boolean;
|
||||
errors?: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
content?: string;
|
||||
type?: string;
|
||||
priority?: string;
|
||||
groupId?: string;
|
||||
general?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function action({ request, params }: ActionFunctionArgs) {
|
||||
const { ruleId } = params;
|
||||
const formData = await request.formData();
|
||||
const isNewRule = ruleId === 'new';
|
||||
|
||||
// 获取表单数据
|
||||
const name = formData.get('name')?.toString() || '';
|
||||
const description = formData.get('description')?.toString() || '';
|
||||
const content = formData.get('content')?.toString() || '';
|
||||
const type = formData.get('type')?.toString() || '';
|
||||
const priority = formData.get('priority')?.toString() || '';
|
||||
const groupId = formData.get('groupId')?.toString() || '';
|
||||
const isActive = formData.get('isActive') === 'true';
|
||||
|
||||
// 表单验证
|
||||
const errors: ActionData['errors'] = {};
|
||||
|
||||
if (!name.trim()) {
|
||||
errors.name = '规则名称不能为空';
|
||||
}
|
||||
|
||||
if (!content.trim()) {
|
||||
errors.content = '规则内容不能为空';
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
errors.type = '必须选择规则类型';
|
||||
}
|
||||
|
||||
if (!priority) {
|
||||
errors.priority = '必须选择规则优先级';
|
||||
}
|
||||
|
||||
if (!groupId) {
|
||||
errors.groupId = '必须选择规则所属组';
|
||||
}
|
||||
|
||||
if (Object.keys(errors).length > 0) {
|
||||
return Response.json({ errors });
|
||||
}
|
||||
|
||||
// 模拟API保存操作,实际项目中应调用API
|
||||
try {
|
||||
// 在这里调用API进行保存
|
||||
console.log('保存规则:', {
|
||||
id: isNewRule ? 'new-id' : ruleId,
|
||||
name,
|
||||
description,
|
||||
content,
|
||||
type,
|
||||
priority,
|
||||
groupId,
|
||||
isActive
|
||||
});
|
||||
|
||||
// 成功后重定向到规则列表页
|
||||
return redirect('/rules');
|
||||
} catch (error) {
|
||||
return Response.json({
|
||||
errors: {
|
||||
general: '保存规则失败,请重试'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default function RuleDetail() {
|
||||
const { rule, ruleTypes, rulePriorities, groupOptions } = useLoaderData<typeof loader>();
|
||||
const actionData = useActionData<typeof action>();
|
||||
const navigate = useNavigate();
|
||||
const submit = useSubmit();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: rule.name,
|
||||
description: rule.description,
|
||||
content: rule.content,
|
||||
type: rule.type,
|
||||
priority: rule.priority,
|
||||
groupId: rule.groupId,
|
||||
isActive: rule.isActive
|
||||
});
|
||||
|
||||
const isNewRule = !rule.id;
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleSwitchChange = (name: string, checked: boolean) => {
|
||||
setFormData(prev => ({ ...prev, [name]: checked }));
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
navigate('/rules');
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 使用useSubmit提交表单
|
||||
const formElement = e.currentTarget;
|
||||
submit(formElement, { method: 'post' });
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb
|
||||
items={[
|
||||
{ title: '评查规则', to: '/rules' },
|
||||
{ title: isNewRule ? '新增规则' : '编辑规则', to: `/rules/${rule.id}` }
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-medium">{isNewRule ? '新增评查规则' : '编辑评查规则'}</h2>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<Form method="post" onSubmit={handleSubmit}>
|
||||
{actionData?.errors?.general && (
|
||||
<div className="error-message mb-4">
|
||||
<i className="ri-error-warning-line mr-1"></i>
|
||||
{actionData.errors.general}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="form-section mb-6">
|
||||
<h3 className="form-section-title">基本信息</h3>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group col-span-6">
|
||||
<label htmlFor="name" className="form-label required">规则名称</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
className={`form-input ${actionData?.errors?.name ? 'error' : ''}`}
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
{actionData?.errors?.name && (
|
||||
<div className="form-error">{actionData.errors.name}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group col-span-6">
|
||||
<label htmlFor="groupId" className="form-label required">所属规则组</label>
|
||||
<select
|
||||
id="groupId"
|
||||
name="groupId"
|
||||
className={`form-select ${actionData?.errors?.groupId ? 'error' : ''}`}
|
||||
value={formData.groupId}
|
||||
onChange={handleChange}
|
||||
required
|
||||
>
|
||||
<option value="">选择规则组</option>
|
||||
{groupOptions.map((option: { value: string; label: string }) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
{actionData?.errors?.groupId && (
|
||||
<div className="form-error">{actionData.errors.groupId}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group col-span-12">
|
||||
<label htmlFor="description" className="form-label">规则描述</label>
|
||||
<textarea
|
||||
id="description"
|
||||
name="description"
|
||||
className="form-textarea"
|
||||
rows={3}
|
||||
value={formData.description}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section mb-6">
|
||||
<h3 className="form-section-title">规则设置</h3>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group col-span-4">
|
||||
<label htmlFor="type" className="form-label required">规则类型</label>
|
||||
<select
|
||||
id="type"
|
||||
name="type"
|
||||
className={`form-select ${actionData?.errors?.type ? 'error' : ''}`}
|
||||
value={formData.type}
|
||||
onChange={handleChange}
|
||||
required
|
||||
>
|
||||
<option value="">选择规则类型</option>
|
||||
{ruleTypes.map((option: { value: string; label: string }) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
{actionData?.errors?.type && (
|
||||
<div className="form-error">{actionData.errors.type}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group col-span-4">
|
||||
<label htmlFor="priority" className="form-label required">规则优先级</label>
|
||||
<select
|
||||
id="priority"
|
||||
name="priority"
|
||||
className={`form-select ${actionData?.errors?.priority ? 'error' : ''}`}
|
||||
value={formData.priority}
|
||||
onChange={handleChange}
|
||||
required
|
||||
>
|
||||
<option value="">选择优先级</option>
|
||||
{rulePriorities.map((option: { value: string; label: string }) => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
{actionData?.errors?.priority && (
|
||||
<div className="form-error">{actionData.errors.priority}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-group col-span-4">
|
||||
<label htmlFor="isActive" className="form-label">状态</label>
|
||||
<div className="flex items-center h-10 mt-1">
|
||||
<label className="switch" aria-label="切换规则状态">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="isActive"
|
||||
checked={formData.isActive}
|
||||
onChange={(e) => handleSwitchChange('isActive', e.target.checked)}
|
||||
/>
|
||||
<span className="slider round"></span>
|
||||
</label>
|
||||
<input type="hidden" name="isActive" value={formData.isActive ? 'true' : 'false'} />
|
||||
<span className="ml-2">{formData.isActive ? '启用' : '禁用'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<div className="form-group col-span-12">
|
||||
<label htmlFor="content" className="form-label required">规则内容</label>
|
||||
<textarea
|
||||
id="content"
|
||||
name="content"
|
||||
className={`form-textarea code-editor ${actionData?.errors?.content ? 'error' : ''}`}
|
||||
rows={8}
|
||||
value={formData.content}
|
||||
onChange={handleChange}
|
||||
required
|
||||
></textarea>
|
||||
{actionData?.errors?.content && (
|
||||
<div className="form-error">{actionData.errors.content}</div>
|
||||
)}
|
||||
{formData.type === 'regex' && (
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
<i className="ri-information-line mr-1"></i>
|
||||
输入正则表达式,用于匹配文档内容
|
||||
</div>
|
||||
)}
|
||||
{formData.type === 'ai' && (
|
||||
<div className="text-xs text-gray-500 mt-1">
|
||||
<i className="ri-information-line mr-1"></i>
|
||||
请使用自然语言描述规则检查的要求,AI将自动理解并执行检查
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-section mb-6">
|
||||
<h3 className="form-section-title">测试工具</h3>
|
||||
|
||||
<div className="p-4 bg-gray-50 rounded-md">
|
||||
<div className="mb-4">
|
||||
<label htmlFor="testContent" className="form-label">测试内容</label>
|
||||
<textarea
|
||||
id="testContent"
|
||||
className="form-textarea"
|
||||
rows={4}
|
||||
placeholder="粘贴待测试的文本内容..."
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button type="default">
|
||||
<i className="ri-test-tube-line mr-1"></i>
|
||||
测试规则
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end space-x-2">
|
||||
<Button type="default" onClick={handleCancel}>
|
||||
取消
|
||||
</Button>
|
||||
<Button type="primary">
|
||||
{isNewRule ? '创建规则' : '保存修改'}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -448,7 +448,7 @@ export default function RulesIndex() {
|
||||
width: "10%",
|
||||
render: (_: unknown, record: Rule) => (
|
||||
<div className="operations-cell">
|
||||
<Link to={`/rules/new?id=${record.id}`} className="operation-btn">
|
||||
<Link to={`/rules-new?id=${record.id}`} className="operation-btn">
|
||||
<i className="ri-edit-line"></i> 编辑
|
||||
</Link>
|
||||
<button className="operation-btn" onClick={() => handleCopy(record)}>
|
||||
|
||||
@@ -26,13 +26,46 @@
|
||||
|
||||
/* 表单样式 */
|
||||
.file-upload-page .form-group {
|
||||
@apply mb-4;
|
||||
@apply mb-2;
|
||||
}
|
||||
|
||||
/* 复选框样式 */
|
||||
.file-upload-page .switch-container {
|
||||
@apply flex items-center mt-2;
|
||||
}
|
||||
|
||||
.file-upload-page .switch {
|
||||
@apply relative inline-block w-10 h-5 mr-2;
|
||||
}
|
||||
|
||||
.file-upload-page .switch input {
|
||||
@apply opacity-0 w-0 h-0;
|
||||
}
|
||||
|
||||
.file-upload-page .slider {
|
||||
@apply absolute cursor-pointer inset-0 bg-gray-300 rounded-full transition-all duration-300;
|
||||
}
|
||||
|
||||
.file-upload-page .slider:before {
|
||||
@apply absolute content-[''] h-4 w-4 left-0.5 bottom-0.5 bg-white rounded-full transition-all duration-300;
|
||||
}
|
||||
|
||||
.file-upload-page input:checked + .slider {
|
||||
@apply bg-[var(--primary-color)];
|
||||
}
|
||||
|
||||
.file-upload-page input:checked + .slider:before {
|
||||
@apply transform translate-x-5;
|
||||
}
|
||||
|
||||
.file-upload-page .form-label {
|
||||
@apply block text-sm font-medium text-gray-700 mb-2;
|
||||
}
|
||||
|
||||
.file-upload-page .form-textarea {
|
||||
@apply block w-full px-3 py-2 text-base rounded-md shadow-sm focus:outline-none border;
|
||||
}
|
||||
|
||||
.file-upload-page .form-tip {
|
||||
@apply text-xs text-gray-500 mt-1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user