# 评查点系统 API v3 对接实施计划 > **制定日期**: 2025-11-25 > **版本**: v1.0 > **目标**: 逐步对接评查点分组和评查点管理的 API v3 接口,确保前端与后端完全兼容 --- ## 📋 目录 1. [项目概况](#项目概况) 2. [模块 1:评查点分组管理](#模块-1评查点分组管理) 3. [模块 2:评查点管理](#模块-2评查点管理) 4. [验收标准](#验收标准) 5. [风险与注意事项](#风险与注意事项) --- ## 📊 项目概况 ### 涉及文件 **文档**: - `docs/evaluation/evaluation_point_groups.md` - 分组 API 文档 - `docs/evaluation/evaluation_points.md` - 评查点 API 文档 **API 客户端**: - `app/api/evaluation_points/rule-groups.ts` - `app/api/evaluation_points/rules.ts` **路由组件**: - `app/routes/rule-groups._index.tsx` - `app/routes/rule-groups.new.tsx` - `app/routes/rules.list.tsx` - `app/routes/rules.new.tsx` ### 技术栈 - **后端**: PostgreSQL + PostgREST - **前端**: Remix + React + TypeScript - **API 协议**: RESTful API (PostgREST 规范) --- ## 模块 1:评查点分组管理 ### 📌 当前状态分析 **已实现功能**: - ✅ 获取分组列表(含子分组) - ✅ 获取单个分组详情 - ✅ 创建分组 - ✅ 更新分组 - ✅ 删除分组 **缺失功能**: - ❌ 批量启用/禁用分组 - ❌ 批量删除分组 - ❌ 统计信息接口 --- ### 阶段 1.1:查询接口对接 ⏱️ 1-2 天 #### 任务清单 **1. 更新 `getRuleGroups` 函数** **文件**: `app/api/evaluation_points/rule-groups.ts` **当前实现**: ```typescript export async function getRuleGroups(token?: string): Promise> { const response = await postgrestGet('evaluation_point_groups', { filter: { 'pid': 'is.null' }, select: '*', order: { created_at: 'desc' }, token }); // ... } ``` **需要改进**: - ✅ 已使用 PostgREST 接口 - ⚠️ 缺少分页参数支持 - ⚠️ 缺少筛选条件支持(名称、编码、状态) **改进方案**: ```typescript export interface RuleGroupQueryParams { // 分页 page?: number; pageSize?: number; // 筛选 name?: string; // 模糊搜索 code?: string; // 模糊搜索 is_enabled?: boolean; pid?: string | null; // 父级ID,null表示一级分组 // 排序 orderBy?: 'created_at' | 'updated_at' | 'name' | 'code'; order?: 'asc' | 'desc'; token?: string; } export async function getRuleGroups( params?: RuleGroupQueryParams ): Promise> { const { page = 1, pageSize = 50, name, code, is_enabled, pid, orderBy = 'created_at', order = 'desc', token } = params || {}; const filter: Record = {}; // 构建筛选条件 if (name) filter['name'] = `ilike.*${name}*`; if (code) filter['code'] = `ilike.*${code}*`; if (is_enabled !== undefined) filter['is_enabled'] = `eq.${is_enabled}`; if (pid === null) { filter['pid'] = 'is.null'; } else if (pid) { filter['pid'] = `eq.${pid}`; } const response = await postgrestGet('evaluation_point_groups', { filter, select: '*', order: { [orderBy]: order }, range: { from: (page - 1) * pageSize, to: page * pageSize - 1 }, token }); return response; } ``` **验收标准**: - [ ] 支持分页(page, pageSize) - [ ] 支持名称模糊搜索 - [ ] 支持编码模糊搜索 - [ ] 支持状态筛选 - [ ] 支持获取一级分组(pid=null)或二级分组(pid=具体ID) - [ ] 返回总数(通过 PostgREST 的 `Prefer: count=exact` header) --- **2. 更新 `getChildGroups` 函数** **当前实现**: ```typescript export async function getChildGroups(parentId: string, token?: string): Promise> { const response = await postgrestGet('evaluation_point_groups', { filter: { 'pid': `eq.${parentId}` }, select: '*', order: { created_at: 'desc' }, token }); // ... } ``` **需要改进**: - ✅ 已使用 PostgREST 接口 - ⚠️ 建议合并到 `getRuleGroups` 函数(通过 `pid` 参数区分) **改进方案**: ```typescript // 删除独立的 getChildGroups 函数,统一使用 getRuleGroups // 使用示例: // 获取一级分组 const level1Groups = await getRuleGroups({ pid: null, token }); // 获取指定父级的子分组 const childGroups = await getRuleGroups({ pid: parentId, token }); ``` **验收标准**: - [ ] `getChildGroups` 函数已废弃 - [ ] 路由组件已更新为使用 `getRuleGroups({ pid: parentId })` --- **3. 更新 `getRuleGroup` 函数(获取单个分组详情)** **当前实现**: ```typescript export async function getRuleGroup(id: string, token?: string): Promise> { const response = await postgrestGet('evaluation_point_groups', { filter: { 'id': `eq.${id}` }, select: '*', token }); // ... } ``` **需要改进**: - ✅ 已使用 PostgREST 接口 - ⚠️ 缺少统计信息(评查点数量) **改进方案**: ```typescript export async function getRuleGroup(id: string, token?: string): Promise> { // 方案 1:使用 PostgREST 的关联查询 const response = await postgrestGet('evaluation_point_groups', { filter: { 'id': `eq.${id}` }, select: '*, evaluation_points(count)', token }); // 方案 2:分两次查询 // 1. 获取分组信息 // 2. 查询该分组下的评查点数量 return response; } ``` **验收标准**: - [ ] 返回分组详细信息 - [ ] 包含该分组下的评查点数量统计 --- ### 阶段 1.2:创建/更新接口对接 ⏱️ 1 天 #### 任务清单 **1. 更新 `createRuleGroup` 函数** **当前实现**: ```typescript export async function createRuleGroup( data: RuleGroupCreateUpdateDto, token?: string ): Promise> { const response = await postgrestPost('evaluation_point_groups', data, token); // ... } ``` **验证清单**: - [ ] 必填字段验证(name, code) - [ ] 编码唯一性验证 - [ ] 父级ID验证(如果是二级分组) - [ ] 返回新创建的分组完整信息 --- **2. 更新 `updateRuleGroup` 函数** **当前实现**: ```typescript export async function updateRuleGroup( id: string, data: RuleGroupCreateUpdateDto, token?: string ): Promise> { const response = await postgrestPut( 'evaluation_point_groups', data, { id }, token ); // ... } ``` **验证清单**: - [ ] ID 有效性验证 - [ ] 不允许修改 `pid`(防止分组层级混乱) - [ ] 编码唯一性验证(排除自身) - [ ] 返回更新后的分组完整信息 --- ### 阶段 1.3:删除接口对接 ⏱️ 0.5 天 #### 任务清单 **1. 更新 `deleteRuleGroup` 函数** **当前实现**: ```typescript export async function deleteRuleGroup(id: string, token?: string): Promise> { const response = await postgrestDelete('evaluation_point_groups', { id }, token); // ... } ``` **需要改进**: - ⚠️ 缺少级联删除提示 - ⚠️ 需要检查是否有关联的评查点 **改进方案**: ```typescript export async function deleteRuleGroup(id: string, token?: string): Promise> { // 1. 检查是否有子分组 const childGroups = await getRuleGroups({ pid: id, token }); if (childGroups.data && childGroups.data.length > 0) { return { success: false, error: '该分组下存在子分组,请先删除子分组', status: 400 }; } // 2. 检查是否有关联的评查点 const points = await postgrestGet('evaluation_points', { filter: { 'evaluation_point_groups_id': `eq.${id}` }, select: 'count', token }); if (points.data && points.data.length > 0) { return { success: false, error: '该分组下存在评查点,请先删除或移动评查点', status: 400 }; } // 3. 执行删除 const response = await postgrestDelete('evaluation_point_groups', { id }, token); return response; } ``` **验收标准**: - [ ] 删除前检查子分组 - [ ] 删除前检查关联评查点 - [ ] 提供清晰的错误提示 - [ ] 删除成功后返回成功状态 --- ### 阶段 1.4:批量操作接口对接 ⏱️ 1 天 #### 任务清单 **1. 新增 `batchUpdateRuleGroupStatus` 函数** ```typescript export interface BatchUpdateStatusDto { ids: string[]; is_enabled: boolean; } export async function batchUpdateRuleGroupStatus( data: BatchUpdateStatusDto, token?: string ): Promise> { const { ids, is_enabled } = data; const response = await postgrestPatch( 'evaluation_point_groups', { is_enabled }, { id: `in.(${ids.join(',')})` }, token ); return { success: true, data: { updated_count: response.data?.length || 0 } }; } ``` **验收标准**: - [ ] 支持批量启用/禁用 - [ ] 返回更新数量 - [ ] 处理部分失败的情况 --- **2. 新增 `batchDeleteRuleGroups` 函数** ```typescript export async function batchDeleteRuleGroups( ids: string[], token?: string ): Promise> { const failedIds: string[] = []; let deletedCount = 0; for (const id of ids) { const result = await deleteRuleGroup(id, token); if (result.success) { deletedCount++; } else { failedIds.push(id); } } return { success: failedIds.length === 0, data: { deleted_count: deletedCount, failed_ids: failedIds } }; } ``` **验收标准**: - [ ] 支持批量删除 - [ ] 返回删除成功数量 - [ ] 返回删除失败的 ID 列表 - [ ] 提供详细的失败原因 --- ### 阶段 1.5:前端组件更新 ⏱️ 2 天 #### 任务清单 **1. 更新 `rule-groups._index.tsx`** **需要改进的功能**: a) **分页功能** ```typescript // 当前:无分页 // 改进:添加分页组件 const [pagination, setPagination] = useState({ page: 1, pageSize: 50 }); // 在 loader 中使用分页参数 const response = await getRuleGroups({ page: pagination.page, pageSize: pagination.pageSize, ...filters, token: frontendJWT }); ``` b) **筛选功能优化** ```typescript // 当前:客户端筛选 // 改进:服务端筛选 const handleFilterChange = (filters: RuleGroupQueryParams) => { const newParams = new URLSearchParams(); if (filters.name) newParams.set('name', filters.name); if (filters.code) newParams.set('code', filters.code); if (filters.is_enabled !== undefined) { newParams.set('is_enabled', filters.is_enabled.toString()); } setSearchParams(newParams); }; ``` c) **批量操作功能** ```typescript // 新增批量选择状态 const [selectedIds, setSelectedIds] = useState([]); // 批量启用/禁用 const handleBatchEnable = async (enable: boolean) => { const result = await batchUpdateRuleGroupStatus({ ids: selectedIds, is_enabled: enable }, frontendJWT); if (result.success) { toastService.success(`已${enable ? '启用' : '禁用'} ${result.data.updated_count} 个分组`); // 刷新列表 } }; // 批量删除 const handleBatchDelete = async () => { messageService.show({ title: "确认批量删除", message: `确认删除选中的 ${selectedIds.length} 个分组吗?`, type: "warning", onConfirm: async () => { const result = await batchDeleteRuleGroups(selectedIds, frontendJWT); toastService.success(`成功删除 ${result.data.deleted_count} 个分组`); if (result.data.failed_ids.length > 0) { toastService.warning(`有 ${result.data.failed_ids.length} 个分组删除失败`); } // 刷新列表 } }); }; ``` **验收标准**: - [ ] 表格支持多选(Checkbox) - [ ] 顶部批量操作按钮区域 - [ ] 批量启用/禁用功能正常 - [ ] 批量删除功能正常 - [ ] 分页功能正常 - [ ] 服务端筛选功能正常 --- **2. 更新 `rule-groups.new.tsx`** **需要改进的功能**: a) **表单验证增强** ```typescript // 添加异步验证:检查编码唯一性 const validateCodeUnique = async (code: string, currentId?: string) => { const response = await getRuleGroups({ code, token: frontendJWT }); if (response.data && response.data.length > 0) { // 编辑模式下排除自身 if (currentId && response.data[0].id === currentId) { return true; } return false; } return true; }; ``` b) **父级分组选择优化** ```typescript // 当前:手动过滤 // 改进:使用 API 筛选 // 获取一级分组(用于二级分组的父级选择) const parentGroupsResponse = await getRuleGroups({ pid: null, is_enabled: true, token: frontendJWT }); ``` **验收标准**: - [ ] 编码唯一性实时验证 - [ ] 父级分组列表仅显示一级分组 - [ ] 父级分组列表仅显示启用状态的分组 - [ ] 保存成功后正确跳转 --- ## 模块 2:评查点管理 ### 📌 当前状态分析 **已实现功能**: - ✅ 获取评查点列表 - ✅ 获取单个评查点详情 - ✅ 创建评查点 - ✅ 更新评查点 - ✅ 删除评查点 - ✅ 复制评查点 **缺失功能**: - ❌ 批量启用/禁用评查点 - ❌ 批量删除评查点 - ❌ 统计信息接口 - ❌ 评查点使用记录查询 --- ### 阶段 2.1:查询接口对接 ⏱️ 2 天 #### 任务清单 **1. 更新 `getRulesList` 函数** **文件**: `app/api/evaluation_points/rules.ts` **当前实现**: ```typescript export async function getRulesList(params: { ruleType?: string; groupId?: string; isActive?: boolean; keyword?: string; area?: string; page?: number; pageSize?: number; token?: string; }): Promise> { // ... } ``` **需要改进**: - ✅ 已支持分页 - ✅ 已支持筛选 - ⚠️ 缺少排序参数 - ⚠️ 返回格式需要优化 **改进方案**: ```typescript export interface RuleQueryParams { // 分页 page?: number; pageSize?: number; // 筛选 keyword?: string; // 名称或编码模糊搜索 evaluation_point_groups_pid?: string; // 评查点类型(一级分组) evaluation_point_groups_id?: string; // 所属规则组(二级分组) risk?: 'low' | 'medium' | 'high'; // 风险等级 is_enabled?: boolean; // 启用状态 area?: string; // 地区过滤 // 排序 orderBy?: 'created_at' | 'updated_at' | 'name' | 'code' | 'usage_count'; order?: 'asc' | 'desc'; token?: string; } export async function getRulesList( params?: RuleQueryParams ): Promise> { const { page = 1, pageSize = 10, keyword, evaluation_point_groups_pid, evaluation_point_groups_id, risk, is_enabled, area, orderBy = 'created_at', order = 'desc', token } = params || {}; const filter: Record = {}; // 构建筛选条件 if (keyword) { // PostgREST 不支持 OR 条件,需要使用 or 语法 filter['or'] = `(name.ilike.*${keyword}*,code.ilike.*${keyword}*)`; } if (evaluation_point_groups_pid) { filter['evaluation_point_groups_pid'] = `eq.${evaluation_point_groups_pid}`; } if (evaluation_point_groups_id) { filter['evaluation_point_groups_id'] = `eq.${evaluation_point_groups_id}`; } if (risk) filter['risk'] = `eq.${risk}`; if (is_enabled !== undefined) filter['is_enabled'] = `eq.${is_enabled}`; // 地区过滤(需要通过 code 字段的后缀实现) if (area) { filter['code'] = `like.*--${area}`; } const response = await postgrestGet('evaluation_points', { filter, select: '*,evaluation_point_groups:evaluation_point_groups_id(*)', order: { [orderBy]: order }, range: { from: (page - 1) * pageSize, to: page * pageSize - 1 }, count: 'exact', token }); if (response.data) { return { success: true, data: { rules: response.data, totalCount: response.count || 0 } }; } return response; } ``` **验收标准**: - [ ] 支持关键词搜索(名称或编码) - [ ] 支持按评查点类型筛选 - [ ] 支持按规则组筛选 - [ ] 支持按风险等级筛选 - [ ] 支持按启用状态筛选 - [ ] 支持按地区筛选 - [ ] 支持排序(创建时间、更新时间、使用次数等) - [ ] 返回总数(用于分页) - [ ] 关联查询规则组信息 --- **2. 新增 `getRuleStatistics` 函数(统计信息)** ```typescript 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: string; group_name: string; count: number; }>; } export async function getRuleStatistics( token?: string ): Promise> { // 使用 PostgREST 的聚合查询 const response = await postgrestGet('evaluation_points', { select: ` count, is_enabled, risk, evaluation_point_groups_id `, token }); // 处理响应数据,计算统计信息 // ... return { success: true, data: statistics }; } ``` **验收标准**: - [ ] 返回总评查点数 - [ ] 返回启用/禁用数量 - [ ] 返回按风险等级分组的数量 - [ ] 返回按规则组分组的数量 --- ### 阶段 2.2:创建/更新接口对接 ⏱️ 2 天 #### 任务清单 **1. 更新 `createRule` 函数** **当前实现**: ```typescript // 当前通过 postgrestPost 直接创建 const response = await postgrestPost('evaluation_points', finalData, frontendJWT); ``` **需要改进**: - ⚠️ 缺少数据验证 - ⚠️ 缺少 JSONB 字段格式验证 **改进方案**: ```typescript export interface CreateRuleDto { name: string; code: string; risk: 'low' | 'medium' | 'high'; is_enabled?: boolean; description?: string; references_laws?: { name: string; articles: string[]; content: string; }; evaluation_point_groups_pid: string; evaluation_point_groups_id: string; extraction_config: { llm?: { fields: string[]; prompt_setting: { type: string; template: string; }; }; vlm?: { fields: Array; prompt_setting: { type: string; template: string; }; }; regex?: { fields: Array<{ field: string; pattern: string }>; }; }; evaluation_config: { logicType: 'and' | 'or' | 'custom'; customLogic?: string; rules: Array<{ id: string; type: string; config: Record; }>; }; pass_message?: string; fail_message?: string; suggestion_message?: string; suggestion_message_type?: 'info' | 'warning' | 'error'; post_action?: string; action_config?: string; score?: number; } export async function createRule( data: CreateRuleDto, token?: string ): Promise> { // 1. 验证必填字段 if (!data.name || !data.code) { return { success: false, error: '名称和编码不能为空', status: 400 }; } // 2. 验证编码唯一性 const existingRule = await getRulesList({ keyword: data.code, token }); if (existingRule.data && existingRule.data.rules.length > 0) { return { success: false, error: '评查点编码已存在', status: 400 }; } // 3. 验证 JSONB 字段格式 try { JSON.parse(JSON.stringify(data.extraction_config)); JSON.parse(JSON.stringify(data.evaluation_config)); } catch (error) { return { success: false, error: 'extraction_config 或 evaluation_config 格式无效', status: 400 }; } // 4. 执行创建 const response = await postgrestPost('evaluation_points', data, token); return response; } ``` **验收标准**: - [ ] 必填字段验证 - [ ] 编码唯一性验证 - [ ] JSONB 字段格式验证 - [ ] 返回新创建的评查点完整信息 --- **2. 更新 `updateRule` 函数** **改进方案**: ```typescript export async function updateRule( id: string, data: Partial, token?: string ): Promise> { // 1. 验证 ID 有效性 const existing = await postgrestGet('evaluation_points', { filter: { id: `eq.${id}` }, token }); if (!existing.data || existing.data.length === 0) { return { success: false, error: '评查点不存在', status: 404 }; } // 2. 验证编码唯一性(排除自身) if (data.code) { const duplicate = await getRulesList({ keyword: data.code, token }); if (duplicate.data && duplicate.data.rules.length > 0) { const isDuplicate = duplicate.data.rules.some(r => r.id !== id); if (isDuplicate) { return { success: false, error: '评查点编码已存在', status: 400 }; } } } // 3. 验证 JSONB 字段格式 if (data.extraction_config || data.evaluation_config) { try { if (data.extraction_config) { JSON.parse(JSON.stringify(data.extraction_config)); } if (data.evaluation_config) { JSON.parse(JSON.stringify(data.evaluation_config)); } } catch (error) { return { success: false, error: 'extraction_config 或 evaluation_config 格式无效', status: 400 }; } } // 4. 执行更新 const response = await postgrestPut( 'evaluation_points', data, { id }, token ); return response; } ``` **验收标准**: - [ ] ID 有效性验证 - [ ] 编码唯一性验证(排除自身) - [ ] JSONB 字段格式验证 - [ ] 返回更新后的评查点完整信息 - [ ] 支持部分字段更新 --- ### 阶段 2.3:复制功能对接 ⏱️ 0.5 天 #### 任务清单 **1. 验证 `copyRule` 函数** **当前实现**: ```typescript // 在 rules.new.tsx 中处理 // 通过 URL 参数 mode=copy 触发复制模式 ``` **验证清单**: - [ ] 复制时移除 `id`, `created_at`, `updated_at`, `usage_count` - [ ] 清洗评查点编码(移除地区后缀) - [ ] 提示用户修改编码和名称 - [ ] 保存时验证编码唯一性 --- ### 阶段 2.4:删除接口对接 ⏱️ 0.5 天 #### 任务清单 **1. 更新 `deleteRule` 函数** **当前实现**: ```typescript export async function deleteRule(id: string, token?: string): Promise> { const response = await postgrestDelete('evaluation_points', { id }, token); return response; } ``` **需要改进**: - ⚠️ 缺少关联检查(评查结果) **改进方案**: ```typescript export async function deleteRule(id: string, token?: string): Promise> { // 1. 检查是否有关联的评查结果 const results = await postgrestGet('evaluation_results', { filter: { 'evaluation_point_id': `eq.${id}` }, select: 'count', token }); if (results.data && results.data.length > 0) { return { success: false, error: '该评查点已被使用,无法删除。如需停用,请使用禁用功能。', status: 400 }; } // 2. 执行删除 const response = await postgrestDelete('evaluation_points', { id }, token); return response; } ``` **验收标准**: - [ ] 删除前检查关联评查结果 - [ ] 提供清晰的错误提示 - [ ] 建议使用禁用功能代替删除 --- ### 阶段 2.5:批量操作接口对接 ⏱️ 1 天 #### 任务清单 **1. 新增 `batchUpdateRuleStatus` 函数** ```typescript export async function batchUpdateRuleStatus( data: { ids: string[]; is_enabled: boolean }, token?: string ): Promise> { const { ids, is_enabled } = data; const response = await postgrestPatch( 'evaluation_points', { is_enabled }, { id: `in.(${ids.join(',')})` }, token ); return { success: true, data: { updated_count: response.data?.length || 0 } }; } ``` **验收标准**: - [ ] 支持批量启用/禁用 - [ ] 返回更新数量 --- **2. 新增 `batchDeleteRules` 函数** ```typescript export async function batchDeleteRules( ids: string[], token?: string ): Promise> { const failedIds: string[] = []; let deletedCount = 0; for (const id of ids) { const result = await deleteRule(id, token); if (result.success) { deletedCount++; } else { failedIds.push(id); } } return { success: failedIds.length === 0, data: { deleted_count: deletedCount, failed_ids: failedIds } }; } ``` **验收标准**: - [ ] 支持批量删除 - [ ] 返回删除成功数量 - [ ] 返回删除失败的 ID 列表 --- ### 阶段 2.6:前端组件更新 ⏱️ 3 天 #### 任务清单 **1. 更新 `rules.list.tsx`** **需要改进的功能**: a) **批量选择功能** ```typescript const [selectedIds, setSelectedIds] = useState([]); const columns = [ { title: , key: "selection", width: "50px", render: (_: unknown, record: Rule) => ( handleSelectRow(record.id)} /> ) }, // ... 其他列 ]; ``` b) **批量操作按钮** ```tsx
``` c) **统计信息展示** ```typescript const { data: statistics } = await getRuleStatistics(frontendJWT); // 在页面顶部展示统计卡片
``` **验收标准**: - [ ] 表格支持多选 - [ ] 批量操作按钮显示/禁用状态正确 - [ ] 批量启用/禁用功能正常 - [ ] 批量删除功能正常 - [ ] 统计信息展示正常 --- **2. 更新 `rules.new.tsx`** **需要改进的功能**: a) **异步验证优化** ```typescript // 编码唯一性验证(防抖) const validateCodeUnique = useMemo( () => debounce(async (code: string, currentId?: string) => { const response = await getRulesList({ keyword: code, token: frontendJWT }); if (response.data && response.data.rules.length > 0) { const isDuplicate = response.data.rules.some(r => r.id !== currentId); if (isDuplicate) { setFormErrors(prev => ({ ...prev, code: '评查点编码已存在' })); } } }, 500), [frontendJWT] ); ``` b) **保存前验证增强** ```typescript const handleSave = async () => { // 1. 基本字段验证 if (!formData.name?.trim()) { toastService.warning("评查点名称不能为空"); return; } // 2. 编码唯一性验证 const codeValid = await validateCodeUnique(formData.code, formData.id); if (!codeValid) { toastService.warning("评查点编码已存在"); return; } // 3. JSONB 字段格式验证 try { JSON.parse(JSON.stringify(formData.extraction_config)); JSON.parse(JSON.stringify(formData.evaluation_config)); } catch (error) { toastService.error("配置格式无效"); return; } // 4. 评查规则完整性验证 // (已有实现,保持不变) // 5. 执行保存 const result = isEditMode ? await updateRule(formData.id!, formData, frontendJWT) : await createRule(formData, frontendJWT); if (result.success) { toastService.success("保存成功"); navigate(`/rules/new?id=${result.data.id}`); } else { toastService.error(result.error || "保存失败"); } }; ``` **验收标准**: - [ ] 编码唯一性异步验证(带防抖) - [ ] JSONB 字段格式验证 - [ ] 保存前完整性验证 - [ ] 错误提示清晰明确 - [ ] 保存成功后正确跳转 --- ## 验收标准 ### 功能验收 #### 模块 1:评查点分组管理 - [ ] **查询功能** - [ ] 获取一级分组列表 - [ ] 获取二级分组列表 - [ ] 按名称筛选 - [ ] 按编码筛选 - [ ] 按状态筛选 - [ ] 分页功能正常 - [ ] **创建功能** - [ ] 创建一级分组 - [ ] 创建二级分组 - [ ] 必填字段验证 - [ ] 编码唯一性验证 - [ ] **更新功能** - [ ] 更新分组信息 - [ ] 不允许修改父级ID - [ ] 编码唯一性验证(排除自身) - [ ] **删除功能** - [ ] 删除前检查子分组 - [ ] 删除前检查关联评查点 - [ ] 级联删除提示 - [ ] **批量操作** - [ ] 批量启用/禁用 - [ ] 批量删除 - [ ] 错误处理 #### 模块 2:评查点管理 - [ ] **查询功能** - [ ] 获取评查点列表 - [ ] 按关键词搜索 - [ ] 按评查点类型筛选 - [ ] 按规则组筛选 - [ ] 按风险等级筛选 - [ ] 按状态筛选 - [ ] 按地区筛选 - [ ] 排序功能 - [ ] 分页功能 - [ ] 统计信息展示 - [ ] **创建功能** - [ ] 创建评查点 - [ ] 必填字段验证 - [ ] 编码唯一性验证 - [ ] JSONB 字段格式验证 - [ ] 评查规则完整性验证 - [ ] **更新功能** - [ ] 更新评查点 - [ ] 编码唯一性验证(排除自身) - [ ] JSONB 字段格式验证 - [ ] 部分字段更新 - [ ] **复制功能** - [ ] 复制评查点 - [ ] 移除不应复制的字段 - [ ] 清洗编码 - [ ] 提示用户修改 - [ ] **删除功能** - [ ] 删除前检查关联评查结果 - [ ] 建议使用禁用功能 - [ ] **批量操作** - [ ] 批量启用/禁用 - [ ] 批量删除 - [ ] 错误处理 --- ### 性能验收 - [ ] 列表页加载时间 < 2 秒 - [ ] 筛选响应时间 < 1 秒 - [ ] 保存响应时间 < 2 秒 - [ ] 批量操作响应时间 < 5 秒 --- ### 代码质量验收 - [ ] TypeScript 类型定义完整 - [ ] 无 ESLint 错误 - [ ] 无 TypeScript 类型错误 - [ ] 代码注释清晰 - [ ] 遵循项目编码规范 --- ## 风险与注意事项 ### 数据迁移风险 **问题**: 现有数据格式可能与 API v3 不完全兼容 **解决方案**: 1. 在开发环境进行充分测试 2. 编写数据迁移脚本 3. 备份生产数据 4. 分批次迁移 ### 性能风险 **问题**: 大数据量情况下查询性能下降 **解决方案**: 1. 添加数据库索引 2. 优化 PostgREST 查询 3. 实现分页和懒加载 4. 使用缓存机制 ### 兼容性风险 **问题**: 旧版本 API 调用可能失败 **解决方案**: 1. 保留旧版本 API(向后兼容) 2. 使用 API 版本控制 3. 提供迁移指南 4. 逐步废弃旧接口 ### JSONB 字段风险 **问题**: JSONB 字段格式复杂,容易出错 **解决方案**: 1. 严格的数据验证 2. 使用 JSON Schema 验证 3. 提供默认值和模板 4. 详细的错误提示 --- ## 实施时间表 | 阶段 | 模块 | 工作量 | 起止日期 | |------|------|--------|---------| | 1.1 | 评查点分组 - 查询接口对接 | 1-2 天 | Day 1-2 | | 1.2 | 评查点分组 - 创建/更新接口对接 | 1 天 | Day 3 | | 1.3 | 评查点分组 - 删除接口对接 | 0.5 天 | Day 4 上午 | | 1.4 | 评查点分组 - 批量操作接口对接 | 1 天 | Day 4 下午 - Day 5 | | 1.5 | 评查点分组 - 前端组件更新 | 2 天 | Day 6-7 | | 2.1 | 评查点 - 查询接口对接 | 2 天 | Day 8-9 | | 2.2 | 评查点 - 创建/更新接口对接 | 2 天 | Day 10-11 | | 2.3 | 评查点 - 复制功能对接 | 0.5 天 | Day 12 上午 | | 2.4 | 评查点 - 删除接口对接 | 0.5 天 | Day 12 下午 | | 2.5 | 评查点 - 批量操作接口对接 | 1 天 | Day 13 | | 2.6 | 评查点 - 前端组件更新 | 3 天 | Day 14-16 | | 测试 | 集成测试 + 修复 Bug | 2 天 | Day 17-18 | | 部署 | 生产环境部署 | 1 天 | Day 19 | **总计**: 约 19 个工作日(约 4 周) --- ## 附录 ### PostgREST 查询语法参考 ```typescript // 1. 筛选条件 filter: { 'name': 'ilike.*关键词*', // 模糊搜索(不区分大小写) 'is_enabled': 'eq.true', // 等于 'risk': 'in.(low,medium)', // 包含 'created_at': 'gte.2024-01-01', // 大于等于 'pid': 'is.null' // 为空 } // 2. OR 条件 filter: { 'or': '(name.ilike.*关键词*,code.ilike.*关键词*)' } // 3. 关联查询 select: '*, evaluation_point_groups:evaluation_point_groups_id(*)' // 4. 排序 order: { 'created_at': 'desc' } // 5. 分页 range: { from: 0, to: 9 } // 前 10 条 // 6. 计数 count: 'exact' ``` ### 类型定义参考 ```typescript // 评查点分组 export interface RuleGroup { id: string; name: string; code: string; pid: string | null; description?: string; is_enabled: boolean; created_at: string; updated_at: string; children?: RuleGroup[]; ruleCount?: number; } // 评查点 export interface EvaluationPoint { id?: string; name: string; code: string; risk: 'low' | 'medium' | 'high'; is_enabled: boolean; description?: string; references_laws?: { name: string; articles: string[]; content: string; }; evaluation_point_groups_pid: string; evaluation_point_groups_id: string; extraction_config: ExtractionConfig; evaluation_config: EvaluationConfig; pass_message?: string; fail_message?: string; suggestion_message?: string; suggestion_message_type?: 'info' | 'warning' | 'error'; post_action?: string; action_config?: string; score?: number; usage_count?: number; created_at?: string; updated_at?: string; } ``` --- **文档结束**