fix: 1. 重新对齐交叉评查的接口。

2. 确认评查结果的接口对接。 3. 新增评查点适配省级创建的响应数据和其他用户创建的单条响应数据。  4. 文档列表的文档类型通过通用的查询接口查询文档类型。优化加载状态的时机。
This commit is contained in:
2025-12-11 11:16:50 +08:00
parent ba517d7b9c
commit d8bba607fc
18 changed files with 3435 additions and 1086 deletions
+1 -754
View File
@@ -434,254 +434,7 @@ export async function getRule(id: string, token?: string): Promise<{data: Rule;
}
}
/**
* 创建新评查点
* @param ruleData 评查点数据
* @param token JWT token (可选)
* @returns 创建的评查点
*/
export async function createRule(ruleData: Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 1. 验证必填字段
if (!ruleData.name || !ruleData.code) {
return { error: '评查点名称和编码不能为空', status: 400 };
}
// 2. 验证名称长度(1-100字符)
const trimmedName = ruleData.name.trim();
if (trimmedName.length === 0 || trimmedName.length > 100) {
return { error: '评查点名称长度必须在1-100个字符之间', status: 400 };
}
// 3. 验证编码格式(仅允许字母、数字、连字符和下划线)
const trimmedCode = ruleData.code.trim();
if (!/^[a-zA-Z0-9-_]+$/.test(trimmedCode)) {
return { error: '评查点编码只能包含字母、数字、连字符和下划线', status: 400 };
}
// 4. 验证编码唯一性
const existingRulesResponse = await getRulesList({
keyword: trimmedCode,
pageSize: 10,
token
});
if (existingRulesResponse.data && existingRulesResponse.data.rules.length > 0) {
// 精确匹配检查(因为keyword是模糊搜索)
const exactMatch = existingRulesResponse.data.rules.some(r => r.code === trimmedCode);
if (exactMatch) {
return { error: '评查点编码已存在,请使用其他编码', status: 409 };
}
}
// 5. 验证分组ID有效性
if (!ruleData.groupId) {
return { error: '必须选择所属规则组', status: 400 };
}
// 检查分组是否存在
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('/api/postgrest/proxy/evaluation_point_groups', {
filter: { 'id': `eq.${ruleData.groupId}` },
select: 'id,name,pid',
token
});
let groupExists = false;
if (groupResponse.data && 'code' in groupResponse.data && groupResponse.data.data) {
groupExists = Array.isArray(groupResponse.data.data) && groupResponse.data.data.length > 0;
} else if (Array.isArray(groupResponse.data)) {
groupExists = groupResponse.data.length > 0;
}
if (!groupExists) {
return { error: '所选规则组不存在', status: 404 };
}
// 将前端模型转换为API接受的格式
const apiRuleData = {
code: trimmedCode,
name: trimmedName,
evaluation_point_groups_id: parseInt(ruleData.groupId),
risk: ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低',
description: ruleData.description || '',
is_enabled: ruleData.isActive !== undefined ? ruleData.isActive : true,
// 以下是默认值,实际应用中需要根据业务逻辑设置
references_laws: {},
extraction_config: {
type: "OCR+LLM",
fields: []
},
evaluation_config: {
rules: [],
logicType: "and"
},
pass_message: "",
fail_message: "",
suggestion_message: "",
suggestion_message_type: "warning",
post_action: "none",
action_config: ""
};
// 使用postgrestPost创建评查点
const response = await postgrestPost<{code: number; msg: string; data: ApiRule}, typeof apiRuleData>('/api/postgrest/proxy/evaluation_points', apiRuleData, token);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 确保响应数据存在且符合预期格式
if (!response.data || !response.data.data) {
return { error: '接口返回数据格式不正确', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(response.data.data);
return { data: rule };
} catch (error) {
console.error('创建评查点出错:', error);
return {
error: error instanceof Error ? error.message : '创建评查点失败',
status: 500
};
}
}
/**
* 更新评查点
* @param id 评查点ID
* @param ruleData 评查点数据
* @param token JWT token (可选)
* @returns 更新后的评查点
*/
export async function updateRule(id: string, ruleData: Partial<Omit<Rule, 'id' | 'createdAt' | 'updatedAt'>>, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 1. 验证评查点ID有效性
const existingRuleResponse = await getRule(id, token);
if (existingRuleResponse.error || !existingRuleResponse.data) {
return { error: '评查点不存在', status: 404 };
}
// 2. 验证名称长度(如果提供)
if (ruleData.name !== undefined) {
const trimmedName = ruleData.name.trim();
if (trimmedName.length === 0 || trimmedName.length > 100) {
return { error: '评查点名称长度必须在1-100个字符之间', status: 400 };
}
}
// 3. 验证编码格式和唯一性(如果提供)
if (ruleData.code !== undefined) {
const trimmedCode = ruleData.code.trim();
// 验证编码格式
if (!/^[a-zA-Z0-9-_]+$/.test(trimmedCode)) {
return { error: '评查点编码只能包含字母、数字、连字符和下划线', status: 400 };
}
// 验证编码唯一性(排除自身)
const existingRulesResponse = await getRulesList({
keyword: trimmedCode,
pageSize: 10,
token
});
if (existingRulesResponse.data && existingRulesResponse.data.rules.length > 0) {
// 精确匹配检查,排除当前评查点自身
const exactMatch = existingRulesResponse.data.rules.some(r => r.code === trimmedCode && r.id !== id);
if (exactMatch) {
return { error: '评查点编码已被其他评查点使用', status: 409 };
}
}
}
// 4. 验证分组ID有效性(如果提供)
if (ruleData.groupId !== undefined) {
const groupResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string; pid: number}> }>('/api/postgrest/proxy/evaluation_point_groups', {
filter: { 'id': `eq.${ruleData.groupId}` },
select: 'id,name,pid',
token
});
let groupExists = false;
if (groupResponse.data && 'code' in groupResponse.data && groupResponse.data.data) {
groupExists = Array.isArray(groupResponse.data.data) && groupResponse.data.data.length > 0;
} else if (Array.isArray(groupResponse.data)) {
groupExists = groupResponse.data.length > 0;
}
if (!groupExists) {
return { error: '所选规则组不存在', status: 404 };
}
}
// 构建API接受的更新数据
const apiRuleData: Record<string, unknown> = {};
if (ruleData.code !== undefined) {
apiRuleData.code = ruleData.code.trim();
}
if (ruleData.name !== undefined) {
apiRuleData.name = ruleData.name.trim();
}
if (ruleData.groupId !== undefined) {
apiRuleData.evaluation_point_groups_id = parseInt(ruleData.groupId);
}
if (ruleData.priority !== undefined) {
apiRuleData.risk = ruleData.priority === 'high' ? '高' : ruleData.priority === 'medium' ? '中' : '低';
}
if (ruleData.description !== undefined) {
apiRuleData.description = ruleData.description;
}
if (ruleData.isActive !== undefined) {
apiRuleData.is_enabled = ruleData.isActive;
}
// 使用postgrestPut更新评查点 - 使用正确的PostgREST格式
const response = await postgrestPut<{code: number; msg: string; data: ApiRule} | ApiRule[], typeof apiRuleData>('/api/postgrest/proxy/evaluation_points', apiRuleData, { id: parseInt(id) }, token);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 处理响应数据(PostgREST可能返回数组或包装对象)
let updatedRule: ApiRule | null = null;
if (response.data) {
// 如果是数组格式(PostgREST标准响应)
if (Array.isArray(response.data)) {
updatedRule = response.data.length > 0 ? response.data[0] : null;
}
// 如果是包装对象格式
else if ('data' in response.data && response.data.data) {
updatedRule = response.data.data as ApiRule;
}
}
if (!updatedRule) {
return { error: '更新成功但无法获取更新后的数据', status: 500 };
}
// 将API返回的数据映射到前端模型
const rule = mapApiRuleToFrontendModel(updatedRule);
return { data: rule };
} catch (error) {
console.error('更新评查点出错:', error);
return {
error: error instanceof Error ? error.message : '更新评查点失败',
status: 500
};
}
}
/**
* 删除评查点
@@ -722,47 +475,7 @@ export async function deleteRule(id: string, token?: string): Promise<{data: {su
}
}
/**
* 复制评查点
* @param id 评查点ID
* @param token JWT token (可选)
* @returns 新创建的评查点
*/
export async function duplicateRule(id: string, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 1. 获取原评查点详情
const ruleResponse = await getRule(id, token);
if (ruleResponse.error || !ruleResponse.data) {
return { error: ruleResponse.error || '获取评查点详情失败', status: 500 };
}
// 2. 准备新评查点数据
const rule = ruleResponse.data;
// 创建新评查点对象
const newRuleData = {
code: `${rule.code}-COPY`,
name: `${rule.name} (复制)`,
ruleType: rule.ruleType,
groupId: rule.groupId,
groupName: rule.groupName,
priority: rule.priority,
description: rule.description,
isActive: rule.isActive
};
// 3. 创建新评查点
return createRule(newRuleData, token);
} catch (error) {
console.error('复制评查点出错:', error);
return {
error: error instanceof Error ? error.message : '复制评查点失败',
status: 500
};
}
}
/**
* 评查点类型
@@ -1143,137 +856,6 @@ export function convertApiRuleToFormData(apiRule: ApiRule): FormattedEvaluationP
return formattedData;
}
/**
* 获取单个评查点数据
* @param id 评查点ID
* @returns 评查点数据
*/
/**
* 获取格式化的评查点数据(用于列表视图)
* @param id 评查点ID
* @returns 格式化的评查点数据
*/
export async function getFormattedEvaluationPoint(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[]>('/api/postgrest/proxy/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[]>('/api/postgrest/proxy/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 {
@@ -1323,211 +905,6 @@ interface EvaluationPointInput {
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>(
`/api/postgrest/proxy/evaluation_points`,
cleanedData,
{id: cleanedData.id!}
);
} else {
// 创建操作
response = await postgrestPost<{code: number; msg: string; data: ApiRule} | ApiRule, typeof cleanedData>(
'/api/postgrest/proxy/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
};
}
}
/**
* 评查点统计信息
@@ -1548,129 +925,6 @@ export interface RuleStatistics {
}>;
}
/**
* 获取评查点统计信息
* @param token JWT token (可选)
* @returns 评查点统计数据
*/
export async function getRuleStatistics(token?: string): Promise<{data: RuleStatistics; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 1. 获取所有评查点基本数据(不需要分页)
const postgrestParams: PostgrestParams = {
select: 'id,is_enabled,risk,evaluation_point_groups_id',
token
};
const response = await postgrestGet<{code: number; msg: string; data: Array<{
id: number;
is_enabled: boolean;
risk: string;
evaluation_point_groups_id: number | null;
}>}>('/api/postgrest/proxy/evaluation_points', postgrestParams);
// 检查是否有错误响应
if (response.error) {
return { error: response.error, status: response.status };
}
// 提取数据
let evaluationPoints: Array<{
id: number;
is_enabled: boolean;
risk: string;
evaluation_point_groups_id: number | null;
}> = [];
if (response.data && 'code' in response.data && response.data.data) {
if (Array.isArray(response.data.data)) {
evaluationPoints = response.data.data;
}
} else if (Array.isArray(response.data)) {
evaluationPoints = response.data;
}
// 2. 计算基础统计
const totalCount = evaluationPoints.length;
const enabledCount = evaluationPoints.filter(p => p.is_enabled).length;
const disabledCount = totalCount - enabledCount;
// 3. 按风险等级统计
const byRisk = {
low: evaluationPoints.filter(p => p.risk === '低').length,
medium: evaluationPoints.filter(p => p.risk === '中').length,
high: evaluationPoints.filter(p => p.risk === '高').length
};
// 4. 按规则组统计
const groupCountMap = new Map<number, number>();
evaluationPoints.forEach(point => {
if (point.evaluation_point_groups_id !== null) {
const currentCount = groupCountMap.get(point.evaluation_point_groups_id) || 0;
groupCountMap.set(point.evaluation_point_groups_id, currentCount + 1);
}
});
// 5. 获取规则组名称
const groupIds = Array.from(groupCountMap.keys());
const byGroup: Array<{
group_id: number;
group_name: string;
count: number;
}> = [];
if (groupIds.length > 0) {
// 批量查询规则组信息
const groupsParams: PostgrestParams = {
select: 'id,name',
filter: {
'id': `in.(${groupIds.join(',')})`
},
token
};
const groupsResponse = await postgrestGet<{code: number; msg: string; data: Array<{id: number; name: string}>}>('/api/postgrest/proxy/evaluation_point_groups', groupsParams);
let groups: Array<{id: number; name: string}> = [];
if (groupsResponse.data && 'code' in groupsResponse.data && groupsResponse.data.data) {
if (Array.isArray(groupsResponse.data.data)) {
groups = groupsResponse.data.data;
}
} else if (Array.isArray(groupsResponse.data)) {
groups = groupsResponse.data;
}
// 组合统计数据
groups.forEach(group => {
byGroup.push({
group_id: group.id,
group_name: group.name,
count: groupCountMap.get(group.id) || 0
});
});
// 按数量降序排序
byGroup.sort((a, b) => b.count - a.count);
}
// 返回统计结果
const statistics: RuleStatistics = {
total_count: totalCount,
enabled_count: enabledCount,
disabled_count: disabledCount,
by_risk: byRisk,
by_group: byGroup
};
return { data: statistics };
} catch (error) {
console.error('获取评查点统计信息失败:', error);
return {
error: error instanceof Error ? error.message : '获取评查点统计信息失败',
status: 500
};
}
}
/**
* 批量更新评查点启用状态
@@ -1696,13 +950,6 @@ export async function batchUpdateRuleStatus(
// 逐个验证并更新
for (const id of ids) {
try {
// 验证评查点是否存在
const existingRule = await getRule(id, token);
if (existingRule.error || !existingRule.data) {
failedIds.push(id);
errors.push({ id, error: '评查点不存在' });
continue;
}
// 执行更新
const updateResult = await updateRule(id, { isActive: is_enabled }, token);