1250 lines
36 KiB
TypeScript
1250 lines
36 KiB
TypeScript
import { postgrestGet, postgrestPost, postgrestPut, postgrestDelete, type PostgrestParams } from '../postgrest-client';
|
||
import { apiRequest } from '../axios-client';
|
||
import { formatDate } from '../../utils';
|
||
import {
|
||
getEvaluationPointGroup,
|
||
getEvaluationPointGroupChildren,
|
||
getEvaluationPointGroupsByDocumentTypes
|
||
} from './rule-groups';
|
||
|
||
/**
|
||
* 从不同格式的 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;
|
||
}
|
||
|
||
|
||
/**
|
||
* 评查点列表查询参数
|
||
*/
|
||
export interface RulesQueryParams {
|
||
page?: number;
|
||
pageSize?: number;
|
||
ruleType?: string; // 评查点类型ID (一级分组ID)
|
||
groupId?: string; // 规则组ID (二级分组ID)
|
||
risk?: '高' | '中' | '低'; // 风险等级
|
||
isActive?: boolean;
|
||
keyword?: string;
|
||
area?: string; // 地区过滤
|
||
documentAttributeType?: string; // 子类型(原文档属性类型)
|
||
orderBy?: string;
|
||
orderDirection?: 'asc' | 'desc';
|
||
userRole?: string; // 用户角色
|
||
token?: string; // JWT token
|
||
}
|
||
|
||
/**
|
||
* 评查点列表响应数据
|
||
*/
|
||
export interface RulesListResponse {
|
||
rules: Rule[];
|
||
totalCount: number;
|
||
}
|
||
|
||
/**
|
||
* API返回的评查点详情
|
||
*/
|
||
export interface ApiRule {
|
||
id: number;
|
||
code: string;
|
||
name: string;
|
||
area?: string; // 地区
|
||
evaluation_point_groups_id: number | null;
|
||
evaluation_point_groups_pid?: number | null; // 一级分组ID (评查点类型)
|
||
risk: string;
|
||
description: string;
|
||
is_enabled: boolean;
|
||
evaluation_point_groups?: {
|
||
first_name: string;
|
||
second_name: string;
|
||
};
|
||
// 🆕 PostgREST 双连接查询返回的字段
|
||
child_group?: {
|
||
id: number;
|
||
name: string;
|
||
} | null;
|
||
parent_group?: {
|
||
id: number;
|
||
name: string;
|
||
} | null;
|
||
// 以下字段仅在详情接口中返回,列表接口可能不包含
|
||
references_laws?: Record<string, unknown>;
|
||
extraction_config?: {
|
||
type: string;
|
||
fields: string[];
|
||
prompt_setting?: {
|
||
type: string;
|
||
template: string;
|
||
};
|
||
};
|
||
evaluation_config?: {
|
||
rules: Array<{
|
||
id: string;
|
||
type: string;
|
||
config: Record<string, unknown>;
|
||
}>;
|
||
logicType: string;
|
||
};
|
||
pass_message?: string;
|
||
fail_message?: string;
|
||
suggestion_message?: string;
|
||
suggestion_message_type?: string;
|
||
post_action?: string;
|
||
action_config?: string;
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
|
||
/**
|
||
* 评查点详情(前端模型)
|
||
*/
|
||
export interface Rule {
|
||
id: string;
|
||
code: string;
|
||
name: string;
|
||
ruleType: string;
|
||
groupId: string;
|
||
groupName: string;
|
||
priority: string;
|
||
description: string;
|
||
isActive: boolean;
|
||
area?: string;
|
||
documentAttributeType?: string;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
/**
|
||
* 映射API返回的评查点数据到前端模型
|
||
* @param apiRule API返回的评查点数据
|
||
* @returns 前端评查点模型
|
||
*/
|
||
function mapApiRuleToFrontendModel(apiRule: ApiRule): Rule {
|
||
// 风险等级映射到优先级
|
||
const priorityMap: Record<string, string> = {
|
||
'高': 'high',
|
||
'中': 'medium',
|
||
'低': 'low'
|
||
};
|
||
|
||
// 🆕 优先使用 PostgREST 双连接查询返回的数据
|
||
let ruleType = '';
|
||
let groupName = '';
|
||
let groupId = '';
|
||
|
||
if (apiRule.child_group || apiRule.parent_group) {
|
||
// 有 PostgREST 双连接查询数据(外键约束方式)
|
||
groupId = apiRule.child_group?.id.toString() || '';
|
||
groupName = apiRule.child_group?.name || '';
|
||
ruleType = apiRule.parent_group?.name || '';
|
||
} else if (apiRule.evaluation_point_groups) {
|
||
// 兼容旧的查询方式(RPC 函数或手动拼接)
|
||
const isGroupIdEmpty = !apiRule.evaluation_point_groups_id;
|
||
ruleType = isGroupIdEmpty ? '' : (apiRule.evaluation_point_groups.first_name || '');
|
||
groupName = isGroupIdEmpty ? '' : (apiRule.evaluation_point_groups.second_name || `${apiRule.evaluation_point_groups_id}`);
|
||
groupId = isGroupIdEmpty ? '' : apiRule.evaluation_point_groups_id?.toString() || '';
|
||
} else {
|
||
// 没有任何分组信息
|
||
const isGroupIdEmpty = !apiRule.evaluation_point_groups_id;
|
||
groupId = isGroupIdEmpty ? '' : apiRule.evaluation_point_groups_id?.toString() || '';
|
||
}
|
||
|
||
// 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符
|
||
// 例如:'code-mis--mz' --> 'code-mis', 'code-mbs--alsi--gz' --> 'code-mbs--alsi'
|
||
let cleanedCode = apiRule.code || '';
|
||
const lastDoubleHyphenIndex = cleanedCode.lastIndexOf('--');
|
||
if (lastDoubleHyphenIndex !== -1) {
|
||
cleanedCode = cleanedCode.substring(0, lastDoubleHyphenIndex);
|
||
}
|
||
|
||
return {
|
||
id: apiRule.id ? apiRule.id.toString() : '',
|
||
code: cleanedCode,
|
||
name: apiRule.name || '',
|
||
ruleType: ruleType,
|
||
groupId: groupId,
|
||
groupName: groupName,
|
||
priority: priorityMap[apiRule.risk] || 'medium',
|
||
description: apiRule.description || '',
|
||
isActive: apiRule.is_enabled === undefined ? false : apiRule.is_enabled,
|
||
createdAt: apiRule.created_at,
|
||
updatedAt: apiRule.updated_at
|
||
};
|
||
}
|
||
|
||
async function enrichRuleGroupRelation(apiRule: ApiRule, token?: string): Promise<ApiRule> {
|
||
if (!apiRule.evaluation_point_groups_id || apiRule.child_group || apiRule.parent_group) {
|
||
return apiRule;
|
||
}
|
||
|
||
const groupResponse = await getEvaluationPointGroup(String(apiRule.evaluation_point_groups_id), false, token);
|
||
if (groupResponse.error || !groupResponse.data) {
|
||
return apiRule;
|
||
}
|
||
|
||
const childGroup = groupResponse.data;
|
||
const parentGroupId = childGroup.pid && childGroup.pid !== '0' ? Number(childGroup.pid) : null;
|
||
|
||
let parentGroup: ApiRule['parent_group'] = null;
|
||
if (parentGroupId) {
|
||
const parentResponse = await getEvaluationPointGroup(String(parentGroupId), false, token);
|
||
if (!parentResponse.error && parentResponse.data) {
|
||
parentGroup = {
|
||
id: Number(parentResponse.data.id),
|
||
name: parentResponse.data.name
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
...apiRule,
|
||
evaluation_point_groups_pid: parentGroupId ?? apiRule.evaluation_point_groups_pid ?? null,
|
||
child_group: {
|
||
id: Number(childGroup.id),
|
||
name: childGroup.name
|
||
},
|
||
parent_group: parentGroup
|
||
};
|
||
}
|
||
|
||
async function enrichEvaluationPointGroupRelation(
|
||
point: EvaluationPointData,
|
||
token?: string
|
||
): Promise<EvaluationPointData> {
|
||
if (!point.evaluation_point_groups_id) {
|
||
return point;
|
||
}
|
||
|
||
const childResponse = await getEvaluationPointGroup(String(point.evaluation_point_groups_id), false, token);
|
||
if (childResponse.error || !childResponse.data) {
|
||
return point;
|
||
}
|
||
|
||
const childGroup = childResponse.data;
|
||
const parentGroupId = childGroup.pid && childGroup.pid !== '0' ? Number(childGroup.pid) : null;
|
||
|
||
let ruleType = point.ruleType || '';
|
||
if (parentGroupId) {
|
||
const parentResponse = await getEvaluationPointGroup(String(parentGroupId), false, token);
|
||
if (!parentResponse.error && parentResponse.data) {
|
||
ruleType = parentResponse.data.name;
|
||
}
|
||
}
|
||
|
||
return {
|
||
...point,
|
||
evaluation_point_groups_pid: parentGroupId ?? point.evaluation_point_groups_pid ?? null,
|
||
groupId: String(childGroup.id),
|
||
groupName: childGroup.name,
|
||
ruleType
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取评查点列表
|
||
* @param params 查询参数
|
||
* @returns 评查点列表、总数和评查点组
|
||
*/
|
||
export async function getRulesList(params: RulesQueryParams): Promise<{data: RulesListResponse; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
const {
|
||
page = 1,
|
||
pageSize = 10,
|
||
ruleType,
|
||
groupId,
|
||
risk,
|
||
isActive,
|
||
keyword,
|
||
area,
|
||
documentAttributeType,
|
||
userRole,
|
||
token
|
||
} = params;
|
||
|
||
// 🔑 如果没有传递 userRole,尝试从 localStorage 中获取
|
||
let user_role = userRole || '';
|
||
if (!user_role && typeof window !== 'undefined' && window.localStorage) {
|
||
try {
|
||
const userInfoStr = localStorage.getItem('user_info');
|
||
if (userInfoStr) {
|
||
const userInfo = JSON.parse(userInfoStr);
|
||
user_role = userInfo.user_role || userInfo.userRole || '';
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ [getRulesList] 解析 localStorage 用户信息失败:', error);
|
||
}
|
||
}
|
||
|
||
// 🆕 调用后端 FastAPI 接口: GET /api/v3/evaluation-points
|
||
// 构建查询参数
|
||
const queryParams = new URLSearchParams();
|
||
queryParams.append('page', page.toString());
|
||
queryParams.append('page_size', pageSize.toString());
|
||
|
||
// 添加一级分组ID(评查点类型)
|
||
if (ruleType) {
|
||
queryParams.append('evaluation_point_groups_pid', ruleType);
|
||
}
|
||
|
||
// 添加二级分组ID(规则组)
|
||
if (groupId) {
|
||
queryParams.append('evaluation_point_groups_id', groupId);
|
||
}
|
||
|
||
// 添加风险等级筛选
|
||
if (risk) {
|
||
queryParams.append('risk', risk);
|
||
}
|
||
|
||
// 添加启用状态筛选
|
||
if (isActive !== undefined) {
|
||
queryParams.append('is_enabled', isActive.toString());
|
||
}
|
||
|
||
// 添加子类型过滤(原 document_attribute_type)
|
||
if (documentAttributeType) {
|
||
queryParams.append('document_attribute_type', documentAttributeType);
|
||
}
|
||
|
||
// 🔑 添加地区过滤
|
||
// if (user_role === 'provincial_admin') {
|
||
// queryParams.append('area', '省级');
|
||
// } else if (area) {
|
||
// queryParams.append('area', area);
|
||
// }
|
||
|
||
// 添加关键词搜索(后端会同时搜索 name 和 code)
|
||
if (keyword) {
|
||
queryParams.append('name', keyword);
|
||
queryParams.append('code', keyword);
|
||
}
|
||
|
||
// console.log('🔍 [getRulesList] 查询参数:', queryParams.toString());
|
||
|
||
// 调用 FastAPI 接口
|
||
const response = await apiRequest<{
|
||
data: EvaluationPointData[];
|
||
total: number;
|
||
page: number;
|
||
page_size: number;
|
||
}>(
|
||
`/api/v3/evaluation-points?${queryParams.toString()}`,
|
||
{
|
||
method: 'GET',
|
||
headers: {
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
if (response.status === 404) {
|
||
return await getRulesListFromPostgrest(params);
|
||
}
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (!response.data || !Array.isArray(response.data.data)) {
|
||
return { error: '接口返回数据格式不正确', status: 500 };
|
||
}
|
||
|
||
// console.log('✅ [getRulesList] 成功获取评查点列表,共', response.data.total, '条');
|
||
|
||
// 🆕 直接映射后端返回的数据到前端 Rule 模型
|
||
// 后端已包含 ruleType、groupName、groupId 字段,无需额外处理
|
||
const mappedRules: Rule[] = response.data.data.map((point: EvaluationPointData) => {
|
||
// 风险等级映射到优先级
|
||
const priorityMap: Record<string, string> = {
|
||
'高': 'high',
|
||
'中': 'medium',
|
||
'低': 'low',
|
||
'high': 'high',
|
||
'medium': 'medium',
|
||
'low': 'low'
|
||
};
|
||
|
||
// 🔑 清洗评查点编码:移除最后一个 '--' 及其后面的字符
|
||
let cleanedCode = point.code || '';
|
||
const lastDoubleHyphenIndex = cleanedCode.lastIndexOf('--');
|
||
if (lastDoubleHyphenIndex !== -1) {
|
||
cleanedCode = cleanedCode.substring(0, lastDoubleHyphenIndex);
|
||
}
|
||
|
||
return {
|
||
id: point.id?.toString() || '',
|
||
code: cleanedCode,
|
||
name: point.name || '',
|
||
ruleType: point.ruleType || '', // ✅ 后端直接返回
|
||
groupId: point.groupId || '', // ✅ 后端直接返回
|
||
groupName: point.groupName || '', // ✅ 后端直接返回
|
||
priority: priorityMap[point.risk] || 'medium',
|
||
description: point.description || '',
|
||
isActive: point.is_enabled,
|
||
createdAt: formatDate(point.created_at || ''),
|
||
updatedAt: formatDate(point.updated_at || ''),
|
||
area: point.area || '',
|
||
documentAttributeType: point.document_attribute_type || ''
|
||
};
|
||
});
|
||
// console.log('✅ [getRulesList] 成功映射评查点列表数据', response.data.data[0]);
|
||
|
||
return {
|
||
data: {
|
||
rules: mappedRules,
|
||
totalCount: response.data.total
|
||
}
|
||
};
|
||
} catch (error) {
|
||
console.error('❌ 获取评查点列表出错:', error);
|
||
if (error instanceof Error && error.message.includes('404')) {
|
||
return await getRulesListFromPostgrest(params);
|
||
}
|
||
return {
|
||
error: error instanceof Error ? error.message : '获取评查点列表失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
async function getRulesListFromPostgrest(
|
||
params: RulesQueryParams
|
||
): Promise<{data: RulesListResponse; error?: never} | {data?: never; error: string; status?: number}> {
|
||
const {
|
||
page = 1,
|
||
pageSize = 10,
|
||
ruleType,
|
||
groupId,
|
||
risk,
|
||
isActive,
|
||
keyword,
|
||
area,
|
||
documentAttributeType,
|
||
token
|
||
} = params;
|
||
|
||
const postgrestParams: PostgrestParams = {
|
||
select: 'id,code,name,area,evaluation_point_groups_id,evaluation_point_groups_pid,risk,description,is_enabled,document_attribute_type,created_at,updated_at,child_group:evaluation_point_groups!fk_evaluation_points_group(id,name),parent_group:evaluation_point_groups!fk_evaluation_points_parent_group(id,name)',
|
||
order: 'updated_at.desc',
|
||
limit: pageSize,
|
||
offset: (page - 1) * pageSize,
|
||
token,
|
||
filter: {}
|
||
};
|
||
|
||
if (ruleType) postgrestParams.filter!.evaluation_point_groups_pid = `eq.${ruleType}`;
|
||
if (groupId) postgrestParams.filter!.evaluation_point_groups_id = `eq.${groupId}`;
|
||
if (risk) postgrestParams.filter!.risk = `eq.${risk}`;
|
||
if (isActive !== undefined) postgrestParams.filter!.is_enabled = `eq.${isActive}`;
|
||
if (area) postgrestParams.filter!.area = `eq.${area}`;
|
||
if (documentAttributeType) postgrestParams.filter!.document_attribute_type = `eq.${documentAttributeType}`;
|
||
if (keyword?.trim()) {
|
||
const safeKeyword = keyword.trim().replace(/,/g, ' ');
|
||
postgrestParams.or = `name.ilike.*${safeKeyword}*,code.ilike.*${safeKeyword}*`;
|
||
}
|
||
|
||
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 };
|
||
}
|
||
|
||
let rows: ApiRule[] = [];
|
||
if (Array.isArray(response.data)) {
|
||
rows = response.data;
|
||
} else if (response.data && 'data' in response.data && Array.isArray(response.data.data)) {
|
||
rows = response.data.data;
|
||
}
|
||
|
||
return {
|
||
data: {
|
||
rules: rows.map(mapApiRuleToFrontendModel),
|
||
totalCount: rows.length
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 获取单个评查点详情
|
||
* @param id 评查点ID
|
||
* @param token JWT token (可选)
|
||
* @returns 评查点详情
|
||
*/
|
||
export async function getRule(id: string, token?: string): Promise<{data: Rule; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 使用postgrestGet获取单个评查点数据
|
||
const postgrestParams: PostgrestParams = {
|
||
// 使用PostgREST查询参数语法
|
||
filter: { 'id': `eq.${id}` },
|
||
// 使用PostgREST资源嵌入语法获取关联数据
|
||
select: `
|
||
id,
|
||
code,
|
||
name,
|
||
evaluation_point_groups_id,
|
||
risk,
|
||
description,
|
||
is_enabled,
|
||
references_laws,
|
||
extraction_config,
|
||
evaluation_config,
|
||
pass_message,
|
||
fail_message,
|
||
suggestion_message,
|
||
suggestion_message_type,
|
||
post_action,
|
||
action_config,
|
||
created_at,
|
||
updated_at
|
||
`,
|
||
token
|
||
};
|
||
|
||
// 获取评查点详情 - 使用正确的PostgREST格式
|
||
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 };
|
||
}
|
||
|
||
// 处理响应数据(PostgREST可能返回数组或包装对象)
|
||
let apiRule: ApiRule | null = null;
|
||
|
||
if (response.data) {
|
||
// 如果是数组格式(PostgREST标准响应)
|
||
if (Array.isArray(response.data)) {
|
||
apiRule = response.data.length > 0 ? response.data[0] : null;
|
||
}
|
||
// 如果是包装对象格式
|
||
else if ('data' in response.data && response.data.data) {
|
||
apiRule = response.data.data as ApiRule;
|
||
}
|
||
}
|
||
|
||
if (!apiRule) {
|
||
return { error: '评查点不存在', status: 404 };
|
||
}
|
||
|
||
try {
|
||
apiRule = await enrichRuleGroupRelation(apiRule, token);
|
||
} catch (error) {
|
||
console.error('获取分组信息失败:', error);
|
||
// 忽略错误,保持评查点详情主链路可用
|
||
}
|
||
|
||
// 将API返回的数据映射到前端模型
|
||
const rule = mapApiRuleToFrontendModel(apiRule);
|
||
|
||
// 格式化日期字段
|
||
rule.createdAt = formatDate(rule.createdAt);
|
||
rule.updatedAt = formatDate(rule.updatedAt);
|
||
|
||
return { data: rule };
|
||
} catch (error) {
|
||
console.error('获取评查点详情出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '获取评查点详情失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 删除评查点
|
||
* @param id 评查点ID
|
||
* @param token JWT token (可选)
|
||
* @returns 删除结果
|
||
*/
|
||
export async function deleteRule(id: string, token?: string): Promise<{data: {success: boolean; message: string}; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 调用后端 FastAPI 接口: DELETE /api/v3/evaluation-points/{id}
|
||
// 后端会处理所有验证逻辑(检查是否存在、是否有关联数据等)
|
||
const response = await apiRequest<{success: boolean; message: string}>(
|
||
`/api/v3/evaluation-points/${id}`,
|
||
{
|
||
method: 'DELETE',
|
||
headers: {
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (response.data && response.data.success) {
|
||
console.log('✅ deleteRule 成功:', response.data.message);
|
||
return { data: response.data };
|
||
}
|
||
|
||
return { error: '删除评查点失败', status: 500 };
|
||
} catch (error) {
|
||
console.error('❌ 删除评查点出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '删除评查点失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/**
|
||
* 评查点类型
|
||
*/
|
||
export interface RuleType {
|
||
id: string;
|
||
pid?: string;
|
||
code?: string;
|
||
name: string;
|
||
description?: string;
|
||
isEnabled: boolean;
|
||
}
|
||
|
||
/**
|
||
* 规则组
|
||
*/
|
||
export interface RuleGroup {
|
||
id: string;
|
||
name: string;
|
||
description?: string;
|
||
isEnabled: boolean;
|
||
typeId?: string; // 关联的评查点类型ID
|
||
}
|
||
|
||
/**
|
||
* 获取评查点类型列表
|
||
* @param documentTypeIds 文档类型 ID 列表
|
||
* @param token JWT token (可选)
|
||
* @returns 评查点类型列表
|
||
*/
|
||
export async function getRuleTypes(documentTypeIds?: number[], token?: string): Promise<{data: RuleType[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
if (!documentTypeIds || documentTypeIds.length === 0) {
|
||
return { data: [] };
|
||
}
|
||
const response = await getEvaluationPointGroupsByDocumentTypes(documentTypeIds, token);
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
return {
|
||
data: (response.data || []).map(item => ({
|
||
id: item.id,
|
||
pid: item.pid,
|
||
code: item.code || '',
|
||
name: item.name,
|
||
description: item.description || '',
|
||
isEnabled: item.is_enabled
|
||
}))
|
||
};
|
||
} catch (error) {
|
||
console.error('获取评查点类型出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '获取评查点类型失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 根据评查点类型ID获取规则组列表
|
||
* @param typeId 评查点类型ID
|
||
* @param token JWT token (可选)
|
||
* @returns 规则组列表
|
||
*/
|
||
export async function getRuleGroupsByType(typeId: string, token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
if (!typeId || typeId === 'all') {
|
||
return { data: [] };
|
||
}
|
||
|
||
const response = await getEvaluationPointGroupChildren(typeId, { pageSize: 500 }, token);
|
||
if (response.error) {
|
||
if (response.status === 404) {
|
||
return {
|
||
data: [
|
||
{ code: '通用', label: '通用' }
|
||
]
|
||
};
|
||
}
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
const ruleGroups = (response.data || []).map(item => ({
|
||
id: item.id,
|
||
name: item.name,
|
||
description: item.description,
|
||
isEnabled: item.is_enabled,
|
||
code: item.code
|
||
}));
|
||
return { data: ruleGroups };
|
||
} catch (error) {
|
||
console.error('获取规则组出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '获取规则组失败',
|
||
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 || null,
|
||
evaluation_point_groups_pid: apiRule.parent_group?.id || apiRule.evaluation_point_groups_pid || 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;
|
||
}
|
||
|
||
|
||
// 用于评查点输入的接口
|
||
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;
|
||
}
|
||
|
||
|
||
/**
|
||
* 评查点统计信息
|
||
*/
|
||
export interface RuleStatistics {
|
||
total_count: number; // 总评查点数
|
||
enabled_count: number; // 已启用数量
|
||
disabled_count: number; // 已禁用数量
|
||
by_risk: { // 按风险等级分组
|
||
low: number; // 低风险数量
|
||
medium: number; // 中风险数量
|
||
high: number; // 高风险数量
|
||
};
|
||
by_group: Array<{ // 按规则组分组
|
||
group_id: number;
|
||
group_name: string;
|
||
count: number;
|
||
}>;
|
||
}
|
||
|
||
|
||
/**
|
||
* 批量更新评查点启用状态
|
||
* @param ids 评查点ID列表
|
||
* @param is_enabled 启用状态
|
||
* @param token JWT token (可选)
|
||
* @returns 批量更新结果
|
||
*/
|
||
export async function batchUpdateRuleStatus(
|
||
ids: string[],
|
||
is_enabled: boolean,
|
||
token?: string
|
||
): Promise<{
|
||
success: boolean;
|
||
updated_count: number;
|
||
failed_ids: string[];
|
||
errors?: Array<{ id: string; error: string }>;
|
||
}> {
|
||
const failedIds: string[] = [];
|
||
const errors: Array<{ id: string; error: string }> = [];
|
||
let updatedCount = 0;
|
||
|
||
// 逐个验证并更新
|
||
for (const id of ids) {
|
||
try {
|
||
// 执行更新 - 使用 updateEvaluationPoint,只传入 is_enabled 字段
|
||
const updateResult = await updateEvaluationPoint(id, { is_enabled }, token);
|
||
if (updateResult.error) {
|
||
failedIds.push(id);
|
||
errors.push({ id, error: updateResult.error });
|
||
} else {
|
||
updatedCount++;
|
||
}
|
||
} catch (error) {
|
||
failedIds.push(id);
|
||
errors.push({
|
||
id,
|
||
error: error instanceof Error ? error.message : '更新失败'
|
||
});
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: failedIds.length === 0,
|
||
updated_count: updatedCount,
|
||
failed_ids: failedIds,
|
||
errors: errors.length > 0 ? errors : undefined
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 批量删除评查点
|
||
* @param ids 评查点ID列表
|
||
* @param token JWT token (可选)
|
||
* @returns 批量删除结果
|
||
*/
|
||
export async function batchDeleteRules(
|
||
ids: string[],
|
||
token?: string
|
||
): Promise<{
|
||
success: boolean;
|
||
deleted_count: number;
|
||
failed_ids: string[];
|
||
errors?: Array<{ id: string; error: string }>;
|
||
}> {
|
||
const failedIds: string[] = [];
|
||
const errors: Array<{ id: string; error: string }> = [];
|
||
let deletedCount = 0;
|
||
|
||
// 逐个验证并删除
|
||
for (const id of ids) {
|
||
try {
|
||
// 使用增强的 deleteRule 函数(包含关联检查)
|
||
const deleteResult = await deleteRule(id, token);
|
||
if (deleteResult.error) {
|
||
failedIds.push(id);
|
||
errors.push({ id, error: deleteResult.error });
|
||
} else {
|
||
deletedCount++;
|
||
}
|
||
} catch (error) {
|
||
failedIds.push(id);
|
||
errors.push({
|
||
id,
|
||
error: error instanceof Error ? error.message : '删除失败'
|
||
});
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: failedIds.length === 0,
|
||
deleted_count: deletedCount,
|
||
failed_ids: failedIds,
|
||
errors: errors.length > 0 ? errors : undefined
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 完整评查点数据结构(对应前端 EvaluationPoint 类型)
|
||
*/
|
||
export interface EvaluationPointData {
|
||
id?: number;
|
||
name: string;
|
||
code: string;
|
||
risk: string;
|
||
is_enabled: boolean;
|
||
description?: string;
|
||
evaluation_point_groups_id: number | null;
|
||
evaluation_point_groups_pid: number | null;
|
||
// 🆕 后端新增的分组名称字段
|
||
ruleType?: string; // 评查点类型(一级分组名称)
|
||
groupName?: string; // 所属规则组(二级分组名称)
|
||
groupId?: string; // 规则组ID(二级分组ID的字符串形式)
|
||
document_attribute_type?: string; // 文档属性类型
|
||
references_laws: {
|
||
name: string;
|
||
content: string;
|
||
articles: string[];
|
||
};
|
||
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;
|
||
area?: string;
|
||
created_at?: string;
|
||
updated_at?: string;
|
||
}
|
||
|
||
/**
|
||
* 创建完整评查点(调用后端 FastAPI 接口)
|
||
* @param evaluationPointData 完整的评查点数据
|
||
* @param token JWT token (可选)
|
||
* @returns 创建的评查点数据
|
||
*/
|
||
export async function createEvaluationPoint(
|
||
evaluationPointData: Omit<EvaluationPointData, 'id' | 'created_at' | 'updated_at'>,
|
||
token?: string
|
||
): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 调用后端 FastAPI 接口: POST /api/v3/evaluation-points
|
||
const response = await apiRequest<EvaluationPointData>(
|
||
'/api/v3/evaluation-points',
|
||
{
|
||
method: 'POST',
|
||
body: JSON.stringify(evaluationPointData),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (response.data) {
|
||
console.log('✅ createEvaluationPoint 成功:', response.data);
|
||
return { data: response.data };
|
||
}
|
||
|
||
return { error: '创建评查点失败:返回数据格式不正确', status: 500 };
|
||
} catch (error) {
|
||
console.error('❌ 创建评查点出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '创建评查点失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 更新完整评查点(调用后端 FastAPI 接口)
|
||
* @param id 评查点ID
|
||
* @param evaluationPointData 完整的评查点数据(部分更新)
|
||
* @param token JWT token (可选)
|
||
* @returns 更新后的评查点数据
|
||
*/
|
||
export async function updateEvaluationPoint(
|
||
id: string,
|
||
evaluationPointData: Partial<Omit<EvaluationPointData, 'created_at' | 'updated_at'>>,
|
||
token?: string
|
||
): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 调用后端 FastAPI 接口: PUT /api/v3/evaluation-points/{id}
|
||
const response = await apiRequest<EvaluationPointData>(
|
||
`/api/v3/evaluation-points/${id}`,
|
||
{
|
||
method: 'PUT',
|
||
body: JSON.stringify(evaluationPointData),
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (response.data) {
|
||
console.log('✅ updateEvaluationPoint 成功:', response.data);
|
||
return { data: response.data };
|
||
}
|
||
|
||
return { error: '更新评查点失败:返回数据格式不正确', status: 500 };
|
||
} catch (error) {
|
||
console.error('❌ 更新评查点出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '更新评查点失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取完整评查点详情(用于编辑页面)
|
||
* @param id 评查点ID
|
||
* @param token JWT token (可选)
|
||
* @returns 完整的评查点数据
|
||
*/
|
||
export async function getEvaluationPoint(
|
||
id: string,
|
||
token?: string
|
||
): Promise<{data: EvaluationPointData; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 调用后端 FastAPI 接口: GET /api/v3/evaluation-points/{id}
|
||
const response = await apiRequest<EvaluationPointData>(
|
||
`/api/v3/evaluation-points/${id}`,
|
||
{
|
||
method: 'GET',
|
||
headers: {
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (!response.data) {
|
||
return { error: '评查点不存在', status: 404 };
|
||
}
|
||
|
||
const enrichedData = await enrichEvaluationPointGroupRelation(response.data, token);
|
||
|
||
console.log('✅ getEvaluationPoint 成功:', enrichedData);
|
||
return { data: enrichedData };
|
||
} catch (error) {
|
||
console.error('❌ 获取评查点出错:', error);
|
||
return {
|
||
error: error instanceof Error ? error.message : '获取评查点失败',
|
||
status: 500
|
||
};
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 适用属性类型选项
|
||
*/
|
||
export interface AttributeTypeOption {
|
||
code: string;
|
||
label: string;
|
||
}
|
||
|
||
/**
|
||
* 获取适用属性类型列表
|
||
* 从后端获取当前评查点表中所有已使用的 document_attribute_type 去重列表
|
||
* @param token JWT token (可选)
|
||
* @returns 适用属性类型列表
|
||
*/
|
||
export async function getAttributeTypes(
|
||
token?: string
|
||
): Promise<{data: AttributeTypeOption[]; error?: never} | {data?: never; error: string; status?: number}> {
|
||
try {
|
||
// 调用后端 FastAPI 接口: GET /api/v3/evaluation-points/attribute-types
|
||
const response = await apiRequest<{
|
||
types: AttributeTypeOption[];
|
||
}>(
|
||
'/api/v3/evaluation-points/attribute-types',
|
||
{
|
||
method: 'GET',
|
||
headers: {
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
}
|
||
}
|
||
);
|
||
|
||
if (response.error) {
|
||
return { error: response.error, status: response.status };
|
||
}
|
||
|
||
if (!response.data || !Array.isArray(response.data.types)) {
|
||
// 返回默认的属性类型列表
|
||
return {
|
||
data: [
|
||
{ code: 'ALL', label: '通用' }
|
||
]
|
||
};
|
||
}
|
||
|
||
console.log('✅ getAttributeTypes 成功:', response.data.types);
|
||
return { data: response.data.types };
|
||
} catch (error) {
|
||
console.error('❌ 获取适用属性类型列表出错:', error);
|
||
// 出错时返回默认列表,不阻塞用户操作
|
||
return {
|
||
data: [
|
||
{ code: 'ALL', label: '通用' }
|
||
]
|
||
};
|
||
}
|
||
}
|