Files
leaudit-platform-frontend/app/api/evaluation_points/rule-groups.ts
T
LiangShiyong 616f059f1e feat: 1. 完善评查点分组的删除逻辑,会涉及文档类型绑定的一级分组,分组绑定的评查点规则。新增一个评查点分组换绑的。
2. 修复交叉评查的任务中的文档列表的历史文档的查看跳转路径。
3. 修复评查点新增中评查点类型只能显示当前文档类型绑定的这几个一级分组。评查点类型=一级分组。
4. 修复文档列表关于pdf的下载失败的问题。
2025-12-19 00:21:49 +08:00

766 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { postgrestGet, postgrestPut, type PostgrestParams } from '../postgrest-client';
import { apiRequest } from '../axios-client';
import { formatDate } from '../../utils';
/**
* 评查点分组接口
*/
export interface RuleGroup {
id: string;
pid: string;
name: string;
code?: string; // 添加分组编码字段
is_enabled: boolean;
ruleCount?: number; // 评查点数量
children?: RuleGroup[]; // 子分组
createdAt?: string; // 添加创建时间字段
description?: string; // 描述
}
// API请求模型
export interface ApiRuleGroup {
id?: number;
pid: number | null; // 允许 null,表示一级分组
name: string;
code?: string;
description?: string;
is_enabled: boolean;
created_at?: string;
updated_at?: string;
}
// 创建或更新分组请求参数
export interface RuleGroupCreateUpdateDto {
name: string;
code: string;
pid: string | null; // 父分组ID,如果是一级分组则为null或'0'
description?: string;
is_enabled: boolean;
}
// 用于替换代码中的 any 类型
interface ApiResponse<T> {
code: number;
msg: string;
data: T;
}
/** 文档类型信息(用于删除换绑) */
export interface DocTypeInfo {
id: number;
name: string;
}
/** 删除分组响应 */
export interface DeleteGroupResponse {
success: boolean;
message?: string;
deleted_count?: number;
deleted_groups?: number;
deleted_points?: number;
/** 是否需要换绑(仅删除一级分组时) */
need_rebind?: boolean;
/** 关联的评查点数量 */
points_count?: number;
/** 只绑定当前分组的文档类型(换绑时替换) */
single_bound_doc_types?: DocTypeInfo[];
/** 绑定多个分组的文档类型(换绑时移除) */
multi_bound_doc_types?: DocTypeInfo[];
error?: string;
status?: number;
}
/** 换绑响应 */
export interface RebindResponse {
success: boolean;
message: string;
rebind_count: number;
doc_types_updated: number;
}
/**
* 从不同格式的 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 RuleGroupQueryParams {
// 分页参数
page?: number;
pageSize?: number;
// 筛选参数
name?: string; // 名称模糊搜索
code?: string; // 编码模糊搜索
is_enabled?: boolean; // 启用状态
pid?: string | null; // 父级ID (null表示一级分组, 具体ID表示查询该父级的子分组)
// 排序参数
orderBy?: 'created_at' | 'updated_at' | 'name' | 'code';
order?: 'asc' | 'desc';
token?: string;
}
/**
* 获取指定分组的子分组(包含评查点数量统计)
* 🆕 已更新为使用 FastAPI v3 接口
* @param parentId 父分组ID
* @param token JWT token (可选)
* @returns 子分组列表
*/
export async function getChildGroups(parentId: string, token?: string): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 🆕 使用 FastAPI v3 的 getEvaluationPointGroupChildren 获取子分组
const response = await getEvaluationPointGroupChildren(
parentId,
{
pageSize: 1000, // 设置较大的页面大小以获取所有子分组
},
token
);
if (response.error) {
return { error: response.error, status: response.status };
}
// 🆕 FastAPI v3 接口已经返回了 rule_count(已转换为 ruleCount),无需手动统计
return { data: response.data || [] };
} catch (error) {
console.error('获取子分组列表出错:', error);
return {
error: error instanceof Error ? error.message : '获取子分组列表失败',
status: 500
};
}
}
// ========================================
// FastAPI v3 接口函数(新版)
// ========================================
/**
* FastAPI v3 响应数据格式
*/
interface EvaluationPointGroupResponse {
id: number;
pid: number | null;
name: string;
code: string;
description: string | null;
is_enabled: boolean;
created_at: string;
updated_at: string;
rule_count?: number | null;
children?: EvaluationPointGroupResponse[] | null;
}
interface EvaluationPointGroupListResponse {
data: EvaluationPointGroupResponse[];
total: number;
page: number;
page_size: number;
}
/**
* 转换 API 响应数据为前端格式
*/
function convertApiGroupToRuleGroup(apiGroup: EvaluationPointGroupResponse): RuleGroup {
return {
id: String(apiGroup.id),
pid: apiGroup.pid !== null ? String(apiGroup.pid) : '0',
name: apiGroup.name,
code: apiGroup.code,
description: apiGroup.description || undefined,
is_enabled: apiGroup.is_enabled,
createdAt: apiGroup.created_at,
ruleCount: apiGroup.rule_count !== null && apiGroup.rule_count !== undefined ? apiGroup.rule_count : undefined,
children: apiGroup.children ? apiGroup.children.map(convertApiGroupToRuleGroup) : undefined
};
}
/**
* 1. 获取一级分组列表(FastAPI v3)
* @param params 查询参数
* @param token JWT token
* @returns 一级分组列表
*/
export async function getEvaluationPointGroups(
params?: RuleGroupQueryParams,
token?: string
): Promise<{data: RuleGroup[]; totalCount?: number; error?: never} | {data?: never; error: string; status?: number}> {
try {
const {
page = 1,
pageSize = 20,
name,
code,
is_enabled,
pid,
orderBy = 'created_at',
order = 'desc'
} = params || {};
// 构建查询参数
const queryParams = new URLSearchParams();
queryParams.append('page', String(page));
queryParams.append('page_size', String(pageSize));
if (name) queryParams.append('name', name);
if (code) queryParams.append('code', code);
if (is_enabled !== undefined) queryParams.append('is_enabled', String(is_enabled));
// 🔑 添加 pid 参数过滤
// pid=null 或 pid='0' 表示只查询一级分组(pid=0)
// 如果 pid 未定义,则不传该参数(默认查询所有分组)
if (pid !== undefined) {
if (pid === null || pid === '0') {
// FastAPI v3 后端期望 pid=0(数字),不接受字符串 "null"
queryParams.append('pid', '0');
} else {
queryParams.append('pid', String(pid));
}
}
const url = `/api/v3/evaluation-point-groups?${queryParams.toString()}`;
const response = await apiRequest<EvaluationPointGroupListResponse>(url, {
method: 'GET',
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
});
if (response.error) {
console.error('❌ getEvaluationPointGroups 错误:', response.error);
return { error: response.error, status: response.status };
}
if (response.data) {
if (!response.data.data) {
console.error('❌ response.data.data 不存在!完整 response.data:', response.data);
return { error: '返回数据格式不正确:缺少 data 字段', status: 500 };
}
const ruleGroups = response.data.data.map(convertApiGroupToRuleGroup);
return {
data: ruleGroups,
totalCount: response.data.total
};
}
return { error: '获取一级分组列表失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 获取一级分组列表出错:', error);
return {
error: error instanceof Error ? error.message : '获取一级分组列表失败',
status: 500
};
}
}
/**
* 2. 获取所有分组(树形结构)(FastAPI v3)
* @param includeDisabled 是否包含禁用的分组
* @param withRuleCount 是否返回评查点数量
* @param token JWT token
* @returns 树形分组结构
*/
export async function getAllEvaluationPointGroups(
includeDisabled: boolean = false,
withRuleCount: boolean = true,
token?: string
): Promise<{data: RuleGroup[]; error?: never} | {data?: never; error: string; status?: number}> {
try {
const queryParams = new URLSearchParams();
queryParams.append('include_disabled', String(includeDisabled));
queryParams.append('with_rule_count', String(withRuleCount));
const url = `/api/v3/evaluation-point-groups/all?${queryParams.toString()}`;
const response = await apiRequest<EvaluationPointGroupListResponse>(url, {
method: 'GET',
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
});
if (response.error) {
return { error: response.error, status: response.status };
}
// ✅ 后端直接返回数组(不包裹在 { data: [...] } 中)
if (response.data && Array.isArray(response.data)) {
const ruleGroups = response.data.map(convertApiGroupToRuleGroup);
return { data: ruleGroups };
}
// ✅ 后端返回 { data: [...] } 格式(向后兼容)
if (response.data && response.data.data && Array.isArray(response.data.data)) {
const ruleGroups = response.data.data.map(convertApiGroupToRuleGroup);
return { data: ruleGroups };
}
// 返回错误(数据格式不正确)
// console.error('❌ 获取分组数据格式错误:', {
// responseData: response.data,
// isArray: Array.isArray(response.data),
// hasDataField: !!(response.data && 'data' in response.data)
// });
return { error: '获取分组树形结构失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 获取分组树形结构出错:', error);
return {
error: error instanceof Error ? error.message : '获取分组树形结构失败',
status: 500
};
}
}
/**
* 3. 获取单个分组详情(FastAPI v3)
* @param id 分组ID
* @param withRuleCount 是否返回评查点数量
* @param token JWT token
* @returns 分组详情
*/
export async function getEvaluationPointGroup(
id: string,
withRuleCount: boolean = true,
token?: string
): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
try {
const queryParams = new URLSearchParams();
queryParams.append('with_rule_count', String(withRuleCount));
const url = `/api/v3/evaluation-point-groups/${id}?${queryParams.toString()}`;
const response = await apiRequest<EvaluationPointGroupResponse>(url, {
method: 'GET',
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
});
if (response.error) {
return { error: response.error, status: response.status };
}
if (response.data) {
const ruleGroup = convertApiGroupToRuleGroup(response.data);
return { data: ruleGroup };
}
return { error: '获取分组详情失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 获取分组详情出错:', error);
return {
error: error instanceof Error ? error.message : '获取分组详情失败',
status: 500
};
}
}
/**
* 4. 获取子分组列表(FastAPI v3
* @param parentId 父分组ID
* @param params 查询参数
* @param token JWT token
* @returns 子分组列表
*/
export async function getEvaluationPointGroupChildren(
parentId: string,
params?: { page?: number; pageSize?: number; is_enabled?: boolean },
token?: string
): Promise<{data: RuleGroup[]; totalCount?: number; error?: never} | {data?: never; error: string; status?: number}> {
try {
const {
page = 1,
pageSize = 20,
is_enabled
} = params || {};
const queryParams = new URLSearchParams();
queryParams.append('page', String(page));
queryParams.append('page_size', String(pageSize));
if (is_enabled !== undefined) queryParams.append('is_enabled', String(is_enabled));
const url = `/api/v3/evaluation-point-groups/${parentId}/children?${queryParams.toString()}`;
const response = await apiRequest<EvaluationPointGroupListResponse>(url, {
method: 'GET',
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
});
if (response.error) {
return { error: response.error, status: response.status };
}
if (response.data) {
const ruleGroups = response.data.data.map(convertApiGroupToRuleGroup);
return {
data: ruleGroups,
totalCount: response.data.total
};
}
return { error: '获取子分组列表失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 获取子分组列表出错:', error);
return {
error: error instanceof Error ? error.message : '获取子分组列表失败',
status: 500
};
}
}
/**
* 5. 创建评查点分组(FastAPI v3
* @param groupData 分组数据
* @param token JWT token
* @returns 创建后的分组数据
*/
export async function createEvaluationPointGroup(
groupData: RuleGroupCreateUpdateDto,
token?: string
): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 转换为 API 格式
const apiGroup = {
name: groupData.name,
code: groupData.code,
pid: groupData.pid === null || groupData.pid === '0' ? null : Number(groupData.pid),
description: groupData.description || null,
is_enabled: groupData.is_enabled !== undefined ? groupData.is_enabled : true
};
const response = await apiRequest<EvaluationPointGroupResponse>(
'/api/v3/evaluation-point-groups',
{
method: 'POST',
body: JSON.stringify(apiGroup),
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
return { error: response.error, status: response.status };
}
if (response.data) {
const ruleGroup = convertApiGroupToRuleGroup(response.data);
return { data: ruleGroup };
}
return { error: '创建分组失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 创建分组出错:', error);
return {
error: error instanceof Error ? error.message : '创建分组失败',
status: 500
};
}
}
/**
* 6. 更新评查点分组(FastAPI v3
* @param id 分组ID
* @param groupData 更新的分组数据(部分更新)
* @param token JWT token
* @returns 更新后的分组数据
*/
export async function updateEvaluationPointGroup(
id: string,
groupData: Partial<RuleGroupCreateUpdateDto>,
token?: string
): Promise<{data: RuleGroup; error?: never} | {data?: never; error: string; status?: number}> {
try {
// 转换为 API 格式
const apiGroup: Partial<{
name: string;
code: string;
pid: number | null;
description: string | null;
is_enabled: boolean;
}> = {};
if (groupData.name !== undefined) apiGroup.name = groupData.name;
if (groupData.code !== undefined) apiGroup.code = groupData.code;
if (groupData.pid !== undefined) {
apiGroup.pid = groupData.pid === null || groupData.pid === '0' ? null : Number(groupData.pid);
}
if (groupData.description !== undefined) apiGroup.description = groupData.description || null;
if (groupData.is_enabled !== undefined) apiGroup.is_enabled = groupData.is_enabled;
const response = await apiRequest<EvaluationPointGroupResponse>(
`/api/v3/evaluation-point-groups/${id}`,
{
method: 'PUT',
body: JSON.stringify(apiGroup),
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
return { error: response.error, status: response.status };
}
if (response.data) {
const ruleGroup = convertApiGroupToRuleGroup(response.data);
return { data: ruleGroup };
}
return { error: '更新分组失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 更新分组出错:', error);
return {
error: error instanceof Error ? error.message : '更新分组失败',
status: 500
};
}
}
/**
* 7. 删除评查点分组(FastAPI v3
* @param id 分组ID
* @param token JWT token
* @returns 删除结果
*/
export async function deleteEvaluationPointGroup(
id: string,
token?: string
): Promise<DeleteGroupResponse> {
try {
const response = await apiRequest<{
success: boolean;
message: string;
deleted_count?: number;
deleted_groups?: number;
deleted_points?: number;
need_rebind?: boolean;
points_count?: number;
single_bound_doc_types?: DocTypeInfo[];
multi_bound_doc_types?: DocTypeInfo[];
}>(
`/api/v3/evaluation-point-groups/${id}`,
{
method: 'DELETE',
...(token ? { headers: { 'Authorization': `Bearer ${token}` } } : {})
}
);
if (response.error) {
return { success: false, error: response.error, status: response.status };
}
if (response.data) {
return {
success: response.data.success,
message: response.data.message,
deleted_count: response.data.deleted_count || 0,
deleted_groups: response.data.deleted_groups || 0,
deleted_points: response.data.deleted_points || 0,
need_rebind: response.data.need_rebind || false,
points_count: response.data.points_count || 0,
single_bound_doc_types: response.data.single_bound_doc_types || [],
multi_bound_doc_types: response.data.multi_bound_doc_types || []
};
}
return { success: false, error: '删除分组失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 删除分组出错:', error);
return {
success: false,
error: error instanceof Error ? error.message : '删除分组失败',
status: 500
};
}
}
/**
* 7.1 换绑一级分组(FastAPI v3
* @param oldGroupId 要删除的一级分组ID
* @param newParentId 新的一级分组ID
* @param token JWT token
* @returns 换绑结果
*/
export async function rebindEvaluationPointGroup(
oldGroupId: string,
newParentId: string,
token?: string
): Promise<{success: boolean; message?: string; rebind_count?: number; doc_types_updated?: number; error?: never} | {success: false; error: string; status?: number}> {
try {
const response = await apiRequest<RebindResponse>(
`/api/v3/evaluation-point-groups/${oldGroupId}/rebind`,
{
method: 'PUT',
body: JSON.stringify({ new_parent_id: Number(newParentId) }),
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
return { success: false, error: response.error, status: response.status };
}
if (response.data) {
return {
success: response.data.success,
message: response.data.message,
rebind_count: response.data.rebind_count,
doc_types_updated: response.data.doc_types_updated
};
}
return { success: false, error: '换绑失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 换绑分组出错:', error);
return {
success: false,
error: error instanceof Error ? error.message : '换绑分组失败',
status: 500
};
}
}
/**
* 8. 批量更新分组启用状态(FastAPI v3)
* @param ids 分组ID列表
* @param is_enabled 启用状态
* @param token JWT token
* @returns 批量更新结果
*/
export async function batchUpdateEvaluationPointGroupStatus(
ids: string[],
is_enabled: boolean,
token?: string
): Promise<{success: boolean; updated_count?: number; message?: string; error?: never} | {success: false; error: string; status?: number}> {
try {
const requestBody = {
ids: ids.map(id => Number(id)),
is_enabled
};
const response = await apiRequest<{
success: boolean;
updated_count: number;
message: string;
}>(
'/api/v3/evaluation-point-groups/batch/status',
{
method: 'PATCH',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
return { success: false, error: response.error, status: response.status };
}
if (response.data) {
return {
success: response.data.success,
updated_count: response.data.updated_count,
message: response.data.message
};
}
return { success: false, error: '批量更新状态失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 批量更新状态出错:', error);
return {
success: false,
error: error instanceof Error ? error.message : '批量更新状态失败',
status: 500
};
}
}
/**
* 9. 批量删除分组(FastAPI v3
* @param ids 分组ID列表
* @param token JWT token
* @returns 批量删除结果
*/
export async function batchDeleteEvaluationPointGroups(
ids: string[],
token?: string
): Promise<{success: boolean; deleted_groups?: number; deleted_points?: number; message?: string; error?: never} | {success: false; error: string; status?: number}> {
try {
const requestBody = {
ids: ids.map(id => Number(id))
};
const response = await apiRequest<{
success: boolean;
deleted_groups: number;
deleted_points: number;
message: string;
}>(
'/api/v3/evaluation-point-groups/batch',
{
method: 'DELETE',
body: JSON.stringify(requestBody),
headers: {
'Content-Type': 'application/json',
...(token ? { 'Authorization': `Bearer ${token}` } : {})
}
}
);
if (response.error) {
return { success: false, error: response.error, status: response.status };
}
if (response.data) {
return {
success: response.data.success,
deleted_groups: response.data.deleted_groups,
deleted_points: response.data.deleted_points,
message: response.data.message
};
}
return { success: false, error: '批量删除分组失败:返回数据格式不正确', status: 500 };
} catch (error) {
console.error('❌ 批量删除分组出错:', error);
return {
success: false,
error: error instanceof Error ? error.message : '批量删除分组失败',
status: 500
};
}
}