## 主要改进 ### 1. 增强 getRuleGroups 函数 - ✅ 添加完整的分页参数支持 (page, pageSize) - ✅ 添加筛选参数 (name, code, is_enabled, pid) - ✅ 添加排序参数 (orderBy, order) - ✅ 返回总数 (totalCount) - ✅ 支持一级分组和二级分组查询 ### 2. 优化 getChildGroups 函数 - ✅ 内部使用改进后的 getRuleGroups 函数 - ✅ 自动添加评查点数量统计 - ✅ 改进类型安全性 ### 3. 优化 getRuleGroup 函数 - ✅ 确保评查点数量统计准确 - ✅ 改进错误处理 - ✅ 优化类型守卫逻辑 ### 4. 类型定义改进 - ✅ 新增 RuleGroupQueryParams 接口 - ✅ ApiRuleGroup.pid 类型支持 null - ✅ 修复所有 TypeScript 类型错误 ### 5. 创建对接计划文档 - ✅ 详细的 API 对接实施计划 - ✅ 分模块逐步实施策略 - ✅ 验收标准和风险评估 ## 相关文件 - app/api/evaluation_points/rule-groups.ts - docs/evaluation/API对接实施计划.md ## 验收清单 - [x] TypeScript 类型检查通过 - [x] 支持分页、筛选、排序 - [x] 返回评查点数量统计 - [x] 向后兼容现有代码 Co-Authored-By: Claude <noreply@anthropic.com>
33 KiB
评查点系统 API v3 对接实施计划
制定日期: 2025-11-25 版本: v1.0 目标: 逐步对接评查点分组和评查点管理的 API v3 接口,确保前端与后端完全兼容
📋 目录
📊 项目概况
涉及文件
文档:
docs/evaluation/evaluation_point_groups.md- 分组 API 文档docs/evaluation/evaluation_points.md- 评查点 API 文档
API 客户端:
app/api/evaluation_points/rule-groups.tsapp/api/evaluation_points/rules.ts
路由组件:
app/routes/rule-groups._index.tsxapp/routes/rule-groups.new.tsxapp/routes/rules.list.tsxapp/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
当前实现:
export async function getRuleGroups(token?: string): Promise<ApiResponse<RuleGroup[]>> {
const response = await postgrestGet('evaluation_point_groups', {
filter: { 'pid': 'is.null' },
select: '*',
order: { created_at: 'desc' },
token
});
// ...
}
需要改进:
- ✅ 已使用 PostgREST 接口
- ⚠️ 缺少分页参数支持
- ⚠️ 缺少筛选条件支持(名称、编码、状态)
改进方案:
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<ApiResponse<RuleGroup[]>> {
const {
page = 1,
pageSize = 50,
name,
code,
is_enabled,
pid,
orderBy = 'created_at',
order = 'desc',
token
} = params || {};
const filter: Record<string, string> = {};
// 构建筛选条件
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=exactheader)
2. 更新 getChildGroups 函数
当前实现:
export async function getChildGroups(parentId: string, token?: string): Promise<ApiResponse<RuleGroup[]>> {
const response = await postgrestGet('evaluation_point_groups', {
filter: { 'pid': `eq.${parentId}` },
select: '*',
order: { created_at: 'desc' },
token
});
// ...
}
需要改进:
- ✅ 已使用 PostgREST 接口
- ⚠️ 建议合并到
getRuleGroups函数(通过pid参数区分)
改进方案:
// 删除独立的 getChildGroups 函数,统一使用 getRuleGroups
// 使用示例:
// 获取一级分组
const level1Groups = await getRuleGroups({ pid: null, token });
// 获取指定父级的子分组
const childGroups = await getRuleGroups({ pid: parentId, token });
验收标准:
getChildGroups函数已废弃- 路由组件已更新为使用
getRuleGroups({ pid: parentId })
3. 更新 getRuleGroup 函数(获取单个分组详情)
当前实现:
export async function getRuleGroup(id: string, token?: string): Promise<ApiResponse<RuleGroup>> {
const response = await postgrestGet('evaluation_point_groups', {
filter: { 'id': `eq.${id}` },
select: '*',
token
});
// ...
}
需要改进:
- ✅ 已使用 PostgREST 接口
- ⚠️ 缺少统计信息(评查点数量)
改进方案:
export async function getRuleGroup(id: string, token?: string): Promise<ApiResponse<RuleGroup>> {
// 方案 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 函数
当前实现:
export async function createRuleGroup(
data: RuleGroupCreateUpdateDto,
token?: string
): Promise<ApiResponse<RuleGroup>> {
const response = await postgrestPost('evaluation_point_groups', data, token);
// ...
}
验证清单:
- 必填字段验证(name, code)
- 编码唯一性验证
- 父级ID验证(如果是二级分组)
- 返回新创建的分组完整信息
2. 更新 updateRuleGroup 函数
当前实现:
export async function updateRuleGroup(
id: string,
data: RuleGroupCreateUpdateDto,
token?: string
): Promise<ApiResponse<RuleGroup>> {
const response = await postgrestPut(
'evaluation_point_groups',
data,
{ id },
token
);
// ...
}
验证清单:
- ID 有效性验证
- 不允许修改
pid(防止分组层级混乱) - 编码唯一性验证(排除自身)
- 返回更新后的分组完整信息
阶段 1.3:删除接口对接 ⏱️ 0.5 天
任务清单
1. 更新 deleteRuleGroup 函数
当前实现:
export async function deleteRuleGroup(id: string, token?: string): Promise<ApiResponse<void>> {
const response = await postgrestDelete('evaluation_point_groups', { id }, token);
// ...
}
需要改进:
- ⚠️ 缺少级联删除提示
- ⚠️ 需要检查是否有关联的评查点
改进方案:
export async function deleteRuleGroup(id: string, token?: string): Promise<ApiResponse<void>> {
// 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 函数
export interface BatchUpdateStatusDto {
ids: string[];
is_enabled: boolean;
}
export async function batchUpdateRuleGroupStatus(
data: BatchUpdateStatusDto,
token?: string
): Promise<ApiResponse<{ updated_count: number }>> {
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 函数
export async function batchDeleteRuleGroups(
ids: string[],
token?: string
): Promise<ApiResponse<{ deleted_count: number; failed_ids: string[] }>> {
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) 分页功能
// 当前:无分页
// 改进:添加分页组件
const [pagination, setPagination] = useState({ page: 1, pageSize: 50 });
// 在 loader 中使用分页参数
const response = await getRuleGroups({
page: pagination.page,
pageSize: pagination.pageSize,
...filters,
token: frontendJWT
});
b) 筛选功能优化
// 当前:客户端筛选
// 改进:服务端筛选
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) 批量操作功能
// 新增批量选择状态
const [selectedIds, setSelectedIds] = useState<string[]>([]);
// 批量启用/禁用
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) 表单验证增强
// 添加异步验证:检查编码唯一性
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) 父级分组选择优化
// 当前:手动过滤
// 改进:使用 API 筛选
// 获取一级分组(用于二级分组的父级选择)
const parentGroupsResponse = await getRuleGroups({
pid: null,
is_enabled: true,
token: frontendJWT
});
验收标准:
- 编码唯一性实时验证
- 父级分组列表仅显示一级分组
- 父级分组列表仅显示启用状态的分组
- 保存成功后正确跳转
模块 2:评查点管理
📌 当前状态分析
已实现功能:
- ✅ 获取评查点列表
- ✅ 获取单个评查点详情
- ✅ 创建评查点
- ✅ 更新评查点
- ✅ 删除评查点
- ✅ 复制评查点
缺失功能:
- ❌ 批量启用/禁用评查点
- ❌ 批量删除评查点
- ❌ 统计信息接口
- ❌ 评查点使用记录查询
阶段 2.1:查询接口对接 ⏱️ 2 天
任务清单
1. 更新 getRulesList 函数
文件: app/api/evaluation_points/rules.ts
当前实现:
export async function getRulesList(params: {
ruleType?: string;
groupId?: string;
isActive?: boolean;
keyword?: string;
area?: string;
page?: number;
pageSize?: number;
token?: string;
}): Promise<ApiResponse<{ rules: ApiRule[]; totalCount: number }>> {
// ...
}
需要改进:
- ✅ 已支持分页
- ✅ 已支持筛选
- ⚠️ 缺少排序参数
- ⚠️ 返回格式需要优化
改进方案:
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<ApiResponse<{ rules: EvaluationPoint[]; totalCount: number }>> {
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<string, string> = {};
// 构建筛选条件
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 函数(统计信息)
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<ApiResponse<RuleStatistics>> {
// 使用 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 函数
当前实现:
// 当前通过 postgrestPost 直接创建
const response = await postgrestPost('evaluation_points', finalData, frontendJWT);
需要改进:
- ⚠️ 缺少数据验证
- ⚠️ 缺少 JSONB 字段格式验证
改进方案:
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<string | { name: string; type: string }>;
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<string, unknown>;
}>;
};
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<ApiResponse<EvaluationPoint>> {
// 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 函数
改进方案:
export async function updateRule(
id: string,
data: Partial<CreateRuleDto>,
token?: string
): Promise<ApiResponse<EvaluationPoint>> {
// 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 函数
当前实现:
// 在 rules.new.tsx 中处理
// 通过 URL 参数 mode=copy 触发复制模式
验证清单:
- 复制时移除
id,created_at,updated_at,usage_count - 清洗评查点编码(移除地区后缀)
- 提示用户修改编码和名称
- 保存时验证编码唯一性
阶段 2.4:删除接口对接 ⏱️ 0.5 天
任务清单
1. 更新 deleteRule 函数
当前实现:
export async function deleteRule(id: string, token?: string): Promise<ApiResponse<void>> {
const response = await postgrestDelete('evaluation_points', { id }, token);
return response;
}
需要改进:
- ⚠️ 缺少关联检查(评查结果)
改进方案:
export async function deleteRule(id: string, token?: string): Promise<ApiResponse<void>> {
// 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 函数
export async function batchUpdateRuleStatus(
data: { ids: string[]; is_enabled: boolean },
token?: string
): Promise<ApiResponse<{ updated_count: number }>> {
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 函数
export async function batchDeleteRules(
ids: string[],
token?: string
): Promise<ApiResponse<{ deleted_count: number; failed_ids: string[] }>> {
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) 批量选择功能
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const columns = [
{
title: <Checkbox checked={allSelected} onChange={handleSelectAll} />,
key: "selection",
width: "50px",
render: (_: unknown, record: Rule) => (
<Checkbox
checked={selectedIds.includes(record.id)}
onChange={() => handleSelectRow(record.id)}
/>
)
},
// ... 其他列
];
b) 批量操作按钮
<div className="batch-actions">
<Button
type="default"
disabled={selectedIds.length === 0}
onClick={() => handleBatchEnable(true)}
>
批量启用
</Button>
<Button
type="default"
disabled={selectedIds.length === 0}
onClick={() => handleBatchEnable(false)}
>
批量禁用
</Button>
<Button
type="danger"
disabled={selectedIds.length === 0}
onClick={handleBatchDelete}
>
批量删除
</Button>
</div>
c) 统计信息展示
const { data: statistics } = await getRuleStatistics(frontendJWT);
// 在页面顶部展示统计卡片
<div className="statistics-cards">
<StatCard title="总评查点" value={statistics.total_count} />
<StatCard title="已启用" value={statistics.enabled_count} color="success" />
<StatCard title="已禁用" value={statistics.disabled_count} color="warning" />
<StatCard title="高风险" value={statistics.by_risk.high} color="error" />
</div>
验收标准:
- 表格支持多选
- 批量操作按钮显示/禁用状态正确
- 批量启用/禁用功能正常
- 批量删除功能正常
- 统计信息展示正常
2. 更新 rules.new.tsx
需要改进的功能:
a) 异步验证优化
// 编码唯一性验证(防抖)
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) 保存前验证增强
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 不完全兼容
解决方案:
- 在开发环境进行充分测试
- 编写数据迁移脚本
- 备份生产数据
- 分批次迁移
性能风险
问题: 大数据量情况下查询性能下降
解决方案:
- 添加数据库索引
- 优化 PostgREST 查询
- 实现分页和懒加载
- 使用缓存机制
兼容性风险
问题: 旧版本 API 调用可能失败
解决方案:
- 保留旧版本 API(向后兼容)
- 使用 API 版本控制
- 提供迁移指南
- 逐步废弃旧接口
JSONB 字段风险
问题: JSONB 字段格式复杂,容易出错
解决方案:
- 严格的数据验证
- 使用 JSON Schema 验证
- 提供默认值和模板
- 详细的错误提示
实施时间表
| 阶段 | 模块 | 工作量 | 起止日期 |
|---|---|---|---|
| 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 查询语法参考
// 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'
类型定义参考
// 评查点分组
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;
}
文档结束