From 7f1a05107f5b21ec3154c4a244add11532c28454 Mon Sep 17 00:00:00 2001 From: Wenyan Date: Tue, 25 Nov 2025 12:16:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(evaluation):=20=E6=A8=A1=E5=9D=971.4=20-?= =?UTF-8?q?=20=E6=96=B0=E5=A2=9E=E8=AF=84=E6=9F=A5=E7=82=B9=E5=88=86?= =?UTF-8?q?=E7=BB=84=E6=89=B9=E9=87=8F=E6=93=8D=E4=BD=9C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要改进 ### 1. 新增 batchUpdateRuleGroupStatus 函数(批量启用/禁用) - ✅ 参数验证(ID列表不为空,每个ID有效) - ✅ 逐个验证分组存在性 - ✅ 逐个执行更新操作 - ✅ 返回详细的操作结果 - updated_count: 成功更新的数量 - failed_ids: 失败的ID列表 - errors: 详细的错误信息(包含ID和错误原因) ### 2. 新增 batchDeleteRuleGroups 函数(批量删除) - ✅ 参数验证(ID列表不为空,每个ID有效) - ✅ 采用安全的阻止删除策略 - ✅ 逐个检查并删除分组 - ✅ 返回详细的操作结果和错误信息 - deleted_count: 成功删除的数量 - failed_ids: 失败的ID列表 - errors: 详细的错误信息(包含子分组/评查点检查结果) ### 3. 批量操作特性 - ✅ **逐个处理**:确保每个分组都能被正确处理 - ✅ **部分成功支持**:即使部分分组操作失败,成功的也会被处理 - ✅ **详细的错误追踪**:记录每个失败的ID及其失败原因 - ✅ **安全性优先**:批量删除继承单个删除的安全检查 ### 4. 返回值结构 ```typescript // 批量更新状态 { success: boolean; // 是否全部成功 updated_count: number; // 成功更新的数量 failed_ids: string[]; // 失败的ID列表 errors?: Array<{ // 详细错误(可选) id: string; error: string; }>; } // 批量删除 { success: boolean; // 是否全部成功 deleted_count: number; // 成功删除的数量 failed_ids: string[]; // 失败的ID列表 errors?: Array<{ // 详细错误(可选) id: string; error: string; details?: { // 删除失败详情 hasChildren?: boolean; hasPoints?: boolean; }; }>; } ``` ## 使用示例 ### 批量启用分组 ```typescript const result = await batchUpdateRuleGroupStatus( ['1', '2', '3'], true, token ); if (result.success) { console.log(`成功启用 ${result.updated_count} 个分组`); } else { console.log(`成功 ${result.updated_count} 个,失败 ${result.failed_ids.length} 个`); result.errors?.forEach(err => { console.log(`分组 ${err.id}: ${err.error}`); }); } ``` ### 批量删除分组 ```typescript const result = await batchDeleteRuleGroups(['1', '2'], token); if (result.success) { console.log(`成功删除 ${result.deleted_count} 个分组`); } else { result.errors?.forEach(err => { if (err.details?.hasChildren) { console.log(`分组 ${err.id} 有子分组,无法删除`); } if (err.details?.hasPoints) { console.log(`分组 ${err.id} 有评查点,无法删除`); } }); } ``` ## 相关文件 - app/api/evaluation_points/rule-groups.ts ## 验收清单 - [x] TypeScript 类型检查通过 - [x] 完整的参数验证 - [x] 支持部分成功场景 - [x] 详细的错误追踪 - [x] 安全的删除策略 Co-Authored-By: Claude --- app/api/evaluation_points/rule-groups.ts | 199 ++++++++++++++++++++++- 1 file changed, 197 insertions(+), 2 deletions(-) diff --git a/app/api/evaluation_points/rule-groups.ts b/app/api/evaluation_points/rule-groups.ts index ee4d4aa..84ba814 100644 --- a/app/api/evaluation_points/rule-groups.ts +++ b/app/api/evaluation_points/rule-groups.ts @@ -937,9 +937,204 @@ async function deleteEvaluationPointsByGroupId(groupId: string, token?: string): return { success: true }; } catch (error) { console.error('删除评查点失败:', error); - return { - success: false, + return { + success: false, error: error instanceof Error ? error.message : '删除评查点失败' }; } +} + +// ==================== 批量操作接口 ==================== + +/** + * 批量更新分组状态(启用/禁用) + * @param ids 分组ID列表 + * @param is_enabled 目标状态 + * @param token JWT token (可选) + * @returns 更新结果 + */ +export async function batchUpdateRuleGroupStatus( + ids: string[], + is_enabled: boolean, + token?: string +): Promise<{ + success: boolean; + updated_count: number; + failed_ids: string[]; + errors?: Array<{ id: string; error: string }>; +}> { + try { + // ========== 1. 参数验证 ========== + + if (!Array.isArray(ids) || ids.length === 0) { + return { + success: false, + updated_count: 0, + failed_ids: [], + errors: [{ id: 'validation', error: 'ID列表不能为空' }] + }; + } + + // 验证每个ID的有效性 + const invalidIds = ids.filter(id => !id || id.trim() === ''); + if (invalidIds.length > 0) { + return { + success: false, + updated_count: 0, + failed_ids: ids, + errors: [{ id: 'validation', error: '存在无效的分组ID' }] + }; + } + + // ========== 2. 逐个更新(确保每个分组都能被正确处理) ========== + + const failedIds: string[] = []; + const errors: Array<{ id: string; error: string }> = []; + let updatedCount = 0; + + for (const id of ids) { + try { + // 验证分组是否存在 + const groupResponse = await getRuleGroup(id, token); + if (groupResponse.error || !groupResponse.data) { + failedIds.push(id); + errors.push({ id, error: '分组不存在或无法访问' }); + continue; + } + + // 执行更新 + const updateResponse = await postgrestPut | RuleGroup, Partial>( + 'evaluation_point_groups', + { is_enabled }, + { id }, + token + ); + + if (updateResponse.error) { + failedIds.push(id); + errors.push({ id, error: updateResponse.error }); + } else { + updatedCount++; + } + } catch (error) { + failedIds.push(id); + errors.push({ + id, + error: error instanceof Error ? error.message : '更新失败' + }); + } + } + + // ========== 3. 返回结果 ========== + + return { + success: failedIds.length === 0, + updated_count: updatedCount, + failed_ids: failedIds, + errors: errors.length > 0 ? errors : undefined + }; + } catch (error) { + console.error('批量更新分组状态失败:', error); + return { + success: false, + updated_count: 0, + failed_ids: ids, + errors: [{ + id: 'batch', + error: error instanceof Error ? error.message : '批量更新失败' + }] + }; + } +} + +/** + * 批量删除分组(安全的阻止删除策略) + * @param ids 分组ID列表 + * @param token JWT token (可选) + * @returns 删除结果 + */ +export async function batchDeleteRuleGroups( + ids: string[], + token?: string +): Promise<{ + success: boolean; + deleted_count: number; + failed_ids: string[]; + errors?: Array<{ id: string; error: string; details?: { hasChildren?: boolean; hasPoints?: boolean } }>; +}> { + try { + // ========== 1. 参数验证 ========== + + if (!Array.isArray(ids) || ids.length === 0) { + return { + success: false, + deleted_count: 0, + failed_ids: [], + errors: [{ id: 'validation', error: 'ID列表不能为空' }] + }; + } + + // 验证每个ID的有效性 + const invalidIds = ids.filter(id => !id || id.trim() === ''); + if (invalidIds.length > 0) { + return { + success: false, + deleted_count: 0, + failed_ids: ids, + errors: [{ id: 'validation', error: '存在无效的分组ID' }] + }; + } + + // ========== 2. 逐个删除(使用安全的阻止删除策略) ========== + + const failedIds: string[] = []; + const errors: Array<{ id: string; error: string; details?: { hasChildren?: boolean; hasPoints?: boolean } }> = []; + let deletedCount = 0; + + for (const id of ids) { + try { + const deleteResult = await deleteRuleGroup(id, token); + + if (!deleteResult.success) { + failedIds.push(id); + errors.push({ + id, + error: deleteResult.error || '删除失败', + details: deleteResult.details ? { + hasChildren: deleteResult.details.hasChildren, + hasPoints: deleteResult.details.hasPoints + } : undefined + }); + } else { + deletedCount++; + } + } catch (error) { + failedIds.push(id); + errors.push({ + id, + error: error instanceof Error ? error.message : '删除失败' + }); + } + } + + // ========== 3. 返回结果 ========== + + return { + success: failedIds.length === 0, + deleted_count: deletedCount, + failed_ids: failedIds, + errors: errors.length > 0 ? errors : undefined + }; + } catch (error) { + console.error('批量删除分组失败:', error); + return { + success: false, + deleted_count: 0, + failed_ids: ids, + errors: [{ + id: 'batch', + error: error instanceof Error ? error.message : '批量删除失败' + }] + }; + } } \ No newline at end of file